1. 指标监控服务重构里为什么要把 Jaeger、Prometheus、Elasticsearch 串成一条链指标监控服务重构最容易踩的坑不是某个组件起不来而是链路断在中间Trace 有数据但 Metrics 看不到Metrics 有曲线但查不到具体 SpanElasticsearch 里堆了一堆索引却没人知道哪条对应哪次请求。我这次要做的就是把 Jaeger、Prometheus、Elasticsearch 和 OpenTelemetry Collector 用一套统一的 Key/API 通道串起来让采集、上报、查询形成闭环。核心检索词先摆清楚Jaeger 负责分布式追踪能还原一次请求跨了哪些服务、卡在哪一步Prometheus 负责指标采集与告警适合看 QPS、延迟、错误率这类时序数据Elasticsearch 负责 Trace 和日志的持久化存储与检索OpenTelemetry Collector 则是中间的管道把不同来源的遥测数据做接收、处理、导出。适合谁正在做微服务可观测性建设、被多套配置割裂折磨的后端和 SRE 同学。这次重构的入口我选 TaoToken 统一 Key/API 通道原因是本地要同时调多个模型做配置生成、报错解释和字段映射校验如果每个工具各配一套 Key环境变量会乱成一锅粥。统一入口之后settings.json 和 config.toml 里只维护一份凭据切换和排障都省事。下面直接给可复制的配置骨架和验证动作。2. TaoToken 前置统一 Key 与 API 通道怎么准备TaoToken 在这里扮演的是统一凭据与 API 通道的角色不是替代 Jaeger 或 Prometheus 本身。你需要先拿到一个可用的 Key然后把它写进本地配置供后续生成配置、解释报错、校验字段时调用。第一步打开官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册并登录。第二步进入控制台创建 API Key地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 。第三步如果你要长期跑编码或 Agent 类任务可以看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。第四步Key 管理页在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。API 基础地址是 https://taotoken.net/api 注意这个地址不带 UTM 参数配置里直接写它。拿到 Key 之后建议先写进环境变量不要硬编码进仓库export TAOTOKEN_API_KEYsk-你的Key export TAOTOKEN_BASE_URLhttps://taotoken.net/api注意Key 只放在本地环境变量或密钥管理里不要提交到 Git。配置文件里用占位符引用环境变量。如果你只是想先验证模型通道是否通可以直接用模型对话页测试https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 。确认通道可用后再往下配可观测性链路。3. 可复制配置settings.json 与 config.toml 骨架这一节是重点配置分两块一块是给本地工具链用的 settings.json一块是给 OpenTelemetry Collector 用的 config.toml。先看 settings.json它负责把 TaoToken 的统一 Key 和各个可观测性组件的地址集中管理。{ taotoken: { baseUrl: https://taotoken.net/api, apiKeyEnv: TAOTOKEN_API_KEY, defaultModel: claude-sonnet }, observability: { jaeger: { collectorGrpc: http://localhost:4317, queryUi: http://localhost:16686, storageType: elasticsearch, esServerUrls: http://localhost:9200 }, prometheus: { scrapeInterval: 15s, remoteWriteUrl: http://localhost:9090/api/v1/write }, elasticsearch: { hosts: [http://localhost:9200], indexPrefix: jaeger }, otelCollector: { otlpGrpc: 0.0.0.0:4317, otlpHttp: 0.0.0.0:4318, metricsExporter: prometheus } } }再看 OpenTelemetry Collector 的 config.toml这里定义 receiver、processor、exporter 和 pipeline。关键点是 spanmetrics connector它作为 receiver 接收上游 trace pipeline 的 span同时作为 exporter 把 span 指标导出到 Prometheus。[receivers.otlp] protocols.grpc.endpoint 0.0.0.0:4317 protocols.http.endpoint 0.0.0.0:4318 [connectors.spanmetrics] histogram.explicit.buckets [2ms, 8ms, 50ms, 100ms, 200ms, 500ms, 1s, 5s] dimensions [ { name http.method }, { name http.status_code }, { name service.name } ] [exporters.prometheus] endpoint 0.0.0.0:8889 namespace otel [exporters.otlp] endpoint jaeger-collector:4317 tls.insecure true [processors.batch] timeout 5s send_batch_size 1024 [service.pipelines.traces] receivers [otlp] processors [batch] exporters [otlp, spanmetrics] [service.pipelines.metrics] receivers [spanmetrics] exporters [prometheus]这里有个容易搞混的点spanmetrics 在 trace pipeline 里是 exporter在 metrics pipeline 里是 receiver。它把 trace 里的 span 转成指标再交给 Prometheus 抓取。Jaeger 的存储后端指向 Elasticsearch所以 collector 启动参数里要带--es.server-urls。jaeger-collector: image: jaegertracing/jaeger-collector environment: - SPAN_STORAGE_TYPEelasticsearch command: - --es.server-urlshttp://elasticsearch:9200 - --es.num-shards1 - --es.num-replicas0 - --log-levelerror ports: - 4317:4317 - 14250:14250Prometheus 侧只需要加一个 scrape 任务抓 otel-collector 暴露的 8889 端口scrape_configs: - job_name: otel-spanmetrics scrape_interval: 15s static_configs: - targets: [otel-collector:8889]4. 验证请求从采集到查询跑通闭环配置写完必须验证否则你不知道断在哪一环。验证顺序建议从下往上先确认 Elasticsearch 可写再确认 Jaeger 能查到 Trace然后确认 Prometheus 能抓到 spanmetrics最后确认 OpenTelemetry 上报正常。第一步检查 Elasticsearch 健康curl -s http://localhost:9200/_cluster/health?pretty返回status: green或yellow都算正常单节点通常是 yellow。第二步发一条测试 Trace。用 curl 直接打 OTLP HTTP 端点curl -X POST http://localhost:4318/v1/traces \ -H Content-Type: application/json \ -d { resourceSpans: [{ resource: { attributes: [{key: service.name, value: {stringValue: demo-service}}] }, scopeSpans: [{ spans: [{ traceId: 5B8EFFF798038103D269B633813FC60C, spanId: EEE19B7EC3C1B174, name: demo-span, kind: 2, startTimeUnixNano: 1692864000000000000, endTimeUnixNano: 1692864001000000000 }] }] }] }第三步打开 Jaeger UIhttp://localhost:16686选择demo-service应该能看到demo-span。如果查不到先看 collector 日志有没有Failed to init storage factory这个报错通常是 Elasticsearch 地址写错或索引模板没建。第四步检查 Prometheus 是否抓到指标。访问http://localhost:9090/targets确认otel-spanmetrics是 UP。然后在查询框输入otel_calls_total应该能看到按 service.name 和 http.status_code 维度聚合的计数。第五步验证 TaoToken 通道。用统一 Key 调一次模型对话确认配置生成和报错解释的入口是通的curl -s https://taotoken.net/api/v1/messages \ -H x-api-key: $TAOTOKEN_API_KEY \ -H anthropic-version: 2023-06-01 \ -H Content-Type: application/json \ -d {model:claude-sonnet,max_tokens:64,messages:[{role:user,content:ping}]}返回里有content字段就说明通道正常。这一步通了后面用模型辅助排查配置才有意义。5. 本篇常见错排查Jaeger 存储、Prometheus 抓取、OTel Pipeline排障这块我按实际遇到的顺序列基本都是配置层面的问题不用改代码。Jaeger collector 起不来报Failed to init storage factory。最常见原因是SPAN_STORAGE_TYPE没设成elasticsearch或者--es.server-urls指向的地址在容器网络里不通。容器内不要写localhost要写服务名http://elasticsearch:9200。另外 Elasticsearch 7.x 默认开了安全本地测试记得xpack.security.enabledfalse。Prometheus 抓不到 spanmetrics。先确认 otel-collector 的 8889 端口有没有暴露再确认 metrics pipeline 里 receiver 写的是spanmetrics而不是otlp。如果 pipeline 配错collector 启动时会直接报 pipeline 校验失败。还有一个坑是 spanmetrics 的 dimensions 配了高基数标签比如把 traceId 当维度会导致 Prometheus 内存暴涨别这么干。Trace 能查到但 Metrics 没有对应数据。检查 trace pipeline 的 exporters 里有没有同时写otlp和spanmetrics。只写otlp的话span 只进 Jaeger不会转成指标。spanmetrics 必须同时出现在 trace pipeline 的 exporter 和 metrics pipeline 的 receiver 里这是 connector 的双重身份决定的。TaoToken 调用返回 401。先确认环境变量TAOTOKEN_API_KEY在当前 shell 里生效echo $TAOTOKEN_API_KEY看一下。如果用的是配置文件确认引用的是环境变量而不是写死的空字符串。接入细节可以对照文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。Jaeger UI 里 Trace 展示和 Grafana 不一致。这通常是采样率或时间窗口不同导致的不是数据丢失。Jaeger 默认按请求查Grafana 按时间范围聚合先对齐查询时间范围再看。6. 继续往下走把统一通道用在长期编码与 Agent 任务上链路跑通之后你会发现真正费时间的不是起容器而是反复调配置、读报错、改字段。这时候统一 Key 的价值就出来了不管是让模型解释Failed to init storage factory还是生成 spanmetrics 的 dimensions 映射都走同一个通道不用在多个 Key 之间切换。如果你只是偶尔验证模型输出用模型对话页就够了https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 。如果你要长期跑编码、Agent 或批量配置生成建议看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。Key 的创建和管理在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 接入方式以文档为准https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。最后留一个我踩过的坑spanmetrics 的 bucket 别照抄默认值按你服务的真实延迟分布调否则 P99 会落在最后一个桶里看起来永远是 5s。先把http.server.duration的直方图打出来再回填 buckets指标才有参考价值。