CodeGym /課程 /C# SELF /結構化日誌和 Serilog

結構化日誌和 Serilog

C# SELF
等級 64 , 課堂 1
開放

1. 介紹

如果一般的純文字日誌是你的筆記本,寫著 "我今天吃了",那麼結構化日誌就把每筆記錄變成有欄位的卡片:{日期: ..., 事件: "吃飯", 卡路里: 500, 菜名: "煎肉"}。意思是你不只可以回頭看日記,還能畫出一個月的卡路里趨勢、用菜名做篩選,或看你哪天吃得太晚。

為什麼純文字不夠?

純文字一開始看起來很簡單……但到某個點就會出問題。試想你要從日誌裡整理錯誤統計、電商的銷售額、或是追蹤某個使用者的行為序列(例如 ID 是 42),當所有資料都只是長長的文字時就很難。結構化日誌可以把分析甚至 AI 接上去,找異常、做 dashboard、或自動回應問題。

好處

  • 可以記錄不只訊息,還有與之相關的資料(欄位/屬性)。
  • 日誌能被自動化分析:計數、過濾、生成報表。
  • 採用標準格式,例如 JSON,機器解析很方便。

Serilog:是什麼、為什麼選它?

Serilog(官方網站:serilog.net,文件:github.com/serilog/serilog/wiki)是 .NET 常用的結構化日誌函式庫。它能很好地整合 Microsoft.Extensions.Logging,支援輸出到很多系統(file、console、Seq、ElasticSearch、Grafana、Azure 等),對效能影響小,設定也簡單。

Serilog 跟「普通日誌」有什麼不同?

  • 結構:日誌是帶欄位的物件,可以用欄位做過濾(例如所有使用者 ID 為 42 的錯誤)。
  • 格式:能寫出不只純文字,還有 JSONXML,方便後續處理。
  • 彈性:有很多現成的 sink(接收端)可以直接把日誌送到各種地方。

Serilog 的日誌結構範例

先看看一筆結構化日誌長什麼樣,再開始寫程式。

{
  "Timestamp": "2024-06-22T10:23:45.123Z",
  "Level": "Information",
  "MessageTemplate": "使用者 {UserId} 已登入系統",
  "Properties": {
    "UserId": 42,
    "IpAddress": "127.0.0.1"
  }
}

這樣連比較簡單的分析也能看出來:是使用者 #42,還有他的 IP 位址。

2. 在 C# 專案中安裝與基本設定 Serilog

步驟 1. 安裝 NuGet 套件

在 Rider/Visual Studio 用 NuGet Package Manager 安裝:

  • Serilog
  • Serilog.Sinks.Console(輸出到 console)
  • Serilog.Extensions.Logging(用來整合 Microsoft.Extensions.Logging

也可以在命令列用:

dotnet add package Serilog
dotnet add package Serilog.Sinks.Console

步驟 2. 最小設定

Program.cs 加上設定,並寫第一條日誌。

using System;
using Serilog;

namespace MySuperApp
{
    class Program
    {
        static void Main(string[] args)
        {
            // 1. Serilog 的基本設定:輸出到 console
            Log.Logger = new LoggerConfiguration()
                .MinimumLevel.Debug()
                .WriteTo.Console()
                .CreateLogger();

            // 2. 結構化日誌的範例
            int userId = 42;
            string ip = "127.0.0.1";
            Log.Information("使用者 {UserId} 已登入系統,IP 為 {IpAddress}", userId, ip);

            Log.CloseAndFlush();
        }
    }
}

在 console 大概會看到類似這樣:

[10:30:16 INF] 使用者 42 已登入系統,IP 為 127.0.0.1

接下來很容易把輸出導到 JSON 檔、Seq 或其他系統。

3. 日誌格式:Message Template

Serilog 裡面不會用字串相加,而是用模板語法:

Log.Information("操作 {Operation} 對檔案 {FileName}", "刪除", "test.txt");

這不只是好看——這是結構化日誌:欄位 OperationFileName 會被記下來,方便後續做篩選與聚合。

string.Format 有什麼不同?

string.Format("操作 {0} 對檔案 {1}", operation, fileName) 只是把字串拼起來,用的是占位符 {0}{1}。而 Serilog 會把它們當成獨立欄位,之後可以用來做分析。

靈活設定:等級、過濾、不同的 sink

Serilog 可以同時把日誌寫到多個地方。

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Debug()
    .WriteTo.Console()
    .WriteTo.File("log.txt", rollingInterval: RollingInterval.Day)
    .CreateLogger();

現在日誌會同時寫到 console 和每天輪替的檔案。

4. 範例

假設我們做一個簡單的 console「記事本」應用,允許建立使用者的筆記。為各種動作加入結構化日誌。

using System;
using Serilog;

namespace NotesApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Log.Logger = new LoggerConfiguration()
                .MinimumLevel.Information()
                .WriteTo.Console()
                .WriteTo.File("notes-log.json", rollingInterval: RollingInterval.Day, 
                              formatter: new Serilog.Formatting.Json.JsonFormatter())
                .CreateLogger();

            Console.WriteLine("請輸入使用者名稱:");
            string userName = Console.ReadLine();

            Log.Information("使用者 {UserName} 啟動了 NotesApp", userName);

            while (true)
            {
                Console.WriteLine("請輸入筆記內容(或輸入 '退出'):");
                string note = Console.ReadLine();

                if (note == "退出")
                {
                    Log.Information("使用者 {UserName} 結束了應用程式", userName);
                    break;
                }

                Log.Information("使用者 {UserName} 建立了筆記:{NoteText}", userName, note);
            }

            Log.CloseAndFlush();
        }
    }
}

