
C# Serilog 新手入门
文章目录
- [C# Serilog 新手入门](# Serilog 新手入门)
-
- [1. 什么是第三方日志库与 Serilog](#1. 什么是第三方日志库与 Serilog)
-
- [1.1 概念讲解:什么是日志?为什么不能只写 `Console.WriteLine`?](#1.1 概念讲解:什么是日志?为什么不能只写
Console.WriteLine?) - [1.2 C# 生态中的优秀日志库对比](# 生态中的优秀日志库对比)
- [1.3 C# 日志技术的演进历程](# 日志技术的演进历程)
- [1.1 概念讲解:什么是日志?为什么不能只写 `Console.WriteLine`?](#1.1 概念讲解:什么是日志?为什么不能只写
- [2. Serilog 结构化日志与传统日志的对比](#2. Serilog 结构化日志与传统日志的对比)
-
- [2.1 结构化日志(Structured Logging)的核心特点](#2.1 结构化日志(Structured Logging)的核心特点)
-
- 传统日志代码与产出
- [Serilog 结构化日志代码与产出](#Serilog 结构化日志代码与产出)
- [2.2 Serilog 在代码与日志查询上的优势](#2.2 Serilog 在代码与日志查询上的优势)
- [2.3 帮助小白快速理解的心智模型](#2.3 帮助小白快速理解的心智模型)
- [3. 引入 Serilog 的意义与架构定位](#3. 引入 Serilog 的意义与架构定位)
-
- [3.1 实际应用场景与效率提升](#3.1 实际应用场景与效率提升)
- [3.2 软件架构中的定位](#3.2 软件架构中的定位)
- [4. Serilog 动手实战](#4. Serilog 动手实战)
-
- [4.1 第一行代码:5 分钟跑通 Hello World](#4.1 第一行代码:5 分钟跑通 Hello World)
-
- [第一步:安装 NuGet 包](#第一步:安装 NuGet 包)
- 第二步:编写极简控制台程序
- [4.2 语法与配置详解](#4.2 语法与配置详解)
-
- [4.2.1 结构化日志语法格式与核心组件](#4.2.1 结构化日志语法格式与核心组件)
- [4.2.2 两种配置方式:Fluent API 代码配置 vs `appsettings.json` 声明式配置](#4.2.2 两种配置方式:Fluent API 代码配置 vs
appsettings.json声明式配置)
- [5. 总结](#5. 总结)
针对刚接触 C# 开发、从未独立使用过第三方日志库的新手,很多关于 Serilog 的官方文档过于偏向 API 说明,容易让人"知其然不知其所以然"。
本教程参考了模块化的递进架构,用通俗直白的语言带你彻底搞懂Serilog到底强在哪?以及如何在真实系统中合理定位它。
1. 什么是第三方日志库与 Serilog
1.1 概念讲解:什么是日志?为什么不能只写 Console.WriteLine?
在刚学 C# 时,很多人习惯用 Console.WriteLine("用户登录成功") 来查看程序运行状态。但在真实的企业级项目(如 Web API、后台服务)中,这种做法存在巨大隐患:
- 数据随用随丢:程序重启或控制台窗口一关,输出的信息全部消失。
- 没有级别区分:无法区分普通提示(Information)、警告(Warning)还是系统崩溃(Fatal)。
- 阻塞性能:直接向控制台频繁写数据是同步且昂贵的 I/O 操作,会导致高并发下程序卡死。
- 缺乏上下文:只写一句"报错了",你不知道是谁在什么时间、哪条线程、哪个请求上报的错。
第三方日志库 就是为此而生的专业管家。它能够异步、高性能地将程序运行过程中的重要状态记录下来,并自动附带时间戳、线程号、日志级别,最终将日志输出到文件、数据库或远程日志中心进行长期保存。
1.2 C# 生态中的优秀日志库对比
C# 生态发展至今,出现了几款经典的第三方日志库:
| 日志库 | 历史与特点 | 适用场景 |
|---|---|---|
| Log4net | 移植自 Java 的 Log4j,历史最悠久,古老且稳定。 | 老旧项目的维护。由于配置繁琐(大量 XML)、缺少现代化特性,新项目不推荐。 |
| NLog | 性能优异,配置灵活,在 C# 生态中流行多年。 | 各种中大型传统项目,功能非常强大。 |
| Serilog | 现代化日志库首选,天然为"结构化日志"而生,社区最活跃。 | 全新项目的首选,特别是结合 Seq、ELK 等现代日志分析平台的项目。 |
1.3 C# 日志技术的演进历程
C# 日志技术经历了四个主要阶段:
- 原始时代 :直接使用
Console.WriteLine()或System.Diagnostics.Trace,功能极其原始。 - 文本日志时代(Log4net / NLog 早期) :开始将日志格式化写入文本文件(如
.log文件),但日志内容全是一串串拼接的字符串。 - 接口抽象时代(Microsoft.Extensions.Logging) :微软推出了官方日志抽象接口
ILogger,框架只定义标准,具体实现交由第三方库(如 Serilog)完成。 - 结构化日志时代(Serilog 领衔):不再把日志当作"一句话",而是当作"带属性的 JSON 数据对象",掀起了现代化运维与排查的变革。
2. Serilog 结构化日志与传统日志的对比
2.1 结构化日志(Structured Logging)的核心特点
传统日志输出的是非结构化纯文本 ,而 Serilog 输出的是结构化键值数据。
传统日志代码与产出
csharp
// 传统拼接字符串
logger.Info("用户 " + userId + " 在 " + DateTime.Now + " 购买了商品 " + productId);
// 产出文本: 用户 10086 在 2026-08-09 10:00:00 购买了商品 9988
当你在数 GB 的日志文件中想要检索"用户 10086 买了哪些商品"时,只能用复杂的正则表达式或全文逐字匹配,非常缓慢且容易误判。
Serilog 结构化日志代码与产出
csharp
// Serilog 消息模板(注意变量名前的名称)
logger.Information("用户 {UserId} 购买了商品 {ProductId}", userId, productId);
Serilog 在后台不仅会生成人眼可读的文本,还会同时保留数据的原始结构:
json
{
"Timestamp": "2026-08-09T10:00:00Z",
"Level": "Information",
"MessageTemplate": "用户 {UserId} 购买了商品 {ProductId}",
"Properties": {
"UserId": 10086,
"ProductId": 9988
}
}
2.2 Serilog 在代码与日志查询上的优势
- 像查 SQL 数据库一样查日志 :因为属性被单独提取保存了,在 Seq 或 Elasticsearch 等日志系统中,你可以直接输入
Properties.UserId == 10086或Properties.ProductId > 1000实施精确过滤。 - 复杂对象的原生拆解 :如果你传入一个 C# 对象,在变量名前加上
@符号(如{@User}),Serilog 会自动将其序列化为 JSON 展开存储,无需手动JsonConvert.SerializeObject。
2.3 帮助小白快速理解的心智模型
- ❌ 传统日志的心智模型 :把日志当成记事本,疯狂向里面追加一行行打印出来的文本。
- ✅ Serilog 的心智模型 :把打日志当成向一个无模式(NoSQL)数据库插入事件记录。你每一次记录日志,都是在发起一次数据结构收集。
3. 引入 Serilog 的意义与架构定位
3.1 实际应用场景与效率提升
- 生产环境快速排错:系统报错时,无需猜测参数,结构化日志直接呈现当时请求入参的完整 JSON 数据。
- 性能瓶颈追踪 :通过在日志中附带
ElapsedMilliseconds,可以秒级筛选出"执行时间大于 2000ms"的所有数据库查询日志。 - 安全审计与业务分析:记录关键业务事件(如支付、修改密码),方便日后查账或统计用户行为轨迹。
3.2 软件架构中的定位
在遵循领域驱动设计(DDD)或分层架构的系统里,日志属于典型的横切关注点(Cross-Cutting Concerns)。
- 配置与初始化:位于系统的最外层(基础设施层/启动入口)。
- 业务层使用 :业务层(应用层、领域层)不直接依赖 Serilog ,而是通过依赖注入使用微软官方抽象的
ILogger<T>接口记录日志。这样可以实现业务逻辑与具体日志实现框架的解耦。
下面的架构图展示了 Serilog 在 DDD 系统中的位置与数据流向:
#mermaid-svg-dztu8kaNlxwCeZtK{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-dztu8kaNlxwCeZtK .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-dztu8kaNlxwCeZtK .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-dztu8kaNlxwCeZtK .error-icon{fill:#552222;}#mermaid-svg-dztu8kaNlxwCeZtK .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dztu8kaNlxwCeZtK .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-dztu8kaNlxwCeZtK .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dztu8kaNlxwCeZtK .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dztu8kaNlxwCeZtK .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-dztu8kaNlxwCeZtK .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dztu8kaNlxwCeZtK .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dztu8kaNlxwCeZtK .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dztu8kaNlxwCeZtK .marker.cross{stroke:#333333;}#mermaid-svg-dztu8kaNlxwCeZtK svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dztu8kaNlxwCeZtK p{margin:0;}#mermaid-svg-dztu8kaNlxwCeZtK .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-dztu8kaNlxwCeZtK .cluster-label text{fill:#333;}#mermaid-svg-dztu8kaNlxwCeZtK .cluster-label span{color:#333;}#mermaid-svg-dztu8kaNlxwCeZtK .cluster-label span p{background-color:transparent;}#mermaid-svg-dztu8kaNlxwCeZtK .label text,#mermaid-svg-dztu8kaNlxwCeZtK span{fill:#333;color:#333;}#mermaid-svg-dztu8kaNlxwCeZtK .node rect,#mermaid-svg-dztu8kaNlxwCeZtK .node circle,#mermaid-svg-dztu8kaNlxwCeZtK .node ellipse,#mermaid-svg-dztu8kaNlxwCeZtK .node polygon,#mermaid-svg-dztu8kaNlxwCeZtK .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-dztu8kaNlxwCeZtK .rough-node .label text,#mermaid-svg-dztu8kaNlxwCeZtK .node .label text,#mermaid-svg-dztu8kaNlxwCeZtK .image-shape .label,#mermaid-svg-dztu8kaNlxwCeZtK .icon-shape .label{text-anchor:middle;}#mermaid-svg-dztu8kaNlxwCeZtK .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-dztu8kaNlxwCeZtK .rough-node .label,#mermaid-svg-dztu8kaNlxwCeZtK .node .label,#mermaid-svg-dztu8kaNlxwCeZtK .image-shape .label,#mermaid-svg-dztu8kaNlxwCeZtK .icon-shape .label{text-align:center;}#mermaid-svg-dztu8kaNlxwCeZtK .node.clickable{cursor:pointer;}#mermaid-svg-dztu8kaNlxwCeZtK .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-dztu8kaNlxwCeZtK .arrowheadPath{fill:#333333;}#mermaid-svg-dztu8kaNlxwCeZtK .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-dztu8kaNlxwCeZtK .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-dztu8kaNlxwCeZtK .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dztu8kaNlxwCeZtK .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-dztu8kaNlxwCeZtK .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dztu8kaNlxwCeZtK .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-dztu8kaNlxwCeZtK .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-dztu8kaNlxwCeZtK .cluster text{fill:#333;}#mermaid-svg-dztu8kaNlxwCeZtK .cluster span{color:#333;}#mermaid-svg-dztu8kaNlxwCeZtK div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-dztu8kaNlxwCeZtK .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-dztu8kaNlxwCeZtK rect.text{fill:none;stroke-width:0;}#mermaid-svg-dztu8kaNlxwCeZtK .icon-shape,#mermaid-svg-dztu8kaNlxwCeZtK .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dztu8kaNlxwCeZtK .icon-shape p,#mermaid-svg-dztu8kaNlxwCeZtK .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-dztu8kaNlxwCeZtK .icon-shape .label rect,#mermaid-svg-dztu8kaNlxwCeZtK .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dztu8kaNlxwCeZtK .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-dztu8kaNlxwCeZtK .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-dztu8kaNlxwCeZtK :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-dztu8kaNlxwCeZtK .highlight>*{fill:#e1f5fe!important;stroke:#0288d1!important;stroke-width:2px!important;}#mermaid-svg-dztu8kaNlxwCeZtK .highlight span{fill:#e1f5fe!important;stroke:#0288d1!important;stroke-width:2px!important;}#mermaid-svg-dztu8kaNlxwCeZtK .serilogNode>*{fill:#fff3e0!important;stroke:#f57c00!important;stroke-width:2px!important;}#mermaid-svg-dztu8kaNlxwCeZtK .serilogNode span{fill:#fff3e0!important;stroke:#f57c00!important;stroke-width:2px!important;} 外部日志接收终端
Cross-Cutting Concerns
Infrastructure Layer
Domain Layer
Application Layer
Presentation Layer
调用
调用
数据持久化
提供实现并注册
依赖注入
依赖注入
依赖注入
异步刷盘/推送
网络发送 JSON
OrdersController
OrderAppService
OrderDomainService
Database Context
Serilog 配置与全局初始化
Microsoft.Extensions.Logging.ILogger
本地磁盘文件
Seq 日志服务器 / ELK
InfraLayer
4. Serilog 动手实战
4.1 第一行代码:5 分钟跑通 Hello World
第一步:安装 NuGet 包
打开 Visual Studio 的 NuGet 包管理器,安装以下三个包:
Serilog(核心库)Serilog.Sinks.Console(输出到控制台插件)Serilog.Sinks.File(输出到文件插件)
第二步:编写极简控制台程序
csharp
using Serilog;
class Program
{
static void Main()
{
// 1. 初始化 Serilog 配置
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug() // 设置最低日志记录级别
.WriteTo.Console() // 输出到控制台
.WriteTo.File("logs/myapp.txt", rollingInterval: RollingInterval.Day) // 按天生成日志文件
.CreateLogger();
// 2. 打出第一行日志
Log.Information("Hello, Serilog! 欢迎来到结构化日志的世界。");
// 3. 带有变量的结构化日志
string userName = "张三";
int age = 18;
Log.Information("创建用户:姓名={UserName}, 年龄={Age}", userName, age);
// 4. 确保程序退出前把内存中的日志刷入磁盘
Log.CloseAndFlush();
}
}
4.2 语法与配置详解
4.2.1 结构化日志语法格式与核心组件
Serilog 由三个核心概念组成:
- Sinks(输出接收端):日志去哪里?(控制台、文件、数据库、Seq、Elasticsearch)。
- Enrichers(上下文增强器):自动补全环境信息(如:当前线程 ID、机器名、环境名)。
- Message Template(消息模板):定义变量提取规则。
关键语法技巧:@ 与 $ 占位符
csharp
var user = new { Id = 1001, Name = "李四", Role = "Admin" };
// 1. 默认处理:Serilog 会调用 user.ToString(),通常输出类名
Log.Information("用户信息:{User}", user);
// 2. 加上 @ 符号(Destructuring 解构):把对象展开为 JSON 存储
Log.Information("用户信息:{@User}", user);
// 3. 加上 $ 符号:强制转换为字符串(即使是复杂对象)
Log.Information("用户信息:{$User}", user);
4.2.2 两种配置方式:Fluent API 代码配置 vs appsettings.json 声明式配置
方式一:Fluent API(代码链式调用,适合小型项目或控制台应用)
csharp
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.Enrich.WithThreadId() // 需要安装 Serilog.Enrichers.Thread 包
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} (Thread:{ThreadId}){NewLine}{Exception}")
.CreateLogger();
方式二:appsettings.json(推荐!适合 ASP.NET Core 项目,灵活可配置)
需要安装包:Serilog.Settings.Configuration 和 Serilog.AspNetCore。
1. 修改 appsettings.json 文件:
json
{
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
},
"WriteTo": [
{ "Name": "Console" },
{
"Name": "File",
"Args": {
"path": "logs/log-.txt",
"rollingInterval": "Day",
"retainedFileCountLimit": 30
}
}
],
"Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ]
}
}
2. 在 ASP.NET Core Program.cs 中挂载:
csharp
var builder = WebApplication.CreateBuilder(args);
// 声明让 ASP.NET Core 读取配置文件并使用 Serilog 接管官方日志系统
builder.Host.UseSerilog((context, services, configuration) => configuration
.ReadFrom.Configuration(context.Configuration)
.ReadFrom.Services(services));
var app = builder.Build();
// 在 Controller 中即可通过标准 ILogger<T> 注入直接使用 Serilog 的强大功能
5. 总结
学习 Serilog 不仅仅是学会安装一个 NuGet 包,更是从传统文本运维 向现代化数据驱动运维转变的关键一步。
- 抛弃旧观念 :不要再使用
Console.WriteLine或简单的字符串拼接写日志。 - 掌握核心价值 :Serilog 的灵魂在于结构化,把日志当作带属性的事件数据保存。
- 架构规范 :业务代码中依赖微软抽象的
ILogger,通过依赖注入在基础设施层配置 Serilog,实现优雅的架构解耦。