1. 程式人生 > >.Net Core 3.0 使用 Serilog 把日誌記錄到 SqlServer

.Net Core 3.0 使用 Serilog 把日誌記錄到 SqlServer

Serilog簡介

Serilog是.net中的診斷日誌庫,可以在所有的.net平臺上面執行。Serilog支援結構化日誌記錄,對複雜、分散式、非同步應用程式的支援非常出色。Serilog可以通過外掛的方式把日誌寫入到各種終端,控制檯、文字、Sqlserver、ElasticSearch,Serilog支援終端的列表:https://github.com/serilog/serilog/wiki/Provided-Sinks 。

Serilog日誌寫入SqlServer

一、Sink LoggerConfiguration

  • connectionString  資料庫連線字串
  • schemaName  資料庫所有者,預設dbo
  • tableName  記錄日誌的表名 
  • autoCreateSqlTable  是否自動建立表,如果設定為ture,則在Serilog啟動時檢測資料庫是否有對應的表,沒有則建立 
  • columnOptions  日誌表中的列定義
  • restrictedToMinimumLevel  記錄日誌的最小level 
  • batchPostingLimit  單次批量處理中提交的最大日誌數量
  • period  進行批量提交的間隔
  • formatProvider 提供特定的格式化處理,https://github.com/serilog/serilog/wiki/Formatting-Output#format-providers

Serilog為我們定義了一套標準列,預設情況下會生成如下列,當然我們也可以自定義列

  • StandardColumn.Id  自增Id 
  • StandardColumn.Message  日誌內容 
  • StandardColumn.MessageTemplate 日誌模板
  • StandardColumn.Level  等級 
  • StandardColumn.TimeStamp  記錄時間
  • StandardColumn.Exception  異常資訊
  • StandardColumn.Properties 日誌事件屬性值

刪除標準列:

columnOptions.Store.Remove(StandardColumn.MessageTemplate);

新增自定義列:

columnOptions.AdditionalColumns = new Collection<SqlColumn>
                {
                    new SqlColumn { DataType = SqlDbType.NVarChar, DataLength = 32, ColumnName = "IP" } 
                };

完整LoggerConfiguration示例如下:

var columnOptions = new ColumnOptions();
            columnOptions.Store.Remove(StandardColumn.MessageTemplate);//刪除標準列
            columnOptions.Properties.ExcludeAdditionalProperties = true;//排除已經自定義列的資料
            columnOptions.AdditionalColumns = new Collection<SqlColumn>//新增自定義列
                {
                    new SqlColumn { DataType = SqlDbType.NVarChar, DataLength = 32, ColumnName = "IP" }
                };

            Log.Logger = new LoggerConfiguration() 
               .WriteTo.MSSqlServer(
                   connectionString: Configuration["Serilog:ConnectionString"],
                   tableName: Configuration["Serilog:TableName"],
                   batchPostingLimit: Configuration.GetValue<int>("Serilog:BatchPostingLimit"),//批量插入資料庫條數
                   period: TimeSpan.FromSeconds(5),//執行時間間隔
                   restrictedToMinimumLevel: Configuration.GetValue<LogEventLevel>("Serilog:MinimumLevel"),
                   columnOptions: columnOptions,
                   autoCreateSqlTable: true
               ).CreateLogger();

上面的配置也可以全部從配置檔案讀取:

{
    "Serilog": {
      "Using": [ "Serilog.Sinks.MSSqlServer" ],
      "MinimumLevel": "Debug",
      "WriteTo": [
        {
          "Name": "MSSqlServer",
          "Args": {
            "connectionString": "NamedConnectionString",
            "schemaName": "EventLogging",
            "tableName": "Logs",
            "autoCreateSqlTable": true,
            "restrictedToMinimumLevel": "Warning",
            "batchPostingLimit": 100,
            "period": "0.00:00:30",
            "columnOptionsSection": {
              "disableTriggers": true,
              "clusteredColumnstoreIndex": false,
              "primaryKeyColumnName": "Id",
              "addStandardColumns": [ "LogEvent" ],
              "removeStandardColumns": [ "MessageTemplate"],
              "additionalColumns": [
                {
                  "ColumnName": "IP",
                  "DataType": "varchar",
                  "DataLength": 32
                } 
              ],
              "id": { "nonClusteredIndex": true },
              "properties": {
                "columnName": "Properties",
                "excludeAdditionalProperties": true,
                "dictionaryElementName": "dict",
                "itemElementName": "item",
                "omitDictionaryContainerElement": false,
                "omitSequenceContainerElement": false,
                "omitStructureContainerElement": false,
                "omitElementIfEmpty": true,
                "propertyElementName": "prop",
                "rootElementName": "root",
                "sequenceElementName": "seq",
                "structureElementName": "struct",
                "usePropertyKeyAsElementName": false
              },
              "timeStamp": {
                "columnName": "Timestamp",
                "convertToUtc": true
              },
              "logEvent": {
                "excludeAdditionalProperties": true,
                "excludeStandardColumns": true
              },
              "message": { "columnName": "message" },
              "exception": { "columnName": "exception" }
            }
          }
        }
      ]
    }
  }
Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(Configuration["Serilog"]);//需要引用Microsoft.Extensions.Configuration

二、Logger使用

1、直接使用Serilog提供的靜態類Log

Log.Information(“message”);

2、使用serilog-extensions-logging 替換.net core預設日誌Microsoft.Extensions.Logging,注入Serilog

public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.CaptureStartupErrors(true)//捕捉啟動異常
                              .UseSetting("detailedErrors", "true")//指定程式應用程式會顯示詳細的啟動錯誤資訊
                              .UseStartup<Startup>()
                              .ConfigureLogging(builder =>
                              {
                                  builder.ClearProviders();
                                  builder.AddSerilog();
                              });
                });
