news 2026/9/18 22:08:52

Grafana Tempo 自动日志(Automatic Logging):通过日志实现 Trace 发现与定位

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Grafana Tempo 自动日志(Automatic Logging):通过日志实现 Trace 发现与定位

Grafana Tempo 自动日志(Automatic Logging):通过日志实现 Trace 发现与定位

【免费下载链接】tempoGrafana Tempo is a high volume, minimal dependency distributed tracing backend.项目地址: https://gitcode.com/GitHub_Trending/tempo1/tempo

当你运行一个已接入分布式追踪(instrumented)的系统时,最大的难题之一往往是"如何知道系统中存在哪些 Trace"。Grafana Tempo 生态中的Automatic logging(自动日志)功能,通过让 Grafana Alloy 为流经追踪管道的每个 span、root span 或 process 生成格式良好的日志行,把 Trace ID 直接写进日志,从而让你在 Loki 中按键值对检索 Trace,并从一条日志一键跳转到 Grafana 中的对应 Trace 视图。读完本文,你将掌握自动日志的完整配置方法(OTLP 端点与 Loki 两种目标)、默认日志字段与自定义属性、Loki 中的 LogQL 查询技巧,以及用 TraceQL 实现等价检索的原生替代方案。

Automatic logging 与 TraceQL 的定位差异

自动日志解决的是"Trace 可发现性"问题:在没有自动日志时,日志与 Trace 彼此割裂,排查问题时往往要从海量日志里手工翻找 Trace ID。而 Automatic logging 则让 Alloy 在追踪管道中自动生成包含 Trace ID 的日志行,写入 Loki,使日志成为进入 Trace 世界的入口。

不过,Tempo 的原生 TraceQL 检索现在已经能提供与自动日志等价的 Trace 发现能力,而且不需要部署 Loki、也不会额外产生日志数据量。TraceQL 可以按服务名(service name)、span 名、持续时间、状态码以及自定义属性检索 Trace;如果需要可视化、免写查询的探索方式,也可以使用 Grafana 的 Traces Drilldown。

自动日志仍然有它的适用场景:如果你的工作流以 Loki 为中心(例如排障时习惯先看应用日志),自动日志会把 Trace ID 和 span 元数据与你的应用日志放在一起,让你在调查日志数据的同时发现 Trace,而不必切换到独立的 Trace 检索界面。这正是"日志优先(logs-first)"工作流的典型诉求。

开始前的前置条件

使用 Automatic logging 需要以下组件就绪:

  • Grafana Alloy:已安装并正在从你的应用接收 Trace。Alloy 是 Tempo 文档中推荐的采集端,兼容 OpenTelemetry Collector 与 Prometheus Agent,其配置使用 Alloy 配置语法(见 Grafana Alloy 章节)。
  • Loki 实例:用于存储自动日志生成的日志数据。
  • Tempo 实例:用于存储 Trace,以便从日志导航到 Trace。
  • Grafana:配置好 [Loki 数据源] 与 Tempo 数据源,二者缺一不可。

配置 Automatic logging

Automatic logging 的核心是otelcol.connector.spanlogs这个 connector 组件:它接收 Trace 并生成日志行,但不会把原始 Trace 继续向下游转发。因此配置时必须把 Trace 同时发给spanlogsconnector 与你的 Trace 后端,否则 Trace 数据会丢失。

对于高吞吐系统,如果对每个 span 都写日志,日志量可能过大。此时应改为按 root span 或 process 粒度记录。connector 会在 span 或 resource 属性中检索一组配置的键,并将其作为日志中的键值对输出,随后你就可以在 Loki 中按这些键值对进行检索。

将 root span 日志发送到 OTLP 端点

下面的示例记录 root span 的日志,并把生成的日志发送到 OTLP 端点。同时,Trace 也被转发到同一端点,确保 Trace 数据不丢失:

otelcol.receiver.otlp "default" { grpc {} http {} output { traces = [ otelcol.connector.spanlogs.default.input, otelcol.exporter.otlp.default.input, ] } } otelcol.connector.spanlogs "default" { roots = true output { logs = [otelcol.exporter.otlp.default.input] } } otelcol.exporter.otlp "default" { client { endpoint = env("OTLP_ENDPOINT") } }

这个配置体现了 Alloy 管道"接收器 → 处理器/连接器 → 导出器"的架构(可参考 Alloy 追踪管道架构说明 中的alloy-pipeline-architecture.svg图示):otelcol.receiver.otlp的 output 是一个数组,把 Trace 同时分发(fan-out)给 spanlogs connector 与 OTLP exporter,这正是上文"不能丢失 Trace"的关键写法。roots = true表示只对 root span 生成日志,适合高吞吐场景控制日志量。

