翼度科技»论坛 编程开发 .net 查看内容

C#实现一个简单的日志类

10

主题

10

帖子

30

积分

新手上路

Rank: 1

积分
30
目录

上个月换工作,新项目又要重新搭建基础框架,把日志实现部分单独记录下来方便以后参考。
自定义日志类

代码大部分使用ChatGPT生成,人工进行了测试和优化,主要特点:

  • 线程安全,日志异步写入文件不影响业务逻辑
  • 支持过期文件自动清理,也可自定义清理逻辑
  • 缓存队列有内存上限防呆,防止异常情况下内存爆满
  • 提供默认的静态日志记录器,也支持自定义多个日志记录器
  • 通过委托方法支持日志文件名、日志记录格式的自定义,更加自由
使用方法:
  1. //正常写入日志
  2. LogManager.Info("This is an info message: {0}", "TestInfo");
  3. LogManager.Debug("This is a debug message: {0}", "TestDebug");
  4. LogManager.Warn("This is an warning message: {0}", "TestInfo");
  5. LogManager.Error("This is a error message: {0}", "TestDebug");
  6. //自定义写入日志,一般情况下使用枚举定义日志记录器的名称
  7. var logger = LogManager.GetLogger("测试日志");
  8. logger.Info("This is an info message: {0}", "TestInfo");
  9. logger.Debug("This is a debug message: {0}", "TestDebug");
  10. logger.Warn("This is an warning message: {0}", "TestInfo");
  11. logger.Error("This is a error message: {0}", "TestDebug");
  12. // 在程序退出前关闭所有日志记录器,默认超时时间是3秒
  13. LogManager.Close(5);
  14. //调试时偶尔使用
  15. if (LogManager.LastException != null)
  16. {
  17.     Console.WriteLine("日志异常:" + LogManager.LastException);
  18. }
复制代码
配置方法(一般情况下使用默认配置即可):
  1. //自定义日志保存路径,默认保存到程序启动目录下的Log文件夹
  2. LogManager.CustomLogPath = () => AppDomain.CurrentDomain.BaseDirectory + "\\CustomLogs";
  3. //自定义日志文件名称,默认文件名为 DateTime.Now.ToString("yyyy-MM-dd") + ".log"
  4. LogManager.CustomLogFileName = () => "MyLog_" + DateTime.Now.ToString("yyyyMMdd") + ".log";
  5. //日志保存天数,默认30天
  6. LogManager.SaveDays = 10;
  7. //日志记录的格式,默认为 $"[{Time:yyyy-MM-dd HH:mm:ss ffff}] [{Level.ToString().ToUpper()}] [{ThreadId}] {Message}"
  8. LogManager.LogFormatter = (item) =>
  9. {
  10.     //可以在这里做日志等级筛选,如果返回string.Empty这该条日志不会记录到文件
  11.     return $"{item.Time:yyyy/MM/dd HH:mm:ss.fff} | {item.Level} | T{item.ThreadId:0000} | {item.Message}";
  12. };
  13. //日志回调,可用于界面实时显示日志或日志保存其它存储介质
  14. LogManager.OnWriteLog = (item) => Console.WriteLine("An event was logged: " + item.ToString());
