<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          動(dòng)手造輪子:實(shí)現(xiàn)一個(gè)簡(jiǎn)單的基于 Console 的日志輸出

          共 15834字,需瀏覽 32分鐘

           ·

          2021-03-30 17:29

          Intro

          之前結(jié)合了微軟的 Logging 框架和 Serilog 寫(xiě)了一個(gè)簡(jiǎn)單的日志框架,但是之前的用法都是基于 log4net、serilog 的,沒(méi)有真正自己實(shí)現(xiàn)一個(gè)日志輸出,比如 Console、文件、數(shù)據(jù)庫(kù)、ES等,關(guān)于日志框架的設(shè)計(jì)可以參考之前的文章 動(dòng)手造輪子:寫(xiě)一個(gè)日志框架

          實(shí)現(xiàn)思路

          把日志放在一個(gè)隊(duì)列中,通過(guò)隊(duì)列方式慢慢的寫(xiě),避免并發(fā)問(wèn)題,同時(shí)異步寫(xiě)到 Console 避免因?yàn)閷?xiě)日志阻塞主線程的執(zhí)行

          輸出的格式如何定義呢,像 log4net/nlog/serilog 這些都會(huì)支持自定義日志輸出格式,所以我們可以設(shè)計(jì)一個(gè)接口,實(shí)現(xiàn)一個(gè)默認(rèn)日志格式,當(dāng)用戶自定義日志格式的時(shí)候就使用用戶自定義的日志格式

          針對(duì)不同的日志級(jí)別的日志應(yīng)該使用不同的顏色來(lái)輸出以方便尋找不同級(jí)別的日志

          使用示例

          來(lái)看一個(gè)使用的示例:

          LogHelper.ConfigureLogging(builder =>
          {
              builder
                  .AddConsole()
                  //.AddLog4Net()
                  //.AddSerilog(loggerConfig => loggerConfig.WriteTo.Console())
                  //.WithMinimumLevel(LogHelperLogLevel.Info)
                  //.WithFilter((category, level) => level > LogHelperLogLevel.Error && category.StartsWith("System"))
                  //.EnrichWithProperty("Entry0", ApplicationHelper.ApplicationName)
                  //.EnrichWithProperty("Entry1", ApplicationHelper.ApplicationName, e => e.LogLevel >= LogHelperLogLevel.Error)
                  ;
          });

          var abc = "1233";
          var logger = LogHelper.GetLogger<LoggerTest>();
          logger.Debug("12333 {abc}", abc);
          logger.Trace("122334334");
          logger.Info($"122334334 {abc}");

          logger.Warn("12333, err:{err}""hahaha");
          logger.Error("122334334");
          logger.Fatal("12333");

          日志輸出如下:

          log output

          默認(rèn)的日志格式是 JSON 字符串,因?yàn)槲矣X(jué)得 JSON 更加結(jié)構(gòu)化,也會(huì)比較方便的去 PATCH 和日志分析,微軟的 Logging 框架也是在 .NET 5.0 中加入了 JsonConsoleFormatter,可以直接輸出 JSON 到控制臺(tái),如果需要也可以自定義一個(gè) Formatter 來(lái)實(shí)現(xiàn)自定義的格式化

          實(shí)現(xiàn)源碼

          使用 IConsoleLogFormatter 接口來(lái)自定義日志格式化

          public interface IConsoleLogFormatter
          {
              string FormatAsString(LogHelperLoggingEvent loggingEvent);
          }

          internal sealed class DefaultConsoleLogFormatter : IConsoleLogFormatter
          {
              private static readonly JsonSerializerSettings _serializerSettings = new()
              {
                  Converters =
                  {
                      new StringEnumConverter()
                  },
                  ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
              };

              public string FormatAsString(LogHelperLoggingEvent loggingEvent)
              {
                  return loggingEvent.ToJson(_serializerSettings);
              }
          }

          實(shí)現(xiàn)的代碼比較簡(jiǎn)單,隊(duì)列的話使用了 BlockingCollection 來(lái)實(shí)現(xiàn)了一個(gè)內(nèi)存中的隊(duì)列

          ConsoleLoggingProvider實(shí)現(xiàn)如下:

          internal sealed class ConsoleLoggingProvider : ILogHelperProvider
          {
              private readonly IConsoleLogFormatter _formatter;

              private readonly BlockingCollection<LogHelperLoggingEvent> _messageQueue = new();
              private readonly Thread _outputThread;

              public ConsoleLoggingProvider(IConsoleLogFormatter formatter)
              {
                  _formatter = formatter;

                  // Start Console message queue processor
                  _outputThread = new Thread(ProcessLogQueue)
                  {
                      IsBackground = true,
                      Name = "Console logger queue processing thread"
                  };
                  _outputThread.Start();
              }

              public void EnqueueMessage(LogHelperLoggingEvent message)
              {
                  if (!_messageQueue.IsAddingCompleted)
                  {
                      try
                      {
                          _messageQueue.Add(message);
                          return;
                      }
                      catch (InvalidOperationException) { }
                  }

                  // Adding is completed so just log the message
                  try
                  {
                      WriteLoggingEvent(message);
                  }
                  catch (Exception)
                  {
                      // ignored
                  }
              }

              public void Log(LogHelperLoggingEvent loggingEvent)
              {
                  EnqueueMessage(loggingEvent);
              }

              private void ProcessLogQueue()
              {
                  try
                  {
                      foreach (LogHelperLoggingEvent message in _messageQueue.GetConsumingEnumerable())
                      {
                          WriteLoggingEvent(message);
                      }
                  }
                  catch
                  {
                      try
                      {
                          _messageQueue.CompleteAdding();
                      }
                      catch
                      {
                          // ignored
                      }
                  }
              }

              private void WriteLoggingEvent(LogHelperLoggingEvent loggingEvent)
              {
                  try
                  {
                      var originalColor = Console.ForegroundColor;
                      try
                      {
                          var log = _formatter.FormatAsString(loggingEvent);
                          var logLevelColor = GetLogLevelConsoleColor(loggingEvent.LogLevel);
                          Console.ForegroundColor = logLevelColor.GetValueOrDefault(originalColor);

                          if (loggingEvent.LogLevel == LogHelperLogLevel.Error
                              || loggingEvent.LogLevel == LogHelperLogLevel.Fatal)
                          {
                              Console.Error.WriteLine(log);
                          }
                          else
                          {
                              Console.WriteLine(log);
                          }
                      }
                      catch (Exception ex)
                      {
                          Console.WriteLine(ex);
                      }
                      finally
                      {
                          Console.ForegroundColor = originalColor;
                      }
                  }
                  catch
                  {
                      Console.WriteLine(loggingEvent.ToJson());
                  }
              }

              private static ConsoleColor? GetLogLevelConsoleColor(LogHelperLogLevel logLevel)
              {
                  return logLevel switch
                  {
                      LogHelperLogLevel.Trace => ConsoleColor.Gray,
                      LogHelperLogLevel.Debug => ConsoleColor.Gray,
                      LogHelperLogLevel.Info => ConsoleColor.DarkGreen,
                      LogHelperLogLevel.Warn => ConsoleColor.Yellow,
                      LogHelperLogLevel.Error => ConsoleColor.Red,
                      LogHelperLogLevel.Fatal => ConsoleColor.DarkRed,
                      _ => null
                  };
              }
          }

          為了方便使用和更好的訪問(wèn)控制,上面的 ConsoleLoggingProvider 聲明成了 internal 并不直接對(duì)外開(kāi)放,并且定義了下面的擴(kuò)展方法來(lái)使用:

          public static ILogHelperLoggingBuilder AddConsole(this ILogHelperLoggingBuilder loggingBuilder, IConsoleLogFormatter? consoleLogFormatter = null)
          {
              loggingBuilder.AddProvider(new ConsoleLoggingProvider(
                  consoleLogFormatter ?? new DefaultConsoleLogFormatter()));
              return loggingBuilder;
          }

          DelegateFormatter

          需要自定義的 Console 日志的格式的時(shí)候就實(shí)現(xiàn)一個(gè) IConsoleLogFormatter 來(lái)實(shí)現(xiàn)自己的格式化邏輯就可以了,不想手寫(xiě)一個(gè)類?也可以實(shí)現(xiàn)一個(gè) Func<LogHelperLoggingEvent, string> 委托,內(nèi)部會(huì)把委托轉(zhuǎn)成一個(gè) IConsoleLogFormatter,實(shí)現(xiàn)如下:

          internal sealed class DelegateConsoleLogFormatter : IConsoleLogFormatter
          {
              private readonly Func<LogHelperLoggingEvent, string> _formatter;

              public DelegateConsoleLogFormatter(Func<LogHelperLoggingEvent, string> formatter)
              {
                  _formatter = formatter ?? throw new ArgumentNullException(nameof(formatter));
              }

              public string FormatAsString(LogHelperLoggingEvent loggingEvent) => _formatter(loggingEvent);
          }

          擴(kuò)展方法:

          public static ILogHelperLoggingBuilder AddConsole(this ILogHelperLoggingBuilder loggingBuilder, Func<LogHelperLoggingEvent, string> formatter)
          {
              loggingBuilder.AddProvider(new ConsoleLoggingProvider(new DelegateConsoleLogFormatter(formatter)));
              return loggingBuilder;
          }

          More

          在寫(xiě)一些小應(yīng)用的時(shí)候,經(jīng)常會(huì)遇到這樣的場(chǎng)景,就是執(zhí)行一個(gè)方法的時(shí)候包一層 try...catch,在發(fā)生異常時(shí)輸出異常信息,稍微包裝了一個(gè)

          public static Action<Exception>? OnInvokeException { getset; }

          public static void TryInvoke(Action action)
          {
              Guard.NotNull(action, nameof(action));
              try
              {
                  action();
              }
              catch (Exception ex)
              {
                  OnInvokeException?.Invoke(ex);
              }
          }

          原來(lái)想突出顯示錯(cuò)誤信息的時(shí)候,我會(huì)特別設(shè)置一個(gè) Console 的顏色以便方便的查看,原來(lái)會(huì)這樣設(shè)置,之前的 gRPC 示例項(xiàng)目原來(lái)就是這樣做的:

          InvokeHelper.OnInvokeException = ex =>
          {
              var originalColor = ForegroundColor;
              ForegroundColor = ConsoleColor.Red;
              WriteLine(ex);
              ForegroundColor = originalColor;
          };

          有了 Console logging 之后,我就可以把上面的委托默認(rèn)設(shè)置為 Log 一個(gè) Error(OnInvokeException = ex => LogHelper.GetLogger(typeof(InvokeHelper)).Error(ex);),只需要配置 Logging 使用 Console 輸出就可以了,也可以設(shè)置日志級(jí)別忽略一些不太需要的日志

          LogHelper.ConfigureLogging(x=>x.AddConsole().WithMinimumLevel(LogHelperLogLevel.Info));

          diff

          References

          • https://github.com/WeihanLi/WeihanLi.Common
          • https://github.com/WeihanLi/WeihanLi.Common/blob/dev/src/WeihanLi.Common/Logging/ConsoleLoggingProvider.cs


          往期精彩回顧




          【推薦】.NET Core開(kāi)發(fā)實(shí)戰(zhàn)視頻課程 ★★★

          .NET Core實(shí)戰(zhàn)項(xiàng)目之CMS 第一章 入門(mén)篇-開(kāi)篇及總體規(guī)劃

          【.NET Core微服務(wù)實(shí)戰(zhàn)-統(tǒng)一身份認(rèn)證】開(kāi)篇及目錄索引

          Redis基本使用及百億數(shù)據(jù)量中的使用技巧分享(附視頻地址及觀看指南)

          .NET Core中的一個(gè)接口多種實(shí)現(xiàn)的依賴注入與動(dòng)態(tài)選擇看這篇就夠了

          10個(gè)小技巧助您寫(xiě)出高性能的ASP.NET Core代碼

          用abp vNext快速開(kāi)發(fā)Quartz.NET定時(shí)任務(wù)管理界面

          在ASP.NET Core中創(chuàng)建基于Quartz.NET托管服務(wù)輕松實(shí)現(xiàn)作業(yè)調(diào)度

          現(xiàn)身說(shuō)法:實(shí)際業(yè)務(wù)出發(fā)分析百億數(shù)據(jù)量下的多表查詢優(yōu)化

          關(guān)于C#異步編程你應(yīng)該了解的幾點(diǎn)建議

          C#異步編程看這篇就夠了


          瀏覽 46
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美日韩一级黄片 | 久久人妻少妇嫩草AV蜜桃漫画 | 免费黄色视频网站在线观看 | 日韩午夜福利 | 成人在线免费观看三级片 |