Serilog.Sinks.Elasticsearch 直连丢字段主因是默认 omitEnclosingObject: true 且未启用 autoRegisterTemplate,导致结构化属性被扁平化为字符串;须设 omitEnclosingObject: false、autoRegisterTemplate: true、indexFormat 为 "logs-{0:yyyy.MM.dd}" 并确保 ILM 模板匹配。

为什么直接用 Serilog.Sinks.Elasticsearch 会丢字段?
因为默认配置下,Serilog 会把整个 LogEvent 序列化成一个扁平的 JSON 对象,Properties 里的结构化数据(比如嵌套对象、字典、自定义类型)全被转成字符串塞进 messageTemplate 或 renderedMessage,ElasticSearch 收到的是“黑盒”文本,没法做聚合或字段过滤。
根本原因是 ElasticsearchSink 默认启用了 omitEnclosingObject: true,且没启用 autoRegisterTemplate: true 和正确的 indexFormat 策略。
- 必须显式设置
autoRegisterTemplate: true,让 Serilog 自动推导并上传ILM兼容的索引模板 -
indexFormat推荐用"logs-{0:yyyy.MM.dd}",避免单索引过大,也方便按天滚动和清理 - 禁用
omitEnclosingObject(即设为false),否则@p记录的结构体直接消失
如何让 LogEvent.Properties 中的匿名对象/类实例真正变成 ES 的 nested 字段?
Serilog 默认不递归序列化复杂对象——它只调用 .ToString()。想保留结构,得靠 Destructure.AsScalar<T>() 或 Destructure.ToMaximumDepth(n),但更稳妥的是用 LogContext + ILogger.ForContext<T>() 显式注入已序列化的 JSON 字符串,并配合 ElasticsearchSink 的 customFormatter。
实操建议:
- 对需要保留结构的类型(如
HttpRequest,ApiResponse<T>),先用System.Text.Json.JsonSerializer.SerializeToString(obj)转成字符串,再用Log.ForContext("RawRequest", json, destructureObjects: false) - 在 Sink 配置里传入自定义
ITextFormatter,继承CompactJsonFormatter并重写Format(),把LogEvent.Properties["RawRequest"]的值原样写入 JSON,不加引号、不转义 - ES 端需提前在索引模板中声明该字段为
"type": "object"或"type": "nested",否则即便写入了 JSON,也会被动态映射成text
IndexName 和 IndexFormat 搞混会导致什么?
很多人以为 indexName 是模板名,其实它是「写入时实际使用的索引名」;而 indexFormat 是用于生成每日索引名的格式字符串(如 "logs-{0:yyyy.MM.dd}")。如果只配 indexName = "logs",所有日志都往一个叫 logs 的索引里写,既无法滚动,又容易触发 ES 的单索引大小上限(50GB 默认),查询也变慢。
-
indexName必须留空(null)或删掉,否则会覆盖indexFormat行为 -
indexFormat中的日期格式必须用DateTime标准格式符,{0:yyyy-MM-dd}不行(ES 不认连字符作索引名),得用{0:yyyy.MM.dd}或{0:yyyyMMdd} - 如果用了 ILM,
indexFormat生成的索引名必须匹配 ILM 策略中的pattern,例如策略匹配"logs-*",那indexFormat就得是"logs-{0:yyyy.MM.dd}"
ES 连接失败却没报错?检查 SelfLog 和 HttpClient 超时
Serilog.Sinks.Elasticsearch 默认静默丢弃发送失败的日志——它不会抛异常,也不会写入本地 fallback 文件(除非你手动配)。最常见原因是 HttpClient 连接超时(默认 10 秒)或 TLS 版本不匹配(ES 8.x 默认要求 TLS 1.2+)。
- 启用 Serilog 自检:
Serilog.Debugging.SelfLog.Enable(Console.Out),能看到底层 HTTP 请求是否发出、响应码多少 - 显式构造
HttpClient并设置Timeout = TimeSpan.FromSeconds(30),传给ElasticsearchSinkOptions.HttpClient - 若 ES 启用了 HTTPS 且证书非权威签发,需在
HttpClientHandler中设置ServerCertificateCustomValidationCallback,否则连接直接被 .NET 拦截 - 别依赖
WriteTo.Async()来“缓解”失败——异步只是把日志压进内存队列,队列满或进程退出时照样丢
结构化日志写入 ES 的关键不在“能不能写”,而在“写的字段能不能查、聚合、告警”。漏掉模板注册、混淆索引命名规则、放任 HttpClient 默认超时,这三处出问题,日志就等于白写。


















