1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
|
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package propagation // import "go.opentelemetry.io/otel/propagation"
import (
"context"
"encoding/hex"
"fmt"
"strings"
"go.opentelemetry.io/otel/trace"
)
const (
supportedVersion = 0
maxVersion = 254
traceparentHeader = "traceparent"
tracestateHeader = "tracestate"
delimiter = "-"
)
// TraceContext is a propagator that supports the W3C Trace Context format
// (https://www.w3.org/TR/trace-context/)
//
// This propagator will propagate the traceparent and tracestate headers to
// guarantee traces are not broken. It is up to the users of this propagator
// to choose if they want to participate in a trace by modifying the
// traceparent header and relevant parts of the tracestate header containing
// their proprietary information.
type TraceContext struct{}
var (
_ TextMapPropagator = TraceContext{}
versionPart = fmt.Sprintf("%.2X", supportedVersion)
)
// Inject injects the trace context from ctx into carrier.
func (tc TraceContext) Inject(ctx context.Context, carrier TextMapCarrier) {
sc := trace.SpanContextFromContext(ctx)
if !sc.IsValid() {
return
}
if ts := sc.TraceState().String(); ts != "" {
carrier.Set(tracestateHeader, ts)
}
// Clear all flags other than the trace-context supported sampling bit.
flags := sc.TraceFlags() & trace.FlagsSampled
var sb strings.Builder
sb.Grow(2 + 32 + 16 + 2 + 3)
_, _ = sb.WriteString(versionPart)
traceID := sc.TraceID()
spanID := sc.SpanID()
flagByte := [1]byte{byte(flags)}
var buf [32]byte
for _, src := range [][]byte{traceID[:], spanID[:], flagByte[:]} {
_ = sb.WriteByte(delimiter[0])
n := hex.Encode(buf[:], src)
_, _ = sb.Write(buf[:n])
}
carrier.Set(traceparentHeader, sb.String())
}
// Extract reads tracecontext from the carrier into a returned Context.
//
// The returned Context will be a copy of ctx and contain the extracted
// tracecontext as the remote SpanContext. If the extracted tracecontext is
// invalid, the passed ctx will be returned directly instead.
func (tc TraceContext) Extract(ctx context.Context, carrier TextMapCarrier) context.Context {
sc := tc.extract(carrier)
if !sc.IsValid() {
return ctx
}
return trace.ContextWithRemoteSpanContext(ctx, sc)
}
func (tc TraceContext) extract(carrier TextMapCarrier) trace.SpanContext {
h := carrier.Get(traceparentHeader)
if h == "" {
return trace.SpanContext{}
}
var ver [1]byte
if !extractPart(ver[:], &h, 2) {
return trace.SpanContext{}
}
version := int(ver[0])
if version > maxVersion {
return trace.SpanContext{}
}
var scc trace.SpanContextConfig
if !extractPart(scc.TraceID[:], &h, 32) {
return trace.SpanContext{}
}
if !extractPart(scc.SpanID[:], &h, 16) {
return trace.SpanContext{}
}
var opts [1]byte
if !extractPart(opts[:], &h, 2) {
return trace.SpanContext{}
}
if version == 0 && (h != "" || opts[0] > 2) {
// version 0 not allow extra
// version 0 not allow other flag
return trace.SpanContext{}
}
// Clear all flags other than the trace-context supported sampling bit.
scc.TraceFlags = trace.TraceFlags(opts[0]) & trace.FlagsSampled
// Ignore the error returned here. Failure to parse tracestate MUST NOT
// affect the parsing of traceparent according to the W3C tracecontext
// specification.
scc.TraceState, _ = trace.ParseTraceState(carrier.Get(tracestateHeader))
scc.Remote = true
sc := trace.NewSpanContext(scc)
if !sc.IsValid() {
return trace.SpanContext{}
}
return sc
}
// upperHex detect hex is upper case Unicode characters.
func upperHex(v string) bool {
for _, c := range v {
if c >= 'A' && c <= 'F' {
return true
}
}
return false
}
func extractPart(dst []byte, h *string, n int) bool {
part, left, _ := strings.Cut(*h, delimiter)
*h = left
// hex.Decode decodes unsupported upper-case characters, so exclude explicitly.
if len(part) != n || upperHex(part) {
return false
}
if p, err := hex.Decode(dst, []byte(part)); err != nil || p != n/2 {
return false
}
return true
}
// Fields returns the keys who's values are set with Inject.
func (tc TraceContext) Fields() []string {
return []string{traceparentHeader, tracestateHeader}
}
|