Android高性能日志系统设计方案

摘要:本日志系统设计基于mmap内存映射技术实现高性能日志记录,支持日志本地存储和上传服务器、按时间段打捞日志,并自动管理15天内的日志存储。同时提供灵活的日志捞取机制。系统采用分层架构设计,包含日志采集层、缓冲层、存储层和上传层。本日志系统设计基于mmap实现高性能日志记录,

一、系统设计目标

  1. 高性能:使用mmap内存映射实现高速日志写入

  2. 可靠性:确保日志不丢失,支持崩溃恢复

  3. 可追溯:支持日志打捞和检索

  4. 可扩展:支持日志上传服务器

  5. 低功耗:减少对设备性能的影响

二、系统架构设计

[日志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特有优化

  1. 单例模式:使用companion object实现线程安全的单例

  2. 属性委托:简化原子变量的使用(虽然这里直接用了AtomicInteger

  3. 扩展函数:可以添加扩展函数简化常用操作

  4. 空安全:充分利用Kotlin的空安全特性

  5. DSL风格构建器:使用Kotlin的DSL特性构建配置

  6. 资源自动管理:使用use扩展函数自动关闭资源

  7. 数据类:使用data class简化配置类的实现

扩展功能建议

  1. 协程支持:可以将上传和日志记录改用协程实现

  2. Flow API:提供日志变化的Flow观察接口

  3. 扩展函数:为Any类添加日志扩展函数

  4. 结构化日志:支持JSON等结构化日志格式

  5. 多平台支持:使用Kotlin Multiplatform实现跨平台日志系统

这个Kotlin实现保留了原有Java版本的所有功能特性,同时利用了Kotlin的语言优势使代码更加简洁和安全。

Kotlin优化点说明

  1. 单例模式:使用伴生对象和@Volatile实现线程安全的单例

  2. 空安全:充分利用Kotlin的空安全特性

  3. 扩展函数:可以添加扩展函数简化日志调用

  4. 属性委托:可以使用属性委托简化部分代码

  5. 协程支持:可以改用协程替代Executor处理异步任务

  6. DSL构建:可以设计DSL风格的日志构建方式

  7. 默认参数:在方法中使用默认参数简化调用

  8. 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")

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值