经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C# » 查看文章
C# 如何设计一个好用的日志库?【架构篇】
来源:cnblogs  作者:橙子家  时间:2023/4/19 9:02:51  对本文有异议

〇、前言

相信你在实际工作期间经常遇到或听到这样的说法:

“我现在加一下日志,等会儿你再操作下。”

“只有在程序出问题以后才会知道打一个好的日志有多么重要。”

可见日志的记录是日常开发的必备技能。

记录日志的必要性:

当业务比较复杂时,在关键代码附件添加合适的日志是非常重要的,这样可以出现异常后,有章可循,较快速的在不停服的情况下,定位问题并解决。特别是在项目组中,人员较多,若没有统一的日志记录规范,查找系统问题原因就更加费时费力。

记录日志的三种实现:

  1. 当业务比较简单,性能要求不高,只是单纯的记录程序的运行是否正常。此时就可以参考本文第一种实现,仅一种级别的文本记录。
  2. 当业务复杂较复杂,对性能有一定要求时,可以根据实际情况,参考本文的第二、第三种实现。
  3. 当业务非常复杂,必然运行的效率就要求比较高,如何即让程序稳定高效的运行,又能合理记录程序运行状态成为关键。高效的的日志操作可以参考本文的第三种实现。

一、日志的简单记录

如下,为简单的记录开发人员预输出的文本内容,其内容为自定义,输出的时间格式和固定标识需相同。

此方法的性能当然是最差的,针对同一个日志文件,需要独占访问,当同时出现多个记录需求时,会出现排队的情况,导致系统出现卡顿。当然,可以采用多目标文件的方式来提高性能表现,若业务较复杂,还是推荐使用后两种方式。

日志内容测试结果:

  1. public static string strlock = string.Empty;
  2. static void Main(string[] args)
  3. {
  4. lock(strlock) // 在同一个日志文件操作范围添加同一个锁,避免多线程操作时因抢占资源而报错
  5. {
  6. WriteLogPublic.WriteLogFunStr("Program", "Main", "日志内容1");
  7. // 实际生成的路径:C:\Logs\Program\Main\202304\log07.log
  8. // 记录的内容:2023-04-07 11-21-31 --- 日志内容1
  9. }
  10. }

 日志类内容:

  1. public class WriteLogPublic
  2. {
  3. /// <summary>
  4. /// 记录日志
  5. /// </summary>
  6. /// <param name="projectname">项目名称</param>
  7. /// <param name="controllername">控制器名称</param>
  8. /// <param name="strlog">日志内容</param>
  9. public static void WriteLogFunStr(string projectname, string controllername, string strlog)
  10. {
  11. string sFilePath = $"C:\\Logs\\{projectname}\\{controllername}\\{DateTime.Now.ToString("yyyyMM")}"; // 根据项目名称等创建文件夹
  12. string sFileName = $"log{DateTime.Now.ToString("dd")}.log";
  13. sFileName = sFilePath + "\\" + sFileName; // 文件的绝对路径
  14. if (!Directory.Exists(sFilePath)) // 验证路径是否存在
  15. Directory.CreateDirectory(sFilePath); // 不存在则创建
  16. FileStream fs;
  17. StreamWriter sw;
  18. if (File.Exists(sFileName)) // 验证文件是否存在,有则追加,无则创建
  19. fs = new FileStream(sFileName, FileMode.Append, FileAccess.Write);
  20. else
  21. fs = new FileStream(sFileName, FileMode.Create, FileAccess.Write);
  22. sw = new StreamWriter(fs);
  23. sw.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss") + " --- " + strlog);
  24. sw.Close();
  25. fs.Close();
  26. }
  27. }

二、通过开源库 HslCommunication 记录不同级别的日志

此方式记录日志,简单高效,可以实现不同级别日志的输出控制,日志选项的配置可以配置在程序的配置文件中,在程序启动时加载即可。

若想实现实时加载,这只能在每次写日志前初始化日志对象,这样估计就影响程序性能了。

日志内容测试结果:

  1. static void Main(string[] args)
  2. {
  3. // 先初始化配置 HslCommunicationOper
  4. HslCommunicationOper.HslComLogCollection("Test.ConsoleApp", "Main", 5, HslCommunication.LogNet.GenerateMode.ByEveryHour);
  5. // HslCommunicationOper.HslComLog("Test.ConsoleApp", "Main"); // 单文件
  6. // HslCommunicationOper.HslComLogSize("Test.ConsoleApp", "MainSize", 5); // 增加日志单文件大小配置
  7. // HslCommunicationOper.HslComLogByDate("Test.ConsoleApp", "MainDate", TimeType.Day); // 按照日期分文件保存
  8. HslCommunicationOper.SetMessageDegree(MessageDegree.WARN);//日志级别
  9. // 记录日志
  10. HslCommunicationOper.logNet.WriteDebug("调试信息");
  11. HslCommunicationOper.logNet.WriteInfo("一般信息");
  12. HslCommunicationOper.logNet.WriteWarn("警告信息");
  13. HslCommunicationOper.logNet.WriteError("错误信息");
  14. HslCommunicationOper.logNet.WriteFatal("致命信息");
  15. HslCommunicationOper.logNet.WriteDebug("KeyWord调试信息", "调试信息");
  16. HslCommunicationOper.logNet.WriteInfo("KeyWord一般信息", "一般信息");
  17. HslCommunicationOper.logNet.WriteWarn("KeyWord警告信息", "警告信息");
  18. HslCommunicationOper.logNet.WriteError("KeyWord错误信息", "错误信息");
  19. HslCommunicationOper.logNet.WriteFatal("KeyWord致命信息", "致命信息");
  20. HslCommunicationOper.logNet.WriteException("KeyWord-WriteException", new IndexOutOfRangeException());
  21. HslCommunicationOper.logNet.WriteDebug("调试信息");
  22. HslCommunicationOper.logNet.WriteInfo("一般信息");
  23. HslCommunicationOper.logNet.WriteWarn("警告信息");
  24. HslCommunicationOper.logNet.WriteError("错误信息");
  25. HslCommunicationOper.logNet.WriteFatal("致命信息");
  26. }
  27. // 日志输出格式示例:
  28. [警告] 2023-04-07 18:22:03.565 Thread:[001] 警告信息
  29. [错误] 2023-04-07 18:22:03.605 Thread:[001] 错误信息
  30. [致命] 2023-04-07 18:22:03.605 Thread:[001] 致命信息
  31. [警告] 2023-04-07 18:22:03.605 Thread:[001] KeyWord警告信息 : 警告信息
  32. [错误] 2023-04-07 18:22:03.605 Thread:[001] KeyWord错误信息 : 错误信息
  33. [致命] 2023-04-07 18:22:03.605 Thread:[001] KeyWord致命信息 : 致命信息
  34. [致命] 2023-04-07 18:22:03.676 Thread:[001] KeyWord-WriteException : 错误信息:Index was outside the bounds of the array.
  35. 错误源:
  36. 错误堆栈:
  37. 错误类型:System.IndexOutOfRangeException
  38. 错误方法:
  39. /=================================================[ Exception ]================================================/
  40. [警告] 2023-04-07 18:22:03.676 Thread:[001] 警告信息
  41. [错误] 2023-04-07 18:22:03.676 Thread:[001] 错误信息
  42. [致命] 2023-04-07 18:22:03.676 Thread:[001] 致命信息

