---
# System prepended metadata

title: 在 net core 使用 serilog

---





### serilog 的 日誌等級

| **Level** | Usage |
| :--: | :-- |
|  ==Verbose==  |Verbose is the noisiest level, rarely (if ever) enabled for a production app.
|  ==Debug==  |	Debug is used for internal system events that are not necessarily observable from the outside, but useful when determining how something happened.
|  ==Information==  |	Information events describe things happening in the system that correspond to its responsibilities and functions. Generally these are the observable actions the system can perform.
|  ==Warning==  |	When service is degraded, endangered, or may be behaving outside of its expected parameters, Warning level events are used.
|  ==Error==  |When functionality is unavailable or expectations broken, an Error event is used.
|  ==Fatal==  |	The most critical level, Fatal events demand immediate attention.

### 安裝套件
這裡用 ==WEB API== 當作範例。
要在 Asp.Net Core 中加入日誌的機制，需要安裝以下套件。


[Serilog.AspNetCore](https://www.nuget.org/packages/Serilog.AspNetCore/)
這個套件已經整合了許多共用的相依套件，只要安裝這個就有日誌的基本功能(Console,file)
```shell
dotnet add package Serilog.AspNetCore 
```




### 基本設定

**第一步**, 移除 ==appsetting.json== 中的 =="Logging"== 區段，並加入 =="Serilog"== 區段。


```json=
  "Serilog": {
    "MinimumLevel": {
      "Default": "Information", //預設Log等級
      "Override": {
        "Microsoft.AspNetCore": "Warning"      // AspNetCore 預設等級。
      }
    },
    "Enrich": [ "FromLogContext" ],
    //輸出 Log 的位置，可以多個輸出設定  
    "WriteTo": [
      // 輸出 Log 到 Console
      { "Name": "Console" },
      // 輸出 Log 到 檔案
      {
        "Name": "File",
        "Args": {
          "rollingInterval": "Day",    //以「天」為單位存放檔案
          "path": "./logs/log-.txt"    //產生的檔名會是 log-yyyymmdd.txt        
        }
      }
    ]
  }
```


**第二步**，在 Program.cs 主程式中加入以下程式碼，設定從 appsetting.json 讀取 Log 設定


```csharp=
var builder = WebApplication.CreateBuilder(args);

Log.Logger = new LoggerConfiguration()
    .ReadFrom.Configuration(builder.Configuration)
    .CreateLogger();
```

**第三步**，在 Program.cs 主程式中加入 ==UseSerilog==，告訴應用程式使用Serilog作為日誌記錄器。
```csharp=

builder.Host.UseSerilog(); //<--加入這一行

var app = builder.Build();
```
**第四步**，加入==app.UseSerilogRequestLogging==Middleware 來整理所有與 Request 相關的紀錄，讓你在一條 Log 中就可以取得目前 Request 所有的相關資訊。
而且可以另外設定一些自訂的內容:例如自訂Log的範本，或是額外新增其他資訊到Log。
```csharp=
//這個指令告訴應用程式使用中介軟體來記錄HTTP請求的詳細信息。
app.UseSerilogRequestLogging(options => {
    // 如果要自訂訊息的範本格式，可以修改這裡，但修改後並不會影響結構化記錄的屬性
    options.MessageTemplate = "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms";

    // 預設輸出的紀錄等級為 Information，你可以在此修改記錄等級
    //options.GetLevel = (httpContext, elapsed, ex) => LogEventLevel.Information;

    // 你可以從 httpContext 取得 HttpContext 下所有可以取得的資訊！
    options.EnrichDiagnosticContext = (diagnosticContext, httpContext) => {
        //diagnosticContext.Set("RequestHost", httpContext.Request.Host.Value);
        //diagnosticContext.Set("RequestScheme", httpContext.Request.Scheme);
        diagnosticContext.Set("UserID", httpContext.User.Identity?.Name); // 使用者 ID
    };
});
```

**第五步**，完整的 program.cs 的內容如下

```csharp=
    try {
        var builder = WebApplication.CreateBuilder(args);

        //setting logger and read from appsettings.json
        Log.Logger = new LoggerConfiguration()
            .ReadFrom.Configuration(builder.Configuration)
            .CreateLogger();

        Log.Information("This is a information log message");
        Log.Warning("This is a warning log message");
        Log.Error("This is an error log message");
        Log.Fatal("This is a fatal log message");

        // Add services to the container.
        builder.Services.AddControllers();

        //這個指令告訴應用程式使用Serilog作為日誌記錄器。
        builder.Host.UseSerilog();

        var app = builder.Build();

        app.UseSerilogRequestLogging();

        // Configure the HTTP request pipeline.

        app.UseHttpsRedirection();

        app.UseAuthorization();


        app.MapControllers();

        app.Run();
    } catch (Exception ex) {
        Log.Error(ex, "Application start-up failed");
    } finally {
        Log.CloseAndFlush();
    }
```

由於 Log 的欄位很多，改用 Serilog.Formatting.Compact 來記錄 JSON 格式的 Log 訊息會清楚很多！
以下是 ==WriteTo== 的內容
```json
{
  "Serilog": {
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft.AspNetCore": "Warning"
      }
    },
    "Enrich": [ "FromLogContext" ],
    "WriteTo": [
      { "Name": "Console" },
      {
        "Name": "File",
        "Args": {
          "path": "./logs/log-.json",
          //以「天」為單位存放檔案
          "rollingInterval": "Day",
          // setting the formatter
          "formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact"
        }
      }
    ]
  },
  "AllowedHosts": "*"
}
```

以下是發出 ==GET /WeatherForecast== API 呼叫的 Log 內容
```json
{
  "@t": "2024-05-09T07:07:18.9931142Z",
  "@mt": "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms",
  "@r": [
    "61.4462"
  ],
  "@tr": "1a152b9fa32c2bee51b0036b5af214a8",
  "@sp": "f9455e1ea28e4ec8",
  "RequestMethod": "GET",
  "RequestPath": "/weatherforecast",
  "StatusCode": 200,
  "Elapsed": 61.4462,
  "SourceContext": "Serilog.AspNetCore.RequestLoggingMiddleware",
  "RequestId": "0HN3FUQL2EOUE:00000001",
  "ConnectionId": "0HN3FUQL2EOUE",
  "Application": "SerilogDemo"
}

```

### 將 Log 存到 SQL Server

將日誌記錄在 SQL Server ，需要安裝這個套件 [Serilog.Sinks.MSSqlServer](https://www.nuget.org/packages/Serilog.Sinks.MSSqlServer)
```shell
dotnet add package Serilog.Sinks.MSSqlServer
```
雖然有提供自動建立表格的設定，但我的習慣是先建立好 資料庫 / 表格 。

```sql=
CREATE TABLE [Logs] (

   [Id] int IDENTITY(1,1) NOT NULL,
   [Message] nvarchar(max) NULL,
   [MessageTemplate] nvarchar(max) NULL,
   [Level] nvarchar(128) NULL,
   [TimeStamp] datetime NOT NULL,
   [Exception] nvarchar(max) NULL,
   [Properties] nvarchar(max) NULL

   CONSTRAINT [PK_Logs] PRIMARY KEY CLUSTERED ([Id] ASC)
);

```

新增使用者以及對應的權限。
```sql=
CREATE ROLE [SerilogWriter];
GRANT SELECT TO [SerilogWriter];
GRANT INSERT TO [SerilogWriter];

CREATE LOGIN [Serilog] WITH PASSWORD = 'password';

CREATE USER [Serilog] FOR LOGIN [Serilog] WITH DEFAULT_SCHEMA = dbo;
GRANT CONNECT TO [Serilog];

ALTER ROLE [SerilogWriter] ADD MEMBER [Serilog];

```
appsetting.json 中 的 WriteTo區塊如下
```json=
"WriteTo": [
  { "Name": "Console" },
  {
    "Name": "File",
    "Args": {
      "path": "./logs/log-.json",
      //以「天」為單位存放檔案
      "rollingInterval": "Day",
      // setting the formatter
      "formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact"
    }
  },
  {
    "Name": "MSSqlServer", //need to install package Serilog.Sinks.MSSqlServer
    "Args": {
      "connectionString": "your connectionString",
      "tableName": "Logs"
    }
  }
]
```


<br>

### 不同等級的日誌分開記錄到不同的檔案
有一個情境如下:想要將不同等級的日誌記錄到不同的檔案中，例如:
==Information== 等級記錄在 Info.json，
==Warning==以上等級記錄在 Error.json。
<br>
需要利用**表達式**來過濾日誌，因此安裝這個套件 [Serilog.Sinks.Expressions](https://www.nuget.org/packages/Serilog.Expressions) ， 
```shell
dotnet add package Serilog.Expressions 
```

appsetting.json 中 的 WriteTo區塊如下
```json=
"WriteTo": [
  { "Name": "Console" },
  {
    "Name": "Logger",
    "Args": {
      "configureLogger": {
        "Filter": [
          {
            "Name": "ByIncludingOnly",
            "Args": {
              "expression": "(@l = 'Information' or @l = 'Debug')"
            }
          }
        ],
        "WriteTo": [
          {
            "Name": "File",
            "Args": {
              "path": "./logs/Information/log-.json",
              "rollingInterval": "Day",
              "formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact" // setting the formatter
            }
          }
        ]
      }
    }
  },
  {
    "Name": "Logger",
    "Args": {
      "configureLogger": {
        "Filter": [
          {
            "Name": "ByIncludingOnly",
            "Args": {
              "expression": "(@l = 'Error' or @l = 'Fatal' or @l = 'Warning')"
            }
          }
        ],
        "WriteTo": [
          {
            "Name": "File",
            "Args": {
              "path": "./logs/Warning/log-.json",
              "rollingInterval": "Day",
              "formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact" // setting the formatter
            }
          }
        ]
      }
    }
  }
]
```


<br>

### 將日誌存放到不同的 Table
另一種情境是:不希望日誌全部都在同一張表裡面，也許透過 Log 等級區分 或是 不同功能區分。

appsetting.json 中 的 WriteTo區塊如下
```json=
"WriteTo": [
  { "Name": "Console" },
  {
    "Name": "Logger",
    "Args": {
      "configureLogger": {
        "Filter": [
          {
            "Name": "ByIncludingOnly",
            "Args": {
              "expression": "LogCategory = 'Application'"
            }
          }
        ],
        "WriteTo": [
          {
            "Name": "MSSqlServer",
            "Args": {
              "connectionString": "your connectionString",
              "tableName": "ApplicationLogs"
            }
          }
        ]
      }
    }
  },
  {
    "Name": "Logger",
    "Args": {
      "configureLogger": {
        "Filter": [
          {
            "Name": "ByIncludingOnly",
            "Args": {
              "expression": "LogCategory = 'Business'"
            }
          }
        ],
        "WriteTo": [
          {
            "Name": "MSSqlServer",
            "Args": {
              "connectionString": "your connectionString",
              "tableName": "BusinessLogs"
            }
          }
        ]
      }
    }
  }  
  
]
```

上面的 ==expression== 設定是透過 ==LogCategory== 屬性的值來決定要存放到哪一個表格
<br>
下面的 4 - 12 行是日誌紀錄的方法

```csharp=
[HttpGet]
public IEnumerable<WeatherForecast> Get() {

    Log.ForContext("LogCategory", "Application").Information("這是一條應用層級的日誌1");
    using (LogContext.PushProperty("LogCategory", "Application")) {
        _logger.LogInformation("這是一條應用層級的日誌2");
    }
    Log.ForContext("LogCategory", "Business").Information("這是一條業務層級的日誌1");

    using (LogContext.PushProperty("LogCategory", "Business")) {
        _logger.LogInformation("這是一條業務層級的日誌2");
    }

    return Enumerable.Range(1, 5).Select(index => new WeatherForecast {
        Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
        TemperatureC = Random.Shared.Next(-20, 55),
        Summary = Summaries[Random.Shared.Next(Summaries.Length)]
    })
    .ToArray();
}
```


<br>

### 新增Log自訂欄位
在 SQL Server 的表格新增了一個欄位 [IP] varchar(50)，
並且修改 ==appsettings.json== 的內容，新增7-15行的設定，


```json=
"WriteTo": [
  {
    "Name": "MSSqlServer",
    "Args": {
      "connectionString": "your connectionString",
      "tableName": "your tableName",
      "columnOptionsSection": {
        "additionalColumns": [
          {
            "ColumnName": "IP",
            "DataType": "varchar",
            "DataLength": 50
          }
        ]
      }
    }
  }
]
```

然後寫入 log 的方式如下:
這裡的範例為:將這筆log 新增了 ==Logcategory== 欄位，設定為 ==Business==，代表這筆log 經過篩選後會寫到 ==BusinessLogs==表格，同時也新增了==IP==欄位。
```csharp
using (LogContext.PushProperty("LogCategory", "Business"))
using (LogContext.PushProperty("IP", clientIP)) {
    _logger.LogInformation(Message);
}
```
程式執行後可以在 ==BusinessLog==表格查詢到以下資訊，IP欄位有正確被設定並且寫入資訊
![image](https://hackmd.io/_uploads/H1mZCkym0.png)


<br>


### 把Log相關功能獨立成服務
寫到這裡會發現，其實應該要把 Log 相關功能獨立出來成為一個 class / service，然後在 program.cs 中注入，如此一來，所有的controller就可以使用這個 LogSevice 進行寫入日誌的功能，以下是一個簡單的範例
<br>

新增一個 Class，名稱為 ==LogService.cs==，內容如下

```csharp=
// LogService.cs
public class LogService {

    private readonly ILogger<LogService> _logger;
    public LogService(ILogger<LogService> logger) {
        _logger = logger;
    }

    public void LogInformationIP(string clientIP, string Message) {
        using (LogContext.PushProperty("LogCategory", "Business"))
        using (LogContext.PushProperty("IP", clientIP)) {
            _logger.LogInformation(Message);
        }
    }
}

```

<br>

在 program.cs 注入==LogService==，位置在 13 行。
```csharp=
//program.cs
try {
    var builder = WebApplication.CreateBuilder(args);

    //setting logger and read from appsettings.json
    Log.Logger = new LoggerConfiguration()
        .ReadFrom.Configuration(builder.Configuration)
        .CreateLogger();


    builder.Services.AddControllers();    

    builder.Services.AddScoped<LogService>(); <--新增這行

    builder.Host.UseSerilog();

    var app = builder.Build();

    app.UseSerilogRequestLogging();

    // Configure the HTTP request pipeline.

    app.UseHttpsRedirection();

    app.UseAuthorization();


    app.MapControllers();

    app.Run();
} catch (Exception ex) {
    Log.Error(ex, "Application start-up failed");
} finally {
    Log.CloseAndFlush();
}
```

<br>

Controller 修改 _logger 的型別為 ==LogService==。
```csharp=
//controller
private readonly LogService _logger;

public WeatherForecastController(LogService logger) {
    _logger = logger;
}

[HttpGet]
public IEnumerable<WeatherForecast> Get() {

    // add ip address to log
    string? ClientIP = HttpContext.Connection.RemoteIpAddress?.ToString();

    ClientIP = ClientIP?.Replace("::1", "127.0.0.1");

    _logger.LogInformationIP(ClientIP!, "這是一條帶有IP地址的日誌");


    return Enumerable.Range(1, 5).Select(index => new WeatherForecast {
        Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
        TemperatureC = Random.Shared.Next(-20, 55),
        Summary = Summaries[Random.Shared.Next(Summaries.Length)]
    })
    .ToArray();
}
```

<br>

這裡只是一個簡單的範例，實際上必須根據你的需求來設計 LogService 的內容。







---
參考來源
[1.NET 6.0 如何使用 Serilog 對應用程式事件進行結構化紀錄-From Will保哥](https://blog.miniasp.com/post/2021/11/29/How-to-use-Serilog-with-NET-6)
[2.Database Logging with Serilog in an ASP.NET Core Application](https://blog.fabritglobal.com/database-logging-serilog-asp-net-core/)