Mit*_*t94 5 go grpc open-telemetry
我刚刚开始使用 OpenTelemetry 并为此创建了两个(微)服务:Standard和GeoMap。
最终用户向标准服务发送请求,标准服务又向GeoMap发送请求以获取信息,然后将结果返回给最终用户。我使用 gRPC 进行所有通信。
我已经这样设置了我的功能:
对于标准:
type standardService struct {
pb.UnimplementedStandardServiceServer
}
func (s *standardService) GetStandard(ctx context.Context, in *pb.GetStandardRequest) (*pb.GetStandardResponse, error) {
conn, _:= createClient(ctx, geomapSvcAddr)
defer conn1.Close()
newCtx, span1 := otel.Tracer(name).Start(ctx, "GetStandard")
defer span1.End()
countryInfo, err := pb.NewGeoMapServiceClient(conn).GetCountry(newCtx,
&pb.GetCountryRequest{
Name: in.Name,
})
//...
return &pb.GetStandardResponse{
Standard: standard,
}, nil
}
func createClient(ctx context.Context, svcAddr string) (*grpc.ClientConn, error) {
return grpc.DialContext(ctx, svcAddr,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()),
)
}
Run Code Online (Sandbox Code Playgroud)
对于地理地图:
type geomapService struct {
pb.UnimplementedGeoMapServiceServer
}
func (s *geomapService) GetCountry(ctx context.Context, in *pb.GetCountryRequest) (*pb.GetCountryResponse, error) {
_, span := otel.Tracer(name).Start(ctx, "GetCountry")
defer span.End()
span.SetAttributes(attribute.String("country", in.Name))
span.AddEvent("Retrieving country info")
//...
span.AddEvent("Country info retrieved")
return &pb.GetCountryResponse{
Country: &country,
}, nil
}
Run Code Online (Sandbox Code Playgroud)
这两个服务都配置为将其跨度发送到 Jaeger 后端并共享几乎相同的主要功能(注释中指出了细微的差异):
const (
name = "mapedia"
service = "geomap" //or standard
environment = "production"
id = 1
)
func tracerProvider(url string) (*tracesdk.TracerProvider, error) {
// Create the Jaeger exporter
exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url)))
if err != nil {
return nil, err
}
tp := tracesdk.NewTracerProvider(
// Always be sure to batch in production.
tracesdk.WithBatcher(exp),
// Record information about this application in a Resource.
tracesdk.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceName(service),
attribute.String("environment", environment),
attribute.Int64("ID", id),
)),
)
return tp, nil
}
func main() {
tp, err := tracerProvider("http://localhost:14268/api/traces")
if err != nil {
log.Fatal(err)
}
defer func() {
if err := tp.Shutdown(context.Background()); err != nil {
log.Fatal(err)
}
}()
otel.SetTracerProvider(tp)
listener, err := net.Listen("tcp", ":"+port)
if err != nil {
panic(err)
}
s := grpc.NewServer(
grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor()),
)
reflection.Register(s)
pb.RegisterGeoMapServiceServer(s, &geomapService{}) // or pb.RegisterStandardServiceServer(s, &standardService{})
if err := s.Serve(listener); err != nil {
log.Fatalf("Failed to serve: %v", err)
}
}
Run Code Online (Sandbox Code Playgroud)
当我查看最终用户对标准服务的请求生成的跟踪时,我可以看到它正如预期的那样调用其GeoMap服务:
但是,我没有看到已添加到子范围的任何属性或事件(在检测GeoMap的GetCountry函数时添加了一个属性和 2 个事件)。
然而,我注意到这些属性在另一个单独的跟踪中可用(在 Jaeger 中的“geomap”服务下可用),其跨度 ID 与标准服务中的子跨度完全无关:
现在我期望的是有一个跟踪,并查看标准范围内的子范围中与GeoMap相关的所有属性/事件。从这里如何达到预期的结果?
跨度上下文(包含跟踪 ID 和跨度 ID,如“服务规范和术语”中所述)应从父跨度传播到子跨度,以便它们成为同一跟踪的一部分。
使用 OpenTelemetry,这通常是通过使用为各种库(包括 gRPC)提供的插件来检测代码来自动完成的。
但是,在您的情况下,传播似乎无法正常工作。
在您的代码中,您将在函数中启动一个新的范围GetStandard,然后newCtx在发出请求时使用该上下文 ( ) GetCountry。这是正确的,因为新上下文应该包含父跨度 ( ) 的跨度上下文GetStandard。
但问题可能与您的createClient功能有关:
func createClient(ctx context.Context, svcAddr string) (*grpc.ClientConn, error) {
return grpc.DialContext(ctx, svcAddr,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()),
)
}
Run Code Online (Sandbox Code Playgroud)
您正确使用了otelgrpc.UnaryClientInterceptor此处,这应该确保上下文正确传播,但尚不清楚何时调用此函数。如果在调用函数之前调用它GetStandard,则用于创建客户端的上下文将不包括来自 的跨度上下文GetStandard。
为了进行测试,请尝试确保在调用函数后创建客户端,并且在整个请求中使用相同的上下文。GetStandard
您可以通过将 直接传递newCtx给GetCountry函数来完成此操作,如函数的修改版本所示GetStandard:
func (s *standardService) GetStandard(ctx context.Context, in *pb.GetStandardRequest) (*pb.GetStandardResponse, error) {
newCtx, span1 := otel.Tracer(name).Start(ctx, "GetStandard")
defer span1.End()
conn, _:= createClient(newCtx, geomapSvcAddr)
defer conn.Close()
countryInfo, err := pb.NewGeoMapServiceClient(conn).GetCountry(newCtx,
&pb.GetCountryRequest{
Name: in.Name,
})
//...
return &pb.GetStandardResponse{
Standard: standard,
}, nil
}
Run Code Online (Sandbox Code Playgroud)
现在,用于创建客户端和发出GetCountry请求的上下文将包括来自 的跨度上下文GetStandard,并且它们应该作为 Jaeger 中同一跟踪的一部分出现。
createClient(与往常一样,请检查和等函数返回的错误GetCountry,为简洁起见,此处未显示)。
此外:
另请检查您的传播器:确保在两个服务中使用相同的上下文传播器,最好是W3C TraceContextPropagator,这是 OpenTelemetry 中的默认传播器。
您可以按如下方式显式设置传播器:
otel.SetTextMapPropagator(propagation.TraceContext{})
Run Code Online (Sandbox Code Playgroud)
将以上行添加到两个服务中函数的开头main。
确保元数据正在传递:gRPC 拦截器应自动从请求的元数据中注入/提取跟踪上下文,但要仔细检查以确保其正常工作。
在函数中启动跨度后GetCountry,您可以记录跟踪 ID 和跨度 ID:
ctx, span := otel.Tracer(name).Start(ctx, "GetCountry")
sc := trace.SpanContextFromContext(ctx)
log.Printf("Trace ID: %s, Span ID: %s", sc.TraceID(), sc.SpanID())
defer span.End()
Run Code Online (Sandbox Code Playgroud)
并在您的函数中执行相同的操作GetStandard:
newCtx, span1 := otel.Tracer(name).Start(ctx, "GetStandard")
sc := trace.SpanContextFromContext(newCtx)
log.Printf("Trace ID: %s, Span ID: %s", sc.TraceID(), sc.SpanID())
defer span1.End()
Run Code Online (Sandbox Code Playgroud)
如果上下文正确传播,两个服务中的跟踪 ID 应该匹配。