OpenTelemetry Go SDK默认不支持日志采集,因v1.22.0+仍无日志导出器实现,需通过zerolog/zap输出JSON日志→由Vector等代理转为OTLP格式发送至Collector。

OpenTelemetry Go SDK 默认不支持日志(Logs)采集
直接调用 log.Printf 或 zerolog/zap 不会自动上报到 OpenTelemetry 后端。OpenTelemetry Go 的 otel/log 包在 v1.22.0 之前是实验性(exp)且未被官方推荐用于生产;v1.22.0+ 虽移除了 exp 前缀,但 仍不提供日志导出器(Exporter)实现 —— 意味着你无法像 traces/metrics 那样直接配置 OTLPLogExporter 并发送日志。
常见错误现象:undefined: otellog.NewLogger 或运行时 panic 提示 “no exporter registered”;或日志完全静默、无任何 OTLP 流量发出。
- OpenTelemetry 规范中 Logs 是独立信号,Go SDK 优先级长期低于 Traces/Metrics
- 当前(2024 年中)唯一稳定路径是:用结构化日志库(如
zerolog)打日志 → 通过日志收集代理(如fluent-bit、vector、filebeat)转发到OTLP/gRPC端点 - 若坚持纯 Go 方案,可手动构造
otlplogs.LogRecord并调用Export,但需自行处理批处理、重试、上下文传播等,不推荐
用 zerolog + OTLP 日志管道的最小可行配置
这是目前最轻量、可控、生产可用的方式:让应用输出 JSON 日志到 stdout/stderr,由外部 agent 抓取并转成 OTLP Logs 发送。
关键点不是“Go 怎么发日志”,而是“Go 日志怎么被 OTLP 接收”。所以重点在日志格式对齐和 agent 配置。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- Go 应用启用
zerologJSON 输出:log.Logger = log.With().Timestamp().Logger() log.Info().Str("event", "app_started").Int("pid", os.Getpid()).Send() - 确保每条日志含
time(ISO8601)、level(小写,如"info")、message字段;其他字段(如trace_id、span_id)需手动注入(见下一点) - 部署
vector(推荐)或fluent-bit,配置其监听stdin或文件,并映射字段到 OTLP Logs schema:[[transforms.add_otel_context]] type = "remap" source = ''' .observed_timestamp = now() .severity_text = .level .body = .message .attributes = merge(., {"service.name": "my-app"}) ''' - Vector 输出目标设为
otlp_logs,地址指向你的 Collector(如http://otel-collector:4317)
如何把 trace context 注入 zerolog 日志
否则日志和 trace 完全割裂,查问题时无法关联。OpenTelemetry Go SDK 提供了 otel.GetTextMapPropagator().Inject,但需你自己触发。
- 不要依赖全局 logger;每次需要记录带 trace 的日志时,先从当前
context.Context提取 trace ID: func LogWithTrace(ctx context.Context, logger *zerolog.Logger, msg string) { span := trace.SpanFromContext(ctx) if span != nil && span.SpanContext().HasTraceID() { sc := span.SpanContext() logger = logger.Str("trace_id", sc.TraceID().String()). Str("span_id", sc.SpanID().String()). Bool("trace_flags", sc.TraceFlags() != 0) } logger.Info().Msg(msg) }- 注意:
trace_id和span_id必须是字符串格式(.String()),否则 vector/fluent-bit 解析失败 - 如果用
otelcol做 collector,它默认不会自动关联 logs/traces;需开启connector(如spanmetrics或自定义 pipeline)或靠后端(如 Grafana Tempo + Loki)做关联查询
为什么不用 zap + otelzap?
otelzap 是社区包(go.opentelemetry.io/contrib/instrumentation/go.uber.org/zap),它只做两件事:把 zap 的 Logger 封装成 log.Logger 接口,并尝试注入 trace context —— 但它依然不解决日志导出问题。
- 它不会启动 exporter,也不会连接 OTLP;只是让日志字段里多几个 trace 字段
- 最终日志还是走 zap 自己的 sink(console/file),没变成 OTLP Logs 协议
- 如果你已经用 zap,可以保留它,但导出环节仍需 vector/fluent-bit 等 agent,和 zerolog 方案无本质区别
- 额外引入
otelzap只增加依赖复杂度,不减少运维步骤
真正卡住的地方从来不是“怎么打日志”,而是“谁来把日志按 OTLP Logs 格式打包发出去”——Go SDK 没打算做这件事,得靠 infra 层补上。这点容易被文档误导,以为加个包就自动接入了。

















