193 lines
5.2 KiB
Go
193 lines
5.2 KiB
Go
/*
|
|
* Copyright 2018 Expedia Group.
|
|
*
|
|
* 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.
|
|
*
|
|
*/
|
|
|
|
package haystack
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/opentracing/opentracing-go"
|
|
)
|
|
|
|
/*Propagator defines the interface for injecting and extracing the SpanContext from the carrier*/
|
|
type Propagator interface {
|
|
// Inject takes `SpanContext` and injects it into `carrier`
|
|
Inject(ctx *SpanContext, carrier interface{}) error
|
|
|
|
// Extract `SpanContext` from the `carrier`
|
|
Extract(carrier interface{}) (*SpanContext, error)
|
|
}
|
|
|
|
/*Codex defines the interface for encoding and decoding the propagated data*/
|
|
type Codex interface {
|
|
Encode(value string) string
|
|
Decode(value string) string
|
|
}
|
|
|
|
/*DefaultCodex is a no op*/
|
|
type DefaultCodex struct{}
|
|
|
|
/*Encode a no-op for encoding the value*/
|
|
func (c DefaultCodex) Encode(value string) string { return value }
|
|
|
|
/*Decode a no-op for decoding the value*/
|
|
func (c DefaultCodex) Decode(value string) string { return value }
|
|
|
|
/*URLCodex encodes decodes a url*/
|
|
type URLCodex struct{}
|
|
|
|
/*Encode a no-op for encoding the value*/
|
|
func (c URLCodex) Encode(value string) string { return url.QueryEscape(value) }
|
|
|
|
/*Decode a no-op for decoding the value*/
|
|
func (c URLCodex) Decode(value string) string {
|
|
decoded, err := url.QueryUnescape(value)
|
|
if err == nil {
|
|
return decoded
|
|
}
|
|
return ""
|
|
}
|
|
|
|
/*PropagatorOpts defines the options need by a propagator*/
|
|
type PropagatorOpts struct {
|
|
TraceIDKEYName string
|
|
SpanIDKEYName string
|
|
ParentSpanIDKEYName string
|
|
BaggagePrefixKEYName string
|
|
}
|
|
|
|
var defaultPropagatorOpts = PropagatorOpts{}
|
|
var defaultCodex = DefaultCodex{}
|
|
|
|
/*TraceIDKEY returns the trace id key in the propagator*/
|
|
func (p *PropagatorOpts) TraceIDKEY() string {
|
|
if p.TraceIDKEYName != "" {
|
|
return p.TraceIDKEYName
|
|
}
|
|
return "Trace-ID"
|
|
}
|
|
|
|
/*SpanIDKEY returns the span id key in the propagator*/
|
|
func (p *PropagatorOpts) SpanIDKEY() string {
|
|
if p.SpanIDKEYName != "" {
|
|
return p.SpanIDKEYName
|
|
}
|
|
return "Span-ID"
|
|
}
|
|
|
|
/*ParentSpanIDKEY returns the parent span id key in the propagator*/
|
|
func (p *PropagatorOpts) ParentSpanIDKEY() string {
|
|
if p.ParentSpanIDKEYName != "" {
|
|
return p.ParentSpanIDKEYName
|
|
}
|
|
return "Parent-ID"
|
|
}
|
|
|
|
/*BaggageKeyPrefix returns the baggage key prefix*/
|
|
func (p *PropagatorOpts) BaggageKeyPrefix() string {
|
|
if p.BaggagePrefixKEYName != "" {
|
|
return p.BaggagePrefixKEYName
|
|
}
|
|
return "Baggage-"
|
|
}
|
|
|
|
/*TextMapPropagator implements Propagator interface*/
|
|
type TextMapPropagator struct {
|
|
opts PropagatorOpts
|
|
codex Codex
|
|
}
|
|
|
|
/*Inject injects the span context in the carrier*/
|
|
func (p *TextMapPropagator) Inject(ctx *SpanContext, carrier interface{}) error {
|
|
textMapWriter, ok := carrier.(opentracing.TextMapWriter)
|
|
if !ok {
|
|
return opentracing.ErrInvalidCarrier
|
|
}
|
|
|
|
textMapWriter.Set(p.opts.TraceIDKEY(), ctx.TraceID)
|
|
textMapWriter.Set(p.opts.SpanIDKEY(), ctx.SpanID)
|
|
textMapWriter.Set(p.opts.ParentSpanIDKEY(), ctx.ParentID)
|
|
|
|
ctx.ForeachBaggageItem(func(key, value string) bool {
|
|
textMapWriter.Set(fmt.Sprintf("%s%s", p.opts.BaggageKeyPrefix(), key), p.codex.Encode(ctx.Baggage[key]))
|
|
return true
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
/*Extract the span context from the carrier*/
|
|
func (p *TextMapPropagator) Extract(carrier interface{}) (*SpanContext, error) {
|
|
textMapReader, ok := carrier.(opentracing.TextMapReader)
|
|
|
|
if !ok {
|
|
return nil, opentracing.ErrInvalidCarrier
|
|
}
|
|
|
|
baggageKeyLowerCasePrefix := strings.ToLower(p.opts.BaggageKeyPrefix())
|
|
traceIDKeyLowerCase := strings.ToLower(p.opts.TraceIDKEY())
|
|
spanIDKeyLowerCase := strings.ToLower(p.opts.SpanIDKEY())
|
|
parentSpanIDKeyLowerCase := strings.ToLower(p.opts.ParentSpanIDKEY())
|
|
|
|
traceID := ""
|
|
spanID := ""
|
|
parentSpanID := ""
|
|
|
|
baggage := make(map[string]string)
|
|
err := textMapReader.ForeachKey(func(k, v string) error {
|
|
lcKey := strings.ToLower(k)
|
|
|
|
if strings.HasPrefix(lcKey, baggageKeyLowerCasePrefix) {
|
|
keySansPrefix := lcKey[len(p.opts.BaggageKeyPrefix()):]
|
|
baggage[keySansPrefix] = p.codex.Decode(v)
|
|
} else if lcKey == traceIDKeyLowerCase {
|
|
traceID = v
|
|
} else if lcKey == spanIDKeyLowerCase {
|
|
spanID = v
|
|
} else if lcKey == parentSpanIDKeyLowerCase {
|
|
parentSpanID = v
|
|
}
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &SpanContext{
|
|
TraceID: traceID,
|
|
SpanID: spanID,
|
|
ParentID: parentSpanID,
|
|
Baggage: baggage,
|
|
IsExtractedContext: true,
|
|
}, nil
|
|
}
|
|
|
|
/*NewDefaultTextMapPropagator returns a default text map propagator*/
|
|
func NewDefaultTextMapPropagator() *TextMapPropagator {
|
|
return NewTextMapPropagator(defaultPropagatorOpts, defaultCodex)
|
|
}
|
|
|
|
/*NewTextMapPropagator returns a text map propagator*/
|
|
func NewTextMapPropagator(opts PropagatorOpts, codex Codex) *TextMapPropagator {
|
|
return &TextMapPropagator{
|
|
opts: opts,
|
|
codex: codex,
|
|
}
|
|
}
|