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