1// Unless explicitly stated otherwise all files in this repository are licensed
2// under the Apache License Version 2.0.
3// This product includes software developed at Datadog (https://www.datadoghq.com/).
4// Copyright 2016 Datadog, Inc.
5
6package tracer
7
8import (
9	"context"
10	"math"
11	"net"
12	"net/http"
13	"os"
14	"path/filepath"
15	"runtime"
16	"strings"
17	"time"
18
19	"gopkg.in/DataDog/dd-trace-go.v1/ddtrace"
20	"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
21	"gopkg.in/DataDog/dd-trace-go.v1/internal"
22	"gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig"
23	"gopkg.in/DataDog/dd-trace-go.v1/internal/log"
24	"gopkg.in/DataDog/dd-trace-go.v1/internal/version"
25
26	"github.com/DataDog/datadog-go/statsd"
27)
28
29// config holds the tracer configuration.
30type config struct {
31	// debug, when true, writes details to logs.
32	debug bool
33
34	// lambda, when true, enables the lambda trace writer
35	logToStdout bool
36
37	// logStartup, when true, causes various startup info to be written
38	// when the tracer starts.
39	logStartup bool
40
41	// serviceName specifies the name of this application.
42	serviceName string
43
44	// version specifies the version of this application
45	version string
46
47	// env contains the environment that this application will run under.
48	env string
49
50	// sampler specifies the sampler that will be used for sampling traces.
51	sampler Sampler
52
53	// agentAddr specifies the hostname and port of the agent where the traces
54	// are sent to.
55	agentAddr string
56
57	// globalTags holds a set of tags that will be automatically applied to
58	// all spans.
59	globalTags map[string]interface{}
60
61	// transport specifies the Transport interface which will be used to send data to the agent.
62	transport transport
63
64	// propagator propagates span context cross-process
65	propagator Propagator
66
67	// httpClient specifies the HTTP client to be used by the agent's transport.
68	httpClient *http.Client
69
70	// hostname is automatically assigned when the DD_TRACE_REPORT_HOSTNAME is set to true,
71	// and is added as a special tag to the root span of traces.
72	hostname string
73
74	// logger specifies the logger to use when printing errors. If not specified, the "log" package
75	// will be used.
76	logger ddtrace.Logger
77
78	// runtimeMetrics specifies whether collection of runtime metrics is enabled.
79	runtimeMetrics bool
80
81	// dogstatsdAddr specifies the address to connect for sending metrics to the
82	// Datadog Agent. If not set, it defaults to "localhost:8125" or to the
83	// combination of the environment variables DD_AGENT_HOST and DD_DOGSTATSD_PORT.
84	dogstatsdAddr string
85
86	// statsd is used for tracking metrics associated with the runtime and the tracer.
87	statsd statsdClient
88
89	// samplingRules contains user-defined rules determine the sampling rate to apply
90	// to spans.
91	samplingRules []SamplingRule
92
93	// tickChan specifies a channel which will receive the time every time the tracer must flush.
94	// It defaults to time.Ticker; replaced in tests.
95	tickChan <-chan time.Time
96
97	// noDebugStack disables the collection of debug stack traces globally. No traces reporting
98	// errors will record a stack trace when this option is set.
99	noDebugStack bool
100}
101
102// StartOption represents a function that can be provided as a parameter to Start.
103type StartOption func(*config)
104
105// newConfig renders the tracer configuration based on defaults, environment variables
106// and passed user opts.
107func newConfig(opts ...StartOption) *config {
108	c := new(config)
109	c.sampler = NewAllSampler()
110	c.agentAddr = defaultAddress
111	statsdHost, statsdPort := "localhost", "8125"
112	if v := os.Getenv("DD_AGENT_HOST"); v != "" {
113		statsdHost = v
114	}
115	if v := os.Getenv("DD_DOGSTATSD_PORT"); v != "" {
116		statsdPort = v
117	}
118	c.dogstatsdAddr = net.JoinHostPort(statsdHost, statsdPort)
119
120	if internal.BoolEnv("DD_TRACE_ANALYTICS_ENABLED", false) {
121		globalconfig.SetAnalyticsRate(1.0)
122	}
123	if os.Getenv("DD_TRACE_REPORT_HOSTNAME") == "true" {
124		var err error
125		c.hostname, err = os.Hostname()
126		if err != nil {
127			log.Warn("unable to look up hostname: %v", err)
128		}
129	}
130	if v := os.Getenv("DD_TRACE_SOURCE_HOSTNAME"); v != "" {
131		c.hostname = v
132	}
133	if v := os.Getenv("DD_ENV"); v != "" {
134		c.env = v
135	}
136	if v := os.Getenv("DD_SERVICE"); v != "" {
137		c.serviceName = v
138		globalconfig.SetServiceName(v)
139	}
140	if ver := os.Getenv("DD_VERSION"); ver != "" {
141		c.version = ver
142	}
143	if v := os.Getenv("DD_TAGS"); v != "" {
144		sep := " "
145		if strings.Index(v, ",") > -1 {
146			// falling back to comma as separator
147			sep = ","
148		}
149		for _, tag := range strings.Split(v, sep) {
150			tag = strings.TrimSpace(tag)
151			if tag == "" {
152				continue
153			}
154			kv := strings.SplitN(tag, ":", 2)
155			key := strings.TrimSpace(kv[0])
156			if key == "" {
157				continue
158			}
159			var val string
160			if len(kv) == 2 {
161				val = strings.TrimSpace(kv[1])
162			}
163			WithGlobalTag(key, val)(c)
164		}
165	}
166	if _, ok := os.LookupEnv("AWS_LAMBDA_FUNCTION_NAME"); ok {
167		// AWS_LAMBDA_FUNCTION_NAME being set indicates that we're running in an AWS Lambda environment.
168		// See: https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html
169		c.logToStdout = true
170	}
171	c.logStartup = internal.BoolEnv("DD_TRACE_STARTUP_LOGS", true)
172	c.runtimeMetrics = internal.BoolEnv("DD_RUNTIME_METRICS_ENABLED", false)
173	c.debug = internal.BoolEnv("DD_TRACE_DEBUG", false)
174	for _, fn := range opts {
175		fn(c)
176	}
177	WithGlobalTag(ext.RuntimeID, globalconfig.RuntimeID())(c)
178	if c.env == "" {
179		if v, ok := c.globalTags["env"]; ok {
180			if e, ok := v.(string); ok {
181				c.env = e
182			}
183		}
184	}
185	if c.version == "" {
186		if v, ok := c.globalTags["version"]; ok {
187			if ver, ok := v.(string); ok {
188				c.version = ver
189			}
190		}
191	}
192	if c.serviceName == "" {
193		if v, ok := c.globalTags["service"]; ok {
194			if s, ok := v.(string); ok {
195				c.serviceName = s
196				globalconfig.SetServiceName(s)
197			}
198		} else {
199			c.serviceName = filepath.Base(os.Args[0])
200		}
201	}
202	if c.transport == nil {
203		c.transport = newTransport(c.agentAddr, c.httpClient)
204	}
205	if c.propagator == nil {
206		c.propagator = NewPropagator(nil)
207	}
208	if c.logger != nil {
209		log.UseLogger(c.logger)
210	}
211	if c.debug {
212		log.SetLevel(log.LevelDebug)
213	}
214	if c.statsd == nil {
215		client, err := statsd.New(c.dogstatsdAddr, statsd.WithMaxMessagesPerPayload(40), statsd.WithTags(statsTags(c)))
216		if err != nil {
217			log.Warn("Runtime and health metrics disabled: %v", err)
218			c.statsd = &statsd.NoOpClient{}
219		} else {
220			c.statsd = client
221		}
222	}
223	return c
224}
225
226func statsTags(c *config) []string {
227	tags := []string{
228		"lang:go",
229		"version:" + version.Tag,
230		"lang_version:" + runtime.Version(),
231	}
232	if c.serviceName != "" {
233		tags = append(tags, "service:"+c.serviceName)
234	}
235	if c.env != "" {
236		tags = append(tags, "env:"+c.env)
237	}
238	if c.hostname != "" {
239		tags = append(tags, "host:"+c.hostname)
240	}
241	for k, v := range c.globalTags {
242		if vstr, ok := v.(string); ok {
243			tags = append(tags, k+":"+vstr)
244		}
245	}
246	return tags
247}
248
249// WithLogger sets logger as the tracer's error printer.
250func WithLogger(logger ddtrace.Logger) StartOption {
251	return func(c *config) {
252		c.logger = logger
253	}
254}
255
256// WithPrioritySampling is deprecated, and priority sampling is enabled by default.
257// When using distributed tracing, the priority sampling value is propagated in order to
258// get all the parts of a distributed trace sampled.
259// To learn more about priority sampling, please visit:
260// https://docs.datadoghq.com/tracing/getting_further/trace_sampling_and_storage/#priority-sampling-for-distributed-tracing
261func WithPrioritySampling() StartOption {
262	return func(c *config) {
263		// This is now enabled by default.
264	}
265}
266
267// WithDebugStack can be used to globally enable or disable the collection of stack traces when
268// spans finish with errors. It is enabled by default. This is a global version of the NoDebugStack
269// FinishOption.
270func WithDebugStack(enabled bool) StartOption {
271	return func(c *config) {
272		c.noDebugStack = !enabled
273	}
274}
275
276// WithDebugMode enables debug mode on the tracer, resulting in more verbose logging.
277func WithDebugMode(enabled bool) StartOption {
278	return func(c *config) {
279		c.debug = enabled
280	}
281}
282
283// WithLambdaMode enables lambda mode on the tracer, for use with AWS Lambda.
284func WithLambdaMode(enabled bool) StartOption {
285	return func(c *config) {
286		c.logToStdout = enabled
287	}
288}
289
290// WithPropagator sets an alternative propagator to be used by the tracer.
291func WithPropagator(p Propagator) StartOption {
292	return func(c *config) {
293		c.propagator = p
294	}
295}
296
297// WithServiceName is deprecated. Please use WithService.
298// If you are using an older version and you are upgrading from WithServiceName
299// to WithService, please note that WithService will determine the service name of
300// server and framework integrations.
301func WithServiceName(name string) StartOption {
302	return func(c *config) {
303		c.serviceName = name
304		if globalconfig.ServiceName() != "" {
305			log.Warn("ddtrace/tracer: deprecated config WithServiceName should not be used " +
306				"with `WithService` or `DD_SERVICE`; integration service name will not be set.")
307		}
308		globalconfig.SetServiceName("")
309	}
310}
311
312// WithService sets the default service name for the program.
313func WithService(name string) StartOption {
314	return func(c *config) {
315		c.serviceName = name
316		globalconfig.SetServiceName(c.serviceName)
317	}
318}
319
320// WithAgentAddr sets the address where the agent is located. The default is
321// localhost:8126. It should contain both host and port.
322func WithAgentAddr(addr string) StartOption {
323	return func(c *config) {
324		c.agentAddr = addr
325	}
326}
327
328// WithEnv sets the environment to which all traces started by the tracer will be submitted.
329// The default value is the environment variable DD_ENV, if it is set.
330func WithEnv(env string) StartOption {
331	return func(c *config) {
332		c.env = env
333	}
334}
335
336// WithGlobalTag sets a key/value pair which will be set as a tag on all spans
337// created by tracer. This option may be used multiple times.
338func WithGlobalTag(k string, v interface{}) StartOption {
339	return func(c *config) {
340		if c.globalTags == nil {
341			c.globalTags = make(map[string]interface{})
342		}
343		c.globalTags[k] = v
344	}
345}
346
347// WithSampler sets the given sampler to be used with the tracer. By default
348// an all-permissive sampler is used.
349func WithSampler(s Sampler) StartOption {
350	return func(c *config) {
351		c.sampler = s
352	}
353}
354
355// WithHTTPRoundTripper is deprecated. Please consider using WithHTTPClient instead.
356// The function allows customizing the underlying HTTP transport for emitting spans.
357func WithHTTPRoundTripper(r http.RoundTripper) StartOption {
358	return WithHTTPClient(&http.Client{
359		Transport: r,
360		Timeout:   defaultHTTPTimeout,
361	})
362}
363
364// WithHTTPClient specifies the HTTP client to use when emitting spans to the agent.
365func WithHTTPClient(client *http.Client) StartOption {
366	return func(c *config) {
367		c.httpClient = client
368	}
369}
370
371// WithUDS configures the HTTP client to dial the Datadog Agent via the specified Unix Domain Socket path.
372func WithUDS(socketPath string) StartOption {
373	return WithHTTPClient(&http.Client{
374		Transport: &http.Transport{
375			DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
376				return net.Dial("unix", socketPath)
377			},
378		},
379		Timeout: defaultHTTPTimeout,
380	})
381}
382
383// WithAnalytics allows specifying whether Trace Search & Analytics should be enabled
384// for integrations.
385func WithAnalytics(on bool) StartOption {
386	return func(cfg *config) {
387		if on {
388			globalconfig.SetAnalyticsRate(1.0)
389		} else {
390			globalconfig.SetAnalyticsRate(math.NaN())
391		}
392	}
393}
394
395// WithAnalyticsRate sets the global sampling rate for sampling APM events.
396func WithAnalyticsRate(rate float64) StartOption {
397	return func(_ *config) {
398		if rate >= 0.0 && rate <= 1.0 {
399			globalconfig.SetAnalyticsRate(rate)
400		} else {
401			globalconfig.SetAnalyticsRate(math.NaN())
402		}
403	}
404}
405
406// WithRuntimeMetrics enables automatic collection of runtime metrics every 10 seconds.
407func WithRuntimeMetrics() StartOption {
408	return func(cfg *config) {
409		cfg.runtimeMetrics = true
410	}
411}
412
413// WithDogstatsdAddress specifies the address to connect to for sending metrics
414// to the Datadog Agent. If not set, it defaults to "localhost:8125" or to the
415// combination of the environment variables DD_AGENT_HOST and DD_DOGSTATSD_PORT.
416// This option is in effect when WithRuntimeMetrics is enabled.
417func WithDogstatsdAddress(addr string) StartOption {
418	return func(cfg *config) {
419		cfg.dogstatsdAddr = addr
420	}
421}
422
423// WithSamplingRules specifies the sampling rates to apply to spans based on the
424// provided rules.
425func WithSamplingRules(rules []SamplingRule) StartOption {
426	return func(cfg *config) {
427		cfg.samplingRules = rules
428	}
429}
430
431// WithServiceVersion specifies the version of the service that is running. This will
432// be included in spans from this service in the "version" tag.
433func WithServiceVersion(version string) StartOption {
434	return func(cfg *config) {
435		cfg.version = version
436	}
437}
438
439// WithHostname allows specifying the hostname with which to mark outgoing traces.
440func WithHostname(name string) StartOption {
441	return func(c *config) {
442		c.hostname = name
443	}
444}
445
446// StartSpanOption is a configuration option for StartSpan. It is aliased in order
447// to help godoc group all the functions returning it together. It is considered
448// more correct to refer to it as the type as the origin, ddtrace.StartSpanOption.
449type StartSpanOption = ddtrace.StartSpanOption
450
451// Tag sets the given key/value pair as a tag on the started Span.
452func Tag(k string, v interface{}) StartSpanOption {
453	return func(cfg *ddtrace.StartSpanConfig) {
454		if cfg.Tags == nil {
455			cfg.Tags = map[string]interface{}{}
456		}
457		cfg.Tags[k] = v
458	}
459}
460
461// ServiceName sets the given service name on the started span. For example "http.server".
462func ServiceName(name string) StartSpanOption {
463	return Tag(ext.ServiceName, name)
464}
465
466// ResourceName sets the given resource name on the started span. A resource could
467// be an SQL query, a URL, an RPC method or something else.
468func ResourceName(name string) StartSpanOption {
469	return Tag(ext.ResourceName, name)
470}
471
472// SpanType sets the given span type on the started span. Some examples in the case of
473// the Datadog APM product could be "web", "db" or "cache".
474func SpanType(name string) StartSpanOption {
475	return Tag(ext.SpanType, name)
476}
477
478// Measured marks this span to be measured for metrics and stats calculations.
479func Measured() StartSpanOption {
480	return Tag(keyMeasured, 1)
481}
482
483// WithSpanID sets the SpanID on the started span, instead of using a random number.
484// If there is no parent Span (eg from ChildOf), then the TraceID will also be set to the
485// value given here.
486func WithSpanID(id uint64) StartSpanOption {
487	return func(cfg *ddtrace.StartSpanConfig) {
488		cfg.SpanID = id
489	}
490}
491
492// ChildOf tells StartSpan to use the given span context as a parent for the
493// created span.
494func ChildOf(ctx ddtrace.SpanContext) StartSpanOption {
495	return func(cfg *ddtrace.StartSpanConfig) {
496		cfg.Parent = ctx
497	}
498}
499
500// StartTime sets a custom time as the start time for the created span. By
501// default a span is started using the creation time.
502func StartTime(t time.Time) StartSpanOption {
503	return func(cfg *ddtrace.StartSpanConfig) {
504		cfg.StartTime = t
505	}
506}
507
508// AnalyticsRate sets a custom analytics rate for a span. It decides the percentage
509// of events that will be picked up by the App Analytics product. It's represents a
510// float64 between 0 and 1 where 0.5 would represent 50% of events.
511func AnalyticsRate(rate float64) StartSpanOption {
512	if math.IsNaN(rate) {
513		return func(cfg *ddtrace.StartSpanConfig) {}
514	}
515	return Tag(ext.EventSampleRate, rate)
516}
517
518// FinishOption is a configuration option for FinishSpan. It is aliased in order
519// to help godoc group all the functions returning it together. It is considered
520// more correct to refer to it as the type as the origin, ddtrace.FinishOption.
521type FinishOption = ddtrace.FinishOption
522
523// FinishTime sets the given time as the finishing time for the span. By default,
524// the current time is used.
525func FinishTime(t time.Time) FinishOption {
526	return func(cfg *ddtrace.FinishConfig) {
527		cfg.FinishTime = t
528	}
529}
530
531// WithError marks the span as having had an error. It uses the information from
532// err to set tags such as the error message, error type and stack trace. It has
533// no effect if the error is nil.
534func WithError(err error) FinishOption {
535	return func(cfg *ddtrace.FinishConfig) {
536		cfg.Error = err
537	}
538}
539
540// NoDebugStack prevents any error presented using the WithError finishing option
541// from generating a stack trace. This is useful in situations where errors are frequent
542// and performance is critical.
543func NoDebugStack() FinishOption {
544	return func(cfg *ddtrace.FinishConfig) {
545		cfg.NoDebugStack = true
546	}
547}
548
549// StackFrames limits the number of stack frames included into erroneous spans to n, starting from skip.
550func StackFrames(n, skip uint) FinishOption {
551	if n == 0 {
552		return NoDebugStack()
553	}
554	return func(cfg *ddtrace.FinishConfig) {
555		cfg.StackFrames = n
556		cfg.SkipStackFrames = skip
557	}
558}
559