mirror of https://github.com/knative/docs.git
193 lines
5.5 KiB
Go
193 lines
5.5 KiB
Go
package traceparent
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"regexp"
|
|
)
|
|
|
|
const (
|
|
// Version represents the maximum `traceparent` header version that is fully supported.
|
|
// The library attempts optimistic forwards compatibility with higher versions.
|
|
Version = 0
|
|
)
|
|
|
|
var (
|
|
// ErrInvalidFormat occurs when the format is invalid, such as if there are missing characters
|
|
// or a field contains an unexpected character set.
|
|
ErrInvalidFormat = errors.New("tracecontext: Invalid traceparent format")
|
|
// ErrInvalidVersion occurs when the encoded version is invalid, i.e., is 255.
|
|
ErrInvalidVersion = errors.New("tracecontext: Invalid traceparent version")
|
|
// ErrInvalidTraceID occurs when the encoded trace ID is invalid, i.e., all bytes are 0
|
|
ErrInvalidTraceID = errors.New("tracecontext: Invalid traceparent trace ID")
|
|
// ErrInvalidSpanID occurs when the encoded span ID is invalid, i.e., all bytes are 0
|
|
ErrInvalidSpanID = errors.New("tracecontext: Invalid traceparent span ID")
|
|
)
|
|
|
|
const (
|
|
maxVersion = 254
|
|
|
|
numVersionBytes = 1
|
|
numTraceIDBytes = 16
|
|
numSpanIDBytes = 8
|
|
numFlagBytes = 1
|
|
)
|
|
|
|
var (
|
|
re = regexp.MustCompile(`^([a-f0-9]{2})-([a-f0-9]{32})-([a-f0-9]{16})-([a-f0-9]{2})(-.*)?$`)
|
|
|
|
invalidTraceIDAllZeroes = make([]byte, numTraceIDBytes, numTraceIDBytes)
|
|
invalidSpanIDAllZeroes = make([]byte, numSpanIDBytes, numSpanIDBytes)
|
|
)
|
|
|
|
// Flags contain recommendations from the caller relevant to the whole trace, e.g., for sampling.
|
|
type Flags struct {
|
|
// Recorded indicates that at least one span in the trace may have been recorded.
|
|
// Tracing systems are advised to record all new spans in recorded traces, as incomplete traces may lead to
|
|
// a degraded tracing experience.
|
|
Recorded bool
|
|
}
|
|
|
|
// String encodes the Flags in an 8-bit field.
|
|
func (f Flags) String() string {
|
|
var flags [1]byte
|
|
if f.Recorded {
|
|
flags[0] = 1
|
|
}
|
|
return fmt.Sprintf("%02x", flags)
|
|
}
|
|
|
|
// TraceParent indicates information about a span and the trace of which it is part,
|
|
// so that child spans started in the same trace may propagate necessary data and share relevant behaviour.
|
|
type TraceParent struct {
|
|
// Version represents the version used to encode the `TraceParent`.
|
|
// Typically, this is the minimum of this library's supported version and the version of the header from which the `TraceParent` was decoded.
|
|
Version uint8
|
|
// TraceID is the trace ID of the whole trace, and should be constant across all spans in a given trace.
|
|
// A `TraceID` that contains only 0 bytes should be treated as invalid.
|
|
TraceID [16]byte
|
|
// SpanID is the span ID of the span from which the `TraceParent` was derived, i.e., the parent of the next span that will be started.
|
|
// Span IDs should be unique within a given trace.
|
|
// A `TraceID` that contains only 0 bytes should be treated as invalid.
|
|
SpanID [8]byte
|
|
// Flags indicate behaviour that is recommended when handling new spans.
|
|
Flags Flags
|
|
}
|
|
|
|
// String encodes the `TraceParent` into a string formatted according to the W3C spec.
|
|
// The string may be invalid if any fields are invalid, e.g., if the `TraceID` contains only 0 bytes.
|
|
func (tp TraceParent) String() string {
|
|
return fmt.Sprintf("%02x-%032x-%016x-%s", tp.Version, tp.TraceID, tp.SpanID, tp.Flags)
|
|
}
|
|
|
|
// Parse attempts to decode a `TraceParent` from a byte array.
|
|
// It returns an error if the byte array is incorrectly formatted or otherwise invalid.
|
|
func Parse(b []byte) (TraceParent, error) {
|
|
return parse(b)
|
|
}
|
|
|
|
// ParseString attempts to decode a `TraceParent` from a string.
|
|
// It returns an error if the string is incorrectly formatted or otherwise invalid.
|
|
func ParseString(s string) (TraceParent, error) {
|
|
return parse([]byte(s))
|
|
}
|
|
|
|
func parse(b []byte) (tp TraceParent, err error) {
|
|
matches := re.FindSubmatch(b)
|
|
if len(matches) < 6 {
|
|
err = ErrInvalidFormat
|
|
return
|
|
}
|
|
|
|
var version uint8
|
|
if version, err = parseVersion(matches[1]); err != nil {
|
|
return
|
|
}
|
|
if version == Version && len(matches[5]) > 0 {
|
|
err = ErrInvalidFormat
|
|
return
|
|
}
|
|
|
|
var traceID [16]byte
|
|
if traceID, err = parseTraceID(matches[2]); err != nil {
|
|
return
|
|
}
|
|
|
|
var spanID [8]byte
|
|
if spanID, err = parseSpanID(matches[3]); err != nil {
|
|
return
|
|
}
|
|
|
|
var flags Flags
|
|
if flags, err = parseFlags(matches[4]); err != nil {
|
|
return
|
|
}
|
|
|
|
tp.Version = Version
|
|
tp.TraceID = traceID
|
|
tp.SpanID = spanID
|
|
tp.Flags = flags
|
|
|
|
return tp, nil
|
|
}
|
|
|
|
func parseVersion(b []byte) (uint8, error) {
|
|
version, ok := parseEncodedSegment(b, numVersionBytes)
|
|
if !ok {
|
|
return 0, ErrInvalidFormat
|
|
}
|
|
if version[0] > maxVersion {
|
|
return 0, ErrInvalidVersion
|
|
}
|
|
return version[0], nil
|
|
}
|
|
|
|
func parseTraceID(b []byte) (traceID [16]byte, err error) {
|
|
id, ok := parseEncodedSegment(b, numTraceIDBytes)
|
|
if !ok {
|
|
return traceID, ErrInvalidFormat
|
|
}
|
|
if bytes.Equal(id, invalidTraceIDAllZeroes) {
|
|
return traceID, ErrInvalidTraceID
|
|
}
|
|
|
|
copy(traceID[:], id)
|
|
|
|
return traceID, nil
|
|
}
|
|
|
|
func parseSpanID(b []byte) (spanID [8]byte, err error) {
|
|
id, ok := parseEncodedSegment(b, numSpanIDBytes)
|
|
if !ok {
|
|
return spanID, ErrInvalidFormat
|
|
}
|
|
if bytes.Equal(id, invalidSpanIDAllZeroes) {
|
|
return spanID, ErrInvalidSpanID
|
|
}
|
|
|
|
copy(spanID[:], id)
|
|
|
|
return spanID, nil
|
|
}
|
|
|
|
func parseFlags(b []byte) (Flags, error) {
|
|
flags, ok := parseEncodedSegment(b, numFlagBytes)
|
|
if !ok {
|
|
return Flags{}, ErrInvalidFormat
|
|
}
|
|
|
|
return Flags{
|
|
Recorded: (flags[0] & 1) == 1,
|
|
}, nil
|
|
}
|
|
|
|
func parseEncodedSegment(src []byte, expectedLen int) ([]byte, bool) {
|
|
dst := make([]byte, hex.DecodedLen(len(src)))
|
|
if n, err := hex.Decode(dst, src); n != expectedLen || err != nil {
|
|
return dst, false
|
|
}
|
|
return dst, true
|
|
}
|