逛园子的时候偶然发现了《日交易额百亿级交易系统的超轻量日志实现》,感觉博主的思路很强,可惜是一个JAVA版本,于是我将它翻译为C#。
开发环境VS2015+.net framework4. 原文地址,http://www.cnblogs.com/cyfonly/p/6139049.html
因为JAVA和C#语言的近似性,很多直接内容直接从原文COPY的,博主勿怪。。
使用方式:(直接Copy原文)
/获取单例 FLogger logger = FLogger.getInstance(); //简便api,只需指定内容 logger.info("Here is your message..."); //指定日志级别和内容,文件名自动映射 logger.writeLog(Constant.INFO, "Here is your customized level message..."); //指定日志输出文件名、日志级别和内容 logger.writeLog("error", Constant.ERROR, "Here is your customized log file and level message...");
配置项如下:(直接Copy原文)
########## 公共环境配置 ########## # 字符集 CHARSET_NAME = UTF-8 ########## 日志信息配置 ########## # 日志级别 0:调试信息 1:普通信息 2:警告信息 3:错误信息 4:严重错误信息 LOG_LEVEL = 0,1,2,3,4 # 日志文件存放路径 LOG_PATH =/log (此处跟原文不同哦) # 日志写入文件的间隔时间(默认为1000毫秒) WRITE_LOG_INV_TIME = 1000 # 单个日志文件的大小(默认为10M) SINGLE_LOG_FILE_SIZE = 10485760 # 单个日志文件缓存的大小(默认为10KB) SINGLE_LOG_CACHE_SIZE = 10240
打印结果:(直接Copy原文)
info.log [INFO] 2016-12-06 21:07:32:840 [main] Here is your message... warn.log [WARN] 2016-12-06 21:07:32:842 [main] Here is your customized level message... error.log [ERROR] 2016-12-06 21:07:32:842 [main] Here is your customized log file and level message... 从上面可以看到,你可以很清楚的分辨出日志的级别、时间和内容等信息。到这其实很明了了,日志由以下几个元素组成: [日志级别] 精确到毫秒的时间 [当前线程名] 日志内容
源码解析
双缓冲队列
FLogger 在内部采用双缓冲队列,那何为双缓冲队列呢?它的作用又是什么呢?
FLogger 为每个日志文件维护了一个内部对象 LogFileItem ,定义如下:
public class LogFileItem { /** 不包括路径,不带扩展名的日志文件名称 如:MsgInner */ public String logFileName = ""; /** 包括路径的完整日志名称 */ public String fullLogFileName = ""; /** 当前日志文件大小 */ public long currLogSize = 0; /** 当前正在使用的日志缓存 */ public char currLogBuff = 'A'; /** 日志缓存列表A */ public List<StringBuilder> alLogBufA = new List<StringBuilder>(); /** 日志缓存列表B */ public List<StringBuilder> alLogBufB = new List<StringBuilder>(); /** 下次日志输出到文件时间 */ public long nextWriteTime = 1000; /** 上次写入时的日期 */ public String lastPCDate = ""; /** 当前已缓存大小 */ public long currCacheSize = 10240; }
在每次写日志时,日志内容作为一个 StringBuffer 添加到当前正在使用的 ArrayList<StringBuffer> 中,另一个则空闲。当内存中的日志输出到磁盘文件时,会将当前使用的 ArrayList<StringBuffer> 与空闲的 ArrayList<StringBuffer> 进行角色交换,交换后之前空闲的 ArrayList<StringBuffer> 将接收日志内容,而之前拥有日志内容的 ArrayList<StringBuffer> 则用来输出日志到磁盘文件。这样就可以避免每次刷盘时影响日志内容的接收(即所谓的 stop-the-world 效应)及多线程问题。
日志接收代码: 此处用lock代替JAVA的synchronized
lock (lfi) { if (lfi.currLogBuff == 'A') { lfi.alLogBufA.Add(logMsg); } else { lfi.alLogBufB.Add(logMsg); } lfi.currCacheSize +=System.Text.Encoding.UTF8.GetBytes(logMsg.ToString()).Length; }
日志刷盘代码:
List<StringBuilder> alWrtLog = null; lock (lfi) { if (lfi.currLogBuff == 'A') { alWrtLog = lfi.alLogBufA; lfi.currLogBuff = 'B'; } else { alWrtLog = lfi.alLogBufB; lfi.currLogBuff = 'A'; } lfi.currCacheSize = 0; } //创建日志文件 createLogFile(lfi); //输出日志 int iWriteSize = writeToFile(lfi.fullLogFileName, alWrtLog); lfi.currLogSize += iWriteSize;
刷盘机制: 暂不支持退出强制触发
刷盘时间间隔触发 配置项如下: # 日志写入文件的间隔时间(默认为1000毫秒) WRITE_LOG_INV_TIME = 1000 当距上次刷盘时间超过间隔时间,将执行内存日志刷盘。 内存缓冲大小触发 配置项如下: # 单个日志文件缓存的大小(默认为10KB) SINGLE_LOG_CACHE_SIZE = 10240 当内存缓冲队列的大小超过配置大小时,将执行内存日志刷盘。
多 RollingFile 机制
//创建日志文件 private void createLogFile(LogFileItem lfi) { //当前系统日期 String currPCDate = TimeUtil.getPCDate('-'); //判断日志root路径是否存在,不存在则先创建 if (Directory.Exists(ConstantCLS.CFG_LOG_PATH)) { if (Directory.Exists(ConstantCLS.CFG_LOG_PATH)) { Directory.CreateDirectory(ConstantCLS.CFG_LOG_PATH); } } //如果超过单个文件大小,则拆分文件 if (lfi.fullLogFileName != null && lfi.fullLogFileName.Length > 0 && lfi.currLogSize >= LogManager.SINGLE_LOG_FILE_SIZE) { if (File.Exists(lfi.fullLogFileName)) { String newFileName = ConstantCLS.CFG_LOG_PATH + "/" + lfi.lastPCDate + "/" + lfi.logFileName + "_" + TimeUtil.getPCDate() + "_" + TimeUtil.getCurrTime() + ".log"; try { File.Move(lfi.fullLogFileName, newFileName); MessageBox("日志已自动备份为 " + newFileName + "成功!"); lfi.fullLogFileName = ""; lfi.currLogSize = 0; } catch (Exception ex) { MessageBox("日志已自动备份为 " + newFileName + "失败!"+ex.ToString()); } } } //创建文件 if (lfi.fullLogFileName == null || lfi.fullLogFileName.Length <= 0 || !lfi.lastPCDate.Equals(currPCDate)) { String sDir = ConstantCLS.CFG_LOG_PATH + "/" + currPCDate; if (!Directory.Exists(sDir)) { DirectoryInfo dirInfo = Directory.CreateDirectory(sDir); } lfi.fullLogFileName = sDir + "/" + lfi.logFileName + ".log"; lfi.lastPCDate = currPCDate; if (File.Exists(lfi.fullLogFileName)) { FileInfo fi = new FileInfo(lfi.fullLogFileName); lfi.currLogSize = fi.Length; } else { File.Create(lfi.fullLogFileName); lfi.currLogSize = 0; } } }
热加载
FLogger 支持热加载,FLogger 内部并没有采用事件驱动方式(即新增、修改和删除配置文件时产生相关事件通知 FLogger 实时热加载),而是以固定频率的方式进行热加载,具体实现就是每执行完100次刷盘后才进行热加载(频率可调),关键代码如下:
public void run() { int i = 0; while (bIsRun) { try { //输出到文件 flush(false); //重新获取日志级别 if (i++ % 100 == 0) { ConstantCLS.CFG_LOG_LEVEL = CommUtil.getConfigByString("LOG_LEVEL", "0,1,2,3,4"); i = 1; } } catch (Exception ex) { MessageBox("开启日志服务错误..." + ex.ToString()); } } }
想当然遇到的坑:
loadxml:在C#中加载的是string类型的XML文件,加载文件需要使用load
streamwrite:在C#中writeLine为Append方式时,传入的参数是string而不是filestream
synchronized:JAVA关键字,C#中科用lock代替
e.printstacktrace:JAVA在命令行打印信息,C#中可用Console.WriteLine(System.Environment.StackTrace);