← 文章 / 未分类
signoz 2小时前 · 2026-09-17 00:16:19 · 2 阅读

如何在 Go 语言中添加手动埋点

自动埋点追踪的是应用调用的第三方库,手动埋点追踪的则是你自己的代码。可以用它来统计结账步骤的耗时、给 span 挂上订单 ID,或者在记录失败时附上说明问题的上下文。

使用自托管 SigNoz?

本页步骤对两种部署方式完全相同,区别只在于 OTLP endpoint 和 ingestion key,详见 Cloud to Self-Hosted

前提条件

  • 应用中必须已经运行 tracer provider 和 exporter。如果你使用 SDK,请先完成 OpenTelemetry Go instrumentation guide 中的核心配置。
  • 在 handler 和业务逻辑中传递 context.Context。手动创建的 span 会从这些 context 派生,从而保持 trace 连贯。
  • 已在 Go 1.25.1 和 OpenTelemetry Go SDK v1.38.0 上测试通过。
已在使用零代码埋点?

手动 span 可以与之共存。使用 compile-time instrumentation 时,注入的 SDK 会自动采集你在本页创建的 span;使用 eBPF agent 时,设置 OTEL_GO_AUTO_GLOBAL=true 即可让 agent 一并导出这些 span。

添加手动埋点

第 1 步:创建手动 span

使用 SDK tracer,把重要的业务逻辑包在自定义 span 里:

manual-span.go
import (
	"context"
 
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"
)
 
func processOrder(ctx context.Context, orderID string) error {
	tracer := otel.Tracer("order-service")
 
	ctx, span := tracer.Start(ctx, "process-order")
	defer span.End()
 
	span.SetAttributes(
		attribute.String("order.id", orderID),
		attribute.String("order.status", "processing"),
	)
 
	// Use ctx for downstream work so child spans stay linked.
	return nil}

几点建议:

  • 复用 tracer 实例,不要每个请求都新建一个。
  • 用与业务步骤对应的描述性名称启动 span(如 checkoutfetch-user 等)。
  • 确保每个 span 都被结束,defer span.End() 是最稳妥的做法。

步骤 2. 传播上下文

Go 不保存任何环境级别的追踪状态。Span 只能通过你传递给它的 context.Context 找到父级,因此 Go 比 Java 或 Python 更容易导致追踪链路断裂。请参见 Context Propagation 了解此步骤背后的概念。

进程内部

tracer.Start 返回的 ctx 传递给所有希望嵌套在该 span 下的函数:

in-process.go
func processOrder(ctx context.Context, orderID string) error {
	ctx, span := tracer.Start(ctx, "process-order")
	defer span.End()
 
	// process-order 的子级。
	validateOrder(ctx, orderID)
 
	// 全新追踪的根 span。到 process-order 的链接已丢失。
	validateOrder(context.Background(), orderID)
 
	return nil
}

如果 goroutine 的生命周期长于请求,你需要故意派生一个新的上下文。一旦 handler 返回,请求上下文就会被取消,因此继续使用该上下文的后台任务将基于一个已取消的上下文开始。context.WithoutCancel 保留上下文中的值(包括 span),同时移除取消信号和截止时间:

goroutine.go
// 保留追踪上下文,丢弃请求的取消信号。适用于 Go 1.21 及更高版本。
bgCtx := context.WithoutCancel(ctx)
 
go func() {
	_, span := tracer.Start(bgCtx, "settle-order")
	defer span.End()
	// 在此处执行后台任务。
}()

跨越服务边界

otelhttp 等插桩库会自动为你完成注入和提取。对于它们未覆盖的环节,请自行编写调用。

在两端设置 span 类型。SigNoz 读取 ClientServer 来构建服务地图和 APM 指标。

在出站时,将上下文注入到 outgoing 请求头中:

inject.go
import (
	"context"
	"io"
	"net/http"
 
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/propagation"
	"go.opentelemetry.io/otel/trace"
)
 
func chargeCard(ctx context.Context, body io.Reader) error {
	ctx, span := tracer.Start(ctx, "charge-card", trace.WithSpanKind(trace.SpanKindClient))
	defer span.End()
 
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://payments.internal/charge", body)
	if err != nil {
		return err
	}
 
	// 将 traceparent 头写入 req。
	otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header))
 
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
 
	// 排空响应体以便复用连接。
	_, err = io.Copy(io.Discard, resp.Body)
	return err
}

在创建入口 span 之前,先进行提取(extract):

