真正集成opentelemetry日志必须包含trace_id和span_id字段,否则无法串联链路;需正确注入上下文、避免冗余记录、配置合理超时重试、分级采样且保障跨语言一致性。

日志字段必须包含 trace_id 和 span_id 才算真正集成
OpenTelemetry 日志不是简单把 console.log 换成 logger.info 就完事。如果日志里看不到 trace_id 和 span_id,那和没集成没区别——排查链路时根本串不起来。
常见错误是只配置了 trace exporter,却忘了给 logger 注入上下文。Node.js 里用 @opentelemetry/sdk-node 时,LoggerProvider 必须显式绑定 context.active();Go 里用 otellog.NewLogger 要传入 otel.WithSpanFromContext。
- Java Spring Boot 用户注意:
logging.pattern.level默认不输出 MDC 字段,得手动加%X{trace_id} %X{span_id} - Python 的
opentelemetry-instrumentation-logging默认只注入 root logger,自定义 logger 需调用set_logger_provider - 字段名别硬编码成
traceId或TraceID,统一用小写下划线trace_id,否则后端 collector(如 OTLP receiver)可能丢弃
避免在日志里重复记录 span 属性
Span 本身已有 attributes,比如 http.method、db.statement。如果再在日志 message 里写 “GET /api/users”,等于冗余且破坏结构化日志原则——查问题时得切两套字段,还容易对不上。
正确做法是:日志只保留语义信息(如 “user not found”),关键上下文全走 structured fields。这样 Grafana 或 Loki 才能用 trace_id 联查 span + log。
- Node.js 用
winston时,别在message拼接字段,改用logger.info("user not found", { user_id: "u123" }) - Java Logback 的
%mdc只能打扁平 key,复杂嵌套属性(如http.request.header)建议走 OTLP 直传,别塞进日志行 - Python 的
structlog推荐配structlog.stdlib.filter_by_level,防止 debug 级别 span attrs 泄露敏感值
OTLP HTTP endpoint 超时和重试必须显式设
http://localhost:4318/v1/logs 看似本地,但微服务高频打日志时,网络抖动或 collector 重启会导致 503 Service Unavailable 或连接超时。默认 client(如 JS 的 @opentelemetry/exporter-otlp-http)往往不重试,日志直接丢。
这不是“偶发丢失”,而是稳定压测下必现的问题。尤其 Java agent 自带的 exporter 默认 timeout_millis=10000,但日志批量压缩+gzip 后常超 12s。
- Node.js:构造
OtlpHttpLogRecordExporter时必须设maxRetries: 3和timeoutMillis: 15000 - Go:
otlphttp.NewClient的WithTimeout是 dial 超时,真要控上传耗时得用WithRetry+MaxAttempts - Kubernetes 环境下,别直连
otel-collectorClusterIP,优先走 headless service + gRPC,HTTP 批量日志在高并发时易触发 connection reset
日志采样不能只依赖 trace 采样率
Trace 采样率设 1% 不代表日志也只留 1%。日志有独立价值:error 日志要 100% 上报,debug 日志可降频,info 日志可按服务分级。混用 trace 采样会漏掉关键异常上下文。
比如某个订单服务报错,trace 被采样掉了,但 error 日志若也被“顺带”丢弃,就彻底失去线索。
- 推荐方案:用
LogLevelBasedSampler(Java)、LogLevelFilter(Python structlog)或自定义LogRecordProcessor分级控制 - error 级别日志强制 bypass 采样,哪怕 trace 已被 drop;warn 级别可设 10%;info 级别才跟 trace 采样率联动
- 别在应用层做 if-else 判断日志级别再决定是否调用 logger,所有日志都走同一 pipeline,由 processor 统一裁决——否则代码里埋太多条件判断,后期难维护
最麻烦的是跨语言一致性:Go 的 zap 默认不支持动态采样,得包一层 wrapper;而 Python 的 opentelemetry-sdk 0.43b 版本前压根没日志采样 API,得自己 patch LogRecordExporter.export。这些细节不踩一遍,线上日志量和 trace 对不上,第一反应永远是“collector 配错了”,其实问题在 SDK 层。