复制代码
日志类完整代码:
  1. using System;
  2. using System.Collections.Concurrent;
  3. using System.IO;
  4. using System.Threading;
  5. using System.Threading.Tasks;
  6. /// <summary>
  7. /// 日志组件,内部维护了一个静态日志记录类
  8. /// </summary>
  9. public static class LogManager
  10. {
  11.     /// <summary>
  12.     /// 日志等级
  13.     /// </summary>
  14.     public enum LogLevel
  15.     { Debug, Info, Warn, Error, Fatal }
  16.     /// <summary>
  17.     /// 日志数据
  18.     /// </summary>
  19.     public class LogItem
  20.     {
  21.         public LogItem(LogLevel level, string message)
  22.         {
  23.             Level = level;
  24.             Message = message;
  25.             Time = DateTime.Now;
  26.             ThreadId = Thread.CurrentThread.ManagedThreadId;
  27.         }
  28.         public DateTime Time { get; private set; }
  29.         public LogLevel Level { get; private set; }
  30.         public int ThreadId { get; private set; }
  31.         public string Message { get; private set; }
  32.         public override string ToString()
  33.         {
  34.             return $"[{Time:yyyy-MM-dd HH:mm:ss ffff}] [{Level.ToString().ToUpper()}] [{ThreadId}] {Message}";
  35.         }
  36.     }
  37.     /// <summary>
  38.     /// 线程安全异步日志基础类,默认缓存10000条日志,超出时日志会阻塞
  39.     /// </summary>
  40.     public class Logger
  41.     {
  42.         /// <summary>
  43.         /// 日志文件存储路径的委托
  44.         /// </summary>
  45.         public Func<string> CustomLogPath { get; set; }
  46.         /// <summary>
  47.         /// 日志文件名的委托,文件扩展名必须是log,否则会影响日志文件的自动清理(可以自定义清理的方法)
  48.         /// </summary>
  49.         public Func<string> CustomLogFileName { get; set; }
  50.         /// <summary>
  51.         /// 日志文件保存时间
  52.         /// </summary>
  53.         public int SaveDays { get; set; } = 30;
  54.         /// <summary>
  55.         /// 日志格式化委托实例
  56.         /// </summary>
  57.         public Func<LogItem, string> LogFormatter { get; set; }
  58.         /// <summary>
  59.         /// 写日志事件
  60.         /// </summary>
  61.         public Action<LogItem> OnWriteLog { get; set; }
  62.         /// <summary>
  63.         /// 日志清理委托实例,传入日志保存时间
  64.         /// </summary>
  65.         public Action<int> LogCleanup { get; set; }
  66.         /// <summary>
  67.         /// 最后一次异常(仅调试时用,不用于正常业务流程)
  68.         /// </summary>
  69.         public Exception LastException { get; set; }
  70.         // 线程安全的日志队列
  71.         private BlockingCollection<string> logQueue = new BlockingCollection<string>(10000);
  72.         // 标识是否允许写入新日志
  73.         private bool allowNewLogs = true;
  74.         public Logger()
  75.         {
  76.             Task.Factory.StartNew(WriteToFile, TaskCreationOptions.LongRunning);
  77.         }
  78.         // 添加日志至队列方法
  79.         public void EnqueueLog(LogLevel level, string message)
  80.         {
  81.             if (!allowNewLogs) return;
  82.             LogItem item = new LogItem(level, message);
  83.             string logMessage;
  84.             if (LogFormatter != null)
  85.             {
  86.                 logMessage = LogFormatter(item);
  87.             }
  88.             else
  89.             {
  90.                 logMessage = item.ToString();
  91.             }
  92.             if (!string.IsNullOrWhiteSpace(logMessage))
  93.             {
  94.                 logQueue.Add(logMessage);
  95.             }
  96.             OnWriteLog?.Invoke(item);
  97.         }
  98.         // 循环写入写日志到文件
  99.         private void WriteToFile()
  100.         {
  101.             string logPath = CustomLogPath?.Invoke() ?? AppDomain.CurrentDomain.BaseDirectory + "\\Log";
  102.             DirectoryInfo logDir = Directory.CreateDirectory(logPath);
  103.             while (true)
  104.             {
  105.                 try
  106.                 {
  107.                     if (!allowNewLogs && logQueue.Count == 0) break;
  108.                     string logMessage;
  109.                     if (logQueue.TryTake(out logMessage, TimeSpan.FromSeconds(1)))
  110.                     {
  111.                         string fileName = CustomLogFileName?.Invoke() ?? DateTime.Now.ToString("yyyy-MM-dd") + ".log";
  112.                         if (!File.Exists(fileName))
  113.                         {
  114.                             // 清理旧日志
  115.                             if (LogCleanup != null)
  116.                             {
  117.                                 LogCleanup(SaveDays);
  118.                             }
  119.                             else
  120.                             {
  121.                                 CleanUpOldLogs(logDir, SaveDays);
  122.                             }
  123.                         }
  124.                         File.AppendAllText(Path.Combine(logPath, fileName), logMessage + Environment.NewLine);
  125.                     }
  126.                 }
  127.                 catch (Exception ex)
  128.                 {
  129.                     LastException = ex;
  130.                     Console.WriteLine("Logger Exception - WriteToFile : " + ex.Message);
  131.                 }
  132.             }
  133.         }
  134.         /// <summary>
  135.         /// 关闭日志器方法,指定超时时间(秒)
  136.         /// </summary>
  137.         /// <param name="waitTimeInSeconds">等待时间</param>
  138.         public void Close(int waitTimeInSeconds = 3)
  139.         {
  140.             allowNewLogs = false;
  141.             CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(waitTimeInSeconds));
  142.             try
  143.             {
  144.                 CancellationToken token = cts.Token;
  145.                 // 标识队列已完成添加
  146.                 logQueue.CompleteAdding();
  147.                 while (!token.IsCancellationRequested)
  148.                 {
  149.                     if (logQueue.Count == 0) break; // 提前退出
  150.                     // 短暂休眠以降低 CPU 占用
  151.                     Task.Delay(100, token).Wait();
  152.                 }
  153.             }
  154.             catch (OperationCanceledException)
  155.             {
  156.                 // 等待时间到,退出方法,不传播异常
  157.             }
  158.             catch (Exception ex)
  159.             {
  160.                 Console.WriteLine("An unexpected exception occurred in the Close method: " + ex.Message);
  161.             }
  162.         }
  163.         /// <summary>
  164.         /// 默认的清理过期日志的方法
  165.         /// </summary>
  166.         /// <param name="logDir"></param>
  167.         /// <param name="saveDays"></param>
  168.         public static void CleanUpOldLogs(DirectoryInfo logDir, int saveDays)
  169.         {
  170.             FileInfo[] logFiles = logDir.GetFiles("*.log");
  171.             foreach (FileInfo file in logFiles)
  172.             {
  173.                 if (DateTime.Now - file.CreationTime >= TimeSpan.FromDays(saveDays))
  174.                 {
  175.                     file.Delete();
  176.                 }
  177.             }
  178.         }
  179.         /// <summary>
  180.         /// 记录Info等级日志
  181.         /// </summary>
  182.         /// <param name="message"></param>
  183.         /// <param name="args"></param>
  184.         public void Info(string message, params object[] args)
  185.         {
  186.             EnqueueLog(LogLevel.Info, string.Format(message, args));
  187.         }
  188.         /// <summary>
  189.         /// 记录Debug等级日志
  190.         /// </summary>
  191.         /// <param name="message"></param>
  192.         /// <param name="args"></param>
  193.         public void Debug(string message, params object[] args)
  194.         {
  195.             EnqueueLog(LogLevel.Debug, string.Format(message, args));
  196.         }
  197.         /// <summary>
  198.         /// 记录Warning等级日志
  199.         /// </summary>
  200.         /// <param name="message"></param>
  201.         /// <param name="args"></param>
  202.         public void Warn(string message, params object[] args)
  203.         {
  204.             EnqueueLog(LogLevel.Warn, string.Format(message, args));
  205.         }
  206.         /// <summary>
  207.         /// 记录Error等级日志
  208.         /// </summary>
  209.         /// <param name="message"></param>
  210.         /// <param name="args"></param>
  211.         public void Error(string message, params object[] args)
  212.         {
  213.             EnqueueLog(LogLevel.Error, string.Format(message, args));
  214.         }
  215.         /// <summary>
  216.         /// 记录Fatal等级日志
  217.         /// </summary>
  218.         /// <param name="message"></param>
  219.         /// <param name="args"></param>
  220.         public void Fatal(string message, params object[] args)
  221.         {
  222.             EnqueueLog(LogLevel.Fatal, string.Format(message, args));
  223.         }
  224.     }
  225.     private static Logger logger = new Logger();
  226.     private static ConcurrentDictionary<string, Logger> logDic = new ConcurrentDictionary<string, Logger>();
  227.     /// <summary>
  228.     /// 获取自定义的日志记录类
  229.     /// </summary>
  230.     /// <param name="name"></param>
  231.     /// <returns></returns>
  232.     public static Logger GetLogger(string name)
  233.     {
  234.         return logDic.GetOrAdd(name, key =>
  235.         {
  236.             var log = new Logger();
  237.             log.CustomLogPath = () => AppDomain.CurrentDomain.BaseDirectory + "\\Log\" + key;
  238.             return log;
  239.         });
  240.     }
  241.     /// <summary>
  242.     /// 日志文件存储路径的委托
  243.     /// </summary>
  244.     public static Func<string> CustomLogPath
  245.     {
  246.         get => logger.CustomLogPath;
  247.         set => logger.CustomLogPath = value;
  248.     }
  249.     /// <summary>
  250.     /// 日志文件名的委托,文件扩展名必须是log,否则会影响日志文件的自动清理(可以自定义清理的方法)
  251.     /// </summary>
  252.     public static Func<string> CustomLogFileName
  253.     {
  254.         get => logger.CustomLogFileName;
  255.         set => logger.CustomLogFileName = value;
  256.     }
  257.     /// <summary>
  258.     /// 日志文件保存时间
  259.     /// </summary>
  260.     public static int SaveDays
  261.     {
  262.         get => logger.SaveDays;
  263.         set => logger.SaveDays = value;
  264.     }
  265.     /// <summary>
  266.     /// 日志格式化委托实例
  267.     /// </summary>
  268.     public static Func<LogItem, string> LogFormatter
  269.     {
  270.         get => logger.LogFormatter;
  271.         set => logger.LogFormatter = value;
  272.     }
  273.     /// <summary>
  274.     /// 写日志事件
  275.     /// </summary>
  276.     public static Action<LogItem> OnWriteLog
  277.     {
  278.         get => logger.OnWriteLog;
  279.         set => logger.OnWriteLog = value;
  280.     }
  281.     /// <summary>
  282.     /// 日志清理委托实例,传入日志保存时间
  283.     /// </summary>
  284.     public static Action<int> LogCleanup
  285.     {
  286.         get => logger.LogCleanup;
  287.         set => logger.LogCleanup = value;
  288.     }
  289.     /// <summary>
  290.     /// 最后一次异常(仅调试时用,不用于正常业务流程)
  291.     /// </summary>
  292.     public static Exception LastException
  293.     {
  294.         get => logger.LastException;
  295.         set => logger.LastException = value;
  296.     }
  297.     /// <summary>
  298.     /// 关闭所有日志记录器,指定超时时间(秒),日志记录器较多时可能耗时较久
  299.     /// </summary>
  300.     /// <param name="waitTimeInSeconds">等待时间</param>
  301.     public static void Close(int waitTimeInSeconds = 3)
  302.     {
  303.         logger.Close(waitTimeInSeconds);
  304.         foreach (var item in logDic.Values)
  305.         {
  306.             item.Close(waitTimeInSeconds);
  307.         }
  308.     }
  309.     /// <summary>
  310.     /// 记录Info等级日志
  311.     /// </summary>
  312.     /// <param name="message"></param>
  313.     /// <param name="args"></param>
  314.     public static void Info(string message, params object[] args)
  315.     {
  316.         logger.EnqueueLog(LogLevel.Info, string.Format(message, args));
  317.     }
  318.     /// <summary>
  319.     /// 记录Debug等级日志
  320.     /// </summary>
  321.     /// <param name="message"></param>
  322.     /// <param name="args"></param>
  323.     public static void Debug(string message, params object[] args)
  324.     {
  325.         logger.EnqueueLog(LogLevel.Debug, string.Format(message, args));
  326.     }
  327.     /// <summary>
  328.     /// 记录Warning等级日志
  329.     /// </summary>
  330.     /// <param name="message"></param>
  331.     /// <param name="args"></param>
  332.     public static void Warn(string message, params object[] args)
  333.     {
  334.         logger.EnqueueLog(LogLevel.Warn, string.Format(message, args));
  335.     }
  336.     /// <summary>
  337.     /// 记录Error等级日志
  338.     /// </summary>
  339.     /// <param name="message"></param>
  340.     /// <param name="args"></param>
  341.     public static void Error(string message, params object[] args)
  342.     {
  343.         logger.EnqueueLog(LogLevel.Error, string.Format(message, args));
  344.     }
  345.     /// <summary>
  346.     /// 记录Fatal等级日志
  347.     /// </summary>
  348.     /// <param name="message"></param>
  349.     /// <param name="args"></param>
  350.     public static void Fatal(string message, params object[] args)
  351.     {
  352.         logger.EnqueueLog(LogLevel.Fatal, string.Format(message, args));
  353.     }
  354. }
