Upgraded DataDog tracing library to 1.14.0

This commit is contained in:
Alex Antonov 2019-05-15 11:04:06 -05:00 committed by Traefiker Bot
parent 1f2fe08c33
commit adc2b62c22
13 changed files with 485 additions and 60 deletions

8
Gopkg.lock generated
View file

@ -1864,7 +1864,7 @@
version = "v1.20.1" version = "v1.20.1"
[[projects]] [[projects]]
digest = "1:b886012746f19e2a7c6c3901ea9f86e8a5e32ff2b4407086f4f3181269976957" digest = "1:b49eceff862a3048ec28dad1fce40bcbdc1703119dbad35d7e5f1beb4f9a4527"
name = "gopkg.in/DataDog/dd-trace-go.v1" name = "gopkg.in/DataDog/dd-trace-go.v1"
packages = [ packages = [
"ddtrace", "ddtrace",
@ -1872,10 +1872,11 @@
"ddtrace/internal", "ddtrace/internal",
"ddtrace/opentracer", "ddtrace/opentracer",
"ddtrace/tracer", "ddtrace/tracer",
"internal/globalconfig",
] ]
pruneopts = "NUT" pruneopts = "NUT"
revision = "7fb2bce4b1ed6ab61f7a9e1be30dea56de19db7c" revision = "c19e9e56d5b5b71b6507ce1b0ec06d85aa3705a1"
version = "v1.8.0" version = "v1.14.0"
[[projects]] [[projects]]
digest = "1:c970218a20933dd0a2eb2006de922217fa9276f57d25009b2a934eb1c50031cc" digest = "1:c970218a20933dd0a2eb2006de922217fa9276f57d25009b2a934eb1c50031cc"
@ -2287,6 +2288,7 @@
"github.com/opentracing/opentracing-go/log", "github.com/opentracing/opentracing-go/log",
"github.com/openzipkin-contrib/zipkin-go-opentracing", "github.com/openzipkin-contrib/zipkin-go-opentracing",
"github.com/patrickmn/go-cache", "github.com/patrickmn/go-cache",
"github.com/pmezard/go-difflib/difflib",
"github.com/prometheus/client_golang/prometheus", "github.com/prometheus/client_golang/prometheus",
"github.com/prometheus/client_golang/prometheus/promhttp", "github.com/prometheus/client_golang/prometheus/promhttp",
"github.com/prometheus/client_model/go", "github.com/prometheus/client_model/go",

View file

@ -285,7 +285,7 @@ required = [
[[constraint]] [[constraint]]
name = "gopkg.in/DataDog/dd-trace-go.v1" name = "gopkg.in/DataDog/dd-trace-go.v1"
version = "1.7.0" version = "1.13.0"
[[constraint]] [[constraint]]
name = "github.com/instana/go-sensor" name = "github.com/instana/go-sensor"

View file

@ -91,6 +91,12 @@ type FinishConfig struct {
// NoDebugStack will prevent any set errors from generating an attached stack trace tag. // NoDebugStack will prevent any set errors from generating an attached stack trace tag.
NoDebugStack bool NoDebugStack bool
// StackFrames specifies the number of stack frames to be attached in spans that finish with errors.
StackFrames uint
// SkipStackFrames specifies the offset at which to start reporting stack frames from the stack.
SkipStackFrames uint
} }
// StartSpanConfig holds the configuration for starting a new span. It is usually passed // StartSpanConfig holds the configuration for starting a new span. It is usually passed
@ -108,4 +114,8 @@ type StartSpanConfig struct {
// Tags holds a set of key/value pairs that should be set as metadata on the // Tags holds a set of key/value pairs that should be set as metadata on the
// new span. // new span.
Tags map[string]interface{} Tags map[string]interface{}
// Force-set the SpanID, rather than use a random number. If no Parent SpanContext is present,
// then this will also set the TraceID to the same value.
SpanID uint64
} }

View file

@ -27,10 +27,15 @@ const (
// HTTPURL sets the HTTP URL for a span. // HTTPURL sets the HTTP URL for a span.
HTTPURL = "http.url" HTTPURL = "http.url"
// TODO: In the next major version, suffix these constants (SpanType, etc) // TODO: In the next major version, prefix these constants (SpanType, etc)
// with "*Key" (SpanTypeKey, etc) to more easily differentiate between // with "Key*" (KeySpanType, etc) to more easily differentiate between
// constants representing tag values and constants representing keys. // constants representing tag values and constants representing keys.
// SpanName is a pseudo-key for setting a span's operation name by means of
// a tag. It is mostly here to facilitate vendor-agnostic frameworks like Opentracing
// and OpenCensus.
SpanName = "span.name"
// SpanType defines the Span type (web, db, cache). // SpanType defines the Span type (web, db, cache).
SpanType = "span.type" SpanType = "span.type"
@ -54,4 +59,20 @@ const (
// Environment specifies the environment to use with a trace. // Environment specifies the environment to use with a trace.
Environment = "env" Environment = "env"
// EventSampleRate specifies the rate at which this span will be sampled
// as an APM event.
EventSampleRate = "_dd1.sr.eausr"
// AnalyticsEvent specifies whether the span should be recorded as a Trace
// Search & Analytics event.
AnalyticsEvent = "analytics.event"
// ManualKeep is a tag which specifies that the trace to which this span
// belongs to should be kept when set to true.
ManualKeep = "manual.keep"
// ManualDrop is a tag which specifies that the trace to which this span
// belongs to should be dropped when set to true.
ManualDrop = "manual.drop"
) )

