线上出问题,你远程桌面连上服务器,打开日志文件夹,用记事本打开 .txt 文件,Ctrl+F 搜 "ERROR"。好不容易找到一行关键异常,却不知道它属于哪个请求、哪个用户、从哪台服务器来------上下文要么没记,要么散落在滚动条之间。这就是纯文本日志的困局:人勉强能看,但机器几乎无法结构化检索。
Serilog 为 .NET 带来结构化日志 ,把日志变成携带属性的完整事件。配合 Seq ------一个带网页界面的日志服务器------你可以让多台服务器的日志统一汇聚,在浏览器里搜索、过滤、分析。哪台机器出问题、某个订单的全链路日志,一秒钟定位。日志从此不再是"文本文件",而是一个可查询的可观测数据源。我们从一个控制台程序开始,让你立刻看到效果。
Serilog 是什么
Serilog 是面向 .NET 的开源诊断日志库
官网:https://serilog.net
https://serilog.net

GitHub:
https://github.com/serilog/serilog
https://github.com/serilog/serilog

它解决的核心工程问题:用结构化事件替代纯文本日志,使日志易于被机器检索、聚合和分析,同时保持对人类友好的阅读体验。
四条核心机制与工程价值:
-
消息模板 + 属性捕获 :
Log.Information("订单 {OrderId} 已支付", order.Id)会将OrderId作为结构化属性存储,而不只是拼成字符串。 -
Sink(输出适配器)架构:通过 Sink 将日志输出到文件、控制台、数据库、Elasticsearch、Seq 等数十种目标,切换目标只需更换 Sink,无需改动打点代码。
-
延迟渲染与高性能:只记录模板和属性,真正的消息字符串在输出时才由 Sink 生成,且大量 Sink 支持异步批处理,对应用性能影响可控。
-
丰富的扩展生态:官方及社区提供了 100+ Sink 和 Enricher(上下文增强器),可自动附加机器名、线程ID、HTTP请求信息等,极少代码即可构建全面的可观测数据。
授权情况 :Serilog 完全开源,采用 Apache License 2.0 ,可免费用于商业项目,无任何功能受限的"商业版"。配套的可视化日志服务器 Seq 有免费版和付费版,但 Serilog 本身不受限制,不需要购买许可。
怎么引入
安装(在 Visual Studio 的 NuGet 包管理器中操作,或执行以下命令):
dotnet add package Serilog # 核心库,提供结构化打点能力
dotnet add package Serilog.Sinks.Console # 输出到控制台,开发调试实时看
dotnet add package Serilog.Sinks.File # 输出到本地文件,持久化兜底
dotnet add package Serilog.Sinks.Seq # 发送到 Seq 服务器,浏览器集中查询

最小接入配置(Program.cs)
cs
using Serilog;
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.WriteTo.File("logs/app.txt", rollingInterval: RollingInterval.Day)
.WriteTo.Seq("http://localhost:5341")
.CreateLogger();
Log.Information("控制台、文件、Seq 三路输出测试");
Log.CloseAndFlush();
快速上手:一个示例演示三路输出
下面的示例完整展示"控制台看、文件留、Seq 查"的效果。所有代码在一个 .NET 8 控制台程序中即可运行。
Seq 安装
- 下载地址:
https://datalust.co/download
https://datalust.co/download

-
Windows 选择
.msi安装包,一路下一步,装完自动后台运行。 -
验证:浏览器打开
http://localhost:5341,看到 Seq 界面表示启动成功。

- 也可用 Docker:
docker run -d --name seq -e ACCEPT_EULA=Y -p 5341:80 datalust/seq
关于商用:Seq 免费版支持单用户,搜索、过滤、事件查看、仪表盘、告警等核心功能完整,个人学习和开发完全够用。多用户权限、SSO、集群等高阶功能需付费版,团队协作按需升级。
完整代码(Program.cs)
cs
using Serilog;
using Serilog.Core;
using Serilog.Events;
using System.Net;
using System.Net.Sockets;
// 配置 Serilog:三路输出 + 全局属性
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.Enrich.WithMachineName()
.Enrich.With<LocalIpEnricher>()
.WriteTo.Console(outputTemplate:
"[{Timestamp:HH:mm:ss} {Level:u3} {MachineName}/{LocalIP}] {Message:lj}{NewLine}{Exception}")
.WriteTo.File("logs/app.txt",
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: 7,
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] {MachineName}/{LocalIP} {Message:lj} {Properties:j}{NewLine}{Exception}")
.WriteTo.Seq("http://localhost:5341",
restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Information)
.CreateLogger();
// 模拟业务打点
Log.Information("========== 订单服务启动 ==========");
var orders = new[] { 1001, 1002, 1003 };
foreach (var orderId in orders)
{
using (Serilog.Context.LogContext.PushProperty("OrderId", orderId))
{
Log.Information("开始处理订单");
Thread.Sleep(200);
if (orderId == 1002)
Log.Warning("订单 {OrderId} 触发风控检查", orderId);
Log.Information("订单处理完成");
}
}
Log.Information("========== 本轮处理结束 ==========");
Log.CloseAndFlush();
Console.WriteLine("日志已输出到控制台、文件、Seq,按任意键退出...");
Console.ReadKey();
// 自定义 Enricher:类必须放在顶级语句后面
public class LocalIpEnricher : ILogEventEnricher
{
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory pf)
{
var ip = Dns.GetHostEntry(Dns.GetHostName())
.AddressList
.FirstOrDefault(a => a.AddressFamily == AddressFamily.InterNetwork)?
.ToString() ?? "127.0.0.1";
logEvent.AddPropertyIfAbsent(pf.CreateProperty("LocalIP", ip));
}
}
关于商用:Seq 免费版支持单用户,搜索、过滤、事件查看、仪表盘、告警等核心功能完整,个人学习和开发完全够用。多用户权限、SSO、集群等高阶功能需付费版,团队协作按需升级。
验证三路输出
- 控制台:运行程序,窗口实时打印带机器名、IP 的日志。

