结账服务
此服务负责处理用户的结账订单。结账服务在处理订单时会调用许多其他服务。
追踪
初始化追踪
OpenTelemetry SDK 在 main 中通过 initTracerProvider 函数进行初始化。
func initTracerProvider() *sdktrace.TracerProvider {
ctx := context.Background()
exporter, err := otlptracegrpc.New(ctx)
if err != nil {
log.Fatal(err)
}
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
sdktrace.WithResource(initResource()),
)
otel.SetTracerProvider(tp)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
return tp
}
在服务关闭时,您应该调用 TracerProvider.Shutdown() 以确保所有 Span 都已导出。此服务在 main 函数的延迟函数中执行此调用。
tp := initTracerProvider()
defer func() {
if err := tp.Shutdown(context.Background()); err != nil {
log.Printf("Error shutting down tracer provider: %v", err)
}
}()
添加 gRPC 自动检测
此服务接收 gRPC 请求,这些请求在 gRPC 服务器创建过程中在 main 函数中进行了检测。
var srv = grpc.NewServer(
grpc.StatsHandler(otelgrpc.NewServerHandler()),
)
此服务将发出几个出站 gRPC 调用,所有这些调用都通过包装 gRPC 客户端的检测来完成。
func createClient(ctx context.Context, svcAddr string) (*grpc.ClientConn, error) {
return grpc.DialContext(ctx, svcAddr,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithStatsHandler(otelgrpc.NewClientHandler()),
)
}
添加 Kafka (Sarama) 自动检测
此服务会将处理后的结果写入 Kafka 主题,然后由其他微服务进行处理。要检测 Kafka 客户端,必须在创建 Producer 后对其进行包装。
saramaConfig := sarama.NewConfig()
producer, err := sarama.NewAsyncProducer(brokers, saramaConfig)
if err != nil {
return nil, err
}
producer = otelsarama.WrapAsyncProducer(saramaConfig, producer)
为自动仪表化的 Span 添加属性
在自动仪表化代码的执行过程中,您可以从上下文中获取当前的 Span。
span := trace.SpanFromContext(ctx)
通过使用 Span 对象上的 SetAttributes 来添加属性到 Span。在 PlaceOrder 函数中,向 Span 添加了几个属性。
span.SetAttributes(
attribute.String("app.order.id", orderID.String()), shippingTrackingAttribute,
attribute.Float64("app.shipping.amount", shippingCostFloat),
attribute.Float64("app.order.amount", totalPriceFloat),
attribute.Int("app.order.items.count", len(prep.orderItems)),
)
添加跨度事件
通过使用 Span 对象上的 AddEvent 来添加 Span 事件。在 PlaceOrder 函数中,添加了几个 Span 事件。有些事件带有附加属性,有些则不带。
添加不带属性的 Span 事件
span.AddEvent("prepared")
添加带附加属性的 Span 事件
span.AddEvent("charged",
trace.WithAttributes(attribute.String("app.payment.transaction.id", txID)))
指标
初始化指标
OpenTelemetry SDK 在 main 中通过 initMeterProvider 函数进行初始化。
func initMeterProvider() *sdkmetric.MeterProvider {
ctx := context.Background()
exporter, err := otlpmetricgrpc.New(ctx)
if err != nil {
log.Fatalf("new otlp metric grpc exporter failed: %v", err)
}
mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(sdkmetric.NewPeriodicReader(exporter)))
global.SetMeterProvider(mp)
return mp
}
在服务关闭时,您应该调用 MeterProvider.Shutdown() 以确保所有记录都已导出。此服务在 main 函数的延迟函数中执行此调用。
mp := initMeterProvider()
defer func() {
if err := mp.Shutdown(context.Background()); err != nil {
log.Printf("Error shutting down meter provider: %v", err)
}
}()
添加 golang 运行时自动检测
Golang 运行时在 main 函数中进行检测。
err := runtime.Start(runtime.WithMinimumReadMemStatsInterval(time.Second))
if err != nil {
log.Fatal(err)
}
日志
您可以通过两种方式将日志发送到 OpenTelemetry Collector
- 直接发送到 Collector
- 通过文件或
stdout
您可以在 Manual Instrumentation 文档的 Logs 部分找到有关如何使用这两种方法的文档。
Checkout 服务将日志直接发送到 Collector,并使用日志桥接器将日志发送到 slog 日志包,该包输出结构化日志。
LoggerProvider 初始化
OpenTelemetry SDK 在 main 中通过 initLoggerProvider 函数进行初始化。
ctx := context.Background()
logExporter, err := otlploggrpc.New(ctx)
if err != nil {
return nil
}
loggerProvider := sdklog.NewLoggerProvider(
sdklog.WithProcessor(sdklog.NewBatchProcessor(logExporter)),
)
global.SetLoggerProvider(loggerProvider)
return loggerProvider
在服务关闭时调用 LoggerProvider.Shutdown() 以确保所有日志都已导出。此服务在 main 函数的延迟函数中执行此调用。
lp := initLoggerProvider()
defer func() {
if err := lp.Shutdown(context.Background()); err != nil {
logger.Error(fmt.Sprintf("Logger Provider Shutdown: %v", err))
}
logger.Info("Shutdown logger provider")
}()
日志记录功能
此服务使用 gRPC 调用将日志发送到 Collector。日志使用 slog 包以结构化格式输出。
首先,初始化日志记录器
logger *slog.Logger
logger = otelslog.NewLogger("checkout")
注意使用 fmt.Sprintf 在将输出发送到日志记录器之前对其进行格式化。
logger.Info(fmt.Sprintf("order confirmation email sent to %q", req.Email))
logger.Warn(fmt.Sprintf("failed to send order confirmation to %q: %+v", req.Email, err))
logger.Error(fmt.Sprintf("Error shutting down logger provider: %v", err))
使用 slog 的优点是可以将其他属性附加到输出。下面的示例附加了几个属性,如 orderID、shippingCost 和 totalPrice。这使得可以将它们作为日志输出的一部分进行查看和解析,并且更容易在 Grafana 中将它们作为单独的列进行查看。
logger.LogAttrs(
ctx,
slog.LevelInfo, "order placed",
slog.String("app.order.id", orderID.String()),
slog.Float64("app.shipping.amount", shippingCostFloat),
slog.Float64("app.order.amount", totalPriceFloat),
slog.Int("app.order.items.count", len(prep.orderItems)),
slog.String("app.shipping.tracking.id", shippingTrackingID),
)