zoukankan      html  css  js  c++  java
  • (转)日交易额百亿级交易系统的超轻量日志实现

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

      修改后源码下载

  • 相关阅读:
    C语言的数组
    C语言的组成 以及预编译
    python实战——网络爬虫之request
    python实战——网络爬虫
    web渗透-sqli-labs-master 下载与安装
    PHP之旅4 php 超全局变量
    PHP之旅9 MySQL数据库
    PHP之旅8 URL与表单
    mysql数据库事务隔离级别与设置
    xsell 过期后的处理方法
  • 原文地址:https://www.cnblogs.com/hhhh2010/p/7347630.html
Copyright © 2011-2022 走看看