Files
sundynix-agentix/sundynix-shared/otelx/otelx.go
T
Blizzard a2d184b7ec feat(observability): OpenTelemetry 全链路追踪(Jaeger + NATS 跨总线传播)
新增 sundynix-shared/otelx:otelx.Init(ctx,服务名) 注册 W3C 传播器 + OTLP/HTTP
批量导出(默认 localhost:4318,docker 里的 Jaeger);OTEL_SDK_DISABLED=true 关导出。
Jaeger 不在线 / 导出器构建失败都不阻断启动(可观测性是增益而非依赖)。

- NATS 跨进程传播(无现成中间件):bus/trace.go 的 natsHeaderCarrier + inject/extract,
  PublishTask/CallTool 注入 traceparent,ConsumeTasks/ServeTool 抽出续上 → 链路跨总线连成一棵树。
- 埋点:gateway 挂 otelgin(HTTP server span,链路根);dispatcher task.execute →
  node.<kind>(每节点,nctx 下传使工具/LLM 挂到节点下)→ llm.stream/llm.generate;
  bus 自动出 tool.call(client)↔tool.serve(server) 成对跨服务 span。
- docker-compose 加 jaeger all-in-one(UI :16686,OTLP :4318)。
- 依赖修复:otlptracehttp 触发 genproto 单体(旧)vs 拆分模块 ambiguous import(milvus 拉旧版),
  pin genproto 至后拆分版(go.work 工作区全局生效)。
- production_readiness.md 1.1 更新为「已实现」。

验证:真实 input→retriever→agent 任务在 Jaeger 出 14 span / 3 服务的完整树,
跨 NATS(publish→consume)、跨服务(tool.call→tool.serve)均连通,
瓶颈 kb_search 692ms、llm 1597ms 一眼可见;四模块 build+vet+test 全绿。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 11:35:08 +08:00

77 lines
3.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package otelx 是 OpenTelemetry 链路追踪的统一启动器与门面。
//
// 各服务 main 启动时调一次 Init(ctx, "服务名"),即:
// - 注册全局 W3C TraceContext 传播器(即便禁用导出,trace_id 仍能跨服务串起来);
// - 把 span 经 OTLP/HTTP 批量导出到 OTEL_EXPORTER_OTLP_ENDPOINT(默认 localhost:4318,即 docker 里的 Jaeger)。
//
// 业务代码用 otelx.Tracer().Start(ctx, "span 名") 开 span;跨 NATS 的传播由 bus 层自动完成。
// 设 OTEL_SDK_DISABLED=true 可整体关闭导出(仍保留传播器,零依赖运行)。
package otelx
import (
"context"
"log"
"os"
"strings"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
"go.opentelemetry.io/otel/trace"
)
// scopeName 是本项目所有 span 的 instrumentation scopeJaeger 里按此归组)。
const scopeName = "github.com/sundynix/sundynix-agentix"
const defaultEndpoint = "localhost:4318" // Jaeger all-in-one 的 OTLP/HTTP 入口
// Init 初始化全局链路追踪。返回 shutdown,进程退出前调用以 flush 残留 span。
// 导出器构建失败 / Jaeger 不在线都不阻断启动(批处理异步丢弃),保证可观测性是「增益」而非「依赖」。
func Init(ctx context.Context, serviceName string) (func(context.Context) error, error) {
// 传播器始终注册:哪怕本服务不导出,也要把上游 traceparent 透传给下游。
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{}, propagation.Baggage{},
))
noop := func(context.Context) error { return nil }
if strings.EqualFold(os.Getenv("OTEL_SDK_DISABLED"), "true") {
log.Printf("[otel] 导出已禁用 (OTEL_SDK_DISABLED=true)service=%s", serviceName)
return noop, nil
}
endpoint := os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT")
if endpoint == "" {
endpoint = defaultEndpoint
}
// 容忍带 scheme 的写法(http://host:port),otlptracehttp.WithEndpoint 只要 host:port。
endpoint = strings.TrimPrefix(strings.TrimPrefix(endpoint, "http://"), "https://")
endpoint = strings.TrimSuffix(endpoint, "/")
exp, err := otlptracehttp.New(ctx,
otlptracehttp.WithEndpoint(endpoint),
otlptracehttp.WithInsecure(), // 本地 Jaeger 无 TLS
)
if err != nil {
log.Printf("[otel] 导出器构建失败(降级为不导出): %v", err)
return noop, nil
}
res, _ := resource.Merge(resource.Default(), resource.NewWithAttributes(
semconv.SchemaURL, semconv.ServiceName(serviceName),
))
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exp),
sdktrace.WithResource(res),
)
otel.SetTracerProvider(tp)
log.Printf("[otel] 链路追踪已启用 service=%s → otlp http://%s", serviceName, endpoint)
return tp.Shutdown, nil
}
// Tracer 返回统一 scope 的 tracer,供业务开 span。
func Tracer() trace.Tracer { return otel.Tracer(scopeName) }