为什么 Serilog 的 WriteTo.File
默认不输出结构化日志?
Serilog 默认用
WriteTo.File写入的是纯文本(
RenderedCompactJsonFormatter以外的格式),JSON 字段被展平成字符串,丢失结构。想保留
Log.Information("User {@User} logged in", user) 中的 @User对象结构,必须显式指定 JSON 序列化器和格式器。 不配置格式器 → 日志是可读字符串,但无法被 ELK、Seq 等工具解析字段 用
JsonFormatter()→ 输出标准 JSON 行,每行一个日志事件,支持结构化消费 注意:.NET 6+ 默认使用
System.Text.Json,若对象含
DateTimeOffset、循环引用或自定义 converter,需传入
JsonSerializerOptions
如何配置滚动文件(按大小/日期轮转)?
Serilog 本身不内置滚动逻辑,依赖
Serilog.Sinks.File扩展包的
rollingInterval和
rollOnFileSizeLimit参数。滚动行为由文件名模板和策略共同决定,不是“自动清理旧文件”——旧文件仍保留在磁盘上,除非手动加
retainedFileCountLimit。
path: "logs/app-.log"→ 滚动后生成
app-20240501.log、
app-20240502.log(按天)
rollingInterval: RollingInterval.Day或
RollingInterval.Hour或
RollingInterval.Infinite
rollOnFileSizeLimit: true+
fileSizeLimitBytes: 10_485_760(10MB)→ 触发按大小切分
retainedFileCountLimit: 7→ 只保留最近 7 个滚动文件(需 NuGet v5.0+)
完整可运行的结构化滚动文件配置示例
以下代码在
Program.cs(.NET 6+)中直接生效,输出带时间戳、级别、消息、属性(含嵌套对象)、异常堆栈的 JSON 行:
using Serilog;
using Serilog.Events;
using Serilog.Formatting.Json;
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.WriteTo.File(
new JsonFormatter(renderMessage: true),
path: "logs/app-.log",
rollingInterval: RollingInterval.Day,
rollOnFileSizeLimit: true,
fileSizeLimitBytes: 10_485_760,
retainedFileCountLimit: 7,
shared: true, // 多进程写入时必需
flushToDiskInterval: TimeSpan.FromSeconds(1))
.CreateLogger();
关键点:
shared: true必须开启,否则多线程/多实例写同一目录会抛
IOException;
flushToDiskInterval控制刷盘频率,避免日志延迟过高。
常见错误和绕过坑
结构化日志落地失败,90% 出现在这几个地方:
忘了安装Serilog.Sinks.FileNuGet 包(仅装
Serilog基础包不行) 路径含中文或空格且未用双引号包裹(Windows 下可能报
DirectoryNotFoundException) 没设
shared: true,IIS 或 Windows Service 多工作进程时写日志失败 用
Console.WriteLine风格写日志(如
Log.Information("User " + user.Name + " logged in")),@符号被忽略,结构丢失 滚动后查不到最新日志 → 检查实际生成的文件名(
app-20240501.log而非
app.log)
滚动文件的“结构化”本质是每行一个合法 JSON 对象,不是把整个日志塞进一个大 JSON 文件里——这点容易误解。