将带自定义属性的 root span 日志发送到 Loki

下面的示例记录 root span,并额外携带http.methodhttp.target属性,随后把生成的日志发送到 Loki 实例。Trace 则单独转发到 Tempo 实例。

由于otelcol.exporter.loki默认不会把日志属性提升为 Loki 标签,你必须使用otelcol.processor.attributes添加一个loki.attribute.labels提示,把traces属性提升为 Loki 标签,这样就能按日志类型过滤:

otelcol.receiver.otlp "default" { grpc {} http {} output { traces = [ otelcol.connector.spanlogs.default.input, otelcol.exporter.otlp.tempo.input, ] } } otelcol.connector.spanlogs "default" { roots = true span_attributes = ["http.method", "http.target"] output { logs = [otelcol.processor.attributes.default.input] } } otelcol.processor.attributes "default" { action { key = "loki.attribute.labels" action = "insert" value = "traces" } output { logs = [otelcol.exporter.loki.default.input] } } otelcol.exporter.loki "default" { forward_to = [loki.write.local.receiver] } loki.write "local" { endpoint { url = "http://loki:3100/loki/api/v1/push" } } otelcol.exporter.otlp "tempo" { client { endpoint = "tempo:4317" } }

要点拆解:

  • span_attributes = ["http.method", "http.target"]:指定要写入日志的 span 属性键,它们会成为日志行中可检索的键值对。
  • otelcol.processor.attributesaction = "insert"key = "loki.attribute.labels"value = "traces":这是让 Loki exporter 把traces属性提升为标签的关键步骤,否则 LogQL 中无法用{traces="root"}这种标签选择器过滤。
  • otelcol.exporter.loki通过forward_to指向loki.write.local.receiver,由loki.write组件最终把日志推送到 Loki 的 push API。
  • Trace 出口与日志出口分离:Trace 走otelcol.exporter.otlp.tempo(端点tempo:4317,即 Tempo 的 OTLP gRPC 端口),日志走 Loki,各司其职。

从仓库示例可以看到,这种 Alloy 配置与 example/docker-compose/distributed/config.alloy 中的 OTLP receiver/exporter 写法一脉相承(该示例中 receiver 同样同时开启grpchttp端点),可以直接作为搭建实验环境时对照的蓝本。

预期输出:默认日志字段与格式

启用自动日志后,connector 会根据你开启的选项,为每个 span、root 或 process 生成一行日志。每行日志使用logfmt 风格的正文,默认键如下:

KeyDescription
svcspan 所属 resource 中的服务名(service name)。
spanspan 的名称。
durspan 的持续时间(纳秒),例如150200000ns
tidTrace ID。
statusspan 的状态。仅在状态被显式设置(非STATUS_CODE_UNSET)时出现。取值为STATUS_CODE_OKSTATUS_CODE_ERROR

你可以通过配置span_attributesprocess_attributesevent_attributes来增加更多键,也可以用overrides块自定义所有键名。

例如,一条 root span 日志可能长这样:

span="HTTP GET" dur=150200000ns http.method=GET http.target=/api/v1/query svc=my-service tid=7bba9f33312b3dbb8b2c2c62bb7abe2d

除了上述键值对,每条日志还带有一个traces属性,用来标识日志类型:spanrootprocessevent。如果你按上文 Loki 示例配置了loki.attribute.labels提示,这个属性就会变成 Loki 标签,可以直接在 LogQL 中过滤。

在 Loki 中查询自动日志数据

在 Grafana Explore 中使用 LogQL 查询自动日志。

查找所有 root span 日志:

{traces="root"}

过滤某个服务的慢请求(时长超过 2 秒):

{traces="root"} | logfmt | dur > 2s and svc="my-service"

这里| logfmt解析器把 logfmt 格式的日志正文解析为字段,之后就能直接对dur(纳秒)与svc做比较运算。

TraceQL 等价查询

下面这些 TraceQL 查询提供了与上述 LogQL 查询相同的 Trace 发现能力,且不需要自动日志,也不需要 Loki 实例

查找某个服务的全部 Trace:

{ resource.service.name = "my-service" }

查找某个服务的慢 Trace(持续时间超过 2 秒):

{ resource.service.name = "my-service" && span:duration > 2s }

查找错误 Trace:

{ status = error }

按特定属性检索:

