C# | Serilog 新手入门

C# Serilog 新手入门

文章目录

  • [C# Serilog 新手入门](# Serilog 新手入门)
    • [1. 什么是第三方日志库与 Serilog](#1. 什么是第三方日志库与 Serilog)
      • [1.1 概念讲解:什么是日志?为什么不能只写 `Console.WriteLine`?](#1.1 概念讲解:什么是日志?为什么不能只写 Console.WriteLine?)
      • [1.2 C# 生态中的优秀日志库对比](# 生态中的优秀日志库对比)
      • [1.3 C# 日志技术的演进历程](# 日志技术的演进历程)
    • [2. Serilog 结构化日志与传统日志的对比](#2. Serilog 结构化日志与传统日志的对比)
      • [2.1 结构化日志(Structured Logging)的核心特点](#2.1 结构化日志(Structured Logging)的核心特点)
      • [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)
      • [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 声明式配置)
          • [方式一:Fluent API(代码链式调用,适合小型项目或控制台应用)](#方式一:Fluent API(代码链式调用,适合小型项目或控制台应用))
          • [方式二:`appsettings.json`(推荐!适合 ASP.NET Core 项目,灵活可配置)](#方式二:appsettings.json(推荐!适合 ASP.NET Core 项目,灵活可配置))
    • [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# 日志技术经历了四个主要阶段:

  1. 原始时代 :直接使用 Console.WriteLine()System.Diagnostics.Trace,功能极其原始。
  2. 文本日志时代(Log4net / NLog 早期) :开始将日志格式化写入文本文件(如 .log 文件),但日志内容全是一串串拼接的字符串。
  3. 接口抽象时代(Microsoft.Extensions.Logging) :微软推出了官方日志抽象接口 ILogger,框架只定义标准,具体实现交由第三方库(如 Serilog)完成。
  4. 结构化日志时代(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 == 10086Properties.ProductId > 1000 实施精确过滤。
  • 复杂对象的原生拆解 :如果你传入一个 C# 对象,在变量名前加上 @ 符号(如 {@User}),Serilog 会自动将其序列化为 JSON 展开存储,无需手动 JsonConvert.SerializeObject

2.3 帮助小白快速理解的心智模型

  • 传统日志的心智模型 :把日志当成记事本,疯狂向里面追加一行行打印出来的文本。
  • Serilog 的心智模型 :把打日志当成向一个无模式(NoSQL)数据库插入事件记录。你每一次记录日志,都是在发起一次数据结构收集。

3. 引入 Serilog 的意义与架构定位

3.1 实际应用场景与效率提升

  1. 生产环境快速排错:系统报错时,无需猜测参数,结构化日志直接呈现当时请求入参的完整 JSON 数据。
  2. 性能瓶颈追踪 :通过在日志中附带 ElapsedMilliseconds,可以秒级筛选出"执行时间大于 2000ms"的所有数据库查询日志。
  3. 安全审计与业务分析:记录关键业务事件(如支付、修改密码),方便日后查账或统计用户行为轨迹。

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 由三个核心概念组成:

  1. Sinks(输出接收端):日志去哪里?(控制台、文件、数据库、Seq、Elasticsearch)。
  2. Enrichers(上下文增强器):自动补全环境信息(如:当前线程 ID、机器名、环境名)。
  3. 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.ConfigurationSerilog.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,实现优雅的架构解耦。
相关推荐
在世修行2 小时前
从零打造 C# 工业视觉检测系统(七):串口通信 SerialPort 封装与开发
开发语言·c#·modbus rtu·rs232/rs485
Hammer_Hans3 小时前
DFT笔记98
java·开发语言·数据库
qq_448011164 小时前
C语言中的野指针和空指针
c语言·开发语言·单片机
估值探索者4 小时前
【Python实时盯盘与预警 #08】成交额突然放大2倍?Python窗口比较抓异动
java·开发语言·python
yk 坤帝4 小时前
用Python实现发送《自动周报》实战脚本
开发语言·python
abbgogo6 小时前
OSPF动态路由协议
开发语言·php
en.en..6 小时前
C 语言嵌入式事件驱动状态机完整教程(枚举配套)
c语言·开发语言
Tim_106 小时前
【C++】024、new[]与delete[]配对使用
java·开发语言
Yyyyyy~7 小时前
[C语言]break和continue作业
c语言·开发语言