
深入掌握 Grafana Tempo TraceQL 查询构建从管道结构到聚合与高级操作符【免费下载链接】tempoGrafana Tempo is a high volume, minimal dependency distributed tracing backend.项目地址: https://gitcode.com/GitHub_Trending/tempo1/tempoTraceQL 是 Grafana Tempo 原生的分布式追踪查询语言它以一次一条 trace 为评估单位将查询组织为链式表达式pipeline帮助你在海量追踪数据中精准定位特定操作、异常结果、级联错误与跨环境流量。读完本文你将掌握 TraceQL 的查询结构、intrinsic 与属性字段体系、比较与结构操作符、聚合与分组、算术与字段选择以及most_recent查询提示的完整用法。本文基于 Grafana Tempo 仓库中 construct-traceql-queries.md 文档并结合 TraceQL 引擎源码 展开源码级讲解。TraceQL 查询的基本结构TraceQL 中一条查询是一个表达式它被逐条 trace求值。查询被组织为一组链式表达式称为管道pipeline。管道中的每个表达式都会从结果集中选择或丢弃 spanset。查询会选出若干组 span并将它们通过由聚合器aggregator和条件构成的管道进行过滤如果对某条给定 trace这个管道最终产出了一个 spanset那么这条 trace 就会被包含在查询结果中。在 TraceQL 中花括号{}永远用于从可用 trace 中选择一组 span。花括号通常与一个条件配合使用以缩减被拉取的 span 数量。最简单的查询是这样{ }花括号内的条件会应用到每一个 span 上如果有匹配span 就会被返回。这个例子没有任何条件因此匹配所有内容。一个更典型的管道查询示例{ span.http.status_code 200 span.http.status_code 300 } | count() 2这个查询把 trace 缩减为满足以下条件的那些 spanhttp.status_code的取值落在200到299之间且一条 trace 内匹配的 span 数量大于 2。关于管道结构可在 TraceQL 引擎的 AST 定义中找到对应实现ast.go 中PipelineElement接口定义了extractConditions与evaluate两个核心方法前者负责把查询条件抽取为底层的取数请求FetchSpansRequest后者负责对 spanset 执行真正的求值过滤。构建查询的通用建议先宽后窄从{ }开始观察全部 trace再逐步添加条件收窄范围优先使用 intrinsic 提升效率trace:duration、trace:rootService等 trace 级 intrinsic 比聚合更快用组合同一 span 上的多个条件同一 span 的多个条件以 AND 连接使用管道操作符|用于聚合与转换用by(field)分组为指标类查询创建时间序列分解。查询构建实战示例以下示例覆盖了最常见的查询场景可以直接作为你编写查询的起点。查找特定操作的 trace要查找特定操作的 trace应同时指定操作名span 属性name和承载该操作的服务名resource 属性service.name。下面的查询在resource.service.name取值frontend且 spanname取值为POST /api/order的 trace 上进行过滤{resource.service.name frontend name POST /api/orders}当同一套 Grafana 技术栈服务于多个环境例如production和staging或存在同名但通过 namespace 区分的服务时查询形如{ resource.service.namespace ecommerce resource.service.name frontend resource.deployment.environment production name POST /api/orders }查找特定结果的 trace查找POST /api/orders操作上存在出错 span 的所有 trace{ resource.service.namefrontend name POST /api/orders status error }查找POST /api/orders操作返回 HTTP 5xx 错误的 trace{ resource.service.namefrontend name POST /api/orders span.http.status_code 500 }查找具有特定行为的 trace你可以对一条 trace 中的多个 span 做组合过滤。下面的查询定位所有GET /api/products/{id}操作中访问过数据库的 trace是排查由缓存问题引发的异常数据库访问比例的常用手段{span.service.namefrontend name GET /api/products/{id}} {span.db.systempostgresql}查找同时流经production与staging实例的 trace下面的查询利用 spanset 的 AND 组合找出同时经过production和staging的 trace是识别跨环境错误配置与数据泄漏的便捷手段{ resource.deployment.environment production } { resource.deployment.environment staging }查找包含数组值的 traceTraceQL 会自动查询数组中的数据数组支持自 vParquet4 起可用。当属性包含数组时比较运算符按元素逐一检查与~只要数组中任一元素满足条件即匹配!与!~仅当数组中没有任何元素满足条件时才匹配。如果span.foo是数组且包含值bar下面的查询可以定位到它{ span.foo bar }下面的查询匹配span.foo是数组、且没有任何元素等于bar的 span{ span.foo ! bar }你也可以用正则表达式匹配数组元素。下面的查询查找Accept请求头中任一元素匹配模式的 span{ span.http.request.header.Accept ~ application.* }下面的查询查找Accept请求头中没有任何元素匹配模式的 span{ span.http.request.header.Accept !~ application.* }使用结构操作符查找包含frontend服务、且该服务或其下游服务存在出错 span 的 trace{ resource.service.namefrontend } { status error }查找所有以productcatalogservice结尾的叶子 spanleaf span{ } ! { resource.service.name productcatalogservice }判断productcatalogservice与frontend是否为兄弟关系{ resource.service.name productcatalogservice } ~ { resource.service.namefrontend }按子 span 数量查找 spanspan:childCountintrinsic 用于统计 span 的直接子 span 数量自 vParquet5 起支持。查找叶子 span没有子 span 的 span通常代表数据库调用、外部 API 请求等终端操作{ span:childCount 0 }查找 frontend 服务中没有子 span 的 span{ resource.service.name frontend span:childCount 0 }查找扇出fan-out超过 10 个子 span 的高扇出 span{ span:childCount 10 }其他常用示例查找 HTTP 状态为200的服务并列出 span 所属的服务名与返回的 trace{ span.http.status_code 200 } | select(resource.service.name)查找 trace 内存在deployment.environmentresource 属性为production、同时存在http.status_code属性为200的 span 的任意 trace——注意这些条件可以落在同一 span 上也可以落在不同的 span 上{ resource.deployment.environment production } { span.http.status_code 200 }查找任意 span 具有http.method为GET且status为ok、并且另有一个 span 具有http.method为DELETE且status不为ok的 trace{ span.http.method GET status ok } { span.http.method DELETE status ! ok }查找deployment.environment属性匹配正则prod-.*且http.status_code为200的 trace{ resource.deployment.environment ~ prod-.* span.http.status_code 200 }查找耗时超过 5 秒的 trace{ trace:duration 5s }查找存在大量快速 span暗示过度埋点的服务{ span:duration 5ms } | count_over_time() by(resource.service.name) | topk(10)查找来自生产环境的大而慢的 trace{ resource.deployment.environment production trace:duration 2s } | count() 30选择 spanintrinsic 与属性TraceQL 区分两类 span 数据intrinsicspan 固有字段与属性attributes可自定义的键值对。你可以用它们构建过滤器并选择 span。Intrinsic 固有字段Intrinsic 字段对 scope 而言是基础性的它们天然存在而不是由开发者附加的键值对。Intrinsic 始终用scope:表示冒号分隔。Intrinsic 示例{ span:name foo } { event:name foo } { trace:id 1234 } { link:traceID 1234 }下表列出了当前可用的带作用域 intrinsic 字段字段类型定义示例span:statusstatus 枚举状态error、ok 或 unset{ span:status ok }span:statusMessagestring伴随 span 状态的可选文本{ span:statusMessage Forbidden }span:durationdurationspan 的结束时间减开始时间{ span:duration 100ms }span:namestring操作名或 span 名{ span:name HTTP POST }span:kindkind 枚举类型server、client、producer、consumer、internal、unspecified{ span:kind server }span:idstring十六进制字符串形式的 span ID{ span:id 0000000000000001 }span:parentIDstring十六进制字符串形式的父 span ID{ span:parentID 000000000000001 }span:childCountintegerspan 的直接子 span 数量{ span:childCount 0 }trace:durationdurationtrace 内所有 span 的 max(end) 减 min(start){ trace:duration 100ms }trace:rootNamestring若存在为 trace 中根 span 的名称{ trace:rootName HTTP GET }trace:rootServicestring若存在为 trace 中根 span 的服务名{ trace:rootService gateway }trace:idstring十六进制字符串形式的 trace ID{ trace:id 1234567890abcde }event:namestring事件名称{ event:name exception }event:timeSinceStartduration事件相对 span 开始时间的时间点{ event:timeSinceStart 2ms}link:spanIDstring十六进制字符串形式的链接 span ID{ link:spanID 0000000000000001 }link:traceIDstring十六进制字符串形式的链接 trace ID{ link:traceID 1234567890abcde }instrumentation:namestringinstrumentation scope 名称{ instrumentation:name grpc }instrumentation:versionstringinstrumentation scope 版本{ instrumentation:version 1.0.0 }源码层面intrinsic 枚举与字符串映射定义在 enum_attributes.go 中例如IntrinsicTraceRootService、IntrinsicTraceDuration、IntrinsicChildCount、IntrinsicEventTimeSinceStart、IntrinsicInstrumentationName等均可在查询文本中通过trace:rootService、trace:duration、span:childCount、event:timeSinceStart、instrumentation:name等关键字解析。trace 级 intrinsic 的性能优势trace:duration、trace:rootName、trace:rootService对同一条 trace 内的所有 span 取值相同。此外这些 intrinsic 在查询时性能显著更优因为它们需要检查的数据远少于 span 级 intrinsic应尽可能优先使用。有时你可能需要按 trace 级 intrinsic 搜索。例如span:name查找的是 trace 内 span 的名称如果希望按名为perf的 trace 名搜索应改用trace:rootName。下面的示例搜索所有名为service-name的 Kubernetes 集群且其 span 的根名称不包含perf{ resource.k8s.cluster.nameservice-name trace:rootName !~ .*perf.*}属性字段Attribute fields自定义属性以scope.为前缀点号分隔如span.、resource.、link.、event.。Resource 没有 intrinsic 值只有自定义属性。属性按点号.分隔而 intrinsic 字段使用冒号:。tracescope 只有 intrinsic在 trace 层没有自定义属性。属性示例{ span.foo bar } { resource.foo bar } { link.foo bar } { event.foo bar }TraceQL 支持这些属性 scopespan 属性、resource 属性、event 属性、link 属性和 instrumentation scope 属性。在 Grafana 界面中展开 span即可同时看到 span 属性和 resource 属性。属性字段从 span 派生而来可以被自定义。Process 与 span 属性类型由属性自身定义依据 OpenTelemetry 规范而 intrinsic 字段具有内建类型。你也可以引用 span 或其 resource 上的动态属性即 tag。查询中的属性以 span、resource、event 或 link scope 开头例如span.http或resource.namespace具体取决于你要查询什么。这种设计带来了显著的性能收益它让 Tempo 只扫描你感兴趣的数据。要查找GET HTTP方法的 trace查询可以写成{ span.http.method GET }各 scope 的使用示例查找经过production环境的 trace{ resource.deployment.environment production }查找指向 Postgres 或 MySQL 数据库的连接串{ span.db.system ~ postgresql|mysql }可以使用eventscope 查询 span 内发生的事件。span 事件是 span 生命周期中一个独特的时间点span 帮助你构建服务的结构层级而 span 事件提供更细粒度的信息有助于更快排查应用问题并维持最优性能。查询 span 事件中的异常{ event.exception.message ~ .*something went wrong.* }如果你已为 trace 埋点 span linkspan link 将一个 span 与一个或多个具有因果关系的 span 关联起来可以使用linkscope 查询链接数据{ link.opentracing.ref_type child_of }instrumentation scope 允许你查询埋点来源instrumentation scope字段从而基于“在哪里、用什么方式埋点”过滤和探索 trace其核心用途是依据产生数据的各类库和客户端来查询 trace 数据。查找 instrumentation scope 的编程语言{ instrumentation.language java }查找某个服务产生埋点的库{ resource.service.name foo } | rate() by (instrumentation:name)带引号的属性名Quoted attribute names属性名可以包含终结字符例如点号.。要搜索含终结字符的 span 属性可使用带引号的属性语法将属性放进双引号内例如example one引号之间的所有字符都视为属性名的一部分。示例查找属性名为attribute name with space的 span{ span.attribute name with space value }带引号属性语法可以与非带引号属性语法混用以下都是合法查询{ span.attribute.attribute name with space value }目前仅支持\与\\两种转义序列。值类型与字面量TraceQL 支持多种字面量类型来表达查询中的值。字面量是直接写在查询里的固定值例如200、GET或5s。整数整数值可正可负{ span.http.status_code 200 } { span.retry_count -1 }TraceQL 提供两个整数边界的特殊常量minInt最小 64 位整数值-9223372036854775808maxInt最大 64 位整数值9223372036854775807当你需要显式表达数值极值、又不想硬编码长字面量时这两个常量非常有用{ span.value ! minInt span.value ! maxInt }这两个常量的解析与展开在语法生成文件 expr.y 中定义并可在 parse_test.go 中找到对应的解析测试{ maxInt }被解析为{ 9223372036854775807 }{ minInt }被解析为{ -9223372036854775808 }。时长Durations时长值表示时间间隔。支持的单位包括ns纳秒、us微秒、ms毫秒、s秒、m分钟和h小时{ span:duration 100ms } { trace:duration 5s }时长也可以是带符号的{ event:timeSinceStart -5s }浮点数浮点值使用十进制记法{ span.value 1.5 }字符串字符串值用双引号括起{ span.http.method GET }Nil使用nil检查缺失或为空的属性用! nil确保属性存在且非空{ span.optional_field nil } { span.required_field ! nil }比较操作符比较操作符用于测试表达式中的值。已实现的比较操作符包括相等!不等大于大于等于小于小于等于~正则表达式!~取反正则表达式TraceQL 使用Golang 正则表达式。所有正则表达式都被视为完全锚定的正则表达式在两端都会被锚定。这种锚定让查询更快也与 PromQL 中正则完全锚定的行为一致。未锚定的查询例如{ span.foo ~ bar }现在会被视为{ span.foo ~ ^bar$ }。如果你在 Grafana 仪表板中使用 TraceQL 正则并希望保留未锚定行为请把查询更新为显式未锚定版本例如{ span.foo ~ ._bar._}。例如要查找所有 span 中http.status_code属性大于400且小于等于500的 trace{ span.http.status_code 400 span.http.status_code 500 }对于字符串类型的http.status_code值同样可以通过字典序lexicographic ordering进行比较{ span.http.status_code 400 }查找http.method属性为GET或DELETE的所有 trace{ span.http.method ~ DELETE|GET }查找any_attribute不为nil即属性在 span 中存在的所有 trace{ span.any_attribute ! nil }查找any_attribute为nil即属性在 span 中不存在的所有 trace{ span.any_attribute nil }查找service.version在 resource 层不存在的所有 trace{ resource.service.version nil }查找 event 属性exception.message不存在的所有 trace{ event.exception.message nil }在源码中全部比较与结构操作符以Operator枚举形式定义于 enum_operators.go其中布尔型操作符isBoolean、算术操作符isArithmetic以及按静态类型校验合法性的binaryTypeValid/unaryTypesValid逻辑共同约束了每种类型上可用的操作符集合。字段表达式Field expressions字段可以多种方式组合实现更灵活的搜索条件。字段表达式是由多个字段复合而成的表达式它定义了返回结果必须匹配的全部条件。示例查找http.status_code为成功码2xx的 trace{ span.http.status_code 200 span.http.status_code 300 }查找使用了DELETEHTTP 方法且 span 的 intrinsic 状态不为 OK 的 trace{ span.http.method DELETE status ! ok }两个表达式都要求所有条件在同一条 span 上成立。一对{}内的整个表达式必须在单个 span 上求值为 true该 span 才会被包含在结果集中。上面的例子中如果一个 span 的http.method属性为DELETE、同时status属性为ok那么这条 trace 不会被包含在返回结果中。用操作符组合 spansetSpanset 操作符让你从一条 trace 中选择不同的 span 集合然后在它们之间做出判断。逻辑操作符这些 spanset 操作符在 span 集合之间执行逻辑检查{condA} {condB}与操作符检查两个条件是否都找到匹配。{condA} || {condB}并集操作符||检查任一条件是否找到匹配相当于 OR 语句。例如查找经过两个特定cloud.region的 trace{ resource.cloud.region us-east-1 } { resource.cloud.region us-west-1 }注意下面这个表达式与上一例的区别{ resource.cloud.region us-east-1 resource.cloud.region us-west-1 }第二个表达式不会返回任何 trace因为单个 span 不可能同时拥有取值为两个 region 的resource.cloud.region属性。可以用类似查询查找经过us-east-1或us-west-1任一 cloud region 的 trace{ resource.cloud.region us-east-1 } || { resource.cloud.region us-west-1 }TraceQL 提供多种途径实现相似查询。例如下面的查询与上一个结果相同但性能更优它使用管道指示可以使用第一个结果或第二个条件本质上是链式选项而不是要求匹配两个 region 中任一/另一个的条件{ resource.cloud.region ~ us-east-1|us-west-1 }结构操作符这些 spanset 操作符查看 trace 的结构与 span 之间的关系。结构操作符始终返回操作符右侧的匹配结果。{condA} {condB}后代操作符查找与{condB}匹配、且是匹配{condA}的 span 的后代的 span{condA} {condB}祖先操作符查找与{condB}匹配、且是匹配{condA}的 span 的祖先的 span{condA} {condB}子操作符查找与{condB}匹配、且是匹配{condA}的父 span 的直接子 span{condA} {condB}父操作符查找与{condB}匹配、且是匹配{condA}的子 span 的直接父 span{condA} ~ {condB}兄弟操作符~查找与{condB}匹配、且至少有一个匹配{condA}的兄弟的 span例如查找某个特定 HTTP API 与特定数据库发生交互的 trace{ span.http.url /path/of/api } { span.db.name db-shard-001 }从源码看结构操作符在抽取取数条件时会向FetchSpansRequest追加对应的结构性 intrinsicIntrinsicStructuralDescendant、IntrinsicStructuralChild、IntrinsicStructuralSibling见 ast.go 中SpansetOperation.extractConditions的实现。联合结构操作符Union structural这些 spanset 操作符同样查看 trace 结构与 span 间的关系其独特之处在于返回操作符两侧都匹配的 span{condA} {condB}后代操作符查找与{condB}匹配且是匹配{condA}的 span 的后代的 span{condA} {condB}祖先操作符查找与{condB}匹配且是匹配{condA}的 span 的祖先的 span{condA} {condB}子操作符查找与{condB}匹配且是匹配{condA}的父 span 的直接子 span{condA} {condB}父操作符查找与{condB}匹配且是匹配{condA}的子 span 的直接父 span{condA} ~ {condB}兄弟操作符~查找与{condB}匹配且至少有一个匹配{condA}的兄弟的 span例如在一条查询中同时得到出错的端点及其所有出错的子 span{ span.http.url /path/of/api status error } { status error }实验性结构操作符这些 spanset 操作符查看 trace 结构与 span 间的关系。它们被标记为实验性因为有时会返回误报但在某些场景下非常有用见下面的示例。{condA} ! {condB}非后代操作符!查找与{condB}匹配、但不是匹配{condA}的父 span 的后代的 span{condA} ! {condB}非祖先操作符!查找与{condB}匹配、但不是匹配{condA}的子 span 的祖先的 span{condA} ! {condB}非子操作符!查找与{condB}匹配、但不是匹配{condA}的父 span 的直接子 span{condA} ! {condB}非父操作符!查找与{condB}匹配、但不是匹配{condA}的子 span 的直接父 span{condA} !~ {condB}非兄弟操作符!~查找与{condB}匹配、且没有匹配{condA}的兄弟的 span例如查找服务foo中带有叶子 span 的 trace{ } ! { resource.service.name foo }查找级联错误序列中的最后一个错误 span{ status error } ! { status error }聚合器Aggregators到目前为止所有示例查询都围绕单个 span 展开。你可以使用聚合函数对一组 span 提出更宏观的问题。当前聚合函数包括countspanset 中 span 的数量。avgspanset 中给定数值属性或 intrinsic 的平均值。maxspanset 中给定数值属性或 intrinsic 的最大值。minspanset 中给定数值属性或 intrinsic 的最小值。sumspanset 中给定数值属性或 intrinsic 的求和值。聚合函数让你对匹配结果执行操作进一步精化返回的 trace。例如查找 span 总数大于10的 tracecount() 10查找 trace 内 span 平均时长大于20ms的 traceavg(duration) 20ms查找具有 3 个以上http.status_code为200的 span 的 trace{ span.http.status_code 200 } | count() 3查找自定义属性bytesProcessed总和超过 1 GB 的 trace{ } | sum(span.bytesProcessed) 1000000000这些关键字在 lexer.go 的保留字表中被分别映射为COUNT、AVG、MAX、MIN、SUM等 token聚合节点的求值逻辑Aggregate类型及其extractConditions位于 ast.go聚合会迫使引擎返回完整 trace参见NeedsFullTrace。分组GroupingTraceQL 支持分组管道操作符可以按任意属性分组。这在查找类似“单个服务出现 1 个以上错误”的场景中非常有用{ status error } | by(resource.service.name) | count() 1分组的by关键字同样定义在 lexer.go 中token 为BY其 AST 节点GroupOperation位于 ast.go在抽取条件时会关闭AllConditions以允许多组条件独立求值。算术ArithmeticTraceQL 支持在查询中进行任意算术运算这让查询更易读{ span.http.request_content_length 10 * 1024 * 1024 }以及任何你能想到的其他用法。从操作符定义看、-、*、/、%、^在 enum_operators.go 中对应OpAdd、OpSub、OpMult、OpDiv、OpMod、OpPower其中算术四则运算isArithmetic适用于数值、时长等静态类型。字段选择SelectionTraceQL 可以任意选择 span 的字段。这一操作尤其高效因为被选择的字段直到所有其他条件满足后才会被取回。例如从所有出错状态的 span 中选择span.http.status_code与span.http.url{ status error } | select(span.http.status_code, span.http.url)select关键字的 token 定义在 lexer.goSELECTAST 节点SelectOperation位于 ast.go它通过newSelectOperation(attrs)构造并保存待选择的属性列表。检索最近结果most_recent实验性排查线上故障或监控生产健康时你常常需要先看到最新的 trace。默认情况下Tempo 的查询引擎偏重速度会返回最先匹配到的N条 trace这些结果可能不是最新的。most_recent提示确保你看到最新鲜的数据从而能诊断最近的错误或性能回退不会因为提前触达行数上限而漏掉结果。你可以将 TraceQL 查询提示most_recenttrue用于任何 TraceQL 选择型查询强制 Tempo 按时间顺序返回最近的结果。示例{} with (most_recenttrue) { span.foo bar } { status error } with (most_recenttrue)使用most_recenttrue时Tempo 会在数据分片data shards间执行更深入的搜索保留最新候选并按 span 开始时间排序返回 trace而不是在首次触达 limit 时就停止。你可以通过query_frontend配置块中的most_recent_shards:指定执行 most recent TraceQL 搜索时把搜索拆分为多少个时间窗口默认值为200query_frontend: search: sharder: most_recent_shards: 200该配置在源码中的默认值与实现位于 search_sharder.godefaultMostRecentShards 200及MostRecentShards字段并在 config.go 中被初始化为默认值most_recent提示本身定义在 enum_hints.go 中HintMostRecent most_recent属于非危险safe提示。使用most_recent对搜索结果的影响大多数搜索函数是确定性的使用相同的搜索条件会得到相同的结果。当你使用most_recenttrue时Tempo 的搜索变为非确定性的如果你执行两次相同的搜索只要你的搜索可能产生的结果数量大于搜索设定的返回数量两次得到的结果列表就会不同。小结与进一步学习通过本文你已掌握 TraceQL 的完整查询构建能力管道结构{}选择 spanset|串联聚合与转换字段体系scope:冒号表示 intrinsicscope.点号表示自定义属性覆盖 span、resource、event、link、instrumentation 五个 scope操作符比较操作符含完全锚定的正则、逻辑与结构 spanset 操作符含联合结构与实验性操作符聚合与分组count/avg/max/min/sum与by(field)分组高级能力算术运算、字段选择以及实验性的most_recent时间排序提示。如果你希望进一步深入可以继续阅读同一目录下的相关文档TraceQL 语言架构总览TraceQL 中的 trace 结构TraceQL 查询性能调优TraceQL 查询编辑器同时仓库中的语法生成文件 expr.y、词法分析器 lexer.go、AST 定义 ast.go 与覆盖数百条合法/非法查询的 test_examples.yaml 测试样例都是深入研究 TraceQL 引擎的绝佳起点。【免费下载链接】tempoGrafana Tempo is a high volume, minimal dependency distributed tracing backend.项目地址: https://gitcode.com/GitHub_Trending/tempo1/tempo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考