private readonly ILogger logger; 

public TestController(ILogger<TestController> logger)
        {
            this.logger = logger; 
        }

logger.Information("Message")

 

三、怎麼把資料寫入自定義列

Serilog並沒有提供 Log.Debug(Message,IP)方法,在我們日常開發中可能會有如下幾種需求:

1、設定全域性Property

例如我需要記錄當前程式伺服器的ip,或者我需要記錄當前服務的名稱,需要一個共用的欄位。那麼我們可以在LoggerConfiguration的時候設定一個全域性的Property,即在相同的LoggerConfiguration下面每條日誌都可以共用,我們可以這樣配置

Log.Logger = new LoggerConfiguration() 
               .Enrich.FromLogContext()
               .Enrich.WithProperty("IP", GetIP())
               .WriteTo.MSSqlServer(
                   connectionString: Configuration["Serilog:ConnectionString"],
                   tableName: Configuration["Serilog:TableName"],
                   batchPostingLimit: Configuration.GetValue<int>("Serilog:BatchPostingLimit"),//批量插入資料庫條數
                   period: TimeSpan.FromSeconds(5),//執行時間間隔
                   restrictedToMinimumLevel: Configuration.GetValue<LogEventLevel>("Serilog:MinimumLevel"),
                   columnOptions: columnOptions,
                   autoCreateSqlTable: true
               ).CreateLogger();

2、設定ForContext寫入Property

例如我需要記錄當前類,需要在記錄日誌的時候設定ForContext

Log.ForContext("Calss", GetType().FullName).Information("message");

這裡只是一個例子,其實serilog已經自動幫我們記錄了Calss的資訊,在Properties中可以找到SourceContext節點,裡面就記錄了相關的名稱空間和類

四、對日誌進行過濾

如果系統日誌太多,我們很難快速找到有用的資訊,所以很多時候我們會對日誌進行過濾

1、通過MinimumLevel進行過濾

設定MinimumLevel的等級進行過濾,Serilog中Level有Verbose,Debug,Information,Warning,Error,Fatal幾個等級,Serilog只記錄當前等級及比當前等級高的日誌。

2、通過Override進行過濾

原理是serilog會記錄SourceContext,裡面包含了名稱空間和類的資訊,這裡我們把SourceContext包含“Microsoft”的資訊過濾掉,只記錄Error及Error級別以上的資訊,配置如下:

Log.Logger = new LoggerConfiguration() 
               .Enrich.FromLogContext()
               .Enrich.WithProperty("IP", GetIP())
               .MinimumLevel.Override("Microsoft", LogEventLevel.Error)
               .WriteTo.MSSqlServer(
                   connectionString: Configuration["Serilog:ConnectionString"],
                   tableName: Configuration["Serilog:TableName"],
                   batchPostingLimit: Configuration.GetValue<int>("Serilog:BatchPostingLimit"),//批量插入資料庫條數
                   period: TimeSpan.FromSeconds(5),//執行時間間隔
                   restrictedToMinimumLevel: Configuration.GetValue<LogEventLevel>("Serilog:MinimumLevel"),
                   columnOptions: columnOptions,
                   autoCreateSqlTable: true
               ).CreateLogger();

3、通過Filter進行過濾

通過Filter可以過濾Properties中的值,比如一般我們會對資料庫的錯誤比較重視,希望把資料庫錯誤單獨放在一個表中,這時需要用到Filter,我們把SourceContext中包含資料訪問層名稱空間的資訊提取出來

string namespace = "DAL";//資料訪問層名稱空間

Log.Logger = new LoggerConfiguration() 
               .Enrich.FromLogContext()
               .Enrich.WithProperty("IP", GetIP())
               .MinimumLevel.Override("Microsoft", LogEventLevel.Error)
               .WriteTo.Logger(lc => lc.Filter.ByIncludingOnly(Matching.WithProperty(namespace))
                                       .WriteTo.MSSqlServer(
                                               connectionString: Configuration["Serilog:ConnectionString"],
                                               tableName: Configuration["Serilog:DBErrorTableName"],
                                               batchPostingLimit: Configuration.GetValue<int>("Serilog:BatchPostingLimit"),//批量插入資料庫條數
                                               period: TimeSpan.FromSeconds(5),//執行時間間隔
                                               columnOptions: columnOptions,
                                               autoCreateSqlTable: true))
               .WriteTo.Logger(lc => lc.Filter.ByExcluding(Matching.WithProperty(namespace))
                                       .WriteTo.MSSqlServer(
                                               connectionString: Configuration["Serilog:ConnectionString"],
                                               tableName: Configuration["Serilog:DefaultTableName"],
                                               batchPostingLimit: Configuration.GetValue<int>("Serilog:BatchPostingLimit"),//批量插入資料庫條數
                                               period: TimeSpan.FromSeconds(5),//執行時間間隔
                                               columnOptions: columnOptions,
                                               autoCreateSqlTable: true))
               .CreateLogger(); 

五、Enricher 

Enricher 的作用主要是增加記錄的資訊,比如Enrich.WithThreadId(),可以記錄執行緒資訊,Enrich.WithProperty()可以增加屬性資訊

自定義Enricher 可以引數這篇文章:https://www.cnblogs.com/weihanli/p/custom-serilog-enricher-to-record-more-info.html

 

參考資料

https://github.com/serilog/serilog

https://www.cnblogs.com/Leo_wl/p/7643400.html

https://www.cnblogs.com/Leo_wl/p/10943285.html