为什么日志要结构化?哪些位置写的日志会丢掉 trace_id?Collector 怎么把日志和 trace 关联起来?采样之后日志里的 trace_id 点进去还有 trace 吗?这篇用 Python logging、OpenTelemetry 和 Collector 的 filelog 逐项量出来。
格式 | 20 万行的体积 | 平均每行字节数 | 提取字段的耗时(CPython) | user 提取错误的行数 ---------------+----------+---------+------------------+------------- 文本行(key=value) | 21.2 MB | 106 | 0.06 s | 3898 JSON 行 | 34.6 MB | 173 | 0.35 s | 0 JSON 每行多了键名和引号,体积更大,但解析是确定的;文本行用正则提取,消息正文里恰好出现 “user=…” 时贪婪匹配会取错字段(这里的错误行就是那 2% 的错误消息)。结构化日志用字段而不是正文来承载信息,查询和聚合更可靠
文本日志靠正则提取字段会被正文干扰;JSON 日志字段确定,代价是体积更大、解析更慢。
日志位置 | 日志行数 | 带有效 trace_id 的行数 | 比例 ----------------------------+------+------------------+----- (a)请求处理函数里(span 内) | 2000 | 2000 | 100% (b)span 结束之后 | 2000 | 0 | 0% (c)线程池任务,没传上下文 | 2000 | 0 | 0% (d)线程池任务,copy_context().run | 2000 | 2000 | 100% 日志过滤器读的是“当前 span”:在 span 内、或者把上下文传到了新线程里,日志才有 trace_id;span 结束之后、没有传递上下文的线程里写的日志,trace_id 是空的,查 trace_id 的时候这些行就找不到了
过滤器读的是当前 span,span 之外或没传上下文的线程里写的日志没有 trace_id。
记录 body='{"time": "2026-10-19T12:00:01.250Z", "le' severityText=ERROR severityNumber=17 timeUnixNano=1792411201250000000 traceId='4bf92f3577b34da6a3ce929d0e0e4736' spanId='00f067aa0ba902b7'
attributes 里还有的键:['level', 'log.file.name', 'msg', 'service', 'span_id', 'time', 'trace_id', 'user']
记录 body='{"time": "2026-10-19T12:00:02.500Z", "le' severityText=INFO severityNumber=9 timeUnixNano=1792411202500000000 traceId='(空)' spanId='(空)'
attributes 里还有的键:['level', 'log.file.name', 'msg', 'service', 'time', 'user']
trace_parser 把 trace_id 和 span_id 从属性提升到 OTLP 日志记录的 traceId / spanId 专门字段(后端据此把日志挂到 trace 上);没有 trace_id 的日志行这两个字段是空的filelog + json_parser + trace_parser 把 trace_id 放进 OTLP 日志记录的专门字段。
日志里不同的 trace_id:1000 个;导出的 span 里能找到的:76 个(8%);其余 924 个 trace_id 在追踪后端里没有对应的 trace(点日志里的 trace_id 链接会打不开)
ERRORS {}日志里的 trace_id 不一定有对应的 trace;采样率越低,死链越多。