
PostHog 可观测性跨信号关联查询从 metric exemplar 到 trace 再到 logs 的 HogQL 实战【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthogPostHog 将 OpenTelemetry 三大信号——metrics、traces、logs——分别存储在posthog.metrics、posthog.trace_spans、logs三张表中并以trace_id作为统一的关联键。本篇基于 PostHog 内置 AI 技能querying-posthog-data中的官方示例文档完整讲解指标异常 → 定位 exemplar 代表样本 → 拉取该请求的 spans 与 logs这条单轮查询single round trip关联链路三张表的命名空间与排序键差异、base64 编码的trace_id在关联与展示中的用法、exemplar 提取在摄取管线中的实现原理以及当前数据下可直接运行的 span-anchored 替代查询最后给出两个高频变体查询exemplar 采样、按服务挑选错误率跳升的样本 trace。读完后你可以直接在 PostHog 的 SQL 入口复制执行文中全部查询并理解每一处过滤条件的工程原因。适用场景一次查询串起指标尖峰、请求链路与日志当你正在排查一个指标异常——延迟尖峰latency spike或错误率跳升error rate jump并希望在一次查询内检查一条代表性请求的完整 span 链路以及该请求打出的日志时就是这篇指南覆盖的场景。典型工作流checkout服务的http.server.duration在过去 15 分钟出现尖峰 → 从指标数据中挑出驱动尖峰的那条请求的trace_idOTel 术语中叫exemplar→ 拉出该 trace 的全部 span 和全部日志按时间戳交错排列得到一条可读的请求时间线。该模式依赖的 schema 设计前提是三张表都存储trace_id且编码方式一致base64因此可以直接做等值连接无需解码。posthog.metrics的 schema 就是按照 OpenTelemetry exemplar 模式设计的——每个指标数据点都可以携带一个 exemplartrace_id当 SDK 在数据点上挂载了 exemplar 时。三表数据模型命名空间、trace_id 编码与空值形态命名空间规则最容易踩的坑三张表在 HogQL 数据库中的注册层级并不对称直接决定了查询里表名怎么写logs注册在 HogQL根层级用裸名即可posthog.trace_spans和posthog.metrics注册在posthog.命名空间下必须带前缀用裸名引用后两者会在 HogQL 编译期直接报 Unknown table。trace_id的统一编码与空值语义三张表都把trace_id存为base64 编码的 16 字节解码后是标准 OTel trace ID。这带来两个直接结论关联查询是简单的等值比较不需要先解码展示给人看时用hex(tryBase64Decode(trace_id))转成 hex 形式。需要注意一个不对称点三张表里没有 trace 上下文的表示方式不同。logs表中未设置trace_id时是AAAAAAAAAAAAAAAAAAAAAA16 个零字节的 base64而非 nullposthog.metrics中无 exemplar 的指标点是空字符串。写过滤条件时必须区分这两种空值形态。各表关键列速查posthog.metrics一行一个指标观测点底层是 ClickHouse 表metrics1分布式别名metrics列类型说明trace_id/span_idStringexemplar 的 trace/span无 exemplar 时为空字符串time_bucketDateTimetoStartOfDay(timestamp)排序键首分量timestampDateTime64(6)观测时间service_nameLowCardinality(String)发出指标的服务metric_nameLowCardinality(String)指标名如http.server.durationmetric_typeLowCardinality(String)gauge/sum/histogram等 OTel 数据点类型valueFloat64观测值直方图为 sumcountUInt64直方图观测数默认 1unitLowCardinality(String)OTel 单位ms、s、By等aggregation_temporalityLowCardinality(String)delta或cumulativeattributes/resource_attributesMap指标点属性 / 资源属性排序键为(team_id, time_bucket, service_name, metric_name, resource_fingerprint, timestamp)因此服务名 指标名 时间窗的过滤非常高效。表上还预建了按分钟预聚合的投影projection_aggregate_counts预聚合键含toStartOfMinute(timestamp)携带count()/sum(value)/min(value)/max(value)定义见 metrics1.py——SELECT 与 GROUP BY 键与投影完全匹配时优化器会自动命中投影。此外用户 HogQL 查询对该表的读取上限为 50 GB。posthog.trace_spans一行一个 spantrace_id、span_id、parent_span_id均为 base64is_root_span是便利标志列根 span 的parent_span_id是AAAAAAAAAAA应使用is_root_span而非字符串匹配status_code遵循 OTel 语义0 Unset、1 OK、2 Error用数值2过滤不是字符串ERRORduration_nano单位是纳秒。logs一行一条日志body为日志正文severity_number为 OTel 严重度数值severity_text为可读级别其排序键为(team_id, service_name, toUnixTimestamp(timestamp))查询时必须带service_name与时间窗过滤。三张表的完整列定义分别见 models-metrics.md、models-apm-spans.md 与 models-logs.md。摄取侧原理exemplar 是如何变成trace_id的文档中metric 行携带 exemplartrace_id这一能力由摄取管线实现。从当前仓库的 rust/capture-logs/src/metric_record.rs 可以看到完整实现flatten_metric将 OTelMetric按数据点展开成KafkaMetricRowgauge / sum / histogram / exponential_histogram 四种数据点都会把dp.exemplars传入build_number_row见 L98、L124、L156、L194exemplar 选择逻辑L276-L285取第一个trace_id恰为 16 字节的 exemplar并通过与 logs / traces 共用的extract_trace_id/extract_span_id助手编码最后以BASE64_STANDARD写入KafkaMetricRow.trace_id/span_id。注释明确说明按长度预筛是为了防止一个 malformed 的 exemplar 遮蔽后面合法的 exemplar没有任何 exemplar或全部 malformed时行内trace_id/span_id回退为空字符串L285 的unwrap_or_default()——这正是查询侧要写trace_id ! 的底层原因同一文件内的单测test_exemplar_populates_trace_and_span_ids、test_first_well_formed_exemplar_is_picked、test_malformed_exemplar_before_valid_picks_valid、test_only_malformed_exemplar_yields_empty_ids等L589 起对上述行为做了回归锁定包括无 exemplar 的 gauge 行trace_id必须为空字符串这条向后兼容承诺。需要如实说明的是本文档的Status注记对应 PR #50936 时点的状态以及 models-metrics.md 中的警告都描述exemplar 提取尚未在摄取管线接上、当前所有 metric 行trace_id 这一过渡状态而从上述当前源码看提取逻辑、长度过滤与编码路径已经完整落地并有单测覆盖。因此当前数据里trace_id是否为空取决于你所在部署的摄取版本与存量数据新摄取且 SDK 携带 exemplar 的数据应能命中主查询对不确定或尚未回填的数据下文Span-anchored 替代查询始终可用不依赖 exemplar。模式一intended patternexemplar → trace → logs三步分解定位尖峰在posthog.metrics中针对特定(service, metric, time window)找到异常挑 exemplarargMax(trace_id, value)返回取值最高的那一行对应的trace_id——即驱动尖峰的代表性请求一次拉取 spans 与 logs对挑出的trace_id用一条UNION ALL同时取出 span 行和 log 行按timestamp排序让两种信号在同一时间线上交错呈现。完整查询WITH exemplar AS ( SELECT argMax(trace_id, value) AS trace_id FROM posthog.metrics WHERE service_name checkout AND metric_name http.server.duration AND timestamp now() - INTERVAL 15 MINUTE AND trace_id ! ) SELECT span AS source, name AS detail, service_name, duration_nano, status_code, NULL AS severity_number, timestamp FROM posthog.trace_spans WHERE trace_id (SELECT trace_id FROM exemplar) UNION ALL SELECT log, body, service_name, NULL, NULL, severity_number, timestamp FROM logs WHERE trace_id (SELECT trace_id FROM exemplar) ORDER BY timestamp结果是一列source标记span或log、detailspan 名或日志正文、service_name、span 的duration_nano/status_code、log 的severity_number以及统一排序的timestamp——一个请求级时间线。逐条 Notes每个过滤与写法背后的工程原因argMax(trace_id, value)很便宜posthog.metrics上的按分钟投影已按(service_name, metric_name, ...)预聚合把时间窗收紧对尖峰排查 15 分钟足够即可让查询走投影、近乎免费必须过滤trace_id ! 没有 exemplar 的指标点用的是空字符串而不是 null不过滤的话argMax可能选中一条无 exemplar 的行用UNION ALL而不是UNIONUNION会去重带来额外成本且这里两类行本来就不应互相去重status_code 2即 ErrorOTel 语义posthog.trace_spans利用该列可以在结果里就地标记错误 span如果还想可视化地看 span 树把结果中的trace_id传给posthog:apm-trace-get工具即可拿到完整 waterfall。模式二Works todayspan-anchored 关联在 exemplar 数据尚未全面落位之前或任何你不想依赖 metrics 表的场景把锚点换到 span 上先找到一条有趣的trace最慢的错误根 span、时长最长的请求、或特定服务的入口 span再拉它的日志。WITH slow_error_trace AS ( SELECT trace_id FROM posthog.trace_spans WHERE service_name checkout AND is_root_span AND status_code 2 AND timestamp now() - INTERVAL 1 HOUR ORDER BY duration_nano DESC LIMIT 1 ) SELECT span AS source, name AS detail, service_name, duration_nano, status_code, NULL AS severity_number, timestamp FROM posthog.trace_spans WHERE trace_id (SELECT trace_id FROM slow_error_trace) UNION ALL SELECT log, body, service_name, NULL, NULL, severity_number, timestamp FROM logs WHERE trace_id (SELECT trace_id FROM slow_error_trace) ORDER BY timestamp这里的 CTE 选出的是checkout服务近 1 小时内、处于根 spanis_root_span代表一次完整请求入口、状态为Error的 span 中耗时最长的那条 trace——最慢的错误请求通常是最有信息量的排查样本。由于trace_id在posthog.trace_spans与logs中都是 base64 编码等值连接直接生效。变体查询变体一一次取多个 exemplar trace 的样本单条 exemplar 可能是偶发噪声。按trace_id分组取 Top-N对每个候选 trace 记下峰值SELECT trace_id, max(value) AS peak FROM posthog.metrics WHERE service_name checkout AND metric_name http.server.duration AND timestamp now() - INTERVAL 15 MINUTE AND trace_id ! GROUP BY trace_id ORDER BY peak DESC LIMIT 5得到的 5 个trace_id可以逐个代入模式一/模式二的时间线查询做交叉验证。变体二按错误率跳升找服务每服务挑一条样本 trace不预设怀疑对象时从 span 侧自底向上找哪个服务的错误率在跳升并同时为每个服务附上一条可直接深挖的错误样本 traceSELECT service_name, countIf(status_code 2) / count() AS error_rate, argMax(trace_id, status_code 2) AS sample_error_trace FROM posthog.trace_spans WHERE timestamp now() - INTERVAL 1 HOUR AND is_root_span GROUP BY service_name HAVING count() 100 ORDER BY error_rate DESC LIMIT 10要点countIf(status_code 2) / count()按根 span 口径统计每个服务的错误率argMax(trace_id, status_code 2)技巧性地返回错误标志为真非零的某一行的trace_id即该服务的一条样本错误 traceHAVING count() 100排除样本量过小、比率不稳定的长尾服务。得到的sample_error_trace可以直接喂给posthog:apm-trace-get或者按trace_id回查logs做日志侧确认。小结三表关联键统一为 base64 的trace_id等值连接即可展示用hex(tryBase64Decode(trace_id))表名注册不对称logs用裸名posthog.metrics/posthog.trace_spans必须带posthog.前缀核心排查链路是metrics 里argMax(trace_id, value)挑 exemplar →UNION ALL一次拉回 spans logs 按时间交错span-anchored 版本在 exemplar 数据不可用时随时可替代空值语义要记牢metrics 无 exemplar 是logs 无 trace 上下文是全零 base64摄取侧 exemplar 提取的选取规则16 字节长度过滤、base64 编码、无 exemplar 回退空串在 rust/capture-logs/src/metric_record.rs 中有完整实现与单测可据此判断你所部署环境是否已具备主查询所需的数据。参考文档example-observability-correlation.md、所属技能入口 SKILL.md。【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考