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

View file

@ -285,7 +285,7 @@ required = [
[[constraint]]
name = "gopkg.in/DataDog/dd-trace-go.v1"
version = "1.7.0"
version = "1.13.0"
[[constraint]]
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 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
@ -108,4 +114,8 @@ type StartSpanConfig struct {
// Tags holds a set of key/value pairs that should be set as metadata on the
// new span.
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 = "http.url"
// TODO: In the next major version, suffix these constants (SpanType, etc)
// with "*Key" (SpanTypeKey, etc) to more easily differentiate between
// TODO: In the next major version, prefix these constants (SpanType, etc)
// with "Key*" (KeySpanType, etc) to more easily differentiate between
// 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 = "span.type"
@ -54,4 +59,20 @@ const (
// Environment specifies the environment to use with a trace.
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}
}
// 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.
func SpanType(name string) opentracing.StartSpanOption {
return opentracing.Tag{Key: ext.SpanType, Value: name}

View file

@ -1,6 +1,7 @@
package tracer
import (
"log"
"net/http"
"os"
"path/filepath"
@ -8,6 +9,7 @@ import (
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
"gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig"
)
// config holds the tracer configuration.
@ -21,7 +23,7 @@ type config struct {
// sampler specifies the sampler that will be used for sampling traces.
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.
agentAddr string
@ -37,6 +39,10 @@ type config struct {
// httpRoundTripper defines the http.RoundTripper used by the agent transport.
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.
@ -47,6 +53,14 @@ func defaults(c *config) {
c.serviceName = filepath.Base(os.Args[0])
c.sampler = NewAllSampler()
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.
@ -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
// 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.
@ -149,6 +179,15 @@ func SpanType(name string) StartSpanOption {
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
// created span.
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
// 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 {
return func(cfg *ddtrace.FinishConfig) {
cfg.Error = err
@ -194,3 +234,14 @@ func NoDebugStack() FinishOption {
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 {
spn.SetTag(ext.SamplingPriority, ext.PriorityAutoReject)
}
spn.SetTag(samplingPriorityRateKey, rate)
spn.SetTag(keySamplingPriorityRate, rate)
}

View file

@ -5,7 +5,9 @@ package tracer
import (
"fmt"
"reflect"
"runtime"
"runtime/debug"
"strconv"
"strings"
"sync"
"time"
@ -30,6 +32,13 @@ var (
_ 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
// complete to ensure it's submitted.
type span struct {
@ -80,8 +89,13 @@ func (s *span) SetTag(key string, value interface{}) {
if s.finished {
return
}
if key == ext.Error {
s.setTagError(value, true)
switch key {
case ext.Error:
s.setTagError(value, &errorConfig{})
return
}
if v, ok := value.(bool); ok {
s.setTagBool(key, v)
return
}
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.
// 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 {
return
}
@ -117,8 +131,12 @@ func (s *span) setTagError(value interface{}, debugStack bool) {
s.Error = 1
s.Meta[ext.ErrorMsg] = v.Error()
s.Meta[ext.ErrorType] = reflect.TypeOf(v).String()
if debugStack {
s.Meta[ext.ErrorStack] = string(debug.Stack())
if !cfg.noDebugStack {
if cfg.stackFrames == 0 {
s.Meta[ext.ErrorStack] = string(debug.Stack())
} else {
s.Meta[ext.ErrorStack] = takeStacktrace(cfg.stackFrames, cfg.stackSkip)
}
}
case nil:
// 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.
func (s *span) setTagString(key, v string) {
switch key {
case ext.SpanName:
s.Name = v
case ext.ServiceName:
s.Service = v
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
// is not safe for concurrent use.
func (s *span) setTagNumeric(key string, v float64) {
switch key {
case ext.SamplingPriority:
// setting sampling priority per spec
s.Metrics[samplingPriorityKey] = v
s.Metrics[keySamplingPriority] = v
s.context.setSamplingPriority(int(v))
default:
s.Metrics[key] = v
@ -172,7 +247,11 @@ func (s *span) Finish(opts ...ddtrace.FinishOption) {
}
if cfg.Error != nil {
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.finish(t)
@ -236,6 +315,8 @@ func (s *span) String() string {
}
const (
samplingPriorityKey = "_sampling_priority_v1"
samplingPriorityRateKey = "_sampling_priority_rate_v1"
keySamplingPriority = "_sampling_priority_v1"
keySamplingPriorityRate = "_sampling_priority_rate_v1"
keyOrigin = "_dd.origin"
keyHostname = "_dd.hostname"
)

View file

@ -25,10 +25,9 @@ type spanContext struct {
traceID uint64
spanID uint64
mu sync.RWMutex // guards below fields
baggage map[string]string
priority int
hasPriority bool
mu sync.RWMutex // guards below fields
baggage map[string]string
origin string // e.g. "synthetics"
}
// 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,
span: span,
}
if v, ok := span.Metrics[samplingPriorityKey]; ok {
context.hasPriority = true
context.priority = int(v)
}
if parent != nil {
context.trace = parent.trace
context.drop = parent.drop
context.hasPriority = parent.hasSamplingPriority()
context.priority = parent.samplingPriority()
context.origin = parent.origin
parent.ForeachBaggageItem(func(k, v string) bool {
context.setBaggageItem(k, v)
return true
@ -59,6 +53,10 @@ func newSpanContext(span *span, parent *spanContext) *spanContext {
if context.trace == nil {
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
context.trace.push(span)
return context
@ -82,22 +80,21 @@ func (c *spanContext) ForeachBaggageItem(handler func(k, v string) bool) {
}
func (c *spanContext) setSamplingPriority(p int) {
c.mu.Lock()
defer c.mu.Unlock()
c.priority = p
c.hasPriority = true
if c.trace == nil {
c.trace = newTrace()
}
c.trace.setSamplingPriority(float64(p))
}
func (c *spanContext) samplingPriority() int {
c.mu.RLock()
defer c.mu.RUnlock()
return c.priority
if c.trace == nil {
return 0
}
return c.trace.samplingPriority()
}
func (c *spanContext) hasSamplingPriority() bool {
c.mu.RLock()
defer c.mu.RUnlock()
return c.hasPriority
return c.trace != nil && c.trace.hasSamplingPriority()
}
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.
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
// between all spans in a trace.
// trace contains shared context information about a trace, such as sampling
// priority, the root reference and a buffer of the spans which are part of the
// trace, if these exist.
type trace struct {
mu sync.RWMutex // guards below fields
spans []*span // all the spans that are part of this trace
finished int // the number of finished spans
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 (
@ -146,6 +151,42 @@ func newTrace() *trace {
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
// a errBufferFull error.
func (t *trace) push(sp *span) {
@ -164,12 +205,16 @@ func (t *trace) push(sp *span) {
}
return
}
if v, ok := sp.Metrics[keySamplingPriority]; ok {
t.setSamplingPriorityLocked(v)
}
t.spans = append(t.spans, sp)
}
// ackFinish aknowledges that another span in the trace has finished, and checks
// if the trace is complete, in which case it calls the onFinish function.
func (t *trace) ackFinish() {
// 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. It uses
// the given priority, if non-nil, to mark the root span.
func (t *trace) finishedOne(s *span) {
t.mu.Lock()
defer t.mu.Unlock()
if t.full {
@ -180,6 +225,13 @@ func (t *trace) ackFinish() {
return
}
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 {
return
}

View file

@ -2,10 +2,12 @@ package tracer
import (
"net/http"
"os"
"strconv"
"strings"
"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
@ -54,6 +56,11 @@ func (c TextMapCarrier) ForeachKey(handler func(key, val string) error) error {
return nil
}
const (
headerPropagationStyleInject = "DD_PROPAGATION_STYLE_INJECT"
headerPropagationStyleExtract = "DD_PROPAGATION_STYLE_EXTRACT"
)
const (
// DefaultBaggageHeaderPrefix specifies the prefix that will be used in
// HTTP headers or text maps to prefix baggage keys.
@ -72,6 +79,10 @@ const (
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.
type PropagatorConfig struct {
// BaggagePrefix specifies the prefix that will be used to store baggage
@ -110,21 +121,86 @@ func NewPropagator(cfg *PropagatorConfig) Propagator {
if cfg.PriorityHeader == "" {
cfg.PriorityHeader = DefaultPriorityHeader
}
return &propagator{cfg}
return &chainedPropagator{
injectors: getPropagators(cfg, headerPropagationStyleInject),
extractors: getPropagators(cfg, headerPropagationStyleExtract),
}
}
// propagator implements a propagator which uses TextMap internally.
// It propagates the trace and span IDs, as well as the baggage from the
// context.
type propagator struct{ cfg *PropagatorConfig }
// chainedPropagator implements Propagator and applies a list of injectors and extractors.
// When injecting, all injectors are called to propagate the span context.
// When extracting, it tries each extractor, selecting the first successful one.
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
// out of the current process. The implementation propagates the
// 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 {
switch v := carrier.(type) {
switch c := carrier.(type) {
case TextMapWriter:
return p.injectTextMap(spanCtx, v)
return p.injectTextMap(spanCtx, c)
default:
return ErrInvalidCarrier
}
@ -141,6 +217,9 @@ func (p *propagator) injectTextMap(spanCtx ddtrace.SpanContext, writer TextMapWr
if ctx.hasSamplingPriority() {
writer.Set(p.cfg.PriorityHeader, strconv.Itoa(ctx.samplingPriority()))
}
if ctx.origin != "" {
writer.Set(originHeader, ctx.origin)
}
// propagate OpenTracing baggage
for k, v := range ctx.baggage {
writer.Set(p.cfg.BaggagePrefix+k, v)
@ -148,11 +227,10 @@ func (p *propagator) injectTextMap(spanCtx ddtrace.SpanContext, writer TextMapWr
return nil
}
// Extract implements Propagator.
func (p *propagator) Extract(carrier interface{}) (ddtrace.SpanContext, error) {
switch v := carrier.(type) {
switch c := carrier.(type) {
case TextMapReader:
return p.extractTextMap(v)
return p.extractTextMap(c)
default:
return nil, ErrInvalidCarrier
}
@ -180,6 +258,8 @@ func (p *propagator) extractTextMap(reader TextMapReader) (ddtrace.SpanContext,
return ErrSpanContextCorrupted
}
ctx.setSamplingPriority(priority)
case originHeader:
ctx.origin = v
default:
if strings.HasPrefix(key, p.cfg.BaggagePrefix) {
ctx.setBaggageItem(strings.TrimPrefix(key, p.cfg.BaggagePrefix), v)
@ -195,3 +275,83 @@ func (p *propagator) extractTextMap(reader TextMapReader) (ddtrace.SpanContext,
}
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 *prioritySampler
// pid of the process
pid string
}
const (
@ -131,6 +133,7 @@ func newTracer(opts ...StartOption) *tracer {
errorBuffer: make(chan error, errorBufferSize),
stopped: make(chan struct{}),
prioritySampling: newPrioritySampler(),
pid: strconv.Itoa(os.Getpid()),
}
go t.worker()
@ -230,7 +233,10 @@ func (t *tracer) StartSpan(operationName string, options ...ddtrace.StartSpanOpt
context = ctx
}
}
id := random.Uint64()
id := opts.SpanID
if id == 0 {
id = random.Uint64()
}
// span defaults
span := &span{
Name: operationName,
@ -248,19 +254,28 @@ func (t *tracer) StartSpan(operationName string, options ...ddtrace.StartSpanOpt
span.TraceID = context.traceID
span.ParentID = context.spanID
if context.hasSamplingPriority() {
span.Metrics[samplingPriorityKey] = float64(context.samplingPriority())
span.Metrics[keySamplingPriority] = float64(context.samplingPriority())
}
if context.span != nil {
// it has a local parent, inherit the service
// local parent, inherit service
context.span.RLock()
span.Service = context.span.Service
context.span.RUnlock()
} else {
// remote parent
if context.origin != "" {
// mark origin
span.Meta[keyOrigin] = context.origin
}
}
}
span.context = newSpanContext(span, context)
if context == nil || context.span == nil {
// 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
for k, v := range opts.Tags {
@ -361,7 +376,7 @@ const sampleRateMetricKey = "_sample_rate"
// Sample samples a span with the internal sampler.
func (t *tracer) sample(span *span) {
if span.context.hasPriority {
if span.context.hasSamplingPriority() {
// sampling decision was already made
return
}

View file

@ -15,7 +15,7 @@ import (
var (
// TODO(gbbr): find a more effective way to keep this up to date,
// 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
// 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()
}