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
原文地址:https://www.cnblogs.com/mirrortom/p/13764802.html