// ExtractInboundSpan is a higher level version of extractInboundSpan(). // If the lower-level attempt to create a span from incoming request was // successful (e.g. when then Tracer supports Zipkin-style trace IDs), // then the application headers are only used to read the Baggage and add // it to the existing span. Otherwise, the standard OpenTracing API supported // by all tracers is used to deserialize the tracing context from the // application headers and start a new server-side span. // Once the span is started, it is wrapped in a new Context, which is returned. func ExtractInboundSpan(ctx context.Context, call *InboundCall, headers map[string]string, tracer opentracing.Tracer) context.Context { var span = call.Response().span if span != nil { if headers != nil { // extract SpanContext from headers, but do not start another span with it, // just get the baggage and copy to the already created span carrier := tracingHeadersCarrier(headers) if sc, err := tracer.Extract(opentracing.TextMap, carrier); err == nil { sc.ForeachBaggageItem(func(k, v string) bool { span.SetBaggageItem(k, v) return true }) } carrier.RemoveTracingKeys() } } else { var parent opentracing.SpanContext if headers != nil { carrier := tracingHeadersCarrier(headers) if p, err := tracer.Extract(opentracing.TextMap, carrier); err == nil { parent = p } carrier.RemoveTracingKeys() } span = tracer.StartSpan(call.MethodString(), ext.RPCServerOption(parent)) ext.PeerService.Set(span, call.CallerName()) span.SetTag("as", string(call.Format())) call.conn.setPeerHostPort(span) call.Response().span = span } return opentracing.ContextWithSpan(ctx, span) }
func TestRPCServerOption(t *testing.T) { tracer := mocktracer.New() parent := tracer.StartSpan("my-trace") parent.SetBaggageItem("bag", "gage") carrier := opentracing.HTTPHeadersCarrier{} err := tracer.Inject(parent.Context(), opentracing.HTTPHeaders, carrier) if err != nil { t.Fatal(err) } parCtx, err := tracer.Extract(opentracing.HTTPHeaders, carrier) if err != nil { t.Fatal(err) } tracer.StartSpan("my-child", ext.RPCServerOption(parCtx)).Finish() rawSpan := tracer.FinishedSpans()[0] assert.Equal(t, map[string]interface{}{ "span.kind": ext.SpanKindRPCServerEnum, }, rawSpan.Tags()) assert.Equal(t, map[string]string{ "bag": "gage", }, rawSpan.Context().(mocktracer.MockSpanContext).Baggage) }
// FromGRPCRequest returns a grpc RequestFunc that tries to join with an // OpenTracing trace found in `req` and starts a new Span called // `operationName` accordingly. If no trace could be found in `req`, the Span // will be a trace root. The Span is incorporated in the returned Context and // can be retrieved with opentracing.SpanFromContext(ctx). func FromGRPCRequest(tracer opentracing.Tracer, operationName string, logger log.Logger) func(ctx context.Context, md *metadata.MD) context.Context { return func(ctx context.Context, md *metadata.MD) context.Context { var span opentracing.Span wireContext, err := tracer.Extract(opentracing.TextMap, metadataReaderWriter{md}) if err != nil && err != opentracing.ErrSpanContextNotFound { logger.Log("err", err) } span = tracer.StartSpan(operationName, ext.RPCServerOption(wireContext)) return opentracing.ContextWithSpan(ctx, span) } }
// FromHTTPRequest returns an http RequestFunc that tries to join with an // OpenTracing trace found in `req` and starts a new Span called // `operationName` accordingly. If no trace could be found in `req`, the Span // will be a trace root. The Span is incorporated in the returned Context and // can be retrieved with opentracing.SpanFromContext(ctx). func FromHTTPRequest(tracer opentracing.Tracer, operationName string, logger log.Logger) kithttp.RequestFunc { return func(ctx context.Context, req *http.Request) context.Context { // Try to join to a trace propagated in `req`. var span opentracing.Span wireContext, err := tracer.Extract( opentracing.TextMap, opentracing.HTTPHeadersCarrier(req.Header), ) if err != nil && err != opentracing.ErrSpanContextNotFound { logger.Log("err", err) } span = tracer.StartSpan(operationName, ext.RPCServerOption(wireContext)) return opentracing.ContextWithSpan(ctx, span) } }
// extractInboundSpan attempts to create a new OpenTracing Span for inbound request // using only trace IDs stored in the frame's tracing field. It only works if the // tracer understand Zipkin-style trace IDs. If such attempt fails, another attempt // will be made from the higher level function ExtractInboundSpan() once the // application headers are read from the wire. func (c *Connection) extractInboundSpan(callReq *callReq) opentracing.Span { spanCtx, err := c.Tracer().Extract(zipkinSpanFormat, &callReq.Tracing) if err != nil { if err != opentracing.ErrUnsupportedFormat && err != opentracing.ErrSpanContextNotFound { c.log.WithFields(ErrField(err)).Error("Failed to extract Zipkin-style span.") } return nil } if spanCtx == nil { return nil } operationName := "" // not known at this point, will be set later span := c.Tracer().StartSpan(operationName, ext.RPCServerOption(spanCtx)) span.SetTag("as", callReq.Headers[ArgScheme]) ext.PeerService.Set(span, callReq.Headers[CallerName]) c.setPeerHostPort(span) return span }
func (h handler) createSpan(ctx context.Context, req *http.Request, treq *transport.Request, start time.Time) (context.Context, opentracing.Span) { // Extract opentracing etc baggage from headers // Annotate the inbound context with a trace span tracer := h.Deps.Tracer() carrier := opentracing.HTTPHeadersCarrier(req.Header) parentSpanCtx, _ := tracer.Extract(opentracing.HTTPHeaders, carrier) // parentSpanCtx may be nil, ext.RPCServerOption handles a nil parent // gracefully. span := tracer.StartSpan( treq.Procedure, opentracing.StartTime(start), opentracing.Tags{ "rpc.caller": treq.Caller, "rpc.service": treq.Service, "rpc.encoding": treq.Encoding, "rpc.transport": "http", }, ext.RPCServerOption(parentSpanCtx), // implies ChildOf ) ext.PeerService.Set(span, treq.Caller) ctx = opentracing.ContextWithSpan(ctx, span) return ctx, span }
func main() { storage, err := tracer.NewGRPC("localhost:9999", &tracer.GRPCOptions{ QueueSize: 1024, FlushInterval: 1 * time.Second, }, grpc.WithInsecure()) if err != nil { log.Fatal(err) } t1 := tracer.NewTracer("frontend", storage, tracer.RandomID{}) t2 := tracer.NewTracer("backend", storage, tracer.RandomID{}) s1 := t1.StartSpan("frontend", ext.RPCServerOption(nil)) ext.HTTPUrl.Set(s1, "/hello") ext.HTTPMethod.Set(s1, "GET") s2 := t1.StartSpan("backend.hello", opentracing.ChildOf(s1.Context())) ext.SpanKindRPCClient.Set(s2) ext.Component.Set(s2, "grpc") carrier := opentracing.TextMapCarrier{} if err := t1.Inject(s2.Context(), opentracing.TextMap, carrier); err != nil { log.Println(err) } c3, err := t2.Extract(opentracing.TextMap, carrier) if err != nil { log.Println(err) } s3 := t2.StartSpan("backend.hello", ext.RPCServerOption(c3)) ext.Component.Set(s3, "grpc") s4 := t2.StartSpan("mysql", opentracing.ChildOf(s3.Context())) ext.SpanKindRPCClient.Set(s4) ext.Component.Set(s4, "mysql") s4.SetTag("sql.query", "SELECT * FROM table1") // The MySQL server is not instrumented, so we only get the client // span. s4.Finish() s5 := t2.StartSpan("redis", opentracing.ChildOf(s3.Context())) ext.SpanKindRPCClient.Set(s5) ext.Component.Set(s5, "redis") // The Redis server is not instrumented, so we only get the client // span. s5.Finish() s3.Finish() s2.Finish() s6 := t1.StartSpan("backend.ads", opentracing.ChildOf(s1.Context())) ext.SpanKindRPCClient.Set(s6) ext.Component.Set(s6, "grpc") carrier = opentracing.TextMapCarrier{} if err := t1.Inject(s6.Context(), opentracing.TextMap, carrier); err != nil { log.Println(err) } c7, err := t2.Extract(opentracing.TextMap, carrier) if err != nil { log.Println(err) } s7 := t2.StartSpan("backend.ads", ext.RPCServerOption(c7)) ext.Component.Set(s7, "grpc") s7.Finish() s6.Finish() ext.HTTPStatusCode.Set(s1, 200) s1.Finish() // Wait for spans to be flushed. Production code wouldn't need // this. time.Sleep(2 * time.Second) }