三个相关日志类:

  • HslCommunicationOper:操作类;
  • LogNetCollection:扩展类(提供日志文件的大小、生成新文件频率的配置);
  • MessageDegree:消息级别枚举。
  1. public static class HslCommunicationOper
  2. {
  3. public static ILogNet logNet = null;
  4. /// <summary>
  5. /// 日志文件根目录
  6. /// </summary>
  7. public static string rootpath = "C:\\Log";
  8. /// <summary>
  9. /// 单日志文件存储
  10. /// </summary>
  11. /// <param name="projectname"></param>
  12. /// <param name="opername">日志文件名</param>
  13. public static void HslComLog(string projectname, string opername)
  14. {
  15. logNet = new LogNetSingle($"{rootpath}\\{projectname}\\{opername}.txt");
  16. logNet.SetMessageDegree(HslMessageDegree.DEBUG); // 默认存储最低级别为 DEBUG
  17. }
  18. /// <summary>
  19. /// 限定日志文件大小
  20. /// </summary>
  21. /// <param name="projectname"></param>
  22. /// <param name="opername">日志上级文件夹名</param>
  23. /// <param name="logfilesize">日志文件大小(单位:M) 1~20,默认 5</param>
  24. public static void HslComLogSize(string projectname, string opername, int logfilesize = 5)
  25. {
  26. if (logfilesize < 1 || logfilesize > 20)
  27. logfilesize = 5;
  28. logNet = new LogNetFileSize($"{rootpath}\\{projectname}\\{opername}", logfilesize * 1024 * 1024); // 单位M(5M):5 * 1024 * 1024
  29. logNet.SetMessageDegree(HslMessageDegree.DEBUG); // 默认存储最低级别为 DEBUG
  30. }
  31. /// <summary>
  32. /// 按照日期存储
  33. /// </summary>
  34. /// <param name="projectname"></param>
  35. /// <param name="opername">日志上级文件夹名</param>
  36. /// <param name="recodemode">传入枚举类型(TimeType),值范围:Minute、Hour、Day、Month、Season、Year</param>
  37. public static void HslComLogByDate(string projectname, string opername, GenerateMode generateMode = GenerateMode.ByEveryDay)
  38. {
  39. logNet = new LogNetDateTime($"{rootpath}\\{projectname}\\{opername}", generateMode); // 按每天
  40. logNet.SetMessageDegree(HslMessageDegree.DEBUG); // 默认存储最低级别为 DEBUG
  41. }
  42. /// <summary>
  43. /// 按照文件或日期存储
  44. /// </summary>
  45. /// <param name="projectname"></param>
  46. /// <param name="opername">日志上级文件夹名</param>
  47. /// <param name="generateMode">传入枚举类型 GenerateMode</param>
  48. public static void HslComLogCollection(string projectname, string opername, int filesize, GenerateMode generateMode = GenerateMode.ByEveryDay)
  49. {
  50. logNet = new LogNetCollection($"{rootpath}\\{projectname}\\{opername}", filesize * 1024 * 1024, generateMode);
  51. logNet.SetMessageDegree(HslMessageDegree.DEBUG); // 默认存储最低级别为 DEBUG
  52. }
  53. /// <summary>
  54. /// 单独配置日志级别
  55. /// </summary>
  56. /// <param name="messageDegree">默认 DEBUG</param>
  57. public static void SetMessageDegree(MessageDegree messageDegree = MessageDegree.DEBUG)
  58. {
  59. switch (messageDegree)
  60. {
  61. case MessageDegree.DEBUG:
  62. logNet.SetMessageDegree(HslMessageDegree.DEBUG); // 所有等级存储
  63. break;
  64. case MessageDegree.INFO:
  65. logNet.SetMessageDegree(HslMessageDegree.INFO); // 除 DEBUG 外,都存储
  66. break;
  67. case MessageDegree.WARN:
  68. logNet.SetMessageDegree(HslMessageDegree.WARN); // 除 DEBUG 和 INFO 外,都存储
  69. break;
  70. case MessageDegree.ERROR:
  71. logNet.SetMessageDegree(HslMessageDegree.ERROR); // 只存储 ERROR 和 FATAL
  72. break;
  73. case MessageDegree.FATAL:
  74. logNet.SetMessageDegree(HslMessageDegree.FATAL); // 只存储 FATAL
  75. break;
  76. case MessageDegree.None:
  77. logNet.SetMessageDegree(HslMessageDegree.None); // 不存储任何等级
  78. break;
  79. }
  80. }
  81. }
  1. public class LogNetCollection : LogPathBase, ILogNet, IDisposable
  2. {
  3. private int fileMaxSize = 10485760; // 默认 10M
  4. private int currentFileSize = 0;
  5. private GenerateMode generateMode = GenerateMode.ByEveryYear;
  6. public LogNetCollection(string filePath, int fileMaxSize = 10485760, GenerateMode generateMode = GenerateMode.ByEveryDay, int fileQuantity = -1)
  7. {
  8. base.filePath = filePath;
  9. this.fileMaxSize = fileMaxSize;
  10. this.generateMode = generateMode;
  11. controlFileQuantity = fileQuantity;
  12. base.LogSaveMode = LogSaveMode.FileFixedSize;
  13. if (!string.IsNullOrEmpty(filePath) && !Directory.Exists(filePath))
  14. {
  15. Directory.CreateDirectory(filePath);
  16. }
  17. }
  18. protected override string GetFileSaveName()
  19. {
  20. if (string.IsNullOrEmpty(filePath))
  21. {
  22. return string.Empty;
  23. }
  24. if (string.IsNullOrEmpty(fileName))
  25. {
  26. fileName = GetLastAccessFileName();
  27. }
  28. if (File.Exists(fileName))
  29. {
  30. FileInfo fileInfo = new FileInfo(fileName);
  31. if (fileInfo.Length > fileMaxSize)
  32. {
  33. fileName = GetDefaultFileName();
  34. }
  35. else
  36. {
  37. currentFileSize = (int)fileInfo.Length;
  38. }
  39. }
  40. return fileName;
  41. }
  42. private string GetLastAccessFileName()
  43. {
  44. string[] existLogFileNames = GetExistLogFileNames();
  45. foreach (string result in existLogFileNames)
  46. {
  47. FileInfo fileInfo = new FileInfo(result);
  48. if (fileInfo.Length < fileMaxSize) // 判断已创建的日志文件是否达到最大内存
  49. {
  50. currentFileSize = (int)fileInfo.Length;
  51. return result;
  52. }
  53. }
  54. return GetDefaultFileName(); // 若未创建过,通过指定方式创建
  55. }
  56. private string GetDefaultFileName()
  57. {
  58. switch (generateMode)
  59. {
  60. case GenerateMode.ByEveryMinute:
  61. return Path.Combine(filePath, "Logs_" + DateTime.Now.ToString("yyyyMMdd_HHmm") + ".txt");
  62. case GenerateMode.ByEveryHour:
  63. return Path.Combine(filePath, "Logs_" + DateTime.Now.ToString("yyyyMMdd_HH") + ".txt");
  64. case GenerateMode.ByEveryDay:
  65. return Path.Combine(filePath, "Logs_" + DateTime.Now.ToString("yyyyMMdd") + ".txt");
  66. case GenerateMode.ByEveryWeek:
  67. {
  68. GregorianCalendar gregorianCalendar = new GregorianCalendar();
  69. int weekOfYear = gregorianCalendar.GetWeekOfYear(DateTime.Now, CalendarWeekRule.FirstDay, DayOfWeek.Monday);
  70. return Path.Combine(filePath, "Logs_" + DateTime.Now.Year + "_W" + weekOfYear + ".txt");
  71. }
  72. case GenerateMode.ByEveryMonth:
  73. return Path.Combine(filePath, "Logs_" + DateTime.Now.ToString("yyyy_MM") + ".txt");
  74. case GenerateMode.ByEverySeason:
  75. return Path.Combine(filePath, "Logs_" + DateTime.Now.Year + "_Q" + (DateTime.Now.Month / 3 + 1) + ".txt");
  76. case GenerateMode.ByEveryYear:
  77. return Path.Combine(filePath, "Logs_" + DateTime.Now.Year + ".txt");
  78. default:
  79. return string.Empty;
  80. }
  81. }
  82. public override string ToString()
  83. {
  84. return $"LogNetFileSize[{fileMaxSize}];LogNetDateTime[{generateMode}]";
  85. }
  86. }
  1. /// <summary>
  2. /// 消息级别
  3. /// </summary>
  4. public enum MessageDegree
  5. {
  6. DEBUG = 1,
  7. INFO = 2,
  8. WARN = 3,
  9. ERROR = 4,
  10. FATAL = 5,
  11. None = 9
  12. }

