serilog的writeto.file默认不输出结构化日志,因其默认使用纯文本格式(非jsonformatter),导致@符号标记的对象被展平为字符串;需显式配置jsonformatter()并搭配serilog.sinks.file的滚动参数(如rollinginterval、rollonfilesizelimit、retainedfilecountlimit)及shared:true等选项才能实现按天/大小滚动的结构化json日志输出。

为什么 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 文件里——这点容易误解。










