1// Copyright The OpenTelemetry Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package propagation // import "go.opentelemetry.io/otel/propagation" 16 17import ( 18 "context" 19 "encoding/hex" 20 "fmt" 21 "regexp" 22 "strings" 23 24 "go.opentelemetry.io/otel/attribute" 25 "go.opentelemetry.io/otel/trace" 26) 27 28const ( 29 supportedVersion = 0 30 maxVersion = 254 31 traceparentHeader = "traceparent" 32 tracestateHeader = "tracestate" 33) 34 35// TraceContext is a propagator that supports the W3C Trace Context format 36// (https://www.w3.org/TR/trace-context/) 37// 38// This propagator will propagate the traceparent and tracestate headers to 39// guarantee traces are not broken. It is up to the users of this propagator 40// to choose if they want to participate in a trace by modifying the 41// traceparent header and relevant parts of the tracestate header containing 42// their proprietary information. 43type TraceContext struct{} 44 45var _ TextMapPropagator = TraceContext{} 46var traceCtxRegExp = regexp.MustCompile("^(?P<version>[0-9a-f]{2})-(?P<traceID>[a-f0-9]{32})-(?P<spanID>[a-f0-9]{16})-(?P<traceFlags>[a-f0-9]{2})(?:-.*)?$") 47 48// Inject set tracecontext from the Context into the carrier. 49func (tc TraceContext) Inject(ctx context.Context, carrier TextMapCarrier) { 50 sc := trace.SpanContextFromContext(ctx) 51 if !sc.IsValid() { 52 return 53 } 54 55 carrier.Set(tracestateHeader, sc.TraceState().String()) 56 57 // Clear all flags other than the trace-context supported sampling bit. 58 flags := sc.TraceFlags() & trace.FlagsSampled 59 60 h := fmt.Sprintf("%.2x-%s-%s-%s", 61 supportedVersion, 62 sc.TraceID(), 63 sc.SpanID(), 64 flags) 65 carrier.Set(traceparentHeader, h) 66} 67 68// Extract reads tracecontext from the carrier into a returned Context. 69// 70// The returned Context will be a copy of ctx and contain the extracted 71// tracecontext as the remote SpanContext. If the extracted tracecontext is 72// invalid, the passed ctx will be returned directly instead. 73func (tc TraceContext) Extract(ctx context.Context, carrier TextMapCarrier) context.Context { 74 sc := tc.extract(carrier) 75 if !sc.IsValid() { 76 return ctx 77 } 78 return trace.ContextWithRemoteSpanContext(ctx, sc) 79} 80 81func (tc TraceContext) extract(carrier TextMapCarrier) trace.SpanContext { 82 h := carrier.Get(traceparentHeader) 83 if h == "" { 84 return trace.SpanContext{} 85 } 86 87 matches := traceCtxRegExp.FindStringSubmatch(h) 88 89 if len(matches) == 0 { 90 return trace.SpanContext{} 91 } 92 93 if len(matches) < 5 { // four subgroups plus the overall match 94 return trace.SpanContext{} 95 } 96 97 if len(matches[1]) != 2 { 98 return trace.SpanContext{} 99 } 100 ver, err := hex.DecodeString(matches[1]) 101 if err != nil { 102 return trace.SpanContext{} 103 } 104 version := int(ver[0]) 105 if version > maxVersion { 106 return trace.SpanContext{} 107 } 108 109 if version == 0 && len(matches) != 5 { // four subgroups plus the overall match 110 return trace.SpanContext{} 111 } 112 113 if len(matches[2]) != 32 { 114 return trace.SpanContext{} 115 } 116 117 var scc trace.SpanContextConfig 118 119 scc.TraceID, err = trace.TraceIDFromHex(matches[2][:32]) 120 if err != nil { 121 return trace.SpanContext{} 122 } 123 124 if len(matches[3]) != 16 { 125 return trace.SpanContext{} 126 } 127 scc.SpanID, err = trace.SpanIDFromHex(matches[3]) 128 if err != nil { 129 return trace.SpanContext{} 130 } 131 132 if len(matches[4]) != 2 { 133 return trace.SpanContext{} 134 } 135 opts, err := hex.DecodeString(matches[4]) 136 if err != nil || len(opts) < 1 || (version == 0 && opts[0] > 2) { 137 return trace.SpanContext{} 138 } 139 // Clear all flags other than the trace-context supported sampling bit. 140 scc.TraceFlags = trace.TraceFlags(opts[0]) & trace.FlagsSampled 141 142 scc.TraceState = parseTraceState(carrier.Get(tracestateHeader)) 143 scc.Remote = true 144 145 sc := trace.NewSpanContext(scc) 146 if !sc.IsValid() { 147 return trace.SpanContext{} 148 } 149 150 return sc 151} 152 153// Fields returns the keys who's values are set with Inject. 154func (tc TraceContext) Fields() []string { 155 return []string{traceparentHeader, tracestateHeader} 156} 157 158func parseTraceState(in string) trace.TraceState { 159 if in == "" { 160 return trace.TraceState{} 161 } 162 163 kvs := []attribute.KeyValue{} 164 for _, entry := range strings.Split(in, ",") { 165 parts := strings.SplitN(entry, "=", 2) 166 if len(parts) != 2 { 167 // Parse failure, abort! 168 return trace.TraceState{} 169 } 170 kvs = append(kvs, attribute.String(parts[0], parts[1])) 171 } 172 173 // Ignoring error here as "failure to parse tracestate MUST NOT 174 // affect the parsing of traceparent." 175 // https://www.w3.org/TR/trace-context/#tracestate-header 176 ts, _ := trace.TraceStateFromKeyValues(kvs...) 177 return ts 178} 179