??参考:C# 日志记录分级功能使用 按照日期,大小,或是单文件存储

三、通过开源库 NLog 实现通过配置文件配置日志选项

 NLog 是一个基于 .net 平台编写的日志记录类库,我们可以使用 NLog 在应用程序中添加极为完善的跟踪调试代码。

 本文将通过日志框架 Nlog 和 ConcurrentQueue 队列,实现一个高性能的日志库。

 首先,为什么相中了 Nlog ?

  • NLog 是适用于各个 .net 平台的灵活且免费的日志记录平台。通过 NLog, 可以轻松地写入多个目标(例如:数据库、文件、控制台等), 并可动态更改日志记录配置信息。
  • NLog 支持结构化和传统日志记录。
  • NLog 的特点: 高性能、易于使用、易于扩展和灵活配置。

ConcurrentQueue:表示线程安全的先进先出(FIFO)集合。所有公共成员和受保护成员 ConcurrentQueue<T> 都是线程安全的,可以从多个线程并发使用。

1. 配置文件

对于 ASP.NET 应用程序,存在嵌入程序配置文件和单独配置文件两种方式,程序在启动时,会在应用程序主目录下依次查找:web.config(*.exe.config、*.web.config)、web.nlog(*.exe.nlog)、NLog.config

个人推荐单独文件配置,便于修改和迭代使用。

