Tracing
With OpenCensus.io and AWS X-ray
Adding tracing using AWS-Xray as the exporter
This example uses the AWS-Xray exporter with a global trace setting. Note that AWS X-ray exporter does not handle any metrics only tracing.
- Add the following imports
xray "contrib.go.opencensus.io/exporter/aws"
"go.opencensus.io/plugin/ocgrpc"
"go.opencensus.io/plugin/ochttp"
"go.opencensus.io/trace"
- Register the AWS X-ray exporter for the GRPC server
xrayExporter, err := xray.NewExporter(
xray.WithVersion("latest"),
// Add your AWS region.
xray.WithRegion("ap-southeast-1"),
)
if err != nil {
// Handle any error.
}
// Do not forget to call Flush() before the application terminates.
defer xrayExporter.Flush()
// Register the trace exporter.
trace.RegisterExporter(xrayExporter)
- Add a global tracing configuration
// Always trace in this example.
// In production this can be set to a trace.ProbabilitySampler.
trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()})
- Add
ocgrpc.ClientHandler
for tracing the gRPC client calls
conn, err := grpc.NewClient(
// Other options goes here.
// Add ocgrpc.ClientHandler for tracing the grpc client calls.
grpc.WithStatsHandler(&ocgrpc.ClientHandler{}),
)
- Wrap the gateway mux with the OpenCensus HTTP handler
gwmux := runtime.NewServeMux()
openCensusHandler := &ochttp.Handler{
Handler: gwmux,
}
gwServer := &http.Server{
Addr: "0.0.0.0:10000",
Handler: openCensusHandler,
}),
}
Without a global configuration
In this example we have added the gRPC Health Checking Protocol and we do not wish to trace any health checks.
Follow step
1
,2
and4
from the previous section.Since we are not using a global configuration we can decide what paths we want to trace.
gwmux := runtime.NewServeMux()
openCensusHandler := &ochttp.Handler{
Handler: gwmux,
GetStartOptions: func(r *http.Request) trace.StartOptions {
startOptions := trace.StartOptions{}
if strings.HasPrefix(r.URL.Path, "/api") {
// This example will always trace anything starting with /api.
startOptions.Sampler = trace.AlwaysSample()
}
return startOptions
},
}
- No global configuration means we have to use the per span sampler.
A method we want to trace
func (s *service) Name(ctx context.Context, req *pb.Request) (*pb.Response, error) {
// Here we add the span ourselves.
ctx, span := trace.StartSpan(ctx, "name.to.use.in.trace", trace.
// Select a sampler that fits your implementation.
WithSampler(trace.AlwaysSample()))
defer span.End()
/// Other stuff goes here.
}
A method we do not wish to trace
func (s *service) Check(ctx context.Context, in *health.HealthCheckRequest) (*health.HealthCheckResponse, error) {
// Note no span here.
return &health.HealthCheckResponse{Status: health.HealthCheckResponse_SERVING}, nil
}
OpenTracing Support
If your project uses OpenTracing and you’d like spans to propagate through the gateway, you can add some middleware which parses the incoming HTTP headers to create a new span correctly.
import (
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
)
var grpcGatewayTag = opentracing.Tag{Key: string(ext.Component), Value: "grpc-gateway"}
func tracingWrapper(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
parentSpanContext, err := opentracing.GlobalTracer().Extract(
opentracing.HTTPHeaders,
opentracing.HTTPHeadersCarrier(r.Header))
if err == nil || err == opentracing.ErrSpanContextNotFound {
serverSpan := opentracing.GlobalTracer().StartSpan(
"ServeHTTP",
// this is magical, it attaches the new span to the parent parentSpanContext, and creates an unparented one if empty.
ext.RPCServerOption(parentSpanContext),
grpcGatewayTag,
)
r = r.WithContext(opentracing.ContextWithSpan(r.Context(), serverSpan))
defer serverSpan.Finish()
}
h.ServeHTTP(w, r)
})
}
// Then just wrap the mux returned by runtime.NewServeMux() like this
if err := http.ListenAndServe(":8080", tracingWrapper(mux)); err != nil {
log.Fatalf("failed to start gateway server on 8080: %v", err)
}
Finally, don’t forget to add a tracing interceptor when registering the services. E.g.
import (
"google.golang.org/grpc"
"github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing"
)
opts := []grpc.DialOption{
grpc.WithUnaryInterceptor(
grpc_opentracing.UnaryClientInterceptor(
grpc_opentracing.WithTracer(opentracing.GlobalTracer()),
),
),
}
if err := pb.RegisterMyServiceHandlerFromEndpoint(ctx, mux, serviceEndpoint, opts); err != nil {
log.Fatalf("could not register HTTP service: %v", err)
}
OpenTelemetry
If your project uses OpenTelemetry and you would like spans to propagate through the gateway, you can refer to the OpenTelemetry gRPC-Gateway Boilerplate project. This repository provides a sample project that showcases the integration of OpenTelemetry with gRPC-Gateway to set up an OpenTelemetry-enabled gRPC-Gateway REST server. The project includes a simple SayHello
method implemented on the gRPC server that returns a greeting message to the client.