extract.go
func handleCharge(w http.ResponseWriter, r *http.Request) {
	// 若存在 traceparent 头,返回携带调用方 span 的 context;否则原样返回 r.Context()
	ctx := otel.GetTextMapPropagator().Extract(r.Context(), propagation.HeaderCarrier(r.Header))
 
	ctx, span := tracer.Start(ctx, "handle-charge", trace.WithSpanKind(trace.SpanKindServer))
	defer span.End()
 
	// 此时 handle-charge 成为调用方 trace 中 charge-card 的子 span。
	capturePayment(ctx)
}

对于非 http.Header 的载体(例如队列消息元数据),请使用 propagation.MapCarrier

queue.go
// 发布者
carrier := propagation.MapCarrier{}
otel.GetTextMapPropagator().Inject(ctx, carrier)
msg.Metadata = carrier // map[string]string
 
// 消费者
ctx := otel.GetTextMapPropagator().Extract(context.Background(), propagation.MapCarrier(msg.Metadata))
Info

InjectExtract 只有在通过 otel.SetTextMapPropagator 注册了 propagator 之后才会生效。Go instrumentation 指南initTracer 中配置了 propagation.TraceContext{}propagation.Baggage{}

步骤 3. 添加属性和事件

属性在 SigNoz 中以键值对形式呈现,方便筛选和聚合 spans。事件则用于记录 span 内的关键时刻。

attributes.go
import (
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/trace"
)
 
func handlePayment(ctx context.Context, amount float64) {
	span := trace.SpanFromContext(ctx)
 
	span.SetAttributes(
		attribute.Float64("payment.amount", amount),
		attribute.String("payment.currency", "USD"),
		attribute.String("payment.method", "credit_card"),
	)
 
	span.AddEvent("payment.processed", trace.WithAttributes(
		attribute.String("status", "success"),
		attribute.String("transaction.id", "txn_123456"),
	))
}
  • 属性键保持一致(尽量遵循语义约定)。
  • 用事件标记重试、缓存命中/未命中、队列等待等关键节点。

第 4 步:记录错误

在 span 上标记失败,方便在 SigNoz 中查询。

error-handling.go
import (
	"go.opentelemetry.io/otel/codes"
	"go.opentelemetry.io/otel/trace"
)
 
func riskyOperation(ctx context.Context) error {
	span := trace.SpanFromContext(ctx)
 
	err := doSomethingRisky()
	if err != nil {
		span.RecordError(err)
		span.SetStatus(codes.Error, err.Error())
		return err
	}
 
	span.SetStatus(codes.Ok, "")
	return nil
}
  • RecordError 会附带堆栈和错误信息。
  • 将状态设为 codes.Error 后,span 会出现在 SigNoz 的错误视图和告警中。
  • 继续向上游传播错误,让调用方能够做出响应。

验证

  1. 触发会生成手动 span 的代码路径。
  2. 在 SigNoz 的 Traces 中按 service.name 或 span 名称过滤。
  3. 打开某条 trace,检查属性、事件和错误状态。
  4. 查看 Errors 视图,确认失败记录中包含已记录的异常。

故障排查

在 SigNoz 中仍然看不到数据?请参阅排查缺失的 traces、日志和指标,其中涵盖 SDK 诊断、Collector 连接问题以及三类信号常见的数据接入错误。

为什么在 SigNoz 中看不到自定义 span?

  • 确认 initTracer(以及自动埋点)在应用 handler 之前运行。
  • 检查采样器配置。在开发环境使用 TraceIDRatioBased(0.01) 可能会丢弃大部分手动 span。
  • 确认请求流量确实打到了你插入 span 的函数上。

为什么明明创建了子 span,但它们却缺失了?

  • 确保将 tracer.Start(或 trace.SpanFromContext)返回的 ctx 传递给下游函数。
  • HTTP/数据库客户端调用也必须接收传播的 context,否则链路追踪会在跨服务边界时断裂。

为什么下游服务总是自己开启新的 trace?

  • 检查调用方是否对出站请求执行了 Inject,并确认请求中携带了 traceparent。
  • 检查接收方是否执行了 Extract,并将返回的 context 传入 tracer.Start,而不是 r.Context()context.Background()
  • 确保两个服务注册了相同的 propagator 格式。参见 上下文传播

为什么 span 上看不到 attributes 或 events?

  • 属性键必须是字符串,且值必须使用正确的辅助方法(如 attribute.Stringattribute.Float64 等)。
  • span.End() 之前调用 span.AddEventspan.SetAttributes。span 结束后的修改会被忽略。
  • 避免复用已结束的 span。每次调用都应创建新的 span。

后续步骤

获取帮助

如需本文操作步骤方面的帮助,请通过SigNoz 社区 Slack联系我们。如果你是 SigNoz Cloud 用户,可使用 SigNoz 实例右下角的产品内聊天支持,或邮件联系cloud-support@signoz.io

原始来源: signoz

评论 (0)