第一种方式:单独配置文件

常用名称为 NLog.config。此时需要在根节点 nlog 加上智能感知(Intellisense)的属性配置,详见下文配置文件 XML 代码。

1/5 targets(必须有) - 定义日志目标/输出

  • name:是指的输出地方的一个名词(给 rules 调用的);
  • xsi:type:输出文件的类型,File 指的是文件,Console 控制台输出;
  • fileName:输出到目标文件的地址,使用的相对路径,可以自行配置输出的地点。
  • layout:在最简单的形式中,布局是带有嵌入标记的文本,这些嵌入标记样子例如:${xxxx};
  • archiveFileName:表示滚动日志存放路径;
  • archiveAboveSize:单次日志的存储大小(单位是Byte),超过配置,会 archiveFileName 中创建新的日志;
  • archiveNumbering:Sequence(排序),Rolling(滚动);
  • concurrentWrites:支持多个并发一起写文件,提高文件写入性能;
  • keepFileOpen:为了提高文件写入性能,避免每次写入文件都开关文件;
  • autoFlush:为了提高日志写入性能,不必每次写入日志都直接写入到硬盘;
  • header/footer:给每个日志文件添加头/尾的固定内容;

其中,layout 属性的标记变量(${xxx})解析可以参考以下代码:

点击展开 查看标记释义
  1. ${cached} - 将缓存应用于另一个布局输出。
  2. ${db-null} - 为数据库呈现 DbNull
  3. ${exception} - 通过调用记录器方法之一提供的异常信息
  4. ${level} - 日志级别(例如错误、调试)或级别序号(数字)
  5. ${literal} - 字符串 literal。(文本) - 用于转义括号
  6. ${logger} - 记录器名称。GetLogger GetCurrentClassLogger
  7. ${message} - (格式化的)日志消息。
  8. ${newline} - 换行符文字。
  9. ${object-path} - 呈现对象的(嵌套)属性
  10. ${onexception} - 仅在为日志消息定义了异常时才输出内部布局。
  11. ${onhasproperties} - 仅当事件属性包含在日志事件中时才输出内部布局。
  12. ${var} - 渲染变量
  13. // 调用站点和堆栈跟踪
  14. ${callite} - 调用站点(类名、方法名和源信息)
  15. ${callite-filename} - 调用站点源文件名。
  16. ${callsite-linenumber} - 呼叫站点源行编号。
  17. ${stacktrace} - Render the Stack trace
  18. // 条件
  19. ${when} - 仅在满足指定条件时输出内部布局。
  20. ${whenempty} - 当内部布局生成空结果时输出备用布局。
  21. // 上下文信息
  22. ${activity} - System.Diagnostics.Activity.Current NLog.DiagnosticSource External 捕获跟踪上下文
  23. ${activityid} - System.Diagnostics 跟踪关联 ID 放入日志中。
  24. ${all-event-properties} - 记录所有事件上下文数据。
  25. ${event-context} - 记录事件属性数据 - 替换为 ${事件属性}
  26. ${event-properties} - 记录事件属性数据 - 重命名 ${事件-上下文}
  27. ${gdc} - 全局诊断上下文项。用于保存每个应用程序实例值的字典结构。
  28. ${install-context} - 安装参数(传递给 InstallNLogConfig)。
  29. ${mdc} - 映射的诊断上下文 - 线程本地结构。
  30. ${mdlc} - 异步映射诊断上下文 - 作用域内上下文的线程本地结构。MDC 的异步版本。
  31. ${ndc} - 嵌套诊断上下文 - 线程本地结构。
  32. ${ndlc} - 异步嵌套诊断上下文 - 线程本地结构。
  33. // 计数器
  34. ${counter} - 计数器值(在每次布局呈现时增加)
  35. ${guid} - 全局唯一标识符(GUID)。
  36. ${sequenceid} - 日志序列 ID
  37. // 日期和时间
  38. ${date} - 当前日期和时间。
  39. ${longdate} - 日期和时间采用长而可排序的格式"yyyy-MM-dd HH:mm:ss.ffff"
  40. ${qpc} - 高精度计时器,基于从 QueryPerformanceCounter 返回的值。
  41. ${shortdate} - 可排序格式为 yyyy-MM-dd 的短日期。
  42. ${ticks} - 当前日期和时间的分笔报价值。
  43. ${time} - 24 小时可排序格式的时间 HHmmss.mmm
  44. // 编码和字符串转换
  45. ${json-encode} - 使用 JSON 规则转义另一个布局的输出。
  46. ${left} - 文本的剩余部分
  47. ${lowercase} - 将另一个布局输出的结果转换为小写。
  48. ${norawvalue} - 防止将另一个布局呈现器的输出视为原始值
  49. ${pad} - 将填充应用于另一个布局输出。
  50. ${replace} - 将另一个布局输出中的字符串替换为另一个字符串。使用正则表达式可选
  51. ${replace-newlines} - 将换行符替换为另一个字符串。
  52. ${right} - 文本的右侧部分
  53. ${rot13} - 使用 ROT-13 解码"加密"的文本。
  54. ${substring} - 文本的子字符串
  55. ${trim-whitespace} - 从另一个布局呈现器的结果中修剪空格。
  56. ${uppercase} - 将另一个布局输出的结果转换为大写。
  57. ${url-encode} - 对另一个布局输出的结果进行编码,以便与 URL 一起使用。
  58. ${wrapline} - 以指定的行长度换行另一个布局输出的结果。
  59. ${xml-encode} - 将另一个布局输出的结果转换为符合 XML 标准。
  60. // 环境和配置文件
  61. ${appsetting} - 来自 .config 文件 NLog.Extended 的应用程序配置设置
  62. ${configsetting} - 来自 appsettings.json 的值或 ASP.NET Core & .NET Core NLog.Extensions.LoggingNLog.Extensions.HostingNLog.Web.AspNetCore
  63. ${environment} - 环境变量。(例如 PATHOSVersion
  64. ${environment-user} - 用户标识信息(用户名)。
  65. ${registry} - 来自 Windows 注册表的值。
  66. // 文件和目录
  67. ${basedir} - 当前应用程序域的基目录。
  68. ${currentdir} - 应用程序的当前工作目录。
  69. ${dir-separator} - 操作系统相关目录分隔符。
  70. ${file-contents} - 呈现指定文件的内容。
  71. ${filesystem-normalize} - 通过将文件名中不允许使用的字符替换为安全字符来筛选它们。
  72. ${nlogdir} - NLog.dll所在的目录。
  73. ${processdir} - 应用程序的可执行进程目录。
  74. ${specialfolder} - 系统特殊文件夹路径(包括"我的文档""我的音乐""程序文件""桌面"等)。
  75. ${tempdir} - 一个临时目录。
  76. // 身份
  77. ${identity} - 线程标识信息(名称和身份验证信息)。
  78. ${windows-identity} - Thread Windows identity information username
  79. ${windows-identity} - Thread Windows identity information username Nlog.WindowsIdentity
  80. // 集成
  81. ${gelf} - LogEvents 转换为 GELF 格式以发送到 Graylog NLog.GelfLayout External
  82. ${log4jxmlevent} - XML 事件描述与 log4jChainsaw NLogViewer 兼容。
  83. // 进程、线程和程序集
  84. ${appdomain} - 当前应用域。
  85. ${assembly-version} - 默认应用程序域中可执行文件的版本。
  86. ${gc} - 有关垃圾回收器的信息。
  87. ${hostname} - 运行进程的计算机的主机名。
  88. ${local-ip} - 来自网络接口的本地 IP 地址。
  89. ${machinename} - 运行进程的计算机名称。
  90. ${performancecounter} - 性能计数器。
  91. ${processid} - 当前进程的标识符。
  92. ${processinfo} - 有关正在运行的进程的信息,例如 StartTimePagedMemorySize
  93. ${processname} - 当前进程的名称。
  94. ${processtime} - 格式为 HHmmss.mmm 的处理时间。
  95. ${threadid} - 当前线程的标识符。
  96. ${threadname} - 当前线程的名称。
  97. // 银光
  98. ${document-uri} - 承载当前 Silverlight 应用程序的 HTML 页面的 URI
  99. ${sl-appinfo} - 有关 Silverlight 应用程序的信息。
  100. // 网络、ASP.NET 和 ASP.NET 核心
  101. ${aspnet-appbasepath} - ASP.NET Application base path Content Root NLog.WebNLog.Web.AspNetCore
  102. ${aspnet-application} - ASP.NET Application variable. NLog.Web
  103. ${aspnet-environment} - ASP.NET Environment name NLog.Web.AspNetCore
  104. ${aspnet-item} - ASP.NET 'HttpContext' item variable. NLog.WebNLog.Web.AspNetCore
  105. ${aspnet-mvc-action} - ASP.NET MVC Action Name from routing parameters NLog.WebNLog.Web.AspNetCore
  106. ${aspnet-mvc-controller} - ASP.NET MVC Controller Name from routing parameters NLog.WebNLog.Web.AspNetCore
  107. ${aspnet-request} - ASP.NET Request variable. NLog.WebNLog.Web.AspNetCore
  108. ${aspnet-request-contenttype} - ASP.NET Content-Type header Ex. application/json NLog.Web.AspNetCore
  109. ${aspnet-request-cookie} - ASP.NET Request cookie content. NLog.WebNLog.Web.AspNetCore
  110. ${aspnet-request-form} - ASP.NET Request form content. NLog.WebNLog.Web.AspNetCore
  111. ${aspnet-request-headers} - ASP.NET Header key/value pairs. NLog.Web.Web.AspNetCore
  112. ${aspnet-request-host} - ASP.NET Request host. NLog.WebNLog.Web.AspNetCore
  113. ${aspnet-request-ip} - Client IP. NLog.WebNLog.Web.AspNetCore
  114. ${aspnet-request-method} - ASP.NET Request method GET POST etc). NLog.WebNLog.Web.AspNetCore
  115. ${aspnet-request-posted-body} - ASP.NET posted body / payload NLog.WebNLog.Web.AspNetCore
  116. ${aspnet-request-querystring} - ASP.NET Request querystring. NLog.WebNLog.Web.AspNetCore
  117. ${aspnet-request-referrer} - ASP.NET Request referrer. NLog.WebNLog.Web.AspNetCore
  118. ${aspnet-request-routeparameters} - ASP.NET Request route parameters. NLog.WebNLog.Web.AspNetCore
  119. ${aspnet-request-url} - ASP.NET Request URL. NLog.WebNLog.Web.AspNetCore
  120. ${aspnet-request-useragent} - ASP.NET Request useragent. NLog.WebNLog.Web.AspNetCore
  121. ${aspnet-response-statuscode} - ASP.NET Response status code content. NLog.WebNLog.Web.AspNetCore
  122. ${aspnet-session} - ASP.NET Session variable. NLog.WebNLog.Web.AspNetCore
  123. ${aspnet-sessionid} - ASP.NET Session ID variable. NLog.WebNLog.Web.AspNetCore
  124. ${aspnet-traceidentifier} - ASP.NET trace identifier NLog.WebNLog.Web.AspNetCore
  125. ${aspnet-user-authtype} - ASP.NET User auth. NLog.WebNLog.Web.AspNetCore
  126. ${aspnet-user-claim} - ASP.NET User Claims 授权值 NLog.Web.AspNetCore
  127. ${aspnet-user-identity} - ASP.NET User variable. NLog.WebNLog.Web.AspNetCore
  128. ${aspnet-user-isauthenticated} - ASP.NET User authenticated NLog.WebNLog.Web.AspNetCore
  129. ${aspnet-webrootpath} - ASP.NET Web root path wwwroot NLog.WebNLog.Web.AspNetCore
  130. ${iis-site-name} - IIS site name. NLog.WebNLog.Web.AspNetCore
  131. //参考: https://www.cnblogs.com/zmy2020/p/15936886.html

