产品目录服务
此服务负责返回有关产品的信息。该服务可用于获取所有产品、搜索特定产品或返回单个产品的详细信息。
追踪
初始化追踪
OpenTelemetry SDK 在 `main` 中通过 `initTracerProvider` 函数进行初始化。
func initTracerProvider() *sdktrace.TracerProvider {
ctx := context.Background()
exporter, err := otlptracegrpc.New(ctx)
if err != nil {
log.Fatalf("OTLP Trace gRPC Creation: %v", 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.Fatalf("Tracer Provider Shutdown: %v", err)
}
}()
添加 gRPC 自动仪表化
此服务接收 gRPC 请求,这些请求在 main 函数中作为 gRPC 服务器创建的一部分进行仪表化。
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()),
)
}
为自动仪表化的 Span 添加属性
在自动仪表化代码的执行过程中,您可以从上下文中获取当前的 Span。
span := trace.SpanFromContext(ctx)
通过 Span 对象上的 `SetAttributes` 来添加 Span 属性。在 `GetProduct` 函数中,为产品 ID 添加了一个属性到 Span 中。
span.SetAttributes(
attribute.String("app.product.id", req.Id),
)
设置 Span 状态
此服务可以根据功能标志捕获和处理错误情况。在错误情况下,Span 状态会使用 Span 对象上的 `SetStatus` 进行相应设置。您可以在 `GetProduct` 函数中看到这一点。
msg := fmt.Sprintf("Error: ProductCatalogService Fail Feature Flag Enabled")
span.SetStatus(otelcodes.Error, msg)
添加跨度事件
通过 Span 对象上的 `AddEvent` 来添加 Span 事件。在 `GetProduct` 函数中,当处理错误情况或成功找到产品时,会添加一个 Span 事件。
span.AddEvent(msg)
指标
初始化指标
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
}
在服务关闭时,您应该调用 `initMeterProvider.Shutdown()` 以确保所有记录都被导出。此服务在 main 函数的延迟函数中调用了该函数。
mp := initMeterProvider()
defer func() {
if err := mp.Shutdown(context.Background()); err != nil {
log.Fatalf("Error shutting down meter provider: %v", err)
}
}()
添加 Go 运行时自动仪表化
Go 运行时在 main 函数中进行了仪表化。
err := runtime.Start(runtime.WithMinimumReadMemStatsInterval(time.Second))
if err != nil {
log.Fatal(err)
}
日志
您可以通过两种方式将日志发送到 OpenTelemetry Collector:
- 直接发送到 Collector
- 通过文件或 `stdout`
您可以在 日志 部分的 手动仪表化 文档中找到有关如何使用这两种方法的文档。
产品目录服务直接将日志发送到 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
logger *slog.Logger
logger = otelslog.NewLogger("product-catalog")
请注意使用 `fmt.Sprintf` 在发送到 logger 之前格式化输出。
logger.Info("Loading Product Catalog...")
logger.Info(fmt.Sprintf("Product Catalog reload interval: %d", interval))
logger.Error(fmt.Sprintf("Error shutting down meter provider: %v", err))
使用 `slog` 的优点是可以将其他属性附加到输出中。以下示例附加了 `product.name` 和 `product.id` 属性。这使得可以在日志输出中查看和解析这些属性,并在 Grafana 中更轻松地将它们显示为单独的列。
logger.LogAttrs(
ctx,
slog.LevelInfo, "Product Found",
slog.String("app.product.name", found.Name),
slog.String("app.product.id", req.Id),
)