View file

@ -18,6 +18,11 @@ func ResourceName(name string) opentracing.StartSpanOption {
return opentracing.Tag{Key: ext.ResourceName, Value: name} return opentracing.Tag{Key: ext.ResourceName, Value: name}
} }
// SpanName sets the Datadog operation name for the span.
func SpanName(name string) opentracing.StartSpanOption {
return opentracing.Tag{Key: ext.SpanName, Value: name}
}
// SpanType can be used with opentracing.StartSpan to set the type of a span. // SpanType can be used with opentracing.StartSpan to set the type of a span.
func SpanType(name string) opentracing.StartSpanOption { func SpanType(name string) opentracing.StartSpanOption {
return opentracing.Tag{Key: ext.SpanType, Value: name} return opentracing.Tag{Key: ext.SpanType, Value: name}

View file

@ -1,6 +1,7 @@
package tracer package tracer
import ( import (
"log"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
@ -8,6 +9,7 @@ import (
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
"gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig"
) )
// config holds the tracer configuration. // config holds the tracer configuration.
@ -21,7 +23,7 @@ type config struct {
// sampler specifies the sampler that will be used for sampling traces. // sampler specifies the sampler that will be used for sampling traces.
sampler Sampler sampler Sampler
// agentAddr specifies the hostname and of the agent where the traces // agentAddr specifies the hostname and port of the agent where the traces
// are sent to. // are sent to.
agentAddr string agentAddr string
@ -37,6 +39,10 @@ type config struct {
// httpRoundTripper defines the http.RoundTripper used by the agent transport. // httpRoundTripper defines the http.RoundTripper used by the agent transport.
httpRoundTripper http.RoundTripper httpRoundTripper http.RoundTripper
// hostname is automatically assigned when the DD_TRACE_REPORT_HOSTNAME is set to true,
// and is added as a special tag to the root span of traces.
hostname string
} }
// StartOption represents a function that can be provided as a parameter to Start. // StartOption represents a function that can be provided as a parameter to Start.
@ -47,6 +53,14 @@ func defaults(c *config) {
c.serviceName = filepath.Base(os.Args[0]) c.serviceName = filepath.Base(os.Args[0])
c.sampler = NewAllSampler() c.sampler = NewAllSampler()
c.agentAddr = defaultAddress c.agentAddr = defaultAddress
if os.Getenv("DD_TRACE_REPORT_HOSTNAME") == "true" {
var err error
c.hostname, err = os.Hostname()
if err != nil {
log.Printf("%sunable to look up hostname: %v\n", errorPrefix, err)
}
}
} }
// WithPrioritySampling is deprecated, and priority sampling is enabled by default. // WithPrioritySampling is deprecated, and priority sampling is enabled by default.
@ -117,6 +131,22 @@ func WithHTTPRoundTripper(r http.RoundTripper) StartOption {
} }
} }
// WithAnalytics allows specifying whether Trace Search & Analytics should be enabled
// for integrations.
func WithAnalytics(on bool) StartOption {
if on {
return WithAnalyticsRate(1.0)
}
return WithAnalyticsRate(0.0)
}
// WithAnalyticsRate sets the global sampling rate for sampling APM events.
func WithAnalyticsRate(rate float64) StartOption {
return func(_ *config) {
globalconfig.SetAnalyticsRate(rate)
}
}
// StartSpanOption is a configuration option for StartSpan. It is aliased in order // StartSpanOption is a configuration option for StartSpan. It is aliased in order
// to help godoc group all the functions returning it together. It is considered // to help godoc group all the functions returning it together. It is considered
// more correct to refer to it as the type as the origin, ddtrace.StartSpanOption. // more correct to refer to it as the type as the origin, ddtrace.StartSpanOption.
@ -149,6 +179,15 @@ func SpanType(name string) StartSpanOption {
return Tag(ext.SpanType, name) return Tag(ext.SpanType, name)
} }
// WithSpanID sets the SpanID on the started span, instead of using a random number.
// If there is no parent Span (eg from ChildOf), then the TraceID will also be set to the
// value given here.
func WithSpanID(id uint64) StartSpanOption {
return func(cfg *ddtrace.StartSpanConfig) {
cfg.SpanID = id
}
}
// ChildOf tells StartSpan to use the given span context as a parent for the // ChildOf tells StartSpan to use the given span context as a parent for the
// created span. // created span.
func ChildOf(ctx ddtrace.SpanContext) StartSpanOption { func ChildOf(ctx ddtrace.SpanContext) StartSpanOption {
@ -179,7 +218,8 @@ func FinishTime(t time.Time) FinishOption {
} }
// WithError marks the span as having had an error. It uses the information from // WithError marks the span as having had an error. It uses the information from
// err to set tags such as the error message, error type and stack trace. // err to set tags such as the error message, error type and stack trace. It has
// no effect if the error is nil.
func WithError(err error) FinishOption { func WithError(err error) FinishOption {
return func(cfg *ddtrace.FinishConfig) { return func(cfg *ddtrace.FinishConfig) {
cfg.Error = err cfg.Error = err
@ -194,3 +234,14 @@ func NoDebugStack() FinishOption {
cfg.NoDebugStack = true cfg.NoDebugStack = true
} }
} }
// StackFrames limits the number of stack frames included into erroneous spans to n, starting from skip.
func StackFrames(n, skip uint) FinishOption {
if n == 0 {
return NoDebugStack()
}
return func(cfg *ddtrace.FinishConfig) {
cfg.StackFrames = n
cfg.SkipStackFrames = skip
}
}

View file

@ -140,5 +140,5 @@ func (ps *prioritySampler) apply(spn *span) {
} else { } else {
spn.SetTag(ext.SamplingPriority, ext.PriorityAutoReject) spn.SetTag(ext.SamplingPriority, ext.PriorityAutoReject)
} }
spn.SetTag(samplingPriorityRateKey, rate) spn.SetTag(keySamplingPriorityRate, rate)
} }