2/5 rules(必须有) - 定义日志路由规则

rules 下只有一种节点 logger(可同时配置多个),其属性释义如下:

  • name:logger 名称,若为 * 则表示适用于所有日志,?:匹配单个字符;
  • minlevel:表示记录的最低日志级别,只有大于等于该日志级别才会被记录;
  • maxlevel:记录的最高级别;
  • level:单极记录,只记录一个级别日志;
  • levels:同时记录多个级别的日志,用逗号分隔;
  • writeTo:和 target 节点的 name 属性值匹配,一个 rules 对应一个 target;
  • enabled:通过值为 false 禁用规则,而不用删除;
  • ruleName:规则标识符,允许使用 Configuration.FindRuleByName 和进行规则查找 Configuration.RemoveRuleByName,在 NLog 4.6.4 中引入。

3/5 variables - 声明变量的值

variable 元素定义了配置文件中需要用到的变量,一般用来表示复杂或者重复的表达式(例如文件名)。变量需要先定义后使用,否则配置文件将初始化失败。

  • name:变量名;
  • value:变量值。

定义变量之后,可以通过 ${my_name} 语法来使用。

??4/5 extensions - 定义要加载的 NLog 扩展项 *.dll 文件

extensions 节点可以添加额外的 NLog 元包或自定义功能,assembly 属性指定的被包含程序集不带后缀 .dll 。示例如下:

  1. <nlog>
  2. <extensions>
  3. <add assembly="MyAssembly" />
  4. </extensions>
  5. <targets>
  6. <target name="a1" type="MyFirst" host="localhost" />
  7. </targets>
  8. <rules>
  9. <logger name="*" minLevel="Info" appendTo="a1" />
  10. </rules>
  11. </nlog>

