直接用opentelemetry sdk直连signoz后端最稳定可控;golang无字节码agent,避免java agent套用导致上下文丢失;sidecar模式易因配置不当引发oom、trace中断或本地调试失败;须显式初始化采样器、资源(含service.name)和otlp exporter,并严格按顺序设置propagator与tracer provider,确保trace/metrics/logs通过context关联对齐。

直接用 OpenTelemetry SDK + Signoz 后端是当前最稳定、可控的方式;别依赖 Java Agent 或自动注入,Golang 没有等效的 bytecode agent 机制,硬套会丢 trace 上下文或漏指标。
为什么不用 otel-collector sidecar 模式
Sidecar 模式在 Kubernetes 中看似标准,但对 Golang 微服务实际带来三个隐性问题:
-
otel-collector默认配置不启用batch和memory_limiter,小流量时 trace 数据发不出去,大流量时直接 OOM - Golang 的
context.Context跨 goroutine 传递 trace ID 需显式处理,sidecar 不参与 HTTP handler 链路,traceparentheader 容易中断 - 本地开发调试时,
localhost:4317连不上 sidecar(Docker 网络隔离),而host.docker.internal在 Linux 上不默认支持
推荐直接在应用进程内初始化 OTLP exporter,直连 Signoz 的 otlp-gateway(通常是 http://signoz-otel-collector:4317)。
初始化 OpenTelemetry SDK 的关键参数
Golang 的 go.opentelemetry.io/otel/sdk 初始化必须显式控制采样、资源、exporter 三部分,缺一不可:
- 采样器设为
oteltrace.AlwaysSample()或基于 QPS 的oteltrace.ParentBased(oteltrace.TraceIDRatioBased(0.1));默认的NeverSample会导致所有 trace 丢失 - 资源必须包含
service.name,否则 Signoz 前端无法归类服务:用resource.NewWithAttributes(semconv.SchemaURL, semconv.ServiceNameKey.String("my-order-service")) - OTLP exporter 的
WithEndpoint()必须指向 Signoz 的 collector 地址,且协议要明确写http://(不是https://,除非你配了 TLS);endpoint 错误时otel.Exporter不报错,只静默丢数据
示例片段:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
exp, err := otlphttp.NewClient(otlphttp.WithEndpoint("signoz-otel-collector:4317"), otlphttp.WithInsecure())
if err != nil {
log.Fatal(err)
}
HTTP handler 必须手动注入 trace context
Golang 标准库 net/http 不自动传播 traceparent,必须在每个 handler 入口解析并注入 context:
- 别只靠
otelhttp.NewHandler包一层——它只处理入站请求,出站调用(如调其他微服务)仍需手动传 context - 用
otel.GetTextMapPropagator().Extract(r.Context(), propagation.HeaderCarrier(r.Header))显式提取 trace context - 下游 HTTP client 调用时,必须用
otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header))注入 header - 漏掉任意一环,trace 就断成两段,Signoz 里看到的就是孤立的 span,不是完整链路
常见错误:req, _ := http.NewRequestWithContext(r.Context(), ...) 这行代码本身不传播 trace,只是把父 context 带过去,没 inject header。
metrics 和 logs 怎么和 trace 对齐
Signoz 要求 metrics 和 logs 带上和 trace 相同的 trace_id 和 span_id 才能联动查询,但 prometheus/client_golang 默认不关联 OTel context:
- 不要用
promauto.NewCounter直接打指标——它生成的 metric label 里没有 trace 上下文 - 改用
otelmetric.MustNewMeterProvider(otelmetric.WithResource(res)).Meter("myapp")获取 meter,再创建 counter;这样所有 metric 都自动绑定当前 active span 的 trace_id - log 库(如
zap)需注册otelzap.AddTraceID()字段,否则日志里看不到 trace_id - 如果用了
log/slog,必须用otellog.NewLogger()替代slog.Default(),否则日志不带 span context
没对齐的结果:你在 Signoz UI 点开某个 trace,右上角 “Related Logs” 或 “Related Metrics” 是空的——不是功能没开,是数据根本没带上关联字段。
最易被忽略的是资源(Resource)定义和 propagator 初始化顺序:必须先 otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.Baggage{}, propagation.TraceContext{})),再初始化 tracer provider,否则所有 span 的 traceparent 都是空字符串。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










