a2d184b7ec
新增 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>
77 lines
3.0 KiB
Go
77 lines
3.0 KiB
Go
// 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 scope(Jaeger 里按此归组)。
|
||
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) }
|