{ span.http.method = "GET" && span.http.target = "/api/v1/query" }

需要说明的是,TraceQL 中的span:duration表示单个 span 的起止时间差(end - start),而非整条 Trace 端到端的trace:duration,二者语义不同(详见 构造 TraceQL 查询 中关于 duration 的说明)。此外,status = error这种按状态过滤的写法,以及resource.service.namespan.http.method这类属性选择器,都在 construct-traceql-queries.md 的示例中有对应的完整用法(如{resource.service.name = "frontend" && name = "POST /api/orders" && status = error})。TraceQL 是 Tempo 的原生查询语言,其语法与语义与 PromQL、LogQL 相似(见 TraceQL 总览),如果你已熟悉日志查询,上手成本很低。

从日志跳转到 Trace

要把日志行与其 Tempo 中的 Trace 直接关联起来,需要在 Grafana 的 Loki 数据源上配置derived fields(派生字段)

  1. 进入Connections>Data sources,选择你的 Loki 数据源。
  2. Derived fields中添加新字段,设置如下:
    • NameTraceID
    • TypeLabel
    • Match field nametid
    • 勾选Internal link,并指向你的 Tempo 数据源
  3. 保存数据源配置。

配置完成后,Explore 中的日志结果会在tid字段旁边显示一个链接,点击即可直接跳转到对应 Trace 的瀑布视图。Tempo 首页也提到,借助 Grafana 的 derived fields 支持,可以在 LogQL 过滤出关心的请求后一键跳转到 Trace(参见 Tempo 项目首页)。

总结

Automatic logging 通过otelcol.connector.spanlogs组件把 Trace 元数据自动落成日志,适合以 Loki 为中心的排障工作流:日志行默认携带svcspandurtidstatus键,可扩展自定义 span/process/event 属性,配合loki.attribute.labels提示实现按traces标签过滤,再通过 derived fields 实现"日志 → Trace"的一键跳转。若你不需要 Loki 且希望减少日志量,Tempo 原生的 TraceQL 提供了完全等价且更轻量的检索路径,两种方案互为补充,你可以根据团队现有的观测技术栈选择。

【免费下载链接】tempoGrafana Tempo is a high volume, minimal dependency distributed tracing backend.项目地址: https://gitcode.com/GitHub_Trending/tempo1/tempo

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 22:06:57

C语言回调函数实战:从函数指针到工程级应用

我最早对回调函数有“顿悟感”,是在维护一个串口通信模块的时候。那会儿协议解析、数据分包、命令分发全写在一个循环里,每加一个功能就要改主逻辑,眼看着代码越来越像一团打了结的耳机线。后来把“收到数据之后干什么”这个动作抽出来&#…

作者头像 李华
网站建设 2026/9/18 22:06:45

Hugo 模板函数 crypto.MD5 完全指南:md5 哈希与 Gravatar 头像实战

Hugo 模板函数 crypto.MD5 完全指南:md5 哈希与 Gravatar 头像实战 【免费下载链接】hugo The world’s fastest framework for building websites. 项目地址: https://gitcode.com/gh_mirrors/hu/hugo crypto.MD5 是 Hugo 模板系统中 crypto 命名空间下的哈…

作者头像 李华
网站建设 2026/9/18 22:06:34

BusyBox根文件系统/dev目录创建:静态mknod、devtmpfs、mdev三方案详解

做嵌入式Linux的兄弟应该都干过这事:往板子上烧完内核,手搓了一个BusyBox根文件系统,结果启动到一半卡在“Creating 5 entries in /dev”或者挂载根文件系统之后VFS报一堆节点不存在,console登录不了,串口一片死寂。这…

作者头像 李华
网站建设 2026/9/18 22:06:34

Gyroflow 镜头校准 5 步指南:自制一份精准镜头配置文件

Gyroflow 镜头校准 5 步指南:自制一份精准镜头配置文件 【免费下载链接】gyroflow Video stabilization using gyroscope data 项目地址: https://gitcode.com/GitHub_Trending/gy/gyroflow Gyroflow 用陀螺仪数据为视频防抖,而防抖后画面是否变形…

作者头像 李华
网站建设 2026/9/18 22:03:39

pnpm shamefully-hoist:依赖提升的代价与替代方案

我第一次在同事的.npmrc里看到shamefully-hoisttrue这行配置时,第一反应是:这玩意怎么自带情绪。后来翻 pnpm 官方文档才明白,名字真没开玩笑——在 pnpm 作者眼里,把依赖全部“提”到node_modules顶层这件事,本质上就…

作者头像 李华