复制代码
NLog版本的日志类

有时候因为公司或团队原因必须使用NLog日志库,这里提供一个简单的NLog版本的日志类,日志规则基本和上面的自定义类一致。
使用方法如下:
  1. //可以指定日志保存天数,默认30天
  2. Logger.Init();
  3. //订阅写日志事件
  4. Logger.GetLogEventTarget().LogReceived += Logger_LogWrite;
  5. //记录日志
  6. Logger.Info("软件启动");
复制代码
NLog版本的日志类只是对NLog的常用配置做一个封装,如果需要自定义日志格式或存储位置需要自己改封装部分的代码。
完整代码:
  1. using NLog;
  2. using NLog.Config;
  3. using NLog.Layouts;
  4. using NLog.Targets;
  5. using NLog.Targets.Wrappers;
  6. using System;
  7. /// <summary>
  8. /// 全局日志类
  9. /// </summary>
  10. public static class Logger
  11. {
  12.     #region 私有字段方法
  13.     private static ILogger logger;
  14.     private static void ConfigureNLog(int saveDays = 30)
  15.         {
  16.             var config = new LoggingConfiguration();
  17.             // 创建目标(这里是 FileTarget 用于写入文件)
  18.             var fileTarget = new FileTarget("logfile")
  19.             {
  20.                 FileName = "${basedir}/Log/${date:format=yyyy-MM-dd}.log",
  21.                 Layout = "${longdate} ${uppercase:${level}} ${message}  ${exception:format=tostring}",
  22.                 ArchiveOldFileOnStartup = true,
  23.                 ArchiveEvery = FileArchivePeriod.Day,
  24.                 MaxArchiveFiles = saveDays,
  25.             };
  26.             // 使用 AsyncTargetWrapper 实现异步写日志
  27.             var asyncFileTarget = new AsyncTargetWrapper(fileTarget, 1000, AsyncTargetWrapperOverflowAction.Discard);
  28.             // 设置规则
  29.             var rule = new LoggingRule("*", LogLevel.Debug, asyncFileTarget);
  30.             config.LoggingRules.Add(rule);
  31.             // 创建自定义Target
  32.             var logEventTarget = new LogEventTarget
  33.             {
  34.                 Name = "logEvent",
  35.                 MessageLayout = "${longdate}  ${uppercase:${level}} ${message}  ${exception:format=tostring}"
  36.             };
  37.             config.AddTarget(logEventTarget);
  38.             // 添加规则
  39.             var eventRule = new LoggingRule("*", LogLevel.Debug, logEventTarget);
  40.             config.LoggingRules.Add(eventRule);
  41.             // 应用配置
  42.             LogManager.Configuration = config;
  43.         }
  44.     #endregion 私有字段方法
  45.     #region 公共字段方法
  46.     /// <summary>
  47.     /// 初始化日志,程序初始化时启动
  48.     /// </summary>
  49.     /// <param name="saveDays"></param>
  50.     public static void Init(int saveDays = 30)
  51.     {
  52.         ConfigureNLog(saveDays);
  53.         logger = LogManager.GetCurrentClassLogger();
  54.     }
  55.     /// <summary>
  56.     /// 获取日志回调Target
  57.     /// </summary>
  58.     /// <returns></returns>
  59.     public static LogEventTarget GetLogEventTarget()
  60.     {
  61.         return (LogEventTarget)LogManager.Configuration.FindTargetByName("logEvent");
  62.     }
  63.     /// <summary>
  64.     /// 关闭日志,程序退出时调用
  65.     /// </summary>
  66.     public static void Close()
  67.     {
  68.         LogManager.Flush();
  69.         LogManager.Shutdown();
  70.     }
  71.     #endregion 公共字段方法
  72.     #region 日志接口
  73.     /// <summary>
  74.     /// 跟踪日志级别:最详细的级别,用于开发,很少用于生产。
  75.     /// </summary>
  76.     /// <param name="message"></param>
  77.     /// <param name="args"></param>
  78.     public static void Trace(string message, params object[] args)
  79.     {
  80.         logger.Trace(message, args);
  81.     }
  82.     /// <summary>
  83.     /// 调试日志级别:根据感兴趣的内部事件调试应用程序行为。
  84.     /// </summary>
  85.     /// <param name="message"></param>
  86.     /// <param name="args"></param>
  87.     public static void Debug(string message, params object[] args)
  88.     {
  89.         logger.Debug(message, args);
  90.     }
  91.     /// <summary>
  92.     /// 信息日志级别:突出显示进度或应用程序生存期事件的信息。
  93.     /// </summary>
  94.     /// <param name="message"></param>
  95.     /// <param name="args"></param>
  96.     public static void Info(string message, params object[] args)
  97.     {
  98.         logger.Info(message, args);
  99.     }
  100.     /// <summary>
  101.     /// 警告日志级别:关于可以恢复的验证问题或临时故障的警告。
  102.     /// </summary>
  103.     /// <param name="message"></param>
  104.     /// <param name="args"></param>
  105.     public static void Warn(string message, params object[] args)
  106.     {
  107.         logger.Warn(message, args);
  108.     }
  109.     /// <summary>
  110.     /// 错误日志级别:功能失败或捕捉到System.Exception的错误。
  111.     /// </summary>
  112.     /// <param name="message"></param>
  113.     /// <param name="args"></param>
  114.     public static void Error(string message, params object?[] args)
  115.     {
  116.         logger.Error(message, args);
  117.     }
  118.     /// <summary>
  119.     /// 致命日志级别:最关键级别,应用程序即将中止。
  120.     /// </summary>
  121.     /// <param name="message"></param>
  122.     /// <param name="args"></param>
  123.     public static void Fatal(string message, params object[] args)
  124.     {
  125.         logger.Fatal(message, args);
  126.     }
  127.     #endregion 日志接口
  128. }
  129. /// <summary>
  130. /// 日志事件目标
  131. /// </summary>
  132. public class LogEventTarget : Target
  133. {
  134.     /// <summary>
  135.     /// 日志事件
  136.     /// </summary>
  137.     public event EventHandler<string> LogReceived;
  138.     /// <summary>
  139.     /// 日志布局
  140.     /// </summary>
  141.     [RequiredParameter]
  142.     public Layout MessageLayout { get; set; }
  143.     protected override void Write(LogEventInfo logEvent)
  144.     {
  145.         string logMessage = MessageLayout.Render(logEvent);
  146.         OnLogReceived(logMessage);
  147.     }
  148.     private void OnLogReceived(string message)
  149.     {
  150.         LogReceived?.Invoke(this, message);
  151.     }
  152. }
复制代码
来源:https://www.cnblogs.com/timefiles/archive/2023/06/25/17503732.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

举报 回复 使用道具