c# 高并发下的日志聚合方案 Serilog, OpenTelemetry 和 ELK

来源:这里教程网 时间:2026-02-21 17:40:00 作者:

高并发下 Serilog 直连 Elasticsearch 容易崩

Serilog 的

ElasticsearchSink
默认使用同步 HTTP 写入,每条日志都发一次请求,在 QPS 过千时会迅速拖垮线程池或触发连接池耗尽,常见错误是
System.Net.Http.HttpRequestException: Connection refused
或大量
TimeoutException
。它不适合直连生产环境的 ES 集群。

实操建议:

禁用
ElasticsearchSink
batchPostingLimit
period
默认值(它们太保守,反而加剧小包高频请求)
改用
Serilog.Sinks.Async
包做外层缓冲,但仅限缓解,不能根治
真正可行的是把 Serilog 当作“日志生产端”,只输出到本地文件或
Seq
,再由独立采集器转发

OpenTelemetry .NET SDK 的日志导出需绕过 Serilog 原生集成

目前

OpenTelemetry.Exporter.OpenTelemetryProtocol
对日志(LogRecord)的支持仍属实验性,且 Serilog 本身不原生兼容 OTLP 日志协议。直接调用
AddOtlpExporter()
并不会捕获
Log.Information()
产生的日志——它只收
Activity
LogRecord
(来自
ILogger<t></t>
的结构化日志,且需启用
UseOpenTelemetryLoggerFactory
)。

实操建议:

若已用
Microsoft.Extensions.Logging
,优先走
ILogger<t></t>
+
AddOpenTelemetry()
+
AddOtlpExporter()
路径
若强依赖 Serilog API(如
Log.Logger
全局实例),必须通过
Serilog.Sinks.OpenTelemetry
(非官方第三方包)桥接,注意其
BatchExportIntervalMs
MaxExportBatchSize
必须显式设大(例如 5000ms / 500 条)
避免同时启用 Serilog 的
ConsoleSink
和 OTLP 导出,否则日志重复且上下文丢失

ELK 链路中 Logstash 不是高并发友好组件

Logstash 默认单进程、JVM 启动、内存占用高,在日志峰值超 10k EPS(Events Per Second)时 CPU 持续 90%+,

pipeline.batch.delay
pipeline.workers
调优收益有限。更严重的是,它对 JSON 字段嵌套过深(如 Serilog 的
@l
,
@mt
,
@x
)解析慢,容易触发
json parse failure

实操建议:

Filebeat
替代 Logstash 做日志采集:轻量、Go 编写、支持背压、内置 ES 输出和 OTLP 输出插件
若必须用 Logstash,关闭所有 filter(尤其
grok
),只用
json
codec 解析,并将
pipeline.batch.size
提高到 1000+
ES 端建索引时禁用
index.mapping.dynamic
,预定义
log.level
,
log.eventId
,
trace_id
等字段类型,避免 mapping explosion

真正落地的聚合链路是「Serilog → Filebeat → OpenTelemetry Collector → ELK」

这条链路把职责切得干净:Serilog 只管高性能写本地 JSON 文件;Filebeat 负责可靠采集、节流、TLS 加密;OTel Collector 做统一转换(如补全 trace_id)、采样、路由;ES 只专注存储与检索。中间任何一环挂掉,上游都有缓冲(Filebeat spooling / OTel queue)。

关键配置点:

Serilog 输出格式必须为
CompactJsonFormatter
(不是
JsonFormatter
),否则 Filebeat 无法流式解析
Filebeat 的
output.opentelemetry
需指向 OTel Collector 的
otlphttp
receiver(默认
http://localhost:4318/v1/logs
OTel Collector 的
exporters
要配
elasticsearch
(而非
logging
),并开启
routing
插件按 service.name 分索引

最易被忽略的是时间戳精度:Serilog 默认只写到毫秒,而 OTel Collector 和 ES 默认按纳秒处理,会导致日志在 Kibana 中排序错乱。必须在 Serilog 的

Enrich.WithProperty("timestamp", DateTimeOffset.UtcNow)
强制补全 ISO8601 格式带毫秒的字符串,或在 Filebeat pipeline 中用
date
processor 覆盖
@timestamp

相关推荐