- 文件 :打开
logs\app{日期}.txt,每行是一条 JSON,包含OrderId等属性,可长期留存。

- Seq :浏览器访问
http://localhost:5341,所有日志集中展示。搜索OrderId = 1002,该订单所有日志立即呈现;搜索@Level = "Warning",只看警告;搜索MachineName = "你的机器名",多机部署时只看某台服务器的日志。


Seq 架构示意
Seq 的部署架构非常简单,本质是一个"收日志的网站":
cs
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 服务器 A │ │ 服务器 B │ │ 服务器 C │
│ Serilog │ │ Serilog │ │ Serilog │
│ + MachineName│ │ + MachineName│ │ + MachineName│
│ + LocalIP │ │ + LocalIP │ │ + LocalIP │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
│ 结构化日志 │ │
└──────────────────┼──────────────────┘
│
▼
┌─────────────────────┐
│ Seq 服务器 │
│ http://seq:5341 │
│ │
│ • 接收并存储日志 │
│ • 提供 Web 查询界面 │
│ • 支持过滤、搜索 │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ 你的浏览器 │
│ http://seq:5341 │
│ │
│ 全局搜索、实时查看 │
└─────────────────────┘
每台应用服务器只需要在 Program.cs 里加一行 .WriteTo.Seq("http://seq服务器地址:5341"),日志就会自动发送过去。Seq 本身是一个独立的进程(Windows 服务或 Docker 容器),提供类似数据库的查询能力,让你用浏览器取代记事本和远程桌面。
常见报错

适用场景
-
单机应用:控制台 + 文件足够,开发调试方便,生产留 JSON 文件备查。
-
多服务器:所有服务用同样的 Enricher 配置,日志统一发到 Seq。浏览器中按机器名、业务Id 搜索,故障定位从数小时降到秒级。
-
高并发:Seq Sink 内置内存缓冲与自动重连,Seq 暂不可达时不丢日志;加上文件兜底双保险。
-
不适用场景:已有 ELK 等平台的,可不用 Seq,但结构化打点方式完全一样,只需把 Sink 换成 Elasticsearch。
前置条件:Seq 服务器需能被所有应用服务器访问;生产环境建议为 Seq 配置 HTTPS 和登录认证(免费版支持单用户)。
实战建议
坑位1:拼字符串导致属性丢失 错误:Log.Information("用户 " + name + " 登录")修复:Log.Information("用户 {UserName} 登录", name),{UserName} 自动成为属性。
坑位2:文件未限制级别占满磁盘 错误:生产配 Verbose,海量 Debug 日志写满硬盘。修复:生产设 Warning,或对文件单独限制 .WriteTo.File("...", restrictedToMinimumLevel: LogEventLevel.Warning),并配置滚动和保留天数。
坑位3:只依赖 Seq,无本地兜底 错误:Seq 宕机同时进程崩溃,内存日志丢失。修复:始终加 .WriteTo.File() 双写,文件作为永久存档和容灾备份。
选型建议
果断用 Serilog + Seq 的场景:
-
.NET 项目,希望日志可查询,不再翻记事本。
-
多机部署,需要集中看日志,讨厌逐台远程。
-
团队不限,免费版 Seq 足够开发测试和个人使用。
可执行决策:新项目直接 Serilog + File + Seq,几小时接入。已有项目从关键模块开始替换,逐步享受结构化日志的红利。
从一个控制台程序开始,Serilog 让日志从无序文本变成了结构化属性。喜欢可视化的同学,Seq 把多台机器的日志汇集到一个浏览器标签页里;不想折腾额外服务的,只用文件 Sink 也完全够用 ------哪怕只是把 Debug.WriteLine 换成 Log.Information("处理订单 {OrderId}", id),然后加一行 .WriteTo.File("logs/app.txt"),你的日志就已经是结构化的 JSON,每一条都带属性,将来导入任何平台都无缝对接。
落地建议 :今天就给项目加上 Serilog,至少配一个文件输出,所有日志用 {属性} 模板。Seq 是可选项,按需再上。哪怕现在只有一台服务器、只写本地文件,你也已经拥有了日志可查询的基因。