NLog 4.0 之后,与 NLog.dll 同目录下名如 NLog*.dll 的程序集(如:NLog.CustomTarget.dll)会被自动加载。

5/5 includes - 指定当前配置文件包含多个子配置文件

通过 ${} 语法可以使用环境变量,下例展示包含一个名为当前机器名的配置文件。

  1. <nlog>
  2. ...
  3. <include file="${machinename}.config" />
  4. ...
  5. </nlog>

NLog 4.4.2 之后可以使用通配符 * 指定多个文件。例如:<include file="nlog-*.config" />

示例配置:

  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.nlog-project.org/schemas/NLog.xsd NLog.xsd"
  5. autoReload="true"
  6. throwExceptions="false"
  7. internalLogLevel="Off" internalLogFile="c:\temp\nlog-internal.log">
  8. <variable name="appName" value="ConsoleAppDemo"/>
  9. <targets>
  10. <target name="logconsole" xsi:type="Console"
  11. layout="${longdate} [${uppercase:${level}}] ${callsite}(${callsite-filename:includeSourcePath=False}:${callsite-linenumber}) - ${message} ${exception:format=ToString}"
  12. />
  13. <target name="logfile"
  14. xsi:type="File"
  15. fileName="${basedir}/logs/${appName}-${shortdate}.log"
  16. layout="${longdate} [${uppercase:${level}}] ${callsite}(${callsite-filename:includeSourcePath=False}:${callsite-linenumber}) - ${message} ${exception:format=ToString}"
  17. maxArchiveFiles="999"
  18. archiveFileName="${basedir}/logs/${appName}-${shortdate}-${###}.log"
  19. createDirs="true"
  20. archiveAboveSize="102400"
  21. archiveEvery="Day"
  22. encoding="UTF-8"
  23. />
  24. </targets>
  25. <rules>
  26. <logger name="*" minlevel="Debug" writeTo="logfile" />
  27. </rules>
  28. </nlog>

参考:完善 .Net Core 项目 — NLog入门 (日志组件)

第二种方式:嵌入程序配置文件

NLog 配置信息可以嵌入在 .net 应用程序自身的配置文件中,例如 *.exe.config 或者 *.web.config 中,需要使用 configSections 节点配置,如下 XML 代码,再将其他配置填入 nlog 节点即可。

nlog 节点内的内容,参考前边‘第一种方式’。

  1. <configuration>
  2. <configSections>
  3. <section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog"/>
  4. </configSections>
  5. <nlog>
  6. ......
  7. </nlog>
  8. </configuration>

2. 测试代码

  1. static void Main(string[] args)
  2. {
  3. try
  4. {
  5. LoggerHelper._.Info($"完成");
  6. LoggerHelper._.Debug($"Debug完成");
  7. LoggerHelper._.Error($"Error完成");
  8. throw (new Exception());
  9. }
  10. catch (Exception ex)
  11. {
  12. LoggerHelper._.Error(ex.Message);
  13. }
  14. }
  15. // 输出日志
  16. 2023-04-04 17:14:45.6651 [INFO] YOKAVerse.Net.Log.LoggerHelper.Info(Logger.cs:40) - 完成
  17. 2023-04-04 17:14:46.7303 [DEBUG] YOKAVerse.Net.Log.LoggerHelper.Debug(Logger.cs:28) - Debug完成
  18. 2023-04-04 17:14:47.2924 [ERROR] YOKAVerse.Net.Log.LoggerHelper.Error(Logger.cs:76) - Error完成
  19. 2023-04-04 17:14:49.5869 [ERROR] YOKAVerse.Net.Log.LoggerHelper.Error(Logger.cs:76) - Exception of type 'System.Exception' was thrown.

