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 的錯誤)。
- 格式:能寫出不只純文字,還有 JSON、XML,方便後續處理。
- 彈性:有很多現成的 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");
這不只是好看——這是結構化日誌:欄位 Operation 和 FileName 會被記下來,方便後續做篩選與聚合。
跟 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 等級——明智地使用 Information 和 Warning。
- 在模板裡使用命名參數,而不是字串相加。
- 絕對不要記錄敏感資料(密碼、token、金鑰)。
- 記錄重要的業務事件,而不只是錯誤與例外。
- 設定好輪替與清理,避免磁碟被日誌塞滿。
視覺化與分析:Seq, Kibana, Application Insights
Serilog 支援很多 sink(接收端)——也就是把日誌送去哪裡。
| Sink | 簡短說明 | 使用場合 |
|---|---|---|
| Console | 直接輸出到 console | 開發、測試 |
| File | 寫到本機或網路檔案 | 小型專案、dev 環境 |
| Seq | 帶欄位過濾與 dashboard 的網頁介面 | 企業內部、分析 |
| ElasticSearch | 強大的儲存與分析系統 | 大型公司 |
| Azure Application Insights | 雲端監控與遙測 | Azure-heavy 的服務 |
Seq(datalust.co/seq)是開發與內部使用很受歡迎的解決方案:過濾方便、欄位搜尋好用、部署快。
表格與視覺化
下面是可以結構化記錄的範例清單:
| 要記錄的項目 | 在 Serilog 裡長什麼樣子 | 範例值 |
|---|---|---|
| 使用者 ID | |
123 |
| 動作 | |
"刪除" |
| 錯誤 | |
"註冊模組" |
| 操作時間 | {Elapsed:0.000} 秒 | 1.234 |
| 檔名 | |
"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——之後要換成 NLog 或 Log4Net 也很方便。
7. 使用 Serilog 時常見的錯誤
日誌過量:把所有東西都記下來會讓重要訊息被淹沒。
把例外當文字記錄:應該用帶例外物件的重載,這樣錯誤的結構化資訊會被記錄下來。
try
{
// some code
}
catch (Exception ex)
{
Log.Error(ex, "執行請求時發生錯誤");
}
濫用設定:不要把設定變成垃圾桶——只加你需要的 sink 和等級。
GO TO FULL VERSION