首页 > 代码库 > (转)日交易额百亿级交易系统的超轻量日志实现

(转)日交易额百亿级交易系统的超轻量日志实现

          逛园子的时候偶然发现了《日交易额百亿级交易系统的超轻量日志实现》,感觉博主的思路很强,可惜是一个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);

 

  修改后源码下载

 

(转)日交易额百亿级交易系统的超轻量日志实现