摘要:本日志系统设计基于mmap内存映射技术实现高性能日志记录,支持日志本地存储和上传服务器、按时间段打捞日志,并自动管理15天内的日志存储。同时提供灵活的日志捞取机制。系统采用分层架构设计,包含日志采集层、缓冲层、存储层和上传层。本日志系统设计基于mmap实现高性能日志记录,
一、系统设计目标
-
高性能:使用mmap内存映射实现高速日志写入
-
可靠性:确保日志不丢失,支持崩溃恢复
-
可追溯:支持日志打捞和检索
-
可扩展:支持日志上传服务器
-
低功耗:减少对设备性能的影响
二、系统架构设计
[日志API层]
↓
[内存缓冲区] ←→ [MMAP文件存储]
↓
[日志压缩模块]
↓
[上传管理器] → [服务器]
↑
[日志检索模块]
系统架构
┌─────────────────────────────────────────────────┐
│ Log System │
├─────────────────┬───────────────┬───────────────┤
│ Log Core │ Log Cache │ Log Uploader │
├─────────────────┼───────────────┼───────────────┤
│ - mmap storage │ - Memory cache│ - Network ops │
│ - Log writing │ - Batch ops │ - Retry mech. │
│ - Log rotation │ │ │
└─────────────────┴───────────────┴───────────────┘
三、核心类设计
1. MmapLogManager (核心管理类)
import java.io.File
import java.io.IOException
import java.io.RandomAccessFile
import java.nio.MappedByteBuffer
import java.nio.channels.FileChannel
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.atomic.AtomicLong
import kotlin.collections.ArrayList
/**
* 基于mmap的高性能日志存储实现
*/
class MmapLogStorage private constructor() {
companion object {
private const val MAX_LOG_DAYS = 15
private const val DAY_IN_MS = 86400000L
private const val MMAP_SIZE = 10 * 1024 * 1024 // 10MB
@Volatile
private var instance: MmapLogStorage? = null
fun getInstance(context: Context): MmapLogStorage {
return instance ?: synchronized(this) {
instance ?: MmapLogStorage().also {
it.initialize(context.applicationContext)
instance = it
}
}
}
}
private lateinit var mMappedFile: File
private lateinit var mMappedBuffer: MappedByteBuffer
private val mWritePosition = AtomicLong(0)
/**
* 初始化mmap文件
* @param context Android上下文
* @throws IOException 文件操作异常
*/
@Throws(IOException::class)
private fun initialize(context: Context) {
// 创建日志目录
val logDir = File(context.filesDir, "logs").apply {
if (!exists()) mkdirs()
}
// 创建当天日志文件
val today = SimpleDateFormat("yyyy-MM-dd", Locale.US).format(Date())
mMappedFile = File(logDir, "log_$today.dat")
// 初始化mmap
RandomAccessFile(mMappedFile, "rw").use { raf ->
val channel = raf.channel
mMappedBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, MMAP_SIZE.toLong())
}
// 清理过期日志
cleanExpiredLogs(logDir)
}
/**
* 清理超过15天的日志文件
* @param logDir 日志目录
*/
private fun cleanExpiredLogs(logDir: File) {
val cutoffTime = System.currentTimeMillis() - (MAX_LOG_DAYS * DAY_IN_MS)
logDir.listFiles()?.forEach { file ->
if (file.lastModified() < cutoffTime) {
file.delete()
}
}
}
/**
* 写入日志到mmap
* @param log 日志内容
* @return 写入是否成功
*/
fun writeLog(log: String): Boolean {
if (isLogExpired()) {
return false
}
return try {
val logBytes = log.toByteArray(Charsets.UTF_8)
val logLength = logBytes.size
// 检查空间是否足够
if (mWritePosition.get() + logLength + 4 > mMappedBuffer.capacity()) {
return false
}
// 先写入日志长度
mMappedBuffer.putInt(mWritePosition.get().toInt(), logLength)
mWritePosition.addAndGet(4)
// 写入日志内容
mMappedBuffer.position(mWritePosition.get().toInt())
mMappedBuffer.put(logBytes)
mWritePosition.addAndGet(logLength.toLong())
true
} catch (e: Exception) {
false
}
}
/**
* 检查日志是否过期(超过15天)
* @return 是否过期
*/
private fun isLogExpired(): Boolean {
return (System.currentTimeMillis() - mMappedFile.lastModified()) > (MAX_LOG_DAYS * DAY_IN_MS)
}
/**
* 读取指定时间范围的日志
* @param startTime 开始时间(毫秒)
* @param endTime 结束时间(毫秒)
* @return 日志列表
*/
fun readLogs(startTime: Long, endTime: Long): List<String> {
val logs = mutableListOf<String>()
val logDir = mMappedFile.parentFile
val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.US)
// 遍历目录下所有日志文件
logDir.listFiles()?.forEach { file ->
try {
// 解析文件名中的日期
val dateStr = file.name.removePrefix("log_").removeSuffix(".dat")
val fileDate = sdf.parse(dateStr)
// 检查文件日期是否在查询范围内
if (fileDate != null && fileDate.time in startTime..endTime) {
// 读取文件内容
RandomAccessFile(file, "r").use { raf ->
val channel = raf.channel
val buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size())
var position = 0L
while (position < buffer.capacity()) {
val logLength = buffer.getInt(position.toInt())
position += 4
if (logLength <= 0 || position + logLength > buffer.capacity()) {
break
}
val logBytes = ByteArray(logLength)
buffer.position(position.toInt())
buffer.get(logBytes)
position += logLength
logs.add(String(logBytes, Charsets.UTF_8))
}
}
}
} catch (e: Exception) {
// 忽略解析错误的文件
}
}
return logs
}
}
2. LogCache (日志缓存)
import java.util.concurrent.LinkedBlockingQueue
/**
* 日志内存缓存,减少IO操作
*/
class LogCache {
companion object {
private const val MAX_CACHE_SIZE = 1000
}
private val mLogQueue = LinkedBlockingQueue<String>(MAX_CACHE_SIZE)
/**
* 添加日志到缓存
* @param log 日志内容
* @return 是否添加成功
*/
fun addLog(log: String): Boolean = mLogQueue.offer(log)
/**
* 批量获取并清空缓存
* @return 日志列表
*/
fun drainCache(): List<String> {
val logs = mutableListOf<String>()
mLogQueue.drainTo(logs)
return logs
}
/**
* 获取当前缓存大小
* @return 缓存中的日志数量
*/
fun getCacheSize(): Int = mLogQueue.size
}
3. LogUploader (日志上传)
import java.util.concurrent.Executors
/**
* 日志上传管理器
*/
class LogUploader {
companion object {
private const val MAX_RETRY = 3
private const val RETRY_DELAY_MS = 5000L
}
private val mUploadExecutor = Executors.newSingleThreadExecutor()
/**
* 上传日志到服务器
* @param logs 要上传的日志列表
* @param callback 上传结果回调
*/
fun uploadLogs(logs: List<String>, callback: UploadCallback?) {
mUploadExecutor.execute {
var retryCount = 0
var success = false
while (retryCount < MAX_RETRY && !success) {
try {
// 模拟上传操作
success = doUpload(logs)
if (!success && retryCount < MAX_RETRY - 1) {
Thread.sleep(RETRY_DELAY_MS)
}
} catch (e: Exception) {
// 记录上传错误
}
retryCount++
}
callback?.onUploadComplete(success)
}
}
/**
* 实际执行上传操作
* @param logs 日志列表
* @return 是否上传成功
*/
private fun doUpload(logs: List<String>): Boolean {
// 这里实现实际的上传逻辑
// 可以使用OkHttp或其他网络库
return true
}
interface UploadCallback {
fun onUploadComplete(success: Boolean)
}
}
4. LogManager (入口类)
import android.content.Context
import android.os.Handler
import android.os.HandlerThread
import android.util.Log
import java.text.SimpleDateFormat
import java.util.*
/**
* 日志系统入口类
*/
class LogManager private constructor(context: Context) {
companion object {
@Volatile
private var instance: LogManager? = null
private const val FLUSH_INTERVAL = 5000L // 5秒
fun getInstance(context: Context): LogManager {
return instance ?: synchronized(this) {
instance ?: LogManager(context.applicationContext).also { instance = it }
}
}
}
private val mLogStorage = MmapLogStorage.getInstance(context)
private val mLogCache = LogCache()
private val mLogUploader = LogUploader()
private val mHandler: Handler
init {
// 初始化后台线程的Handler
val handlerThread = HandlerThread("LogFlusher").apply { start() }
mHandler = Handler(handlerThread.looper)
// 定时刷新缓存
mHandler.postDelayed(mFlushRunnable, FLUSH_INTERVAL)
}
/**
* 记录日志
* @param level 日志级别
* @param tag 日志标签
* @param message 日志消息
*/
fun log(level: Int, tag: String, message: String) {
val timestamp = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US).format(Date())
val levelStr = when (level) {
Log.VERBOSE -> "V"
Log.DEBUG -> "D"
Log.INFO -> "I"
Log.WARN -> "W"
Log.ERROR -> "E"
else -> "?"
}
val logEntry = "$timestamp [$levelStr] $tag: $message"
// 先尝试写入缓存
if (!mLogCache.addLog(logEntry)) {
// 缓存已满,直接写入存储
mLogStorage.writeLog(logEntry)
}
}
/**
* 打捞指定时间段的日志
* @param startTime 开始时间(毫秒)
* @param endTime 结束时间(毫秒)
* @return 日志列表
* @throws IllegalArgumentException 时间范围无效
* @throws IllegalStateException 超过15天限制
*/
@Throws(IllegalArgumentException::class, IllegalStateException::class)
fun retrieveLogs(startTime: Long, endTime: Long): List<String> {
// 检查时间范围是否有效
if (startTime < 0 || endTime < 0 || startTime > endTime) {
throw IllegalArgumentException("Invalid time range")
}
// 检查是否超过15天限制
val fifteenDaysAgo = System.currentTimeMillis() - (15 * 24 * 60 * 60 * 1000L)
if (startTime < fifteenDaysAgo) {
throw IllegalStateException("Cannot retrieve logs older than 15 days")
}
return mLogStorage.readLogs(startTime, endTime)
}
/**
* 上传指定时间段的日志到服务器
* @param startTime 开始时间(毫秒)
* @param endTime 结束时间(毫秒)
* @param callback 上传结果回调
*/
fun uploadLogs(startTime: Long, endTime: Long, callback: LogUploader.UploadCallback? = null) {
val logs = retrieveLogs(startTime, endTime)
mLogUploader.uploadLogs(logs, callback)
}
/**
* 定时将缓存中的日志写入存储
*/
private val mFlushRunnable = object : Runnable {
override fun run() {
mLogCache.drainCache().forEach { log ->
mLogStorage.writeLog(log)
}
// 重新调度
mHandler.postDelayed(this, FLUSH_INTERVAL)
}
}
/**
* 关闭日志系统,释放资源
*/
fun shutdown() {
mHandler.removeCallbacks(mFlushRunnable)
// 确保所有缓存日志都被写入
mFlushRunnable.run()
}
}
使用示例
// 初始化
val logManager = LogManager.getInstance(context)
// 记录日志
logManager.log(Log.INFO, "MainActivity", "App started")
// 打捞最近3天的日志
val endTime = System.currentTimeMillis()
val startTime = endTime - 3 * 24 * 60 * 60 * 1000L
val logs = logManager.retrieveLogs(startTime, endTime)
// 上传日志
logManager.uploadLogs(startTime, endTime) { success ->
if (success) {
// 上传成功处理
} else {
// 上传失败处理
}
}
// 应用退出时
logManager.shutdown()
Kotlin特有优化
-
单例模式:使用
companion object
实现线程安全的单例 -
属性委托:简化原子变量的使用(虽然这里直接用了
AtomicInteger
) -
扩展函数:可以添加扩展函数简化常用操作
-
空安全:充分利用Kotlin的空安全特性
-
DSL风格构建器:使用Kotlin的DSL特性构建配置
-
资源自动管理:使用
use
扩展函数自动关闭资源 -
数据类:使用
data class
简化配置类的实现
扩展功能建议
-
协程支持:可以将上传和日志记录改用协程实现
-
Flow API:提供日志变化的Flow观察接口
-
扩展函数:为
Any
类添加日志扩展函数 -
结构化日志:支持JSON等结构化日志格式
-
多平台支持:使用Kotlin Multiplatform实现跨平台日志系统
这个Kotlin实现保留了原有Java版本的所有功能特性,同时利用了Kotlin的语言优势使代码更加简洁和安全。
Kotlin优化点说明
-
单例模式:使用伴生对象和
@Volatile
实现线程安全的单例 -
空安全:充分利用Kotlin的空安全特性
-
扩展函数:可以添加扩展函数简化日志调用
-
属性委托:可以使用属性委托简化部分代码
-
协程支持:可以改用协程替代Executor处理异步任务
-
DSL构建:可以设计DSL风格的日志构建方式
-
默认参数:在方法中使用默认参数简化调用
-
Lambda表达式:简化回调接口的使用
扩展函数示例
可以添加以下扩展函数简化日志调用:
fun LogManager.info(tag: String, message: String) = log(Log.INFO, tag, message)
fun LogManager.debug(tag: String, message: String) = log(Log.DEBUG, tag, message)
fun LogManager.error(tag: String, message: String) = log(Log.ERROR, tag, message)
fun LogManager.warn(tag: String, message: String) = log(Log.WARN, tag, message)
fun LogManager.verbose(tag: String, message: String) = log(Log.VERBOSE, tag, message)
这样使用时可以更简洁:
logManager.info("MainActivity", "App started")