1. Introduction
如果普通的文本日志是你写的日记,记录着 "我今天吃了" 这样的备注,结构化日志把每条记录变成带字段的卡片:{日期: ..., 事件: "吃饭", 卡路里: 500, 菜品: "烤肉"}。这意味着你不仅能读日记,还能画出一个月的卡路里图表、按菜品过滤事件、查出什么时候吃得太晚。
Why is plain text not enough?
纯文本日志在开始时很方便……但很快会受限。想统计日志里的错误、统计电商的销售量、按用户 ID 跟踪某个用户的一系列操作(比如 42),如果所有数据都是一大串文本,会很难。结构化日志可以把分析和甚至 AI 挂上去,用来找异常、做仪表盘、自动响应问题。
Advantages
- 可以记录不仅仅是消息,还有相关数据(字段/属性)。
- 日志可以被自动分析:计数、过滤、生成报表。
- 使用标准格式,比如 JSON,机器很容易解析。
Serilog: что это и почему он?
Serilog (официальный сайт: serilog.net, документация: github.com/serilog/serilog/wiki) — 一个在 .NET 里流行的结构化日志库。它能很好地和 Microsoft.Extensions.Logging 集成,支持把日志输出到很多系统(文件、Console、Seq、ElasticSearch、Grafana、Azure 等),对性能影响小,配置也简单。
Чем Serilog отличается от «просто логирования»?
- Structure:日志是带字段的对象,可以按字段过滤(比如所有用户 ID 为 42 的错误)。
- Formats:不仅能写文本,还能写 JSON、XML,便于后续处理。
- Flexibility:有很多现成的 sink 包,可以把日志发到任何地方。
Structure записи лога с Serilog
先看一条结构化的日志,再写代码。
{
"Timestamp": "2024-06-22T10:23:45.123Z",
"Level": "Information",
"MessageTemplate": "用户 {UserId} 登录系统",
"Properties": {
"UserId": 42,
"IpAddress": "127.0.0.1"
}
}
即使不深度分析也能看出来:这是用户 №42 以及他的 IP。
2. Установка и базовая настройка Serilog в C# проекте
Step 1. Installation of NuGet packages
在 Rider/Visual Studio 里通过 NuGet Package Manager 安装:
- Serilog
- Serilog.Sinks.Console(输出到控制台)
- Serilog.Extensions.Logging(用于和 Microsoft.Extensions.Logging 集成)
通过命令行:
dotnet add package Serilog
dotnet add package Serilog.Sinks.Console
Step 2. Minimal configuration
在 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();
}
}
}
在控制台大概会看到类似:
[10:30:16 INF] 用户 42 使用 IP 127.0.0.1 登录了系统
之后很容易把输出导到 JSON 文件、Seq 或其它系统。
3. Logging formatting: Message Template
在 Serilog 里不用字符串拼接,而用模板语法:
Log.Information("Operation {Operation} on file {FileName}", "deletion", "test.txt");
这不仅是好看的写法 —— 这是结构化日志:记录会包含字段 Operation 和 FileName,可以用于过滤和聚合。
Чем отличается от string.Format?
string.Format("Operation {0} on file {1}", operation, fileName) 只是把字符串拼起来,用占位符 {0}, {1}。而 Serilog 会把它们做成独立字段,便于后续分析。
Flexible setup: levels, filters, various "sinks"
Serilog 可以同时写到多个地方。
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console()
.WriteTo.File("log.txt", rollingInterval: RollingInterval.Day)
.CreateLogger();
现在日志会同时写到控制台和按天滚动的文件。
4. Example
假设我们做一个控制台版「笔记」应用,能创建用户笔记。给它加上结构化日志。
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();
}
}
}
Comment:
日志会记录谁启动了程序、输入了什么、什么时候结束。在文件 notes-log.json 里每条记录都是一个 JSON 对象,便于分析。
5. Useful tips
Best practices for structured logging
- 别在生产环境滥用 Debug/Trace 级别——要合理使用 Information 和 Warning。
- 在模板里使用具名参数,不要用字符串拼接。
- 切勿记录敏感数据(密码、token、密钥)。
- 记录重要的业务事件,而不仅仅是错误和异常。
- 配置日志轮转和清理,避免磁盘被写满。
Visualization and analysis: Seq, Kibana, Application Insights
Serilog 支持很多 sink —— 日志要发到的终点。
| Sink | 简短说明 | 使用场景 |
|---|---|---|
| Console | 直接输出到 Console | 开发,测试 |
| File | 写入本地或网络文件 | 小项目,dev 环境 |
| Seq | 带过滤和仪表盘的 Web 界面 | 企业、分析 |
| ElasticSearch | 强大的存储与分析系统 | 大型公司 |
| Azure Application Insights | 云监控和遥测 | 在 Azure 上运行的服务 |
Seq (datalust.co/seq) — 在开发和内部使用中很受欢迎:过滤、按字段搜索很方便,部署也快。
Tables and visualization
下面是可以结构化记录的一些示例:
| 记录内容 | 在 Serilog 中的样子 | 示例值 |
|---|---|---|
| 用户 ID | |
123 |
| 动作 | |
"删除" |
| 错误 | |
"注册模块" |
| 操作耗时 | {Elapsed:0.000} 秒 | 1.234 |
| 文件名 | |
"report.pdf" |
Интересные фишки и дополнительные возможности Serilog
- Enrichers:给每条日志加入属性(比如 .Enrich.WithMachineName())。
- Linked logs:加入 RequestId 来关联事件链。
- Configuration via appsettings.json:适合生产环境配置。
{
"Serilog": {
"MinimumLevel": "Debug",
"WriteTo": [
{ "Name": "Console" },
{ "Name": "File", "Args": { "path": "log.txt" } }
]
}
}
高级 sink:还能把日志发到 Slack、Telegram、邮件(不过注意别把每个错误都变成一千封邮件)。
6. Практическая часть: интеграция с Microsoft.Extensions.Logging
在 .NET 里通常使用标准接口 ILogger,这样不绑定特定库。Serilog 可以作为一个 provider 接入。
Step 1. Package installation
dotnet add package Serilog.Extensions.Logging
Step 2. Configuring
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();
Comment:
现在你用 ILogger<T> 写的代码就和具体提供者无关了——想换到 NLog 或 Log4Net 都可以。
7. Типичные ошибки при работе с Serilog
日志过载:如果什么都记录,重要的信息会很难找到。
把异常当文本记录:用带 exception 的重载,这样日志里会有异常结构。
try
{
// some code
}
catch (Exception ex)
{
Log.Error(ex, "执行请求时发生错误");
}
滥用配置:别把配置弄成一堆乱七八糟的东西——只加必要的 sink 和级别。
GO TO FULL VERSION