3. 日志记录类

后续跟新内容:再次感谢评论区的大佬们,博主已经意识到实际上 Nlog 本身已经足够强大,本身就支持队列缓存,此部分就画蛇添足了,不建议使用!

以下代码对 NLog 进行了封装,将日志记录先存在线程安全的队列里,以避免调用写入文件时 I/O 的耗时操作拖垮应用程序

队列有两个,一个是操作队列-concurrentQueue_operation,一个是助手队列-concurrentQueue_assistant,程序中的日志记录需求直接写入助手队列,避免影响程序频繁写入造成的系统等待。当操作队列中的记录处理完成后,再将助手队列的记录转至操作队列,继续进行比较耗时的写入操作。

当然这种方法在提高系统响应速度的同时,也存在一个弊端,就是在程序崩溃而异常退出时,可能造成积压在队列中的日志记录未全部完成落地,导致日志内容丢失。所以使用时还请权衡利弊,慎重使用。

此部分代码额外添加了 ConcurrentQueue 支持,效果不及 NLog async="true" 的异步方式,不建议参考
  1. public class LoggerHelper
  2. {
  3. /// <summary>
  4. /// 实例化nLog,即为获取配置文件相关信息(获取以当前正在初始化的类命名的记录器)
  5. /// </summary>
  6. private readonly NLog.Logger logger = LogManager.GetCurrentClassLogger();
  7. private static LoggerHelper _obj;
  8. /// <summary>
  9. /// 辅助队列
  10. /// </summary>
  11. private static ConcurrentQueue<LogModel> concurrentQueue_assistant = new ConcurrentQueue<LogModel>();
  12. /// <summary>
  13. /// 操作队列
  14. /// </summary>
  15. private static ConcurrentQueue<LogModel> concurrentQueue_operation = new ConcurrentQueue<LogModel>();
  16. private static string lockobj_assistant = string.Empty;
  17. private static string lockobj_operation = string.Empty;
  18. public static LoggerHelper LHR
  19. {
  20. get => _obj ?? (_obj = new LoggerHelper());
  21. set => _obj = value;
  22. }
  23. public LoggerHelper()
  24. {
  25. InitializeTask();
  26. }
  27. private static LogModel logModel_init = null;
  28. /// <summary>
  29. /// 初始化后台线程
  30. /// </summary>
  31. private void InitializeTask()
  32. {
  33. if (logModel_init == null)
  34. {
  35. logModel_init = new LogModel();
  36. Thread t = new Thread(new ThreadStart(LogOperation));
  37. t.IsBackground = false;
  38. t.Start();
  39. }
  40. }
  41. /// <summary>
  42. /// 记录日志
  43. /// </summary>
  44. private void LogOperation()
  45. {
  46. while (true) // 线程持续处理
  47. {
  48. if (concurrentQueue_assistant.Count > 0 && concurrentQueue_operation.Count == 0)
  49. {
  50. lock (lockobj_assistant)
  51. {
  52. concurrentQueue_operation = concurrentQueue_assistant; // 将数据转至操作队列
  53. concurrentQueue_assistant = new ConcurrentQueue<LogModel>(); // 注意此处不可用 .Clear() 因为 ConcurrentQueue<T> 为引用类型
  54. }
  55. LogModel logModel;
  56. // 取出队列 concurrentQueue_operation 中待写入的日志记录,直至全部记录完成
  57. while (concurrentQueue_operation.Count > 0 && concurrentQueue_operation.TryDequeue(out logModel))
  58. {
  59. switch (logModel.type) // 日志类型分流
  60. {
  61. case NLogLevel.Trace:
  62. if (logModel.exobj != null)
  63. logger.Trace(logModel.content);
  64. else
  65. logger.Trace(logModel.content, logModel.exobj);
  66. break;
  67. case NLogLevel.Debug:
  68. if (logModel.exobj != null)
  69. logger.Debug(logModel.content);
  70. else
  71. logger.Debug(logModel.content, logModel.exobj);
  72. break;
  73. case NLogLevel.Info:
  74. if (logModel.exobj != null)
  75. logger.Info(logModel.content, logModel.exobj);
  76. else
  77. logger.Info(logModel.content);
  78. break;
  79. case NLogLevel.Error:
  80. if (logModel.exobj != null)
  81. logger.Error(logModel.content, logModel.exobj);
  82. else
  83. logger.Error(logModel.content);
  84. break;
  85. case NLogLevel.Warn:
  86. if (logModel.exobj != null)
  87. logger.Warn(logModel.content, logModel.exobj);
  88. else
  89. logger.Warn(logModel.content);
  90. break;
  91. case NLogLevel.Fatal:
  92. if (logModel.exobj != null)
  93. logger.Fatal(logModel.content, logModel.exobj);
  94. else
  95. logger.Fatal(logModel.content);
  96. break;
  97. default:
  98. break;
  99. }
  100. }
  101. }
  102. else
  103. Thread.Sleep(1000);
  104. }
  105. }
  106. /// <summary>
  107. /// 加入队列前,根据日志级别统一验证
  108. /// </summary>
  109. /// <param name="logModel"></param>
  110. public void EnqueueLogModel(LogModel logModel)
  111. {
  112. if ((logModel.type == NLogLevel.Trace && logger.IsTraceEnabled) || (logModel.type == NLogLevel.Debug && logger.IsDebugEnabled)
  113. || (logModel.type == NLogLevel.Info && logger.IsInfoEnabled) || (logModel.type == NLogLevel.Warn && logger.IsWarnEnabled)
  114. || (logModel.type == NLogLevel.Error && logger.IsErrorEnabled) || (logModel.type == NLogLevel.Fatal && logger.IsFatalEnabled))
  115. {
  116. lock (lockobj_assistant)
  117. {
  118. concurrentQueue_assistant.Enqueue(logModel);
  119. }
  120. }
  121. }
  122. /// <summary>
  123. /// Trace,追踪,非常详细的日志,该日志等级通常仅在开发过程中被使用
  124. /// </summary>
  125. /// <param name="msg"></param>
  126. public void Trace(string logcontent)
  127. {
  128. EnqueueLogModel(new LogModel() { type = NLogLevel.Trace, content = logcontent });
  129. }
  130. public void Trace(string logcontent, Exception exception)
  131. {
  132. EnqueueLogModel(new LogModel() { type = NLogLevel.Trace, content = logcontent, exobj = exception });
  133. }
  134. /// <summary>
  135. /// Debug,调试,详尽信息次于 Trace,在生产环境中通常不启用
  136. /// </summary>
  137. /// <param name="msg"></param>
  138. public void Debug(string logcontent)
  139. {
  140. EnqueueLogModel(new LogModel() { type = NLogLevel.Debug, content = logcontent });
  141. }
  142. public void Debug(string logcontent, Exception exception)
  143. {
  144. EnqueueLogModel(new LogModel() { type = NLogLevel.Debug, content = logcontent, exobj = exception });
  145. }
  146. /// <summary>
  147. /// Info,信息,通常在生产环境中通常启用
  148. /// </summary>
  149. /// <param name="msg"></param>
  150. public void Info(string logcontent)
  151. {
  152. EnqueueLogModel(new LogModel() { type = NLogLevel.Info, content = logcontent });
  153. }
  154. public void Info(string logcontent, Exception exception)
  155. {
  156. EnqueueLogModel(new LogModel() { type = NLogLevel.Info, content = logcontent, exobj = exception });
  157. }
  158. /// <summary>
  159. /// Warn,警告,通常用于非关键问题,这些问题可以恢复,或者是暂时的故障
  160. /// </summary>
  161. /// <param name="msg"></param>
  162. public void Warn(string logcontent)
  163. {
  164. EnqueueLogModel(new LogModel() { type = NLogLevel.Warn, content = logcontent });
  165. }
  166. public void Warn(string logcontent, Exception exception)
  167. {
  168. EnqueueLogModel(new LogModel() { type = NLogLevel.Warn, content = logcontent, exobj = exception });
  169. }
  170. /// <summary>
  171. /// Error,错误,多数情况下记录Exceptions(异常)信息
  172. /// </summary>
  173. /// <param name="msg"></param>
  174. public void Error(string logcontent)
  175. {
  176. EnqueueLogModel(new LogModel() { type = NLogLevel.Error, content = logcontent });
  177. }
  178. public void Error(string logcontent, Exception exception)
  179. {
  180. EnqueueLogModel(new LogModel() { type = NLogLevel.Error, content = logcontent, exobj = exception });
  181. }
  182. /// <summary>
  183. /// Fatal,致命错误,非常严重的错误
  184. /// </summary>
  185. /// <param name="msg"></param>
  186. public void Fatal(string logcontent)
  187. {
  188. EnqueueLogModel(new LogModel() { type = NLogLevel.Fatal, content = logcontent });
  189. }
  190. public void Fatal(string logcontent, Exception exception)
  191. {
  192. EnqueueLogModel(new LogModel() { type = NLogLevel.Fatal, content = logcontent, exobj = exception });
  193. }
  194. }
  195. public class LogModel
  196. {
  197. public NLogLevel type { get; set; }
  198. public string content { get; set; }
  199. public Exception exobj { get; set; }
  200. }
  201. /// <summary>
  202. /// NLog 日志等级
  203. /// </summary>
  204. public enum NLogLevel
  205. {
  206. Trace,
  207. Debug,
  208. Info,
  209. Warn,
  210. Error,
  211. Fatal
  212. }

