2020-03-23 22:41:10 -07:00
|
|
|
// Copyright The OpenTelemetry Authors
|
2019-10-08 10:22:15 -07:00
|
|
|
//
|
|
|
|
// Licensed 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.
|
|
|
|
|
2020-01-28 19:13:46 +01:00
|
|
|
package trace
|
2019-10-08 10:22:15 -07:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2020-07-07 16:38:52 -07:00
|
|
|
"errors"
|
2019-10-08 10:22:15 -07:00
|
|
|
"strings"
|
|
|
|
|
2020-01-28 19:13:46 +01:00
|
|
|
"go.opentelemetry.io/otel/api/propagation"
|
2019-10-08 10:22:15 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2020-06-30 18:21:13 -07:00
|
|
|
// Default B3 Header names.
|
2020-07-07 16:38:52 -07:00
|
|
|
b3ContextHeader = "b3"
|
|
|
|
b3DebugFlagHeader = "x-b3-flags"
|
|
|
|
b3TraceIDHeader = "x-b3-traceid"
|
|
|
|
b3SpanIDHeader = "x-b3-spanid"
|
|
|
|
b3SampledHeader = "x-b3-sampled"
|
|
|
|
b3ParentSpanIDHeader = "x-b3-parentspanid"
|
2020-06-30 18:21:13 -07:00
|
|
|
|
|
|
|
b3TraceIDPadding = "0000000000000000"
|
2020-07-07 16:38:52 -07:00
|
|
|
|
|
|
|
// B3 Single Header encoding widths.
|
|
|
|
separatorWidth = 1 // Single "-" character.
|
|
|
|
samplingWidth = 1 // Single hex character.
|
|
|
|
traceID64BitsWidth = 64 / 4 // 16 hex character Trace ID.
|
|
|
|
traceID128BitsWidth = 128 / 4 // 32 hex character Trace ID.
|
|
|
|
spanIDWidth = 16 // 16 hex character ID.
|
|
|
|
parentSpanIDWidth = 16 // 16 hex character ID.
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
empty = EmptySpanContext()
|
|
|
|
|
|
|
|
errInvalidSampledByte = errors.New("invalid B3 Sampled found")
|
|
|
|
errInvalidSampledHeader = errors.New("invalid B3 Sampled header found")
|
|
|
|
errInvalidTraceIDHeader = errors.New("invalid B3 TraceID header found")
|
|
|
|
errInvalidSpanIDHeader = errors.New("invalid B3 SpanID header found")
|
|
|
|
errInvalidParentSpanIDHeader = errors.New("invalid B3 ParentSpanID header found")
|
|
|
|
errInvalidScope = errors.New("require either both TraceID and SpanID or none")
|
|
|
|
errInvalidScopeParent = errors.New("ParentSpanID requires both TraceID and SpanID to be available")
|
|
|
|
errInvalidScopeParentSingle = errors.New("ParentSpanID requires TraceID, SpanID and Sampled to be available")
|
|
|
|
errEmptyContext = errors.New("empty request context")
|
|
|
|
errInvalidTraceIDValue = errors.New("invalid B3 TraceID value found")
|
|
|
|
errInvalidSpanIDValue = errors.New("invalid B3 SpanID value found")
|
|
|
|
errInvalidParentSpanIDValue = errors.New("invalid B3 ParentSpanID value found")
|
|
|
|
)
|
|
|
|
|
|
|
|
// B3Encoding is a bitmask representation of the B3 encoding type.
|
|
|
|
type B3Encoding uint8
|
|
|
|
|
|
|
|
// supports returns if e has o bit(s) set.
|
|
|
|
func (e B3Encoding) supports(o B3Encoding) bool {
|
|
|
|
return e&o == o
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
// B3MultipleHeader is a B3 encoding that uses multiple headers to
|
|
|
|
// transmit tracing information all prefixed with `x-b3-`.
|
|
|
|
B3MultipleHeader B3Encoding = 1 << iota
|
|
|
|
// B3SingleHeader is a B3 encoding that uses a single header named `b3`
|
|
|
|
// to transmit tracing information.
|
|
|
|
B3SingleHeader
|
|
|
|
// B3Unspecified is an unspecified B3 encoding.
|
|
|
|
B3Unspecified B3Encoding = 0
|
2019-10-08 10:22:15 -07:00
|
|
|
)
|
|
|
|
|
2020-05-05 14:20:09 -04:00
|
|
|
// B3 propagator serializes SpanContext to/from B3 Headers.
|
2020-07-07 16:38:52 -07:00
|
|
|
// This propagator supports both versions of B3 headers,
|
|
|
|
// 1. Single Header:
|
|
|
|
// b3: {TraceId}-{SpanId}-{SamplingState}-{ParentSpanId}
|
2019-10-08 10:22:15 -07:00
|
|
|
// 2. Multiple Headers:
|
2020-07-07 16:38:52 -07:00
|
|
|
// x-b3-traceid: {TraceId}
|
|
|
|
// x-b3-parentspanid: {ParentSpanId}
|
|
|
|
// x-b3-spanid: {SpanId}
|
|
|
|
// x-b3-sampled: {SamplingState}
|
|
|
|
// x-b3-flags: {DebugFlag}
|
2019-12-03 22:52:04 +01:00
|
|
|
type B3 struct {
|
2020-07-07 16:38:52 -07:00
|
|
|
// InjectEncoding are the B3 encodings used when injecting trace
|
|
|
|
// information. If no encoding is specified (i.e. `B3Unspecified`)
|
|
|
|
// `B3MultipleHeader` will be used as the default.
|
|
|
|
InjectEncoding B3Encoding
|
2019-10-08 10:22:15 -07:00
|
|
|
}
|
|
|
|
|
2020-02-14 08:16:05 +01:00
|
|
|
var _ propagation.HTTPPropagator = B3{}
|
2019-10-16 10:24:38 -07:00
|
|
|
|
2020-07-07 16:38:52 -07:00
|
|
|
// Inject injects a context into the supplier as B3 headers.
|
|
|
|
// The parent span ID is omitted because it is not tracked in the
|
|
|
|
// SpanContext.
|
2020-02-14 08:16:05 +01:00
|
|
|
func (b3 B3) Inject(ctx context.Context, supplier propagation.HTTPSupplier) {
|
2020-01-28 19:13:46 +01:00
|
|
|
sc := SpanFromContext(ctx).SpanContext()
|
2020-07-07 16:38:52 -07:00
|
|
|
|
|
|
|
if b3.InjectEncoding.supports(B3SingleHeader) {
|
|
|
|
header := []string{}
|
|
|
|
if sc.TraceID.IsValid() && sc.SpanID.IsValid() {
|
|
|
|
header = append(header, sc.TraceID.String(), sc.SpanID.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
if sc.isDebug() {
|
|
|
|
header = append(header, "d")
|
|
|
|
} else if !sc.isDeferred() {
|
|
|
|
if sc.IsSampled() {
|
|
|
|
header = append(header, "1")
|
|
|
|
} else {
|
|
|
|
header = append(header, "0")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
supplier.Set(b3ContextHeader, strings.Join(header, "-"))
|
2020-03-05 10:12:10 -08:00
|
|
|
}
|
2019-10-08 10:22:15 -07:00
|
|
|
|
2020-07-07 16:38:52 -07:00
|
|
|
if b3.InjectEncoding.supports(B3MultipleHeader) || b3.InjectEncoding == B3Unspecified {
|
|
|
|
if sc.TraceID.IsValid() && sc.SpanID.IsValid() {
|
|
|
|
supplier.Set(b3TraceIDHeader, sc.TraceID.String())
|
|
|
|
supplier.Set(b3SpanIDHeader, sc.SpanID.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
if sc.isDebug() {
|
|
|
|
// Since Debug implies deferred, don't also send "X-B3-Sampled".
|
|
|
|
supplier.Set(b3DebugFlagHeader, "1")
|
|
|
|
} else if !sc.isDeferred() {
|
|
|
|
if sc.IsSampled() {
|
|
|
|
supplier.Set(b3SampledHeader, "1")
|
|
|
|
} else {
|
|
|
|
supplier.Set(b3SampledHeader, "0")
|
|
|
|
}
|
2019-10-08 10:22:15 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-07 16:38:52 -07:00
|
|
|
// Extract extracts a context from the supplier if it contains B3 headers.
|
2020-02-14 08:16:05 +01:00
|
|
|
func (b3 B3) Extract(ctx context.Context, supplier propagation.HTTPSupplier) context.Context {
|
2020-07-07 16:38:52 -07:00
|
|
|
var (
|
|
|
|
sc SpanContext
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
|
|
|
|
// Default to Single Header if a valid value exists.
|
|
|
|
if h := supplier.Get(b3ContextHeader); h != "" {
|
|
|
|
sc, err = extractSingle(h)
|
|
|
|
if err == nil && sc.IsValid() {
|
|
|
|
return ContextWithRemoteSpanContext(ctx, sc)
|
|
|
|
}
|
|
|
|
// The Single Header value was invalid, fallback to Multiple Header.
|
2019-10-08 10:22:15 -07:00
|
|
|
}
|
2020-07-07 16:38:52 -07:00
|
|
|
|
|
|
|
var (
|
|
|
|
traceID = supplier.Get(b3TraceIDHeader)
|
|
|
|
spanID = supplier.Get(b3SpanIDHeader)
|
|
|
|
parentSpanID = supplier.Get(b3ParentSpanIDHeader)
|
|
|
|
sampled = supplier.Get(b3SampledHeader)
|
|
|
|
debugFlag = supplier.Get(b3DebugFlagHeader)
|
|
|
|
)
|
|
|
|
sc, err = extractMultiple(traceID, spanID, parentSpanID, sampled, debugFlag)
|
|
|
|
if err != nil || !sc.IsValid() {
|
2020-04-27 20:37:40 +02:00
|
|
|
return ctx
|
|
|
|
}
|
2020-02-14 08:16:05 +01:00
|
|
|
return ContextWithRemoteSpanContext(ctx, sc)
|
2019-10-08 10:22:15 -07:00
|
|
|
}
|
|
|
|
|
2020-07-07 16:38:52 -07:00
|
|
|
func (b3 B3) GetAllKeys() []string {
|
|
|
|
header := []string{}
|
|
|
|
if b3.InjectEncoding.supports(B3SingleHeader) {
|
|
|
|
header = append(header, b3ContextHeader)
|
|
|
|
}
|
|
|
|
if b3.InjectEncoding.supports(B3MultipleHeader) || b3.InjectEncoding == B3Unspecified {
|
|
|
|
header = append(header, b3TraceIDHeader, b3SpanIDHeader, b3SampledHeader, b3DebugFlagHeader)
|
2020-05-05 14:20:09 -04:00
|
|
|
}
|
2020-07-07 16:38:52 -07:00
|
|
|
return header
|
2020-05-05 14:20:09 -04:00
|
|
|
}
|
|
|
|
|
2020-07-07 16:38:52 -07:00
|
|
|
// extractMultiple reconstructs a SpanContext from header values based on B3
|
|
|
|
// Multiple header. It is based on the implementation found here:
|
|
|
|
// https://github.com/openzipkin/zipkin-go/blob/v0.2.2/propagation/b3/spancontext.go
|
|
|
|
// and adapted to support a SpanContext.
|
|
|
|
func extractMultiple(traceID, spanID, parentSpanID, sampled, flags string) (SpanContext, error) {
|
|
|
|
var (
|
|
|
|
err error
|
|
|
|
requiredCount int
|
|
|
|
sc = SpanContext{}
|
|
|
|
)
|
|
|
|
|
|
|
|
// correct values for an existing sampled header are "0" and "1".
|
|
|
|
// For legacy support and being lenient to other tracing implementations we
|
|
|
|
// allow "true" and "false" as inputs for interop purposes.
|
|
|
|
switch strings.ToLower(sampled) {
|
|
|
|
case "0", "false":
|
|
|
|
// Zero value for TraceFlags sample bit is unset.
|
|
|
|
case "1", "true":
|
|
|
|
sc.TraceFlags = FlagsSampled
|
|
|
|
case "":
|
|
|
|
sc.TraceFlags = FlagsDeferred
|
|
|
|
default:
|
|
|
|
return empty, errInvalidSampledHeader
|
2019-10-08 10:22:15 -07:00
|
|
|
}
|
2020-07-07 16:38:52 -07:00
|
|
|
|
|
|
|
// The only accepted value for Flags is "1". This will set Debug to
|
|
|
|
// true. All other values and omission of header will be ignored.
|
|
|
|
if flags == "1" {
|
|
|
|
sc.TraceFlags |= FlagsDebug
|
2019-10-08 10:22:15 -07:00
|
|
|
}
|
|
|
|
|
2020-07-07 16:38:52 -07:00
|
|
|
if traceID != "" {
|
|
|
|
requiredCount++
|
|
|
|
id := traceID
|
|
|
|
if len(traceID) == 16 {
|
|
|
|
// Pad 64-bit trace IDs.
|
|
|
|
id = b3TraceIDPadding + traceID
|
|
|
|
}
|
|
|
|
if sc.TraceID, err = IDFromHex(id); err != nil {
|
|
|
|
return empty, errInvalidTraceIDHeader
|
|
|
|
}
|
2019-10-08 10:22:15 -07:00
|
|
|
}
|
2020-07-07 16:38:52 -07:00
|
|
|
|
|
|
|
if spanID != "" {
|
|
|
|
requiredCount++
|
|
|
|
if sc.SpanID, err = SpanIDFromHex(spanID); err != nil {
|
|
|
|
return empty, errInvalidSpanIDHeader
|
|
|
|
}
|
2019-10-08 10:22:15 -07:00
|
|
|
}
|
|
|
|
|
2020-07-07 16:38:52 -07:00
|
|
|
if requiredCount != 0 && requiredCount != 2 {
|
|
|
|
return empty, errInvalidScope
|
2019-10-08 10:22:15 -07:00
|
|
|
}
|
|
|
|
|
2020-07-07 16:38:52 -07:00
|
|
|
if parentSpanID != "" {
|
|
|
|
if requiredCount == 0 {
|
|
|
|
return empty, errInvalidScopeParent
|
|
|
|
}
|
|
|
|
// Validate parent span ID but we do not use it so do not save it.
|
|
|
|
if _, err = SpanIDFromHex(parentSpanID); err != nil {
|
|
|
|
return empty, errInvalidParentSpanIDHeader
|
|
|
|
}
|
2019-10-08 10:22:15 -07:00
|
|
|
}
|
|
|
|
|
2020-07-07 16:38:52 -07:00
|
|
|
return sc, nil
|
2019-10-08 10:22:15 -07:00
|
|
|
}
|
|
|
|
|
2020-07-07 16:38:52 -07:00
|
|
|
// extractSingle reconstructs a SpanContext from contextHeader based on a B3
|
|
|
|
// Single header. It is based on the implementation found here:
|
|
|
|
// https://github.com/openzipkin/zipkin-go/blob/v0.2.2/propagation/b3/spancontext.go
|
|
|
|
// and adapted to support a SpanContext.
|
|
|
|
func extractSingle(contextHeader string) (SpanContext, error) {
|
|
|
|
if contextHeader == "" {
|
|
|
|
return empty, errEmptyContext
|
2019-10-08 10:22:15 -07:00
|
|
|
}
|
|
|
|
|
2020-07-07 16:38:52 -07:00
|
|
|
var (
|
|
|
|
sc = SpanContext{}
|
|
|
|
sampling string
|
|
|
|
)
|
2019-10-16 10:24:38 -07:00
|
|
|
|
2020-07-07 16:38:52 -07:00
|
|
|
headerLen := len(contextHeader)
|
2019-10-16 10:24:38 -07:00
|
|
|
|
2020-07-07 16:38:52 -07:00
|
|
|
if headerLen == samplingWidth {
|
|
|
|
sampling = contextHeader
|
|
|
|
} else if headerLen == traceID64BitsWidth || headerLen == traceID128BitsWidth {
|
|
|
|
// Trace ID by itself is invalid.
|
|
|
|
return empty, errInvalidScope
|
|
|
|
} else if headerLen >= traceID64BitsWidth+spanIDWidth+separatorWidth {
|
|
|
|
pos := 0
|
|
|
|
var traceID string
|
|
|
|
if string(contextHeader[traceID64BitsWidth]) == "-" {
|
|
|
|
// traceID must be 64 bits
|
|
|
|
pos += traceID64BitsWidth // {traceID}
|
|
|
|
traceID = b3TraceIDPadding + string(contextHeader[0:pos])
|
|
|
|
} else if string(contextHeader[32]) == "-" {
|
|
|
|
// traceID must be 128 bits
|
|
|
|
pos += traceID128BitsWidth // {traceID}
|
|
|
|
traceID = string(contextHeader[0:pos])
|
|
|
|
} else {
|
|
|
|
return empty, errInvalidTraceIDValue
|
2019-10-08 10:22:15 -07:00
|
|
|
}
|
2020-07-07 16:38:52 -07:00
|
|
|
var err error
|
|
|
|
sc.TraceID, err = IDFromHex(traceID)
|
2019-10-28 14:05:06 -03:00
|
|
|
if err != nil {
|
2020-07-07 16:38:52 -07:00
|
|
|
return empty, errInvalidTraceIDValue
|
2019-10-08 10:22:15 -07:00
|
|
|
}
|
2020-07-07 16:38:52 -07:00
|
|
|
pos += separatorWidth // {traceID}-
|
2019-10-08 10:22:15 -07:00
|
|
|
|
2020-07-07 16:38:52 -07:00
|
|
|
sc.SpanID, err = SpanIDFromHex(contextHeader[pos : pos+spanIDWidth])
|
|
|
|
if err != nil {
|
|
|
|
return empty, errInvalidSpanIDValue
|
|
|
|
}
|
|
|
|
pos += spanIDWidth // {traceID}-{spanID}
|
2019-10-08 10:22:15 -07:00
|
|
|
|
2020-07-07 16:38:52 -07:00
|
|
|
if headerLen > pos {
|
|
|
|
if headerLen == pos+separatorWidth {
|
|
|
|
// {traceID}-{spanID}- is invalid.
|
|
|
|
return empty, errInvalidSampledByte
|
|
|
|
}
|
|
|
|
pos += separatorWidth // {traceID}-{spanID}-
|
2019-10-08 10:22:15 -07:00
|
|
|
|
2020-07-07 16:38:52 -07:00
|
|
|
if headerLen == pos+samplingWidth {
|
|
|
|
sampling = string(contextHeader[pos])
|
|
|
|
} else if headerLen == pos+parentSpanIDWidth {
|
|
|
|
// {traceID}-{spanID}-{parentSpanID} is invalid.
|
|
|
|
return empty, errInvalidScopeParentSingle
|
|
|
|
} else if headerLen == pos+samplingWidth+separatorWidth+parentSpanIDWidth {
|
|
|
|
sampling = string(contextHeader[pos])
|
|
|
|
pos += samplingWidth + separatorWidth // {traceID}-{spanID}-{sampling}-
|
|
|
|
|
|
|
|
// Validate parent span ID but we do not use it so do not
|
|
|
|
// save it.
|
|
|
|
_, err = SpanIDFromHex(contextHeader[pos:])
|
|
|
|
if err != nil {
|
|
|
|
return empty, errInvalidParentSpanIDValue
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return empty, errInvalidParentSpanIDValue
|
|
|
|
}
|
2019-10-08 10:22:15 -07:00
|
|
|
}
|
2020-07-07 16:38:52 -07:00
|
|
|
} else {
|
|
|
|
return empty, errInvalidTraceIDValue
|
2019-10-08 10:22:15 -07:00
|
|
|
}
|
2020-07-07 16:38:52 -07:00
|
|
|
switch sampling {
|
|
|
|
case "":
|
|
|
|
sc.TraceFlags = FlagsDeferred
|
|
|
|
case "d":
|
|
|
|
sc.TraceFlags = FlagsDebug
|
2019-10-08 10:22:15 -07:00
|
|
|
case "1":
|
2020-07-07 16:38:52 -07:00
|
|
|
sc.TraceFlags = FlagsSampled
|
|
|
|
case "0":
|
|
|
|
// Zero value for TraceFlags sample bit is unset.
|
|
|
|
default:
|
|
|
|
return empty, errInvalidSampledByte
|
2019-10-08 10:22:15 -07:00
|
|
|
}
|
2020-02-14 08:16:05 +01:00
|
|
|
|
2020-07-07 16:38:52 -07:00
|
|
|
return sc, nil
|
2020-02-14 08:16:05 +01:00
|
|
|
}
|