如何在HDD就绪前用Logback缓冲日志消息并写入文件?
解决嵌入式设备Logback开机日志丢失问题
针对你遇到的HDD未就绪前日志丢失问题,以下是几种直接可行的解决方案,结合你的代码场景优先推荐内存缓冲+动态切换Appender的实现方式:
方案1:用CyclicBufferAppender临时缓冲,就绪后转存并切换
Logback内置的CyclicBufferAppender可在内存中缓冲指定数量的日志事件,等HDD就绪后,将缓冲日志写入FileAppender,再切换为文件输出作为主要日志通道。
实现步骤:
- 开机初始化阶段,先启动缓冲Appender作为临时输出:
// 开机时先初始化缓冲日志 fun initTemporaryLogger() { val lc = LoggerFactory.getILoggerFactory() as LoggerContext lc.reset() val root = lc.getLogger(Logger.ROOT_LOGGER_NAME) val bufferAppender = CyclicBufferAppender<ILoggingEvent>().apply { context = lc name = "bufferAppender" bufferSize = 1000 // 根据设备内存和日志量调整缓冲上限 start() } root.addAppender(bufferAppender) // 保留原有日志级别配置 lc.getLogger(DNSIncoming::class.java).level = Level.WARN }
- 检测到HDD就绪后,在
startLogger()中完成日志转存和Appender切换:
fun startLogger() { logger.debug("Starting HDD logger") val lc = LoggerFactory.getILoggerFactory() as LoggerContext val root = lc.getLogger(Logger.ROOT_LOGGER_NAME) try { // 初始化原有的FileAppender val logFileAppender = FileAppender<ILoggingEvent>().apply { context = lc name = "logFile" file = "/mnt/hdd/cz.myq.roger.ricoh/logs/logfile.log" encoder = PatternLayoutEncoder().apply { context = lc pattern = "%-12date{yyyy-MM-dd HH:mm:ss.SSS} %logger{12} %-5level - %msg%n" start() } isAppend = true rollingPolicy = TimeBasedRollingPolicy<ILoggingEvent>().apply { context = lc fileNamePattern = "/mnt/hdd/cz.myq.roger.ricoh/logs/logfile-%d{yyyy-MM-dd}.log.zip" maxHistory = 7 setTotalSizeCap(FileSize.valueOf("100MB")) }.also { it.setParent(this) it.start() } start() } // 转存缓冲中的日志到文件 val bufferAppender = lc.getAppender("bufferAppender") as CyclicBufferAppender<ILoggingEvent> bufferAppender.getCopyOfAllElements().forEach { logFileAppender.doAppend(it) } // 切换根Logger的Appender root.detachAppender(bufferAppender) root.addAppender(logFileAppender) // 清理缓冲Appender bufferAppender.stop() } catch (e: Exception) { logger.error("Error starting log", e) } }
方案2:自定义缓冲Appender(灵活度更高)
如果需要更灵活的缓冲策略(比如带内存阈值保护、支持临时存储到RAM盘),可以自定义Appender维护日志队列:
class BufferedFileAppender : UnsynchronizedAppenderBase<ILoggingEvent>() { private val eventQueue = ConcurrentLinkedQueue<ILoggingEvent>() private var delegateFileAppender: FileAppender<ILoggingEvent>? = null private var isHDDReady = false private val MAX_BUFFER_SIZE = 2000 // 内存缓冲上限 // HDD就绪后调用此方法启动文件输出 fun initFileAppender(lc: LoggerContext) { delegateFileAppender = FileAppender<ILoggingEvent>().apply { context = lc name = "delegateLogFile" file = "/mnt/hdd/cz.myq.roger.ricoh/logs/logfile.log" encoder = PatternLayoutEncoder().apply { context = lc pattern = "%-12date{yyyy-MM-dd HH:mm:ss.SSS} %logger{12} %-5level - %msg%n" start() } isAppend = true rollingPolicy = TimeBasedRollingPolicy<ILoggingEvent>().apply { context = lc fileNamePattern = "/mnt/hdd/cz.myq.roger.ricoh/logs/logfile-%d{yyyy-MM-dd}.log.zip" maxHistory = 7 setTotalSizeCap(FileSize.valueOf("100MB")) }.also { it.setParent(this) it.start() } start() } isHDDReady = true // 批量写入缓冲的日志 while (eventQueue.isNotEmpty()) { delegateFileAppender?.doAppend(eventQueue.poll()) } } override fun append(event: ILoggingEvent) { if (isHDDReady) { delegateFileAppender?.doAppend(event) } else { if (eventQueue.size < MAX_BUFFER_SIZE) { eventQueue.offer(event) } else { // 缓冲满时可选择丢弃或写入RAM盘 logger.warn("Log buffer full, dropping event: ${event.message}") } } } }
使用时,开机先初始化这个自定义Appender,HDD就绪后调用initFileAppender()即可。
关键注意事项
- 缓冲容量需结合设备内存大小合理设置,避免内存溢出;
- 若设备配有RAM盘,可优先将临时日志写入RAM盘,HDD就绪后再迁移,比纯内存缓冲更安全;
- 确保HDD就绪的检测逻辑可靠,避免重复初始化Appender。
内容的提问来源于stack exchange,提问作者Mazmart
相关产品推荐
相关产品推荐