参考:C# 超高速高性能写日志 代码开源       .net core 中的那些常用的日志框架(NLog篇)

四、日志查看器 TextAnalysisTool.NET

作为一名研发人员,高效率的日志分析是必须的,当然好的工具也是前提条件。

要想高效分析日志,有几个问题需要解决:

  • 快速定位,在海量日志信息中快速定位目标行;
  • 高亮显示,以不同颜色显示目标行,以便分类提高辨识度;
  • 只显示有用的行。

在日常开发使用最多的莫过于 NotePad++ 了,尽管其可以通过 “搜索-标记/标记所有-使用格式1/2/3/4/5”的操作来实现以上的前两点,但是操作较繁琐,当日志行数比较多时,也无法仅显示标记行,从而造成效率低下。

当然,对于普通的业务量不太高的日志记录,NotePad++ 足以满足使用。

下面介绍一个非常简单实用的开源日志查看工具 TextAnalysisTool.NET。

1. 下载应用程序包

下载完成后,如下图打开最新版的应用程序:

2. 分析的日志文件

按照“File -> Open”选择要打开的日志文件。

双击任意行,便会跳出“Add Filter”窗口:(Text 默认为鼠标焦点行的内容)

可以通过修改“Text Color”和“Background”来指定查询结果的文本和行底色,达到高亮显示目的。

其他选项:Description:描述;Excluding:排除,不包含;Case-sensitive:大小写敏感;Regular-expression:按照正则表达式查询。

如下图示例,查询三个语句,标志为不同的行底色效果:

若想只显示查询目标所在的行,可以如下图鼠标操作,也可使用快捷键 Ctrl+H,取消时重复操作即可。

参考:使用TextAnalysisTool来快速提高你分析文本日志的效率

原文链接:https://www.cnblogs.com/czzj/p/JGP_MyLog.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号