module/apmot/tracer.go (174 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package apmot // import "go.elastic.co/apm/module/apmot/v2" import ( "io" "net/http" "net/textproto" "time" opentracing "github.com/opentracing/opentracing-go" "go.elastic.co/apm/module/apmhttp/v2" "go.elastic.co/apm/v2" ) // New returns a new opentracing.Tracer backed by the supplied // Elastic APM tracer. // // By default, the returned tracer will use apm.DefaultTracer(). // This can be overridden by using a WithTracer option. // The option WithSpanRefValidator allows one to override the // set of spans that are recorded. By default only child-of // spans are recorded. func New(opts ...Option) opentracing.Tracer { t := &otTracer{ tracer: apm.DefaultTracer(), isValidSpanRef: isChildOfSpanRef, } for _, opt := range opts { opt(t) } return t } // otTracer is an opentracing.Tracer backed by an apm.Tracer. type otTracer struct { tracer *apm.Tracer isValidSpanRef SpanRefValidator } // StartSpan starts a new OpenTracing span with the given name and zero or more options. func (t *otTracer) StartSpan(name string, opts ...opentracing.StartSpanOption) opentracing.Span { sso := opentracing.StartSpanOptions{} for _, o := range opts { o.Apply(&sso) } return t.StartSpanWithOptions(name, sso) } // StartSpanWithOptions starts a new OpenTracing span with the given name and options. func (t *otTracer) StartSpanWithOptions(name string, opts opentracing.StartSpanOptions) opentracing.Span { // Because the Context method can be called at any time after // the span is finished, we cannot pool the objects. otSpan := &otSpan{ tracer: t, tags: opts.Tags, ctx: spanContext{ tracer: t, startTime: opts.StartTime, }, } if opts.StartTime.IsZero() { otSpan.ctx.startTime = time.Now() } var parentTraceContext apm.TraceContext if parentCtx, ok := t.parentSpanContext(opts.References); ok { if parentCtx.tx != nil && (parentCtx.tracer == t || parentCtx.tracer == nil) { opts := apm.SpanOptions{ Parent: parentCtx.traceContext, // parent span Start: otSpan.ctx.startTime, } otSpan.span = parentCtx.tx.StartSpanOptions(name, "", opts) otSpan.ctx.tx = parentCtx.tx otSpan.ctx.traceContext = otSpan.span.TraceContext() return otSpan } parentTraceContext = parentCtx.traceContext } // There's no local parent context created by this tracer. otSpan.ctx.tx = t.tracer.StartTransactionOptions(name, "", apm.TransactionOptions{ TraceContext: parentTraceContext, Start: otSpan.ctx.startTime, }) otSpan.ctx.traceContext = otSpan.ctx.tx.TraceContext() return otSpan } func (t *otTracer) Inject(sc opentracing.SpanContext, format interface{}, carrier interface{}) error { spanContext, ok := sc.(*spanContext) if !ok { return opentracing.ErrInvalidSpanContext } switch format { case opentracing.TextMap, opentracing.HTTPHeaders: writer, ok := carrier.(opentracing.TextMapWriter) if !ok { return opentracing.ErrInvalidCarrier } headerValue := apmhttp.FormatTraceparentHeader(spanContext.traceContext) writer.Set(apmhttp.W3CTraceparentHeader, headerValue) if t.tracer.ShouldPropagateLegacyHeader() { writer.Set(apmhttp.ElasticTraceparentHeader, headerValue) } if tracestate := spanContext.traceContext.State.String(); tracestate != "" { writer.Set(apmhttp.TracestateHeader, tracestate) } return nil case opentracing.Binary: writer, ok := carrier.(io.Writer) if !ok { return opentracing.ErrInvalidCarrier } return binaryInject(writer, spanContext.traceContext) default: return opentracing.ErrUnsupportedFormat } } func (t *otTracer) Extract(format interface{}, carrier interface{}) (opentracing.SpanContext, error) { switch format { case opentracing.TextMap, opentracing.HTTPHeaders: var traceparentHeaderValue string var tracestateHeaderValues []string switch carrier := carrier.(type) { case opentracing.HTTPHeadersCarrier: traceparentHeaderValue = http.Header(carrier).Get(apmhttp.W3CTraceparentHeader) if traceparentHeaderValue == "" { traceparentHeaderValue = http.Header(carrier).Get(apmhttp.ElasticTraceparentHeader) } tracestateHeaderValues = http.Header(carrier)[apmhttp.TracestateHeader] case opentracing.TextMapReader: carrier.ForeachKey(func(key, val string) error { switch textproto.CanonicalMIMEHeaderKey(key) { case apmhttp.W3CTraceparentHeader: traceparentHeaderValue = val case apmhttp.ElasticTraceparentHeader: // The W3C header value always trumps the Elastic one, // hence we only set the value if not set already. if traceparentHeaderValue == "" { traceparentHeaderValue = val } case apmhttp.TracestateHeader: tracestateHeaderValues = append(tracestateHeaderValues, val) } return nil }) default: return nil, opentracing.ErrInvalidCarrier } if traceparentHeaderValue == "" { return nil, opentracing.ErrSpanContextNotFound } traceContext, err := apmhttp.ParseTraceparentHeader(traceparentHeaderValue) if err != nil { return nil, err } traceContext.State, _ = apmhttp.ParseTracestateHeader(tracestateHeaderValues...) return &spanContext{tracer: t, traceContext: traceContext}, nil case opentracing.Binary: reader, ok := carrier.(io.Reader) if !ok { return nil, opentracing.ErrInvalidCarrier } traceContext, err := binaryExtract(reader) if err != nil { return nil, err } return &spanContext{tracer: t, traceContext: traceContext}, nil default: return nil, opentracing.ErrUnsupportedFormat } } // Option sets options for the OpenTracing Tracer implementation. type Option func(*otTracer) // WithTracer returns an Option which sets t as the underlying // apm.Tracer for constructing an OpenTracing Tracer. func WithTracer(t *apm.Tracer) Option { if t == nil { panic("t == nil") } return func(o *otTracer) { o.tracer = t } } // SpanRefValidator verifies if a span is valid and should be recorded. type SpanRefValidator func(ref opentracing.SpanReference) bool // WithSpanRefValidator returns an Option which sets the span validation // function. By default only child-of span are considered valid. func WithSpanRefValidator(validator SpanRefValidator) Option { if validator == nil { panic("validator == nil") } return func(o *otTracer) { o.isValidSpanRef = validator } } // TODO(axw) handle binary format once Trace-Context defines one. // OpenTracing mandates that all implementations "MUST" support all // of the builtin formats. var ( binaryInject = binaryInjectUnsupported binaryExtract = binaryExtractUnsupported ) func binaryInjectUnsupported(w io.Writer, traceContext apm.TraceContext) error { return opentracing.ErrUnsupportedFormat } func binaryExtractUnsupported(r io.Reader) (apm.TraceContext, error) { return apm.TraceContext{}, opentracing.ErrUnsupportedFormat }