zoukankan      html  css  js  c++  java
  • NLog实践记录

    环境

    win10 vs2019 .net core3.1 Nlog4.75

    目标

    程序记录日志功能,具体要求如下:

    记录日志到3个目录,SrvLog(win服务) DBLog(数据库) AppLog(程序),

    每年一个目录,名字是年(如:2020),每月一个目录(如:10)

    日志文件每达到2M,另起新文件.日志以年与日为名字,例如: 2020-10-04.log

    logsroot  - >

                     srvlog  -> 2020-10-04.log

          dblog    -> 2020-10-04.log

                    applog  -> 2020-10-04.log

    实现

    使用c#的文件操作类可以容易的实现这个要求,缺点是性能和功能都较弱,在要求不高的小型系统下使用没有问题

      1 public class LogHelp
      2     {
      3         /// <summary>
      4         /// 日志根目录,默认为当前程序运行目录.要在程序部署前设定.
      5         /// 默认是程序运行目录.(设定新的必须是绝对路径)(例 e:/logs 或者 /home/log)
      6         /// </summary>
      7         public static string LogRootPath = AppDomain.CurrentDomain.BaseDirectory;
      8 
      9         private static ReaderWriterLockSlim LogWriteLock
     10             = new ReaderWriterLockSlim();
     11         /// <summary>
     12         /// 日志开关设置 off=不记录 nodebug=DeBugLog()这个记录调试日志的方法不记录 其它值=记录 
     13         /// </summary>
     14         private static string logOnOff = "on";
     15 
     16         /// <summary>
     17         /// 用于记录数据库操作(出错时)日志.包含SQL语句和参数,及异常提示信息
     18         /// 该日志会位于根目录下的DBLogs文件夹下.且以当天日期为文件名
     19         /// </summary>
     20         /// <param name="msg"></param>
     21         /// <returns></returns>
     22         public static void DBLog(string msg, bool yearDir = false, bool monthDir = false, bool dayDir = false)
     23         {
     24             if (logOnOff == "off") return;
     25             Log(msg, "", "DBLog", yearDir, monthDir, dayDir);
     26         }
     27         /// <summary>
     28         /// 添加日志 主要针对WIN服务程序,文件位于根目录的SrvLogs目录下
     29         /// </summary>
     30         /// <param name="msg"></param>
     31         /// <returns></returns>
     32         public static void SrvLog(string msg, bool yearDir = false, bool monthDir = false, bool dayDir = false)
     33         {
     34             if (logOnOff == "off") return;
     35             Log(msg, "", "SrvLog", yearDir, monthDir, dayDir);
     36         }
     37         /// <summary>
     38         /// 添加调试日志 主要是未上线时用
     39         /// </summary>
     40         /// <param name="msg"></param>
     41         /// <returns></returns>
     42         public static void DeBugLog(string msg, bool yearDir = false, bool monthDir = false, bool dayDir = false)
     43         {
     44             if (logOnOff == "nodebug") return;
     45             if (logOnOff == "off") return;
     46             Log(msg, "", "DeBugLog", yearDir, monthDir, dayDir);
     47         }
     48         /// <summary>
     49         /// 添加日志  如果不指定目录名,则文件位于根目录的AppLog默认目录下.日志扩展名固定为.log
     50         /// </summary>
     51         /// <param name="message">日志内容</param>
     52         /// <param name="filename">日志文件名,不含扩展名.省略时以当天年月日为名</param>
     53         /// <param name="directory">一类型日志总目录(应用程序根目录的下一级)</param>
     54         /// <returns></returns>
     55         public static void Log(string message, string filename = "", string logDirName = "AppLog", bool yearDir = false, bool monthDir = false, bool dayDir = false)
     56         {
     57             // 进入写锁定.如果其它线程也来访问,则等待
     58             // 其后至解除锁定之间的代码不能有异常,否则无法解除写锁定
     59             LogWriteLock.EnterWriteLock();
     60             // 日志目录与文件名
     61             string directory = GetLogPath(logDirName, yearDir, monthDir, dayDir);
     62             string fn = filename == ""
     63                 ? DateTime.Now.Date.ToString("yyyyMMdd") : filename;
     64             string path = Path.Combine(directory, fn + ".log");
     65             // 超过2M时存为旧文件,名字如:yyyyMMdd(1)
     66             if (File.Exists(path))
     67             {
     68                 FileInfo fi = new FileInfo(path);
     69                 if (fi.Length > 2000 * 1000)
     70                 {
     71                     int count = 1;
     72                     while (true)
     73                     {
     74                         string oldpath = Path.Combine(directory, $"{fn}({count}).txt");
     75                         if (!File.Exists(oldpath))
     76                         {
     77                             fi.MoveTo(oldpath);
     78                             break;
     79                         }
     80                         count++;
     81                     }
     82                 }
     83             }
     84             // 开始写入
     85             using (StreamWriter sw = new StreamWriter(path, true))
     86             {
     87                 // 日志记录时间.指下面获取的当时时间.不应理解为日志记录的时间.
     88                 // (考虑到并发时,日志缓存到了队列,或者本方法正被访问,
     89                 //   其它线程正在等读写锁解除
     90                 string WriteTime =
     91                     DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff");
     92                 // 当前调用该日志方法的线程ID
     93                 string ThreadId =
     94                     Thread.CurrentThread.ManagedThreadId.ToString();
     95                 string method = System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName;
     96                 // 
     97                 sw.WriteLine(
     98                 $"WriteTime:{WriteTime} TId-{ThreadId}] [{method}{Environment.NewLine}{message}{Environment.NewLine}");
     99             }
    100             // 解除写锁定
    101             LogWriteLock.ExitWriteLock();
    102             //
    103         }
    104 
    105         /// <summary>
    106         /// 获取日志根目录 如 e:/logs/ 如果目录不存在,则会建立
    107         /// 注意:未加异常判断.请保证根目录设置(可能在webconfig的rootPath)及目录名有效
    108         /// </summary>
    109         /// <param name="logDirName">日志根目录的名字</param>
    110         /// <param name="day">是否以天建立目录</param>
    111         /// <param name="month">是否以月建立目录</param>
    112         /// <param name="year">是否以年建立目录</param>
    113         /// <returns></returns>
    114         private static string GetLogPath(string logDirName = "", bool yearDir = false, bool monthDir = false, bool dayDir = false)
    115         {
    116             string logPath = logDirName == "" ? "Logs" : logDirName;
    117             string rootpath = string.IsNullOrWhiteSpace(LogRootPath) ? AppDomain.CurrentDomain.BaseDirectory : LogRootPath;
    118             string directory = string.Format(@"{0}/{1}/", rootpath, logPath);
    119             if (yearDir == true)
    120                 directory = string.Format(@"{0}{1}y/", directory, DateTime.Today.Year);
    121             if (monthDir == true)
    122                 directory = string.Format(@"{0}{1}m/", directory, DateTime.Today.Month);
    123             if (dayDir == true)
    124                 directory = string.Format(@"{0}{1}d/", directory, DateTime.Today.Day);
    125             // 
    126             if (!Directory.Exists(directory))
    127             {
    128                 Directory.CreateDirectory(directory);
    129             }
    130             //
    131             return directory;
    132         }
    133     }
    LogHelp

    log4net

    这个以前用过了,但是不想再用了,因为过于复杂的配置太麻烦了,这次又想起它来,针对目标研究了一下:

    要实现日志按年月建立目录,关键在于这几个地方:

    选择日志输出为文件回转:  RollingFileAppender,这个就是所谓文件回转生成日志,就是日志到大小了,就新建文件

    staticLogFileName //  这个必须为false

    file = "logs/" // 这里写目录名字,而不是文件名字,后面有个 /

    datepattern = "yyyy/MM/yyyy-MM-dd'.log'" // 这个设定就是生成年月目录的关键,除了单引号里的 '.log',其它的回解释成日期对于的部分

    文件大小,保留文件数目,设置 2*1024*1024 (2M) 100(最多备份100个文件)

    还有个文件名扩展名保留的选项,设为true,不然备份的日志就每又扩展名.

    这样设置后,测试发现一个问题,在程序打印日志后,确实按预期实现了,生成了日志:

    logs - >  2020 -> 10 -> 2020-10-04.log

                -> 2020-10-04.1.log

                                     -> 2020-10-04.2.log

    但是

    再次运行程序,发现日志 2020-10-04.log 被追加了,这个正常,但是到大小后,却没有生成 2020-10-04.3.log ,而是覆盖了 2020-10-04.1.log

    预期应该是这样的:

    logs - >  2020 -> 10 -> 2020-10-04.log

                -> 2020-10-04.1.log

                                     -> 2020-10-04.2.log

                -> 2020-10-04.3.log

    这个问题搞了很久,没有解决,然后,就放弃了!!

    哎,,曾经惧怕的log4net配置,这一次发现,其实没有什么的,不喜欢xml的配置,用代码也可以的,还比较简便.XML是太啰嗦了!

    NLog

    文档 https://nlog-project.org/config/?tab=targets

     无奈之下放弃了log4net,同时发现了NLog

    这个日志的设计和log4net很相似啊,开始有一种恐惧感.

    不过很快发现,这个不错,是C#开发的."正宗日志库".

    最后使用这个实现了目标

    它的配置类似于log4net,但是,却没有发现log4net的这个情况.真是救星啊!.

    要实现这3个目录的日志记录器,需要实现3个file目标(就是3个输出到文件的配置),

    配置项目就只有目录不一样,其它的一样,

    这时,把记录器的名字设置成目录的名字,然后再日志路径属性上设置 ${logger},例如

    @"${basedir}/${logger}/${date:format=yyyy}/${date:format=MM}/${shortdate}.log"

    ${basedir} 根目录

    ${logger} 记录器的名字 这里就是 SrvLog DBLog AppLog

    ${date:format=yyyy} 每年一个目录

    ${shortdate}.log       当天时间为名字  2020-10-04.log

    采坑记录:

    为了实现分3个目录,开始时使用字符串替换的方式,修改路径中 ${logger}  这一段目录的值,

    这样做的结果发现,3个日志记录器的所有日志内容,都写到这3个目录的文件里了:

    下面3个记录器,期望是各写各的目录,结果所有内容都写到一起了,每份日志都有这3个记录器的内容.

    这简直就是灾难啊!.然而,偶然发现: 上面的方法后: 在路径中使用变量 ${logger},就不会这样了..

    logSrv.Info(msg);

    logDB.Info(msg);

    log.Info(msg);

    public static class NLogHelp
        {
            /// <summary>
            /// 日志根目录,默认为当前程序运行目录.要在程序部署前设定.
            /// 默认是程序运行目录.(设定新的必须是绝对路径)(例 e:/logs 或者 /home/log)
            /// </summary>
            private static readonly string LogRootPath;
    
            // 3种目录的记录器
            // 1.输出到文件(用于数据库,文件夹 DBLog)
            private static readonly Logger logDB;
            // 2.输出到文件(用于服务,文件夹 SrvLog)
            private static readonly Logger logSrv;
            // 3.输出到文件(用于App,Web,文件夹 AppLog)
            private static readonly Logger log;
    
            // 日志格式说明
            // ${date} 日期,例: 2020/10/03 12:10:01.749
            // ${threadid} 线程ID,例: TID-1
            // ${callsite}/${callsite-linenumber} 类方法行号,例: LibTest.Program.Main/102
            // ${newline} 换行,跨平台的
            // ${message}日志内容
            private static readonly string contLayout = @"${date} TID-${threadid} ${callsite}/${callsite-linenumber}${newline}${message}${newline}${newline}";
    
            // 日志目录文件名,模式
            // ${basedir} 当前应用程序运行根目录.可以修改LogRootPath属性(必须在程序每次部署前)
            // ${data:format=yyyy}每年一个目录
            // ${data:format=MM}每月一个目录
            // 如果日志多,可以继续分下级目录.
            // ${shortdate}.log 以每天年月日做文件名字,2020-10-04.log
            private static readonly string fileNameTpl = @"${basedir}/${logger}/${date:format=yyyy}/${date:format=MM}/${shortdate}.log";
    
            // 日志大小2M上限后,新开文件
            private static readonly long maxFileSize = 2 * 1024 * 1024;
    
            static NLogHelp()
            {
                // 1.根目录检查
                string fileFullPath = fileNameTpl;
                if (!string.IsNullOrWhiteSpace(LogRootPath))
                {
                    fileFullPath = fileNameTpl.Replace("${basedir}", LogRootPath);
                }
    
                // 2.初始化3个文件型日志记录器,区别只在于目录设置不同
                var cfg = new LoggingConfiguration();
                string[] logType = { "SrvLog", "DBLog", "AppLog" };
                for (int i = 0; i < 3; i++)
                {
                    var target = new FileTarget
                    {
                        Name = logType[i],
                        Layout = contLayout,
                        FileName = fileFullPath,
                        ArchiveAboveSize = maxFileSize,
                        // 这个要开启,否则性能极差
                        // https://github.com/NLog/NLog/wiki/File-target
                        KeepFileOpen = true,
                        ConcurrentWrites = false
                    };
                    // 加入到配置器
                    cfg.AddTarget(target);
                    // 级别(Trace,Debug,Info,Warn,Error,Fatal),这里都用Info
                    cfg.AddRule(LogLevel.Info, LogLevel.Info, target);
                }
                // 3.加载配置,生成
                LogManager.Configuration = cfg;
                // 绑定变量
                logSrv = LogManager.GetLogger(logType[0]);
                logDB = LogManager.GetLogger(logType[1]);
                log = LogManager.GetLogger(logType[2]);
            }
    
            public static void SrvLog(string msg)
            {
                logSrv.Info(msg);
            }
            public static void DBLog(string msg)
            {
                logDB.Info(msg);
            }
            public static void Log(string msg)
            {
                log.Info(msg);
            }
        }
    View Code
  • 相关阅读:
    Gates entitled as knight
    the next generation Office
    Beautiful songs
    一则比较搞笑的新闻:【人才】微软招募英才 机会不容错过
    About cmt lint tools
    Alpha's blog open !
    抱歉!
    新闻:IBM的新工具CHIPHOPPER! 一套能够极大扩展Linux应用范围的新工具、新资源和新软件
    读书笔记:Thinking in Java (一)
    The best coders are:/什么是最牛的程序员
  • 原文地址:https://www.cnblogs.com/mirrortom/p/13764802.html
Copyright © 2011-2022 走看看