Serilog 日志库的简介
〇、前言
相较于 log4net,Serilog 则是新项目的首选,现代化、高性能、易用,是 .NET 日志的未来方向。如需了解 log4net 详见往期博文:https://www.cnblogs.com/hnzhengfy/p/19121607/log4net。
log4net 虽然是一个广泛使用的、功能强大的日志记录库,而且专为 .NET 平台设计,但由于她是较老的日志框架,其生态在 .NET Core/.NET 5+ 环境中支持有限,因此还是要慎重考虑。
| 方面 | log4net | Serilog |
|---|---|---|
| 日志模型 | 文本日志:日志是纯字符串,信息混在一起。 例如:"用户 alice 从 192.168.1.1 登录" |
结构化日志:日志是带属性的数据事件。 例如:"用户 {UserName} 从 {IP} 登录",UserName="alice", IP="192.168.1.1" |
| 配置方式 | 主要靠 XML 文件,复杂且不易读。 | 支持代码和 appsettings.json,更现代、简洁。 |
| API 使用 | 需为每个类创建 logger 实例,较繁琐。 | 提供静态 Log 类,一行代码即可记录,更简单。 |
| 生态系统 | 老牌库,Appenders 丰富但更新慢。 | Sinks 生态活跃,原生支持 Elasticsearch、Seq、云平台等。 |
| .NET 集成 | 在 ASP.NET Core 中集成较麻烦。 | 与 ASP.NET Core 深度集成,自动记录请求上下文。 |
| 维护状态 | 活跃维护,持续更新,最新版 3.2.0(20250823)。 | 活跃维护,持续更新,支持最新 .NET 版本。 |
经过对比,当然还是 Serilog 更有优势,那么本文将就概念方面来详细介绍下,后续会继续更新相关用法实践,有兴趣可持续关注。
一、Serilog 简介
Serilog 是一个为 .NET 应用程序设计的强大的诊断日志库,以易于设置、简洁的 API 和跨所有最新 .NET 平台的兼容性而著称。
它是 .NET 平台中非常流行且强大的结构化日志库,其最大特点是结构化日志记录(Structured Logging)。
1.1 核心特点:结构化日志记录(Structured Logging)
日志不是简单的字符串,而是包含命名属性的结构化事件。
// 例如记录用户登录事件
string username = "bob";
string clientIp = "10.0.0.5";
int userId = 789;
Log.Information("用户 {UserName} (ID: {UserId}) 从 {ClientIP} 登录",
username, userId, clientIp);
// 生成的结构化数据(以 JSON 形式表示):
{
"Timestamp": "2025-03-28T14:30:00Z",
"Level": "Information",
"MessageTemplate": "用户 {UserName} (ID: {UserId}) 从 {ClientIP} 登录",
"RenderedMessage": "用户 bob (ID: 789) 从 10.0.0.5 登录",
"Properties": {
"UserName": "bob",
"UserId": 789,
"ClientIP": "10.0.0.5"
}
}
// 可以直接在 Seq 或 Elasticsearch 中搜索,例如:UserName = 'bob'
这样便于和另外的日志接收系统对接,针对日志量较大的场景比较友好。
1.2 核心特点:强大的 Sink 生态系统
Serilog 通过 Sinks(接收器)将日志事件发送到各种目的地。这种“即插即用”的架构是 Serilog 强大和流行的关键。
官方 Sinks:Serilog.Sinks.Console(控制台)、Serilog.Sinks.File(文件)、Serilog.Sinks.Seq(Seq 服务器)、Serilog.Sinks.Elasticsearch(Elasticsearch)等。
第三方 Sinks:支持 Splunk、DataDog、Graylog、Kafka、RabbitMQ、AWS CloudWatch 等
优势:可以同时配置多个 Sinks,将同一日志事件发送到不同地方。
工作原理:
日志事件生成:业务端代码调用 Log.Information("..."),Serilog 创建一个 LogEvent 对象。
事件处理:该事件可能经过过滤、丰富(Enrichment)等处理。
分发到 Sinks:Serilog 核心引擎将处理后的 LogEvent 分发给所有配置的 Sinks。
Sink 执行写入:每个 Sink 根据自己的逻辑,将 LogEvent 转换为适合目标系统的格式(如 JSON、文本行),并通过相应协议(如 HTTP、文件 I/O)发送出去。
1.3 核心特点:简洁易用的 API
使用静态 Log 类,无需依赖注入即可在任何地方记录日志。
它极大地简化了日志记录的开发体验,让开发者能够以最少的代码、最直观的方式完成强大的日志功能。
// Serilog:全局静态 Log 类,任何地方直接调用
Log.[Level](string messageTemplate, params object[] propertyValues); // 格式
Log.Verbose("调试信息: {Detail}", detail); // 示例
Log.Debug("进入方法: {MethodName}", "ProcessOrder");
Log.Information("订单 {OrderId} 已创建", orderId);
Log.Warning("库存不足,商品 {ProductId}", productId);
Log.Error(exception, "处理支付失败,用户 {UserId}", userId);
Log.Fatal("应用程序即将退出");
// 对比传统日志库 log4net 的方式:
// 每个类都需要声明一个 logger 实例,代码重复,不够优雅
private static readonly ILog log = LogManager.GetLogger(typeof(MyClass));
public void DoSomething()
{
log.Info("用户登录");
}
支持所有标准日志级别:Verbose,Debug,Information,Warning,Error,Fatal。
使用 {PropertyName} 占位符,清晰表达意图。
自动提取属性值,生成结构化数据。
支持格式化说明符,如 {LoginTime:HH:mm}。
即使没有提供参数,也不会抛出异常(会原样输出占位符)。
// 异常处理的优雅支持
// 记录异常是常见需求,Serilog 提供了专门的重载,让异常日志既简洁又完整
try
{
// ...
}
catch (Exception ex)
{
Log.Error(ex, "处理订单 {OrderId} 失败", orderId);
}
// 第一个参数是 Exception 对象,Serilog 会自动捕获堆栈跟踪。
// 后续参数用于填充消息模板中的属性。
// 生成的日志事件同时包含异常详情和业务上下文。
| 特性 | 如何体现“简洁易用” |
|---|---|
| 静态 Log 类 | 全局可用,无需实例化,减少样板代码 |
| 统一方法签名 | 所有日志级别使用相同模式,降低记忆负担 |
| 消息模板 | 结构化日志 + 直观语法,代码可读性强 |
| 异常支持 | 一行代码记录异常和上下文 |
| 异步/批量 | 通过 .Async() 包装器轻松实现,无需手动线程管理 |
| 丰富 Sink | 配置简单,更换输出目标只需修改几行代码 |
1.4 核心特点:异步日志记录
Serilog 通过将日志写入操作从应用程序的主要执行线程中分离出来,显著提升了应用程序的性能和响应能力。
异步非由 Serilog 核心库直接实现,而是通过 Serilog.Sinks.Async 这个专门的包来完成。特别适合高并发、高吞吐量的场景。
在传统的同步日志中,当代码执行到Log.Information("...")时,当前线程必须等待日志被完全写入目标(如文件、数据库、网络等)后才能继续执行。如果日志目标响应慢(如网络延迟、磁盘I/O瓶颈),这会直接阻塞业务线程,导致应用性能下降。
Serilog.Sinks.Async 在日志管道中引入了一个异步代理(Asynchronous Sink)。当调用 Log.Information("...") 时,日志事件(Log Event)会被快速地放入一个内存中的队列(通常是 BlockingCollection),然后调用线程立即返回,继续执行后续业务代码。一个或多个后台工作线程会从这个队列中取出日志事件,并在后台安全地将它们写入最终的日志接收器(Sinks)。
异步执行带来的好处:
- 提升应用性能与响应速度
减少主线程阻塞:这是最直接的好处。业务逻辑不再需要等待缓慢的I/O操作,大大缩短了请求处理时间,尤其是在高并发场景下效果显著。
平滑性能波动:即使日志目标出现暂时性延迟,也不会立刻影响到应用程序的响应时间,因为日志事件已经“脱手”进入队列。
- 配置简单,透明集成
使用 Serilog.Sinks.Async 非常简单。只需在配置日志管道时,将任何现有的 Sink(如 WriteTo.File(...))包装在WriteTo.Async(...)中即可。例如:
Log.Logger = new LoggerConfiguration()
.WriteTo.Async(a => a.File("logs/myapp.txt")) // 将文件Sink包装在Async中
.CreateLogger();
业务代码中调用 Log.Information(...) 的方式完全不变,异步化对业务层是透明的。
- 基于队列的缓冲机制
Serilog.Sinks.Async 内部使用一个线程安全的队列来暂存日志事件。
这个队列充当了生产者-消费者模型中的缓冲区。应用程序线程是“生产者”,后台线程是“消费者”。
队列的大小可以配置(通过 AsyncOptions),允许用户在内存使用和日志丢失风险之间进行权衡。
- 可配置的背压处理(Backpressure Handling)
采用队列的方式就会有一个隐患,当日志产生速度远超后台线程处理速度时,队列会不断增长,可能导致内存耗尽。
然而,Serilog.Sinks.Async 提供了多种策略来应对这种情况(通过 AsyncOptions 配置):
Blocking(默认):当队列满时,Log.Information() 调用会阻塞,直到队列有空间。这保证了日志不丢失,但可能影响性能。
DropWrite:当队列满时,新产生的日志事件会被直接丢弃。这保证了应用性能,但牺牲了日志的完整性。
OverflowAction:更高级的选项,允许你定义更复杂的策略,如丢弃旧日志或触发警报。
- 优雅关闭与日志完整性
当应用程序关闭时,正确地处理异步日志至关重要,以确保队列中所有待处理的日志都能被写入。
必须在程序退出前调用 Log.CloseAndFlush()。它会停止接收新日志,然后等待后台线程将队列中剩余的所有日志事件处理完毕,最后才返回。
忽略此步骤是导致日志丢失的最常见原因。
- 与结构化日志的完美结合
Serilog 的核心是结构化日志(Structured Logging),即日志以带有属性的结构化数据形式记录,而非纯文本。
异步记录与结构化日志相得益彰。结构化日志的序列化(如序列化为JSON)可能消耗CPU,异步化可以将这部分开销也移出主线程。
总的来说,Serilog 是构建高性能、高响应性 .NET 应用程序日志方案的关键组件,尤其适用于Web API、高并发服务等对延迟敏感的场景。但对于日志量不大,对延迟不太敏感的场景,还是更推荐同步的,因为异步无法避免的会占用更多资源。
1.5 核心特点:丰富的配置方式
Serilog 允许开发者根据项目类型、环境需求和团队习惯,选择最适合的方式来设置日志管道(Logger Pipeline)。这种灵活性极大地提升了可维护性和适应性。
- 代码配置(Code-based Configuration)
这是最基础、最灵活、也是最推荐的方式,通过 C# 代码直接构建 LoggerConfiguration 对象。
特点:
完全控制:提供对日志配置的完全编程控制,可以执行复杂的逻辑(如条件判断、循环、读取环境变量等)。
编译时检查:得益于强类型,配置错误通常能在编译时被发现。
易于调试:可以在配置代码中设置断点,逐步检查配置过程。
动态性:可以根据运行时条件(如环境变量、命令行参数)动态调整配置。
核心 API:
LoggerConfiguration():创建配置构建器。
.MinimumLevel.*():设置全局或特定来源的最低日志级别(如 .MinimumLevel.Debug())。
.WriteTo.*():添加日志接收器(Sinks),指定日志输出目标(如文件、控制台、数据库)。
.Filter.*():添加过滤器,决定哪些日志事件应被处理或丢弃。
.Enrich.*():添加“丰富器”(Enrichers),为日志事件自动添加上下文信息(如机器名、进程ID、线程ID、请求ID)。
.Destructure.*():配置对象解构策略,控制复杂对象如何被序列化到日志中。
.CreateLogger():最终生成 ILogger 实例并赋值给 Log.Logger。
如下示例详解:
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug() // 全局设置日志的最低输出级别为 Debug
.MinimumLevel.Override("Microsoft", LogEventLevel.Information) // Microsoft.* 相关的日志只有 Information 及以上才会输出
.Enrich.FromLogContext() // 添加 Serilog 的结构化上下文,允许在特定作用域内添加结构化数据(比如用户ID、请求ID)
.Enrich.WithMachineName() // 自动添加当前机器名(Environment.MachineName)
.Enrich.WithThreadId() // 添加当前线程 ID,便于多线程/异步调试时追踪日志来源
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}") // 将日志写入控制台(stdout)示例:[14:23:01 INF] User 'alice' logged in successfully.
.WriteTo.File("logs/myapp.txt", rollingInterval: RollingInterval.Day) // 每天生成一个新的日志文件,这需要引用 Serilog.Sinks.File 和 Serilog.Sinks.RollingFile 包
.WriteTo.Async(a => a.Elasticsearch(new ElasticsearchSinkOptions(new Uri("http://localhost:9200"))
{ // 使用 .WriteTo.Async() 包装 Elasticsearch 输出,使其异步写入,避免阻塞主线程
AutoRegisterTemplate = true
}))
// 使用 Filter.ByExcluding 排除某些日志事件
// Matching.FromSource("Microsoft.AspNetCore.StaticFiles"):匹配来自 Microsoft.AspNetCore.StaticFiles 命名空间的日志源
.Filter.ByExcluding(Matching.FromSource("Microsoft.AspNetCore.StaticFiles"))
.CreateLogger();
- JSON 配置(JSON Configuration)
Serilog 支持从标准的 .NET 配置文件(如 appsettings.json)中读取配置。这对于希望将配置与代码分离的项目非常有用。
特点:
配置与代码分离:配置信息独立于代码,便于非开发人员(如运维)修改。
环境友好:可以利用 appsettings.Development.json、appsettings.Production.json 等文件实现环境差异化配置。
易于部署:部署时只需替换配置文件即可调整日志行为,无需重新编译。
如何实现?
首先安装 Serilog.Settings.Configuration NuGet 包;
在 appsettings.json 中定义 Serilog 配置节点;
在程序启动时,使用 ReadFrom.Configuration(IConfiguration) 方法读取。
配置示例:
{
"Serilog": {
"Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ], // 指定使用的Sinks包
"MinimumLevel": "Debug",
"Override": {
"Microsoft": "Information",
"System": "Warning"
},
"WriteTo": [
{
"Name": "Console",
"Args": {
"outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"
}
},
{
"Name": "File",
"Args": {
"path": "logs/myapp.txt",
"rollingInterval": "Day"
}
}
],
"Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ],
"Destructure": [
{
"Name": "ToMaximumDepth",
"Args": { "maximumDestructuringDepth": 4 }
}
]
}
}
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration) // 从 IConfiguration 读取 Serilog 配置
.CreateLogger();
- App.config / Web.config(XML Configuration)
对于传统的 .NET Framework 应用程序(非 .NET Core),Serilog 也支持通过 app.config 或 web.config 文件进行配置。类似于 JSON 配置,但使用 XML 语法。
配置方法就是,安装 Serilog.Settings.AppSettings NuGet 包。在 .config 文件的 <appSettings> 节点中添加以 serilog: 为前缀的键值对。
配置示例:
<configuration>
<appSettings>
<add key="serilog:minimum-level" value="Debug" />
<add key="serilog:write-to:Console" />
<add key="serilog:write-to:File.path" value="logs\myapp.txt" />
<add key="serilog:enrich:WithMachineName" />
</appSettings>
</configuration>
Log.Logger = new LoggerConfiguration()
.ReadFrom.AppSettings() // 从AppSettings读取
.CreateLogger();
- 环境变量配置
Serilog 可以直接从环境变量中读取配置,这在容器化(Docker, Kubernetes)和云原生环境中非常实用。
特点:
云原生友好:容器和云平台(如 Azure, AWS)通常通过环境变量传递配置。
动态注入:无需修改代码或配置文件,通过部署脚本或平台设置即可改变日志行为。
与 appsettings.json 结合:环境变量可以覆盖 appsettings.json 中的值。
实现方式:
使用 Serilog.Settings.Configuration 包时,IConfiguration 本身支持从环境变量读取。
环境变量的名称需要遵循特定的格式来映射到 JSON 结构。例如,要覆盖 appsettings.json 中的 Serilog:MinimumLevel,可以设置环境变量 Serilog__MinimumLevel=Warning(双下划线 __ 表示层级)。
- 组合与优先级
Serilog 允许组合多种配置方式,通常遵循一定的优先级。
代码配置:最高优先级,直接在代码中设置的值会覆盖其他来源。
环境变量:通常优先级高于配置文件中的静态值。
JSON / XML 配置文件:基础配置来源。
默认值:如果以上都没有设置,则使用 Serilog 的内置默认值。
推荐配置方式:
// 1. 构建 IConfiguration,从多个来源读取(包括环境变量)
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) // 作为基础配置 reloadOnChange:启用配置热重载
.AddJsonFile($"appsettings.{environment}.json", optional: true) // optional: true 允许特定环境文件不存在,此时将回退到基础配置
.AddEnvironmentVariables() // 允许环境变量覆盖,在 Docker、Kubernetes、Azure App Service 等云平台中,环境变量是传递配置的首选方式
.Build();
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration) // 从配置文件和环境变量加载基础配置
.MinimumLevel.Override("MyApp.Sensitive", LogEventLevel.Verbose) // 允许在代码中强制覆盖某些关键部分的日志行为,即“安全护栏”,防止因配置错误导致的严重后果
.CreateLogger();
关于 .AddEnvironmentVariables() 配置:
云原生友好:在 Docker、Kubernetes、Azure App Service 等云平台中,环境变量是传递配置的首选方式。它安全(避免将敏感信息写入代码或配置文件)、灵活(通过部署脚本或平台界面即可修改)且与代码解耦。
最高优先级覆盖:在配置提供程序的加载顺序中,AddEnvironmentVariables() 通常在最后,因此它的值优先级最高,可以覆盖 appsettings.json 中的任何设置。
示例场景:
基础配置 appsettings.json 中设置 Serilog:MinimumLevel=Information。
运维人员在生产环境中发现一个偶发问题,需要临时开启 Debug 日志。
无需修改任何代码或配置文件,只需在服务器或容器中设置环境变量 Serilog__MinimumLevel=Debug。
应用重启后(或配合热重载),日志级别立即生效,问题排查完毕后,移除环境变量即可恢复原状。零代码变更,快速响应。
1.6 核心特点:日志丰富(Enrichment)
日志丰富是 Serilog 区别于许多其他 .NET 日志框架(如内置的 ILogger)的一个关键优势。
它允许在不修改代码或日志语句的情况下,为日志事件自动添加上下文信息。这极大地提升了日志的可读性、可追溯性和诊断能力。
简单来说,Enrichment 就是“给日志事件自动添加额外的、有价值的上下文信息”。
Serilog 的 LoggerConfiguration 允许通过 .Enrich.With(...) 方法注册一个或多个丰富器(Enricher)。
一个丰富器是一个实现了 ILogEventEnricher 接口的对象,它有一个 Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) 方法。Serilog 会在每条日志事件被处理时调用所有注册的丰富器。
Serilog 内置的丰富器(Built-in Enrichers)如下:
Enrich.WithProperty()
// 为所有日志事件添加一个静态的、固定的属性。
// 场景:添加应用名称、版本号、环境(Development/Production)等。
// 例如:.Enrich.WithProperty("Application", "MyWebApp") // 每条日志都会自动包含 "Application": "MyWebApp"
Enrich.WithMachineName() (Serilog.Enrichers.Environment)
// 功能:添加运行应用的机器名称。
// 场景:在多服务器部署中,快速定位日志来源机器。
Enrich.WithEnvironmentUserName() / Enrich.WithEnvironmentName() (Serilog.Enrichers.Environment)
// 功能:添加当前操作系统用户名和环境变量(如 ASPNETCORE_ENVIRONMENT)。
// 场景:了解运行环境和用户。
Enrich.WithThreadId() / Enrich.WithThreadName() (Serilog.Enrichers.Thread)
// 功能:添加当前线程 ID 或名称。
// 场景:在多线程应用中追踪日志的执行线程。
Enrich.WithProcessId() / Enrich.WithProcessName() (Serilog.Enrichers.Process)
// 功能:添加进程 ID 和进程名称。
// 场景:区分同一机器上运行的多个实例。
Enrich.WithDemystifiedStackTraces() (Serilog.Exceptions)
// 功能:当记录异常时,提供更清晰、去除了编译器生成代码的堆栈跟踪(“去神秘化”)。
// 场景:让异常堆栈更易读。
Enrich.WithClientIp() / Enrich.WithClientAgent() (Serilog.AspNetCore)
// 功能:在 ASP.NET Core 应用中,自动从 HTTP 上下文中提取客户端 IP 地址和 User-Agent。
// 场景:Web 应用必备,用于安全审计和用户行为分析。
Enrich.WithRequestHeader() / Enrich.WithRequestProperty() (Serilog.AspNetCore)
// 功能:从 HTTP 请求头或属性中提取特定值作为日志属性。
// 例如
.Enrich.WithRequestHeader("X-Correlation-ID") // 提取关联ID
.Enrich.WithRequestProperty("TenantId") // 提取路由或中间件设置的属性
Enrich.FromLogContext() (极其重要!)
// 功能:这是 Serilog 最强大的丰富器之一。它允许你在代码的特定作用域内动态地添加上下文信息。
// 机制:使用 LogContext.PushProperty("PropertyName", value)。
// 作用域:通过 using 语句创建一个作用域,该作用域内的所有日志都会自动包含 PushProperty 添加的属性。
// 例如:
using (LogContext.PushProperty("TransactionId", transactionId))
using (LogContext.PushProperty("UserId", userId))
{
Log.Information("开始处理交易");
// ... 更多操作 ...
Log.Information("交易处理完成");
// 这两条日志都自动包含 TransactionId 和 UserId
}
// 作用域结束后,这些属性自动移除
// 场景:处理用户请求、数据库事务、消息处理等需要贯穿整个操作流程的上下文。
如何试下自定义丰富器?
实现 ILogEventEnricher 接口。然后在 Enrich 方法中,使用 propertyFactory 创建 LogEventProperty,最后并添加到 logEvent.Properties 中。
如下示例:
public class UtcTimestampEnricher : ILogEventEnricher
{
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
var utcNow = DateTimeOffset.UtcNow;
var property = propertyFactory.CreateProperty("UtcTimestamp", utcNow);
logEvent.AddPropertyIfAbsent(property);
}
}
// 使用
Log.Logger = new LoggerConfiguration()
.Enrich.With<UtcTimestampEnricher>()
.WriteTo.Console()
.CreateLogger();
Enrichment 带来的优势可以概括为:
减少代码重复:无需在每个日志语句中手动添加通用信息。
提高日志质量:确保关键上下文(如 RequestId、UserId)不会被遗漏。
非侵入性:业务代码更干净,日志语句更简洁。
动态上下文:通过 LogContext 支持基于作用域的动态丰富。
易于扩展:可以轻松添加新的丰富器或自定义逻辑。
强大的诊断能力:丰富的上下文信息使得在海量日志中追踪问题、分析用户行为变得非常高效。
1.7 核心特点:对象解构(Destructuring)
简单来说,对象解构是指 Serilog 在记录日志时,能够深入分析(introspect)你传递给日志方法的复杂对象(如自定义类、集合、匿名对象等),并将它们的内部属性和值提取出来,以结构化的方式(通常是 JSON 格式)记录到日志中,而不是仅仅记录对象的类型名称或 ToString() 的结果。
几个主要特点如下:
- 深度属性提取(Deep Property Extraction)
Serilog 不仅能解构对象的直接属性,还能递归地解构嵌套对象。
例如,如果 User 类中有一个 Address 属性(也是一个复杂对象),解构后 Address 的属性(如 Street, City)也会被完整记录。
public class Address { public string Street { get; set; } public string City { get; set; } }
public class User { public int Id { get; set; } public string Name { get; set; } public Address HomeAddress { get; set; } }
var user = new User {
Id = 123,
Name = "Alice",
HomeAddress = new Address { Street = "123 Main St", City = "Wonderland" }
};
logger.LogInformation("User created: {User}", user);
// 解构后的 JSON 片段:
// "User": {
// "Id": 123,
// "Name": "Alice",
// "HomeAddress": {
// "Street": "123 Main St",
// "City": "Wonderland"
// }
// }
- 集合解构(Collection Destructuring)
数组、列表、字典等集合类型也会被解构。
数组和列表会被转换为 JSON 数组。
字典会被转换为 JSON 对象,其键值对成为对象的属性。
var tags = new List<string> { "urgent", "bug", "frontend" };
var metadata = new Dictionary<string, object> { { "Priority", 1 }, { "AssignedTo", "Bob" } };
logger.LogInformation("Task updated with tags: {Tags} and metadata: {Metadata}", tags, metadata);
// 解构后的 JSON 片段:
// "Tags": ["urgent", "bug", "frontend"],
// "Metadata": { "Priority": 1, "AssignedTo": "Bob" }
- 匿名对象支持(Anonymous Object Support)
可以直接传入匿名对象,Serilog 会完美地将其解构并作为日志的一部分。
logger.LogInformation("Operation completed", new { DurationMs = 45, RecordsProcessed = 100 });
// "DurationMs": 45, "RecordsProcessed": 100
- 性能考量与控制(Performance and Control)
潜在开销:深度解构一个非常庞大或深层嵌套的对象可能会带来性能开销(CPU、内存、日志大小)。
解构策略(Destructuring Policies):Serilog 允许你通过 Destructure 配置来精细化控制解构行为:
ByTransforming<T>(...):为特定类型 T 定义自定义的解构逻辑。例如,你可能只想记录 User 对象的 Id 和 Name,而忽略敏感的 PasswordHash。.Destructure.ByTransforming<User>(user => new { user.Id, user.Name })
ByIgnoringPropertiesOfType<T>():忽略特定类型的属性(例如,忽略所有 byte[] 或 Stream 类型的属性,因为它们不适合日志)。
ByIgnoringMembers(...):忽略特定成员(属性或字段)。
MaximumDepth(int depth):设置解构的最大递归深度,防止无限递归或过深的结构。
MaximumStringLength(int length):限制字符串属性的最大长度,防止超长日志条目。
@ 符号(The @ Operator):这是一个非常强大的内联控制语法。在日志消息的占位符前加上 @,可以强制对该位置的对象进行解构,即使它是一个简单的值类型或字符串。
更重要的是,@ 是解构的“开关”。如果你不希望某个复杂对象被解构,可以使用 $ 符号(但 Serilog 实际上是用 @ 来 启用 解构,不加 @ 则可能只记录 ToString())。
在 Serilog 中,想要确保对象被解构,在占位符前使用 @ 就可以了。
var user = new User { ... };
// 推荐写法,明确要求解构
logger.LogInformation("User action: {@User}", user);
// 如果不加 @,且 User 类没有重写 ToString(),可能只记录类型名
logger.LogInformation("User action: {User}", user); // 可能不是你想要的!
// 使用 @ 也可以解构集合或值类型(虽然对值类型意义不大)
logger.LogInformation("Scores: {@Scores}", new[] { 95, 87, 92 });
- 与结构化日志的协同(Synergy with Structured Logging)
对象解构是 Serilog 实现真正结构化日志的核心支柱。
解构后的数据是结构化的 JSON 对象,而不是难以解析的文本。这使得日志可以被现代日志聚合和分析工具(如 Seq, Elasticsearch, Splunk, Datadog)高效地索引、搜索、过滤和可视化。
这样就可以轻松地根据 User.Name 条件,来查询 Alice,其中 Status 为 Failed 的日志,这在纯文本日志中是几乎不可能高效完成的。
Serilog 的对象解构功能将日志记录从简单的文本记录提升到了结构化数据记录的新高度。它通过自动、深度地分析和提取复杂对象的内部结构,生成富含上下文的 JSON 数据,使得日志不再是难以解读的“黑盒”,而是可以被程序高效处理和分析的宝贵资产。熟练掌握 @ 操作符和 Destructure 配置策略,是发挥 Serilog 强大威力的关键。
1.8 几个适用场景
- 微服务架构
每个微服务独立记录日志,通过集中式日志系统(如 Seq、Elasticsearch)进行聚合,便于跨服务的请求追踪和问题排查。
- 云原生应用
与 Kubernetes、Docker 等容器化平台无缝集成,通过 WriteTo.Console() 将日志输出到标准输出,被容器编排平台自动捕获
- 需要复杂日志分析的系统
需要对日志进行聚合、分析和可视化(如使用 Seq 的查询语言),适合需要从日志中提取结构化数据进行业务分析的场景。
- 高性能要求的应用
异步日志记录机制确保日志不会成为性能瓶颈,特别适合高并发、高吞吐量的系统。
- 与现代监控系统集成
与 Seq、Elasticsearch、Splunk、DataDog 等监控平台无缝集成,便于构建完整的可观测性系统。
二、简单列一下有哪些 Serilog Sink 类型
文件系统 Sink、控制台与调试 Sinks、集中式日志与分析平台 Sinks、消息队列 Sinks、数据库 Sinks、其他 Sinks 等等。
Serilog的强大之处在于其Sink的灵活性和可组合性,一个典型的生产环境配置可能会结合多种分类的Sink。
后续博主将继续以 Sink 的各个类型为基础,进行实践并输出实践记录,有兴趣可持续关注。
本文来自博客园,作者:橙子家,欢迎微信扫码关注博主【橙子家czzj】,有任何疑问欢迎沟通,共同成长!

浙公网安备 33010602011771号