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
  • 相关阅读:
    leetcode108 Convert Sorted Array to Binary Search Tree
    leetcode98 Validate Binary Search Tree
    leetcode103 Binary Tree Zigzag Level Order Traversal
    leetcode116 Populating Next Right Pointers in Each Node
    Python全栈之路Day15
    Python全栈之路Day11
    集群监控
    Python全栈之路Day10
    自动部署反向代理、web、nfs
    5.Scss的插值
  • 原文地址:https://www.cnblogs.com/mirrortom/p/13764802.html
Copyright © 2011-2022 走看看