註解:
日誌會記錄誰啟動程式、輸入了什麼、以及何時結束。在檔案 notes-log.json 裡,每筆記錄都是一個可以被解析的 JSON 物件,方便分析。

5. 有用的小細節

結構化日誌的最佳實踐

  • 在 production 別濫用 Debug/Trace 等級——明智地使用 InformationWarning
  • 在模板裡使用命名參數,而不是字串相加。
  • 絕對不要記錄敏感資料(密碼、token、金鑰)。
  • 記錄重要的業務事件,而不只是錯誤與例外。
  • 設定好輪替與清理,避免磁碟被日誌塞滿。

視覺化與分析:Seq, Kibana, Application Insights

Serilog 支援很多 sink(接收端)——也就是把日誌送去哪裡。

Sink 簡短說明 使用場合
Console 直接輸出到 console 開發、測試
File 寫到本機或網路檔案 小型專案、dev 環境
Seq 帶欄位過濾與 dashboard 的網頁介面 企業內部、分析
ElasticSearch 強大的儲存與分析系統 大型公司
Azure Application Insights 雲端監控與遙測 Azure-heavy 的服務

Seqdatalust.co/seq)是開發與內部使用很受歡迎的解決方案:過濾方便、欄位搜尋好用、部署快。

表格與視覺化

下面是可以結構化記錄的範例清單:

要記錄的項目 在 Serilog 裡長什麼樣子 範例值
使用者 ID
{UserId}
123
動作
{Action}
"刪除"
錯誤
Log.Error(ex, "錯誤在 {Module}")
"註冊模組"
操作時間 {Elapsed:0.000} 1.234
檔名
{FileName}
"report.pdf"

Serilog 的有趣功能與擴充

  • Enrichers:可以為每條日誌加上屬性(例如 .Enrich.WithMachineName())。
  • 關聯日誌:加入 RequestId 以便串連事件序列。
  • 透過 appsettings.json 設定:對 production 很方便。
{
  "Serilog": {
    "MinimumLevel": "Debug",
    "WriteTo": [
      { "Name": "Console" },
      { "Name": "File", "Args": { "path": "log.txt" } }
    ]
  }
}

進階的 sink:可以把日誌發到 Slack、Telegram、Email(但要小心,別讓每個錯誤都寄一堆信)。

6. 實作:整合到 Microsoft.Extensions.Logging

在 .NET 常會使用標準介面 ILogger,這樣就不會綁死在某個函式庫。Serilog 可以當作一個 provider 接上去。

步驟 1. 安裝套件

dotnet add package Serilog.Extensions.Logging

步驟 2. 設定

using Microsoft.Extensions.Logging;
using Serilog;

// ...

// 照常配置 Serilog:
Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .CreateLogger();

// 現在使用 Microsoft.Extensions.Logging
var loggerFactory = LoggerFactory.Create(builder =>
{
    builder.AddSerilog();
});

ILogger<Program> logger = loggerFactory.CreateLogger<Program>();
logger.LogInformation("測試訊息: {TestValue}", 123);

// 別忘了關閉:
Log.CloseAndFlush();

註解:
這樣你專案中所有用 ILogger<T> 的程式碼就不會綁定到特定 provider——之後要換成 NLogLog4Net 也很方便。

7. 使用 Serilog 時常見的錯誤

日誌過量:把所有東西都記下來會讓重要訊息被淹沒。

把例外當文字記錄:應該用帶例外物件的重載,這樣錯誤的結構化資訊會被記錄下來。

try
{
    // some code
}
catch (Exception ex)
{
    Log.Error(ex, "執行請求時發生錯誤");
}

濫用設定:不要把設定變成垃圾桶——只加你需要的 sink 和等級。

2
任務
C# SELF, 等級 64, 課堂 1
上鎖
使用 Serilog 進行基礎結構化日誌記錄
使用 Serilog 進行基礎結構化日誌記錄
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION