docs/howto/use-w3c-tracecontext
Mark Fussell 64f2a7e1cf Updates to W3C tracing docs 2020-06-08 22:42:02 -07:00
..
README.md Updates to W3C tracing docs 2020-06-08 22:42:02 -07:00

README.md

Trace calls across services

Contents

Using trace context in Dapr

Dapr tracing is built on OpenCensus specification that supports official W3C HTTP tracing header.

For the gRPC tracing with OpenCensus, the details are here

Dapr supports OpenCensus instrumentation of services when tracing configuration is enabled through a Dapr annotation. Once the tracing configuration is applied, you get instrumentation of traces and you can retrieve the trace correlation id from the standard W3C context headers.

You can also choose to pass the trace context explicitly, then Dapr uses the supplied trace context and propagates this all across the HTTP/gRPC call.

How to pass a trace context

Since Dapr tracing uses OpenCensus, you set the trace context using OpenCensus SDK. OpenCensus supports several different programming languages.

Language SDK
Go Link
Java Link
C# Link
C++ Link
Node.js Link
Python Link

Let's go through an example using the grpc app using the Go SDK.

1. Get the OpenCensus Go SDK

Prerequisites: OpenCensus Go libraries require Go 1.8 or later. For details on installation go here.

2. Import the package "go.opencensus.io/trace"

$ go get -u go.opencensus.io

3. Create trace context

When you want to pass the trace context, you are starting the trace spans. Since a distributed trace tracks the progression of a single user request as it is handled by the services and processes that make up an application, each step is called a span in the trace. Spans include metadata about the step, including the time spent in the step, called the spans latency.

Span is the unit step in a trace. Each span has a name, latency, status and additional metadata.

The code belows shows starting a span for a cache read and ending it when done:

ctx, span := trace.StartSpan(ctx, "cache.Get")
defer span.End()

// Do work to get from cache.

The StartSpan call returns two values. If you want to propagate trace context within your calls in the same process, you can use context.Context to propagate spans. The returned span has the fields 'TraceID' and 'SpanID'. You can read more on these fields usage here

When you call the Dapr API through HTTP/gRPC, you need to pass the trace context across processes or services. Across the network, OpenCensus provides different propagation methods for different protocols. You can read about the propagation package here.

In our example, since these are gRPC calls, the OpenCensus binary propagation format is used.

4. Passing the trace context to Dapr

For gRPC calls

First import the package 'go.opencensus.io/trace/propagation'. Once imported, get the span context from the generated span (as mentioned in above step 3), and convert it to OpenCensus binary propagation format.

traceContext := span.SpanContext()
traceContextBinary := propagation.Binary(traceContext)

You can then pass the trace context through gRPC metadata through grpc-trace-bin header.

ctx = metadata.AppendToOutgoingContext(ctx, "grpc-trace-bin", string(traceContextBinary))

You can then pass this context ctx in Dapr gRPC calls as first parameter. For example InvokeService, context is passed in first parameter.

To retrieve the trace context header when the gRPC call is returned, you can pass the response header reference as gRPC call option which contains response headers:

var responseHeader metadata.MD

// Call the InvokeService with call option
// grpc.Header(&responseHeader)

client.InvokeService(ctx, &pb.InvokeServiceRequest{
		Id: "client",
		Message: &commonv1pb.InvokeRequest{
			Method:      "MyMethod",
			ContentType: "text/plain; charset=UTF-8",
			Data:        &any.Any{Value: []byte("Hello")},
		},
	},
	grpc.Header(&responseHeader))
HTTP calls

HTTP integration uses Zipkins B3 by default, but can be configured to use a custom propagation method by setting another propagation.HTTPFormat.

For control over HTTP client headers, redirect policy, and other settings, you create a client. In this example, net/http is used for HTTP calls.

f := &HTTPFormat{}
req, _ := http.NewRequest("GET", "http://localhost:3500/v1.0/invoke/mathService/method/api/v1/add", nil)

traceContext := span.SpanContext()
f.SpanContextToRequest(traceContext, req)

To retrieve the trace context when the HTTP request is returned, you can use :

sc, ok := f.SpanContextFromRequest(req)

See the Dapr API reference.

Configuring tracing in Dapr

To enable tracing in Dapr, you need to first configure tracing. Create adeployment config yaml e.g. appconfig.yaml with following configuration.

apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
  name: appconfig
spec:
  tracing:
    samplingRate: "1"

In Kubernetes, you can apply the configuration as below :

kubectl apply -f appconfig.yaml

You then set the following tracing annotation in your deployment YAML. You can add the following annotaion in sample grpc app deployment yaml.

dapr.io/config: "appconfig"

Viewing traces

To view traces, you need to deploy OpenCensus supported exporters. This is independent of Dapr configuration. Read how-to-diagnose-with-tracing to set up trace exporters.

Invoking Dapr with trace context

As mentioned earlier in the section, you can create the trace context and pass it when calling Dapr, or Dapr can generate trace context and pass it back to you.

If you choose to pass the trace context explicitly, then Dapr will use the passed trace context and propagate all across the HTTP/gRPC call.

Using the grpc app in the example and putting this all together, the following steps show you how to create a Dapr client and call the Save State operation passing the trace context:

The Rest code snippet and details, refer to the grpc app.

1. Import the package

package main

import (
    pb "github.com/dapr/go-sdk/dapr"
    "go.opencensus.io/trace"
	  "go.opencensus.io/trace/propagation"
	  "google.golang.org/grpc"
	  "google.golang.org/grpc/metadata"
)

2. Create the client

  // Get the Dapr port and create a connection
  daprPort := os.Getenv("DAPR_GRPC_PORT")
  daprAddress := fmt.Sprintf("localhost:%s", daprPort)
  conn, err := grpc.Dial(daprAddress, grpc.WithInsecure())
  if err != nil {
    fmt.Println(err)
  }
  defer conn.Close()

  // Create the client
  client := pb.NewDaprClient(conn)

3. Invoke the InvokeService method With Trace Context

  // Create the Trace Context
  ctx , span := trace.StartSpan(context.Background(), "InvokeService")

  // The returned context can be used to keep propagating the newly created span in the current context.
  // In the same process, context.Context is used to propagate trace context.

  // Across the process, use the propagation format of Trace Context to propagate trace context.
  traceContext := propagation.Binary(span.SpanContext())
  ctx = metadata.NewOutgoingContext(ctx, string(traceContext))

  // Pass the trace context
  resp, err := client.InvokeService(ctx, &pb.InvokeServiceRequest{
		Id: "client",
		Message: &commonv1pb.InvokeRequest{
			Method:      "MyMethod",
			ContentType: "text/plain; charset=UTF-8",
			Data:        &any.Any{Value: []byte("Hello")},
		},
	})

That's it !. Now you can correlate the calls in your app and across services with Dapr using the same trace context.

To view traces, you can refer how-to-diagnose-with-tracing