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.

  1. Add the following imports
  1. xray "contrib.go.opencensus.io/exporter/aws"
  2. "go.opencensus.io/plugin/ocgrpc"
  3. "go.opencensus.io/plugin/ochttp"
  4. "go.opencensus.io/trace"
  1. Register the AWS X-ray exporter for the GRPC server
  1. xrayExporter, err := xray.NewExporter(
  2. xray.WithVersion("latest"),
  3. // Add your AWS region.
  4. xray.WithRegion("ap-southeast-1"),
  5. )
  6. if err != nil {
  7. // Handle any error.
  8. }
  9. // Do not forget to call Flush() before the application terminates.
  10. defer xrayExporter.Flush()
  11. // Register the trace exporter.
  12. trace.RegisterExporter(xrayExporter)
  1. Add a global tracing configuration
  1. // Always trace in this example.
  2. // In production this can be set to a trace.ProbabilitySampler.
  3. trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()})
  1. Add ocgrpc.ClientHandler for tracing the gRPC client calls
  1. // Example using DialContext
  2. conn, err := grpc.DialContext(
  3. // Other options goes here.
  4. // Add ocgrpc.ClientHandler for tracing the grpc client calls.
  5. grpc.WithStatsHandler(&ocgrpc.ClientHandler{}),
  6. )
  1. Wrap the gateway mux with the OpenCensus HTTP handler
  1. gwmux := runtime.NewServeMux()
  2. openCensusHandler := &ochttp.Handler{
  3. Handler: gwmux,
  4. }
  5. gwServer := &http.Server{
  6. Addr: "0.0.0.0:10000",
  7. Handler: openCensusHandler,
  8. }),
  9. }

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.

  1. Follow step 1, 2 and 4 from the previous section.

  2. Since we are not using a global configuration we can decide what paths we want to trace.

  1. gwmux := runtime.NewServeMux()
  2. openCensusHandler := &ochttp.Handler{
  3. Handler: gwmux,
  4. GetStartOptions: func(r *http.Request) trace.StartOptions {
  5. startOptions := trace.StartOptions{}
  6. if strings.HasPrefix(r.URL.Path, "/api") {
  7. // This example will always trace anything starting with /api.
  8. startOptions.Sampler = trace.AlwaysSample()
  9. }
  10. return startOptions
  11. },
  12. }
  1. No global configuration means we have to use the per span sampler.

A method we want to trace

  1. func (s *service) Name(ctx context.Context, req *pb.Request) (*pb.Response, error) {
  2. // Here we add the span ourselves.
  3. ctx, span := trace.StartSpan(ctx, "name.to.use.in.trace", trace.
  4. // Select a sampler that fits your implementation.
  5. WithSampler(trace.AlwaysSample()))
  6. defer span.End()
  7. /// Other stuff goes here.
  8. }

A method we do not wish to trace

  1. func (s *service) Check(ctx context.Context, in *health.HealthCheckRequest) (*health.HealthCheckResponse, error) {
  2. // Note no span here.
  3. return &health.HealthCheckResponse{Status: health.HealthCheckResponse_SERVING}, nil
  4. }

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.

  1. import (
  2. "github.com/opentracing/opentracing-go"
  3. "github.com/opentracing/opentracing-go/ext"
  4. )
  5. var grpcGatewayTag = opentracing.Tag{Key: string(ext.Component), Value: "grpc-gateway"}
  6. func tracingWrapper(h http.Handler) http.Handler {
  7. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  8. parentSpanContext, err := opentracing.GlobalTracer().Extract(
  9. opentracing.HTTPHeaders,
  10. opentracing.HTTPHeadersCarrier(r.Header))
  11. if err == nil || err == opentracing.ErrSpanContextNotFound {
  12. serverSpan := opentracing.GlobalTracer().StartSpan(
  13. "ServeHTTP",
  14. // this is magical, it attaches the new span to the parent parentSpanContext, and creates an unparented one if empty.
  15. ext.RPCServerOption(parentSpanContext),
  16. grpcGatewayTag,
  17. )
  18. r = r.WithContext(opentracing.ContextWithSpan(r.Context(), serverSpan))
  19. defer serverSpan.Finish()
  20. }
  21. h.ServeHTTP(w, r)
  22. })
  23. }
  24. // Then just wrap the mux returned by runtime.NewServeMux() like this
  25. if err := http.ListenAndServe(":8080", tracingWrapper(mux)); err != nil {
  26. log.Fatalf("failed to start gateway server on 8080: %v", err)
  27. }

Finally, don’t forget to add a tracing interceptor when registering the services. E.g.

  1. import (
  2. "google.golang.org/grpc"
  3. "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing"
  4. )
  5. opts := []grpc.DialOption{
  6. grpc.WithUnaryInterceptor(
  7. grpc_opentracing.UnaryClientInterceptor(
  8. grpc_opentracing.WithTracer(opentracing.GlobalTracer()),
  9. ),
  10. ),
  11. }
  12. if err := pb.RegisterMyServiceHandlerFromEndpoint(ctx, mux, serviceEndpoint, opts); err != nil {
  13. log.Fatalf("could not register HTTP service: %v", err)
  14. }