View file

@ -5,7 +5,9 @@ package tracer
import ( import (
"fmt" "fmt"
"reflect" "reflect"
"runtime"
"runtime/debug" "runtime/debug"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -30,6 +32,13 @@ var (
_ msgp.Decodable = (*spanLists)(nil) _ msgp.Decodable = (*spanLists)(nil)
) )
// errorConfig holds customization options for setting error tags.
type errorConfig struct {
noDebugStack bool
stackFrames uint
stackSkip uint
}
// span represents a computation. Callers must call Finish when a span is // span represents a computation. Callers must call Finish when a span is
// complete to ensure it's submitted. // complete to ensure it's submitted.
type span struct { type span struct {
@ -80,8 +89,13 @@ func (s *span) SetTag(key string, value interface{}) {
if s.finished { if s.finished {
return return
} }
if key == ext.Error { switch key {
s.setTagError(value, true) case ext.Error:
s.setTagError(value, &errorConfig{})
return
}
if v, ok := value.(bool); ok {
s.setTagBool(key, v)
return return
} }
if v, ok := value.(string); ok { if v, ok := value.(string); ok {
@ -99,7 +113,7 @@ func (s *span) SetTag(key string, value interface{}) {
// setTagError sets the error tag. It accounts for various valid scenarios. // setTagError sets the error tag. It accounts for various valid scenarios.
// This method is not safe for concurrent use. // This method is not safe for concurrent use.
func (s *span) setTagError(value interface{}, debugStack bool) { func (s *span) setTagError(value interface{}, cfg *errorConfig) {
if s.finished { if s.finished {
return return
} }
@ -117,8 +131,12 @@ func (s *span) setTagError(value interface{}, debugStack bool) {
s.Error = 1 s.Error = 1
s.Meta[ext.ErrorMsg] = v.Error() s.Meta[ext.ErrorMsg] = v.Error()
s.Meta[ext.ErrorType] = reflect.TypeOf(v).String() s.Meta[ext.ErrorType] = reflect.TypeOf(v).String()
if debugStack { if !cfg.noDebugStack {
if cfg.stackFrames == 0 {
s.Meta[ext.ErrorStack] = string(debug.Stack()) s.Meta[ext.ErrorStack] = string(debug.Stack())
} else {
s.Meta[ext.ErrorStack] = takeStacktrace(cfg.stackFrames, cfg.stackSkip)
}
} }
case nil: case nil:
// no error // no error
@ -130,9 +148,40 @@ func (s *span) setTagError(value interface{}, debugStack bool) {
} }
} }
// takeStacktrace takes stacktrace
func takeStacktrace(n, skip uint) string {
var builder strings.Builder
pcs := make([]uintptr, n)
// +2 to exclude runtime.Callers and takeStacktrace
numFrames := runtime.Callers(2+int(skip), pcs)
if numFrames == 0 {
return ""
}
frames := runtime.CallersFrames(pcs[:numFrames])
for i := 0; ; i++ {
frame, more := frames.Next()
if i != 0 {
builder.WriteByte('\n')
}
builder.WriteString(frame.Function)
builder.WriteByte('\n')
builder.WriteByte('\t')
builder.WriteString(frame.File)
builder.WriteByte(':')
builder.WriteString(strconv.Itoa(frame.Line))
if !more {
break
}
}
return builder.String()
}
// setTagString sets a string tag. This method is not safe for concurrent use. // setTagString sets a string tag. This method is not safe for concurrent use.
func (s *span) setTagString(key, v string) { func (s *span) setTagString(key, v string) {
switch key { switch key {
case ext.SpanName:
s.Name = v
case ext.ServiceName: case ext.ServiceName:
s.Service = v s.Service = v
case ext.ResourceName: case ext.ResourceName:
@ -144,13 +193,39 @@ func (s *span) setTagString(key, v string) {
} }
} }
// setTagBool sets a boolean tag on the span.
func (s *span) setTagBool(key string, v bool) {
switch key {
case ext.AnalyticsEvent:
if v {
s.setTagNumeric(ext.EventSampleRate, 1.0)
} else {
s.setTagNumeric(ext.EventSampleRate, 0.0)
}
case ext.ManualDrop:
if v {
s.setTagNumeric(ext.SamplingPriority, ext.PriorityUserReject)
}
case ext.ManualKeep:
if v {
s.setTagNumeric(ext.SamplingPriority, ext.PriorityUserKeep)
}
default:
if v {
s.setTagString(key, "true")
} else {
s.setTagString(key, "false")
}
}
}
// setTagNumeric sets a numeric tag, in our case called a metric. This method // setTagNumeric sets a numeric tag, in our case called a metric. This method
// is not safe for concurrent use. // is not safe for concurrent use.
func (s *span) setTagNumeric(key string, v float64) { func (s *span) setTagNumeric(key string, v float64) {
switch key { switch key {
case ext.SamplingPriority: case ext.SamplingPriority:
// setting sampling priority per spec // setting sampling priority per spec
s.Metrics[samplingPriorityKey] = v s.Metrics[keySamplingPriority] = v
s.context.setSamplingPriority(int(v)) s.context.setSamplingPriority(int(v))
default: default:
s.Metrics[key] = v s.Metrics[key] = v
@ -172,7 +247,11 @@ func (s *span) Finish(opts ...ddtrace.FinishOption) {
} }
if cfg.Error != nil { if cfg.Error != nil {
s.Lock() s.Lock()
s.setTagError(cfg.Error, !cfg.NoDebugStack) s.setTagError(cfg.Error, &errorConfig{
noDebugStack: cfg.NoDebugStack,
stackFrames: cfg.StackFrames,
stackSkip: cfg.SkipStackFrames,
})
s.Unlock() s.Unlock()
} }
s.finish(t) s.finish(t)
@ -236,6 +315,8 @@ func (s *span) String() string {
} }
const ( const (
samplingPriorityKey = "_sampling_priority_v1" keySamplingPriority = "_sampling_priority_v1"
samplingPriorityRateKey = "_sampling_priority_rate_v1" keySamplingPriorityRate = "_sampling_priority_rate_v1"
keyOrigin = "_dd.origin"
keyHostname = "_dd.hostname"
) )

View file

@ -27,8 +27,7 @@ type spanContext struct {
mu sync.RWMutex // guards below fields mu sync.RWMutex // guards below fields
baggage map[string]string baggage map[string]string
priority int origin string // e.g. "synthetics"
hasPriority bool
} }
// newSpanContext creates a new SpanContext to serve as context for the given // newSpanContext creates a new SpanContext to serve as context for the given
@ -42,15 +41,10 @@ func newSpanContext(span *span, parent *spanContext) *spanContext {
spanID: span.SpanID, spanID: span.SpanID,
span: span, span: span,
} }
if v, ok := span.Metrics[samplingPriorityKey]; ok {
context.hasPriority = true
context.priority = int(v)
}
if parent != nil { if parent != nil {
context.trace = parent.trace context.trace = parent.trace
context.drop = parent.drop context.drop = parent.drop
context.hasPriority = parent.hasSamplingPriority() context.origin = parent.origin
context.priority = parent.samplingPriority()
parent.ForeachBaggageItem(func(k, v string) bool { parent.ForeachBaggageItem(func(k, v string) bool {
context.setBaggageItem(k, v) context.setBaggageItem(k, v)
return true return true
@ -59,6 +53,10 @@ func newSpanContext(span *span, parent *spanContext) *spanContext {
if context.trace == nil { if context.trace == nil {
context.trace = newTrace() context.trace = newTrace()
} }
if context.trace.root == nil {
// first span in the trace can safely be assumed to be the root
context.trace.root = span
}
// put span in context's trace // put span in context's trace
context.trace.push(span) context.trace.push(span)
return context return context
@ -82,22 +80,21 @@ func (c *spanContext) ForeachBaggageItem(handler func(k, v string) bool) {
} }
func (c *spanContext) setSamplingPriority(p int) { func (c *spanContext) setSamplingPriority(p int) {
c.mu.Lock() if c.trace == nil {
defer c.mu.Unlock() c.trace = newTrace()
c.priority = p }
c.hasPriority = true c.trace.setSamplingPriority(float64(p))
} }
func (c *spanContext) samplingPriority() int { func (c *spanContext) samplingPriority() int {
c.mu.RLock() if c.trace == nil {
defer c.mu.RUnlock() return 0
return c.priority }
return c.trace.samplingPriority()
} }
func (c *spanContext) hasSamplingPriority() bool { func (c *spanContext) hasSamplingPriority() bool {
c.mu.RLock() return c.trace != nil && c.trace.hasSamplingPriority()
defer c.mu.RUnlock()
return c.hasPriority
} }
func (c *spanContext) setBaggageItem(key, val string) { func (c *spanContext) setBaggageItem(key, val string) {
@ -116,15 +113,23 @@ func (c *spanContext) baggageItem(key string) string {
} }
// finish marks this span as finished in the trace. // finish marks this span as finished in the trace.
func (c *spanContext) finish() { c.trace.ackFinish() } func (c *spanContext) finish() { c.trace.finishedOne(c.span) }
// trace holds information about a specific trace. This structure is shared // trace contains shared context information about a trace, such as sampling
// between all spans in a trace. // priority, the root reference and a buffer of the spans which are part of the
// trace, if these exist.
type trace struct { type trace struct {
mu sync.RWMutex // guards below fields mu sync.RWMutex // guards below fields
spans []*span // all the spans that are part of this trace spans []*span // all the spans that are part of this trace
finished int // the number of finished spans finished int // the number of finished spans
full bool // signifies that the span buffer is full full bool // signifies that the span buffer is full
priority *float64 // sampling priority
locked bool // specifies if the sampling priority can be altered
// root specifies the root of the trace, if known; it is nil when a span
// context is extracted from a carrier, at which point there are no spans in
// the trace yet.
root *span
} }
var ( var (
@ -146,6 +151,42 @@ func newTrace() *trace {
return &trace{spans: make([]*span, 0, traceStartSize)} return &trace{spans: make([]*span, 0, traceStartSize)}
} }
func (t *trace) hasSamplingPriority() bool {
t.mu.RLock()
defer t.mu.RUnlock()
return t.priority != nil
}
func (t *trace) samplingPriority() int {
t.mu.RLock()
defer t.mu.RUnlock()
if t.priority == nil {
return 0
}
return int(*t.priority)
}
func (t *trace) setSamplingPriority(p float64) {
t.mu.Lock()
defer t.mu.Unlock()
t.setSamplingPriorityLocked(p)
}
func (t *trace) setSamplingPriorityLocked(p float64) {
if t.locked {
return
}
if t.root == nil {
// this trace is distributed (no local root); modifications
// to the sampling priority are not allowed.
t.locked = true
}
if t.priority == nil {
t.priority = new(float64)
}
*t.priority = p
}
// push pushes a new span into the trace. If the buffer is full, it returns // push pushes a new span into the trace. If the buffer is full, it returns
// a errBufferFull error. // a errBufferFull error.
func (t *trace) push(sp *span) { func (t *trace) push(sp *span) {
@ -164,12 +205,16 @@ func (t *trace) push(sp *span) {
} }
return return
} }
if v, ok := sp.Metrics[keySamplingPriority]; ok {
t.setSamplingPriorityLocked(v)
}
t.spans = append(t.spans, sp) t.spans = append(t.spans, sp)
} }
// ackFinish aknowledges that another span in the trace has finished, and checks // finishedOne aknowledges that another span in the trace has finished, and checks
// if the trace is complete, in which case it calls the onFinish function. // if the trace is complete, in which case it calls the onFinish function. It uses
func (t *trace) ackFinish() { // the given priority, if non-nil, to mark the root span.
func (t *trace) finishedOne(s *span) {
t.mu.Lock() t.mu.Lock()
defer t.mu.Unlock() defer t.mu.Unlock()
if t.full { if t.full {
@ -180,6 +225,13 @@ func (t *trace) ackFinish() {
return return
} }
t.finished++ t.finished++
if s == t.root && t.priority != nil {
// after the root has finished we lock down the priority;
// we won't be able to make changes to a span after finishing
// without causing a race condition.
t.root.Metrics[keySamplingPriority] = *t.priority
t.locked = true
}
if len(t.spans) != t.finished { if len(t.spans) != t.finished {
return return
} }

View file

@ -2,10 +2,12 @@ package tracer
import ( import (
"net/http" "net/http"
"os"
"strconv" "strconv"
"strings" "strings"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
) )
// HTTPHeadersCarrier wraps an http.Header as a TextMapWriter and TextMapReader, allowing // HTTPHeadersCarrier wraps an http.Header as a TextMapWriter and TextMapReader, allowing
@ -54,6 +56,11 @@ func (c TextMapCarrier) ForeachKey(handler func(key, val string) error) error {
return nil return nil
} }
const (
headerPropagationStyleInject = "DD_PROPAGATION_STYLE_INJECT"
headerPropagationStyleExtract = "DD_PROPAGATION_STYLE_EXTRACT"
)
const ( const (
// DefaultBaggageHeaderPrefix specifies the prefix that will be used in // DefaultBaggageHeaderPrefix specifies the prefix that will be used in
// HTTP headers or text maps to prefix baggage keys. // HTTP headers or text maps to prefix baggage keys.
@ -72,6 +79,10 @@ const (
DefaultPriorityHeader = "x-datadog-sampling-priority" DefaultPriorityHeader = "x-datadog-sampling-priority"
) )
// originHeader specifies the name of the header indicating the origin of the trace.
// It is used with the Synthetics product and usually has the value "synthetics".
const originHeader = "x-datadog-origin"
// PropagatorConfig defines the configuration for initializing a propagator. // PropagatorConfig defines the configuration for initializing a propagator.
type PropagatorConfig struct { type PropagatorConfig struct {
// BaggagePrefix specifies the prefix that will be used to store baggage // BaggagePrefix specifies the prefix that will be used to store baggage
@ -110,21 +121,86 @@ func NewPropagator(cfg *PropagatorConfig) Propagator {
if cfg.PriorityHeader == "" { if cfg.PriorityHeader == "" {
cfg.PriorityHeader = DefaultPriorityHeader cfg.PriorityHeader = DefaultPriorityHeader
} }
return &propagator{cfg} return &chainedPropagator{
injectors: getPropagators(cfg, headerPropagationStyleInject),
extractors: getPropagators(cfg, headerPropagationStyleExtract),
}
} }
// propagator implements a propagator which uses TextMap internally. // chainedPropagator implements Propagator and applies a list of injectors and extractors.
// It propagates the trace and span IDs, as well as the baggage from the // When injecting, all injectors are called to propagate the span context.
// context. // When extracting, it tries each extractor, selecting the first successful one.
type propagator struct{ cfg *PropagatorConfig } type chainedPropagator struct {
injectors []Propagator
extractors []Propagator
}
// getPropagators returns a list of propagators based on the list found in the
// given environment variable. If the list doesn't contain a value or has invalid
// values, the default propagator will be returned.
func getPropagators(cfg *PropagatorConfig, env string) []Propagator {
dd := &propagator{cfg}
ps := os.Getenv(env)
if ps == "" {
return []Propagator{dd}
}
var list []Propagator
for _, v := range strings.Split(ps, ",") {
switch strings.ToLower(v) {
case "datadog":
list = append(list, dd)
case "b3":
list = append(list, &propagatorB3{})
default:
// TODO(cgilmour): consider logging something for invalid/unknown styles.
}
}
if len(list) == 0 {
// return the default
return []Propagator{dd}
}
return list
}
// Inject defines the Propagator to propagate SpanContext data // Inject defines the Propagator to propagate SpanContext data
// out of the current process. The implementation propagates the // out of the current process. The implementation propagates the
// TraceID and the current active SpanID, as well as the Span baggage. // TraceID and the current active SpanID, as well as the Span baggage.
func (p *chainedPropagator) Inject(spanCtx ddtrace.SpanContext, carrier interface{}) error {
for _, v := range p.injectors {
err := v.Inject(spanCtx, carrier)
if err != nil {
return err
}
}
return nil
}
// Extract implements Propagator.
func (p *chainedPropagator) Extract(carrier interface{}) (ddtrace.SpanContext, error) {
for _, v := range p.extractors {
ctx, err := v.Extract(carrier)
if ctx != nil {
// first extractor returns
return ctx, nil
}
if err == ErrSpanContextNotFound {
continue
}
return nil, err
}
return nil, ErrSpanContextNotFound
}
// propagator implements Propagator and injects/extracts span contexts
// using datadog headers. Only TextMap carriers are supported.
type propagator struct {
cfg *PropagatorConfig
}
func (p *propagator) Inject(spanCtx ddtrace.SpanContext, carrier interface{}) error { func (p *propagator) Inject(spanCtx ddtrace.SpanContext, carrier interface{}) error {
switch v := carrier.(type) { switch c := carrier.(type) {
case TextMapWriter: case TextMapWriter:
return p.injectTextMap(spanCtx, v) return p.injectTextMap(spanCtx, c)
default: default:
return ErrInvalidCarrier return ErrInvalidCarrier
} }
@ -141,6 +217,9 @@ func (p *propagator) injectTextMap(spanCtx ddtrace.SpanContext, writer TextMapWr
if ctx.hasSamplingPriority() { if ctx.hasSamplingPriority() {
writer.Set(p.cfg.PriorityHeader, strconv.Itoa(ctx.samplingPriority())) writer.Set(p.cfg.PriorityHeader, strconv.Itoa(ctx.samplingPriority()))
} }
if ctx.origin != "" {
writer.Set(originHeader, ctx.origin)
}
// propagate OpenTracing baggage // propagate OpenTracing baggage
for k, v := range ctx.baggage { for k, v := range ctx.baggage {
writer.Set(p.cfg.BaggagePrefix+k, v) writer.Set(p.cfg.BaggagePrefix+k, v)
@ -148,11 +227,10 @@ func (p *propagator) injectTextMap(spanCtx ddtrace.SpanContext, writer TextMapWr
return nil return nil
} }
// Extract implements Propagator.
func (p *propagator) Extract(carrier interface{}) (ddtrace.SpanContext, error) { func (p *propagator) Extract(carrier interface{}) (ddtrace.SpanContext, error) {
switch v := carrier.(type) { switch c := carrier.(type) {
case TextMapReader: case TextMapReader:
return p.extractTextMap(v) return p.extractTextMap(c)
default: default:
return nil, ErrInvalidCarrier return nil, ErrInvalidCarrier
} }
@ -180,6 +258,8 @@ func (p *propagator) extractTextMap(reader TextMapReader) (ddtrace.SpanContext,
return ErrSpanContextCorrupted return ErrSpanContextCorrupted
} }
ctx.setSamplingPriority(priority) ctx.setSamplingPriority(priority)
case originHeader:
ctx.origin = v
default: default:
if strings.HasPrefix(key, p.cfg.BaggagePrefix) { if strings.HasPrefix(key, p.cfg.BaggagePrefix) {
ctx.setBaggageItem(strings.TrimPrefix(key, p.cfg.BaggagePrefix), v) ctx.setBaggageItem(strings.TrimPrefix(key, p.cfg.BaggagePrefix), v)
@ -195,3 +275,83 @@ func (p *propagator) extractTextMap(reader TextMapReader) (ddtrace.SpanContext,
} }
return &ctx, nil return &ctx, nil
} }
const (
b3TraceIDHeader = "x-b3-traceid"
b3SpanIDHeader = "x-b3-spanid"
b3SampledHeader = "x-b3-sampled"
)
// propagatorB3 implements Propagator and injects/extracts span contexts
// using B3 headers. Only TextMap carriers are supported.
type propagatorB3 struct{}
func (p *propagatorB3) Inject(spanCtx ddtrace.SpanContext, carrier interface{}) error {
switch c := carrier.(type) {
case TextMapWriter:
return p.injectTextMap(spanCtx, c)
default:
return ErrInvalidCarrier
}
}
func (*propagatorB3) injectTextMap(spanCtx ddtrace.SpanContext, writer TextMapWriter) error {
ctx, ok := spanCtx.(*spanContext)
if !ok || ctx.traceID == 0 || ctx.spanID == 0 {
return ErrInvalidSpanContext
}
writer.Set(b3TraceIDHeader, strconv.FormatUint(ctx.traceID, 16))
writer.Set(b3SpanIDHeader, strconv.FormatUint(ctx.spanID, 16))
if ctx.hasSamplingPriority() {
if ctx.samplingPriority() >= ext.PriorityAutoKeep {
writer.Set(b3SampledHeader, "1")
} else {
writer.Set(b3SampledHeader, "0")
}
}
return nil
}
func (p *propagatorB3) Extract(carrier interface{}) (ddtrace.SpanContext, error) {
switch c := carrier.(type) {
case TextMapReader:
return p.extractTextMap(c)
default:
return nil, ErrInvalidCarrier
}
}
func (*propagatorB3) extractTextMap(reader TextMapReader) (ddtrace.SpanContext, error) {
var ctx spanContext
err := reader.ForeachKey(func(k, v string) error {
var err error
key := strings.ToLower(k)
switch key {
case b3TraceIDHeader:
ctx.traceID, err = strconv.ParseUint(v, 16, 64)
if err != nil {
return ErrSpanContextCorrupted
}
case b3SpanIDHeader:
ctx.spanID, err = strconv.ParseUint(v, 16, 64)
if err != nil {
return ErrSpanContextCorrupted
}
case b3SampledHeader:
priority, err := strconv.Atoi(v)
if err != nil {
return ErrSpanContextCorrupted
}
ctx.setSamplingPriority(priority)
default:
}
return nil
})
if err != nil {
return nil, err
}
if ctx.traceID == 0 || ctx.spanID == 0 {
return nil, ErrSpanContextNotFound
}
return &ctx, nil
}

View file

@ -44,6 +44,8 @@ type tracer struct {
// prioritySampling holds an instance of the priority sampler. // prioritySampling holds an instance of the priority sampler.
prioritySampling *prioritySampler prioritySampling *prioritySampler
// pid of the process
pid string
} }
const ( const (
@ -131,6 +133,7 @@ func newTracer(opts ...StartOption) *tracer {
errorBuffer: make(chan error, errorBufferSize), errorBuffer: make(chan error, errorBufferSize),
stopped: make(chan struct{}), stopped: make(chan struct{}),
prioritySampling: newPrioritySampler(), prioritySampling: newPrioritySampler(),
pid: strconv.Itoa(os.Getpid()),
} }
go t.worker() go t.worker()
@ -230,7 +233,10 @@ func (t *tracer) StartSpan(operationName string, options ...ddtrace.StartSpanOpt
context = ctx context = ctx
} }
} }
id := random.Uint64() id := opts.SpanID
if id == 0 {
id = random.Uint64()
}
// span defaults // span defaults
span := &span{ span := &span{
Name: operationName, Name: operationName,
@ -248,19 +254,28 @@ func (t *tracer) StartSpan(operationName string, options ...ddtrace.StartSpanOpt
span.TraceID = context.traceID span.TraceID = context.traceID
span.ParentID = context.spanID span.ParentID = context.spanID
if context.hasSamplingPriority() { if context.hasSamplingPriority() {
span.Metrics[samplingPriorityKey] = float64(context.samplingPriority()) span.Metrics[keySamplingPriority] = float64(context.samplingPriority())
} }
if context.span != nil { if context.span != nil {
// it has a local parent, inherit the service // local parent, inherit service
context.span.RLock() context.span.RLock()
span.Service = context.span.Service span.Service = context.span.Service
context.span.RUnlock() context.span.RUnlock()
} else {
// remote parent
if context.origin != "" {
// mark origin
span.Meta[keyOrigin] = context.origin
}
} }
} }
span.context = newSpanContext(span, context) span.context = newSpanContext(span, context)
if context == nil || context.span == nil { if context == nil || context.span == nil {
// this is either a root span or it has a remote parent, we should add the PID. // this is either a root span or it has a remote parent, we should add the PID.
span.SetTag(ext.Pid, strconv.Itoa(os.Getpid())) span.SetTag(ext.Pid, t.pid)
if t.hostname != "" {
span.SetTag(keyHostname, t.hostname)
}
} }
// add tags from options // add tags from options
for k, v := range opts.Tags { for k, v := range opts.Tags {
@ -361,7 +376,7 @@ const sampleRateMetricKey = "_sample_rate"
// Sample samples a span with the internal sampler. // Sample samples a span with the internal sampler.
func (t *tracer) sample(span *span) { func (t *tracer) sample(span *span) {
if span.context.hasPriority { if span.context.hasSamplingPriority() {
// sampling decision was already made // sampling decision was already made
return return
} }

View file

@ -15,7 +15,7 @@ import (
var ( var (
// TODO(gbbr): find a more effective way to keep this up to date, // TODO(gbbr): find a more effective way to keep this up to date,
// e.g. via `go generate` // e.g. via `go generate`
tracerVersion = "v1.7.0" tracerVersion = "v1.13.1"
// We copy the transport to avoid using the default one, as it might be // We copy the transport to avoid using the default one, as it might be
// augmented with tracing and we don't want these calls to be recorded. // augmented with tracing and we don't want these calls to be recorded.

View file

@ -0,0 +1,28 @@
// Package globalconfig stores configuration which applies globally to both the tracer
// and integrations.
package globalconfig
import "sync"
var cfg = &config{}
type config struct {
mu sync.RWMutex
analyticsRate float64
}
// AnalyticsRate returns the sampling rate at which events should be marked. It uses
// synchronizing mechanisms, meaning that for optimal performance it's best to read it
// once and store it.
func AnalyticsRate() float64 {
cfg.mu.RLock()
defer cfg.mu.RUnlock()
return cfg.analyticsRate
}
// SetAnalyticsRate sets the given event sampling rate globally.
func SetAnalyticsRate(rate float64) {
cfg.mu.Lock()
cfg.analyticsRate = rate
cfg.mu.Unlock()
}