dd-trace 5.25.0 → 5.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/LICENSE-3rdparty.csv +2 -0
  2. package/index.d.ts +10 -8
  3. package/init.js +60 -47
  4. package/package.json +5 -2
  5. package/packages/datadog-core/index.js +1 -3
  6. package/packages/datadog-core/src/storage.js +21 -0
  7. package/packages/datadog-instrumentations/src/express.js +1 -1
  8. package/packages/datadog-instrumentations/src/handlebars.js +40 -0
  9. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  10. package/packages/datadog-instrumentations/src/jest.js +6 -2
  11. package/packages/datadog-instrumentations/src/pug.js +23 -0
  12. package/packages/datadog-instrumentations/src/router.js +2 -3
  13. package/packages/datadog-plugin-amqplib/src/consumer.js +2 -1
  14. package/packages/datadog-plugin-aws-sdk/src/base.js +5 -0
  15. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +9 -7
  16. package/packages/datadog-plugin-aws-sdk/src/services/s3.js +34 -0
  17. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +10 -9
  18. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +59 -45
  19. package/packages/datadog-plugin-cypress/src/support.js +1 -0
  20. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -1
  21. package/packages/datadog-plugin-grpc/src/server.js +2 -1
  22. package/packages/datadog-plugin-http/src/client.js +42 -1
  23. package/packages/datadog-plugin-http2/src/client.js +26 -1
  24. package/packages/datadog-plugin-jest/src/index.js +2 -1
  25. package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -1
  26. package/packages/datadog-plugin-mocha/src/index.js +1 -1
  27. package/packages/datadog-plugin-moleculer/src/server.js +2 -2
  28. package/packages/datadog-plugin-rhea/src/consumer.js +2 -1
  29. package/packages/datadog-plugin-vitest/src/index.js +2 -1
  30. package/packages/dd-trace/src/appsec/api_security_sampler.js +50 -27
  31. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  32. package/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js +33 -16
  33. package/packages/dd-trace/src/appsec/iast/analyzers/template-injection-analyzer.js +18 -0
  34. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +3 -2
  35. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  36. package/packages/dd-trace/src/appsec/index.js +6 -6
  37. package/packages/dd-trace/src/appsec/recommended.json +353 -155
  38. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +1 -1
  39. package/packages/dd-trace/src/appsec/remote_config/index.js +0 -7
  40. package/packages/dd-trace/src/appsec/reporter.js +1 -0
  41. package/packages/dd-trace/src/config.js +13 -4
  42. package/packages/dd-trace/src/constants.js +6 -1
  43. package/packages/dd-trace/src/crashtracking/crashtracker.js +98 -0
  44. package/packages/dd-trace/src/crashtracking/index.js +15 -0
  45. package/packages/dd-trace/src/crashtracking/noop.js +8 -0
  46. package/packages/dd-trace/src/llmobs/sdk.js +1 -1
  47. package/packages/dd-trace/src/llmobs/span_processor.js +1 -1
  48. package/packages/dd-trace/src/llmobs/writers/spans/base.js +3 -0
  49. package/packages/dd-trace/src/log/index.js +10 -13
  50. package/packages/dd-trace/src/log/log.js +52 -0
  51. package/packages/dd-trace/src/log/writer.js +50 -19
  52. package/packages/dd-trace/src/noop/span.js +1 -0
  53. package/packages/dd-trace/src/opentelemetry/span.js +15 -0
  54. package/packages/dd-trace/src/opentracing/propagation/text_map.js +35 -22
  55. package/packages/dd-trace/src/opentracing/span.js +14 -0
  56. package/packages/dd-trace/src/opentracing/span_context.js +1 -0
  57. package/packages/dd-trace/src/plugins/tracing.js +3 -3
  58. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +121 -0
  59. package/packages/dd-trace/src/plugins/util/ip_extractor.js +0 -1
  60. package/packages/dd-trace/src/plugins/util/web.js +39 -11
  61. package/packages/dd-trace/src/proxy.js +5 -0
  62. package/packages/dd-trace/src/telemetry/logs/index.js +16 -11
  63. package/packages/dd-trace/src/telemetry/logs/log-collector.js +3 -8
  64. package/packages/dd-trace/src/telemetry/metrics.js +6 -1
  65. package/packages/dd-trace/src/util.js +16 -1
  66. package/version.js +4 -2
  67. /package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/{code-injection-sensitive-analyzer.js → tainted-range-based-sensitive-analyzer.js} +0 -0
@@ -11,7 +11,7 @@ module.exports = {
11
11
  ASM_CUSTOM_RULES: 1n << 8n,
12
12
  ASM_CUSTOM_BLOCKING_RESPONSE: 1n << 9n,
13
13
  ASM_TRUSTED_IPS: 1n << 10n,
14
- ASM_API_SECURITY_SAMPLE_RATE: 1n << 11n,
14
+ ASM_API_SECURITY_SAMPLE_RATE: 1n << 11n, // deprecated
15
15
  APM_TRACING_SAMPLE_RATE: 1n << 12n,
16
16
  APM_TRACING_LOGS_INJECTION: 1n << 13n,
17
17
  APM_TRACING_HTTP_HEADER_TAGS: 1n << 14n,
@@ -4,7 +4,6 @@ const Activation = require('../activation')
4
4
 
5
5
  const RemoteConfigManager = require('./manager')
6
6
  const RemoteConfigCapabilities = require('./capabilities')
7
- const apiSecuritySampler = require('../api_security_sampler')
8
7
 
9
8
  let rc
10
9
 
@@ -24,18 +23,12 @@ function enable (config, appsec) {
24
23
  rc.updateCapabilities(RemoteConfigCapabilities.ASM_ACTIVATION, true)
25
24
  }
26
25
 
27
- if (config.appsec.apiSecurity?.enabled) {
28
- rc.updateCapabilities(RemoteConfigCapabilities.ASM_API_SECURITY_SAMPLE_RATE, true)
29
- }
30
-
31
26
  rc.setProductHandler('ASM_FEATURES', (action, rcConfig) => {
32
27
  if (!rcConfig) return
33
28
 
34
29
  if (activation === Activation.ONECLICK) {
35
30
  enableOrDisableAppsec(action, rcConfig, config, appsec)
36
31
  }
37
-
38
- apiSecuritySampler.setRequestSampling(rcConfig.api_security?.request_sample_rate)
39
32
  })
40
33
  }
41
34
 
@@ -32,6 +32,7 @@ const contentHeaderList = [
32
32
 
33
33
  const EVENT_HEADERS_MAP = mapHeaderAndTags([
34
34
  ...ipHeaderList,
35
+ 'x-forwarded',
35
36
  'forwarded',
36
37
  'via',
37
38
  ...contentHeaderList,
@@ -444,7 +444,7 @@ class Config {
444
444
  const defaults = setHiddenProperty(this, '_defaults', {})
445
445
 
446
446
  this._setValue(defaults, 'appsec.apiSecurity.enabled', true)
447
- this._setValue(defaults, 'appsec.apiSecurity.requestSampling', 0.1)
447
+ this._setValue(defaults, 'appsec.apiSecurity.sampleDelay', 30)
448
448
  this._setValue(defaults, 'appsec.blockedTemplateGraphql', undefined)
449
449
  this._setValue(defaults, 'appsec.blockedTemplateHtml', undefined)
450
450
  this._setValue(defaults, 'appsec.blockedTemplateJson', undefined)
@@ -467,6 +467,7 @@ class Config {
467
467
  this._setValue(defaults, 'ciVisibilityTestSessionName', '')
468
468
  this._setValue(defaults, 'clientIpEnabled', false)
469
469
  this._setValue(defaults, 'clientIpHeader', null)
470
+ this._setValue(defaults, 'crashtracking.enabled', false)
470
471
  this._setValue(defaults, 'codeOriginForSpans.enabled', false)
471
472
  this._setValue(defaults, 'dbmPropagationMode', 'disabled')
472
473
  this._setValue(defaults, 'dogstatsd.hostname', '127.0.0.1')
@@ -513,6 +514,7 @@ class Config {
513
514
  this._setValue(defaults, 'isTestDynamicInstrumentationEnabled', false)
514
515
  this._setValue(defaults, 'logInjection', false)
515
516
  this._setValue(defaults, 'lookup', undefined)
517
+ this._setValue(defaults, 'inferredProxyServicesEnabled', false)
516
518
  this._setValue(defaults, 'memcachedCommandEnabled', false)
517
519
  this._setValue(defaults, 'openAiLogsEnabled', false)
518
520
  this._setValue(defaults, 'openaiSpanCharLimit', 128)
@@ -569,7 +571,7 @@ class Config {
569
571
  AWS_LAMBDA_FUNCTION_NAME,
570
572
  DD_AGENT_HOST,
571
573
  DD_API_SECURITY_ENABLED,
572
- DD_API_SECURITY_REQUEST_SAMPLE_RATE,
574
+ DD_API_SECURITY_SAMPLE_DELAY,
573
575
  DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING,
574
576
  DD_APPSEC_ENABLED,
575
577
  DD_APPSEC_GRAPHQL_BLOCKED_TEMPLATE_JSON,
@@ -585,6 +587,7 @@ class Config {
585
587
  DD_APPSEC_RASP_ENABLED,
586
588
  DD_APPSEC_TRACE_RATE_LIMIT,
587
589
  DD_APPSEC_WAF_TIMEOUT,
590
+ DD_CRASHTRACKING_ENABLED,
588
591
  DD_CODE_ORIGIN_FOR_SPANS_ENABLED,
589
592
  DD_DATA_STREAMS_ENABLED,
590
593
  DD_DBM_PROPAGATION_MODE,
@@ -675,6 +678,7 @@ class Config {
675
678
  DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH,
676
679
  DD_TRACING_ENABLED,
677
680
  DD_VERSION,
681
+ DD_TRACE_INFERRED_PROXY_SERVICES_ENABLED,
678
682
  OTEL_METRICS_EXPORTER,
679
683
  OTEL_PROPAGATORS,
680
684
  OTEL_RESOURCE_ATTRIBUTES,
@@ -696,7 +700,7 @@ class Config {
696
700
  DD_API_SECURITY_ENABLED && isTrue(DD_API_SECURITY_ENABLED),
697
701
  DD_EXPERIMENTAL_API_SECURITY_ENABLED && isTrue(DD_EXPERIMENTAL_API_SECURITY_ENABLED)
698
702
  ))
699
- this._setUnit(env, 'appsec.apiSecurity.requestSampling', DD_API_SECURITY_REQUEST_SAMPLE_RATE)
703
+ this._setValue(env, 'appsec.apiSecurity.sampleDelay', maybeFloat(DD_API_SECURITY_SAMPLE_DELAY))
700
704
  this._setValue(env, 'appsec.blockedTemplateGraphql', maybeFile(DD_APPSEC_GRAPHQL_BLOCKED_TEMPLATE_JSON))
701
705
  this._setValue(env, 'appsec.blockedTemplateHtml', maybeFile(DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML))
702
706
  this._envUnprocessed['appsec.blockedTemplateHtml'] = DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML
@@ -728,6 +732,7 @@ class Config {
728
732
  this._setValue(env, 'baggageMaxItems', DD_TRACE_BAGGAGE_MAX_ITEMS)
729
733
  this._setBoolean(env, 'clientIpEnabled', DD_TRACE_CLIENT_IP_ENABLED)
730
734
  this._setString(env, 'clientIpHeader', DD_TRACE_CLIENT_IP_HEADER)
735
+ this._setBoolean(env, 'crashtracking.enabled', DD_CRASHTRACKING_ENABLED)
731
736
  this._setBoolean(env, 'codeOriginForSpans.enabled', DD_CODE_ORIGIN_FOR_SPANS_ENABLED)
732
737
  this._setString(env, 'dbmPropagationMode', DD_DBM_PROPAGATION_MODE)
733
738
  this._setString(env, 'dogstatsd.hostname', DD_DOGSTATSD_HOSTNAME)
@@ -862,6 +867,7 @@ class Config {
862
867
  : !!OTEL_PROPAGATORS)
863
868
  this._setBoolean(env, 'tracing', DD_TRACING_ENABLED)
864
869
  this._setString(env, 'version', DD_VERSION || tags.version)
870
+ this._setBoolean(env, 'inferredProxyServicesEnabled', DD_TRACE_INFERRED_PROXY_SERVICES_ENABLED)
865
871
  }
866
872
 
867
873
  _applyOptions (options) {
@@ -874,7 +880,6 @@ class Config {
874
880
  tagger.add(tags, options.tags)
875
881
 
876
882
  this._setBoolean(opts, 'appsec.apiSecurity.enabled', options.appsec.apiSecurity?.enabled)
877
- this._setUnit(opts, 'appsec.apiSecurity.requestSampling', options.appsec.apiSecurity?.requestSampling)
878
883
  this._setValue(opts, 'appsec.blockedTemplateGraphql', maybeFile(options.appsec.blockedTemplateGraphql))
879
884
  this._setValue(opts, 'appsec.blockedTemplateHtml', maybeFile(options.appsec.blockedTemplateHtml))
880
885
  this._optsUnprocessed['appsec.blockedTemplateHtml'] = options.appsec.blockedTemplateHtml
@@ -980,6 +985,7 @@ class Config {
980
985
  this._setBoolean(opts, 'traceId128BitGenerationEnabled', options.traceId128BitGenerationEnabled)
981
986
  this._setBoolean(opts, 'traceId128BitLoggingEnabled', options.traceId128BitLoggingEnabled)
982
987
  this._setString(opts, 'version', options.version || tags.version)
988
+ this._setBoolean(opts, 'inferredProxyServicesEnabled', options.inferredProxyServicesEnabled)
983
989
 
984
990
  // For LLMObs, we want the environment variable to take precedence over the options.
985
991
  // This is reliant on environment config being set before options.
@@ -1134,6 +1140,9 @@ class Config {
1134
1140
  if (iastEnabled || ['auto', 'true'].includes(profilingEnabled) || injectionIncludesProfiler) {
1135
1141
  this._setBoolean(calc, 'telemetry.logCollection', true)
1136
1142
  }
1143
+ if (this._env.injectionEnabled?.length > 0) {
1144
+ this._setBoolean(calc, 'crashtracking.enabled', true)
1145
+ }
1137
1146
  }
1138
1147
 
1139
1148
  _applyRemote (options) {
@@ -46,5 +46,10 @@ module.exports = {
46
46
  SCHEMA_OPERATION: 'schema.operation',
47
47
  SCHEMA_NAME: 'schema.name',
48
48
  GRPC_CLIENT_ERROR_STATUSES: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
49
- GRPC_SERVER_ERROR_STATUSES: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
49
+ GRPC_SERVER_ERROR_STATUSES: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
50
+ S3_PTR_KIND: 'aws.s3.object',
51
+ SPAN_POINTER_DIRECTION: Object.freeze({
52
+ UPSTREAM: 'u',
53
+ DOWNSTREAM: 'd'
54
+ })
50
55
  }
@@ -0,0 +1,98 @@
1
+ 'use strict'
2
+
3
+ // Load binding first to not import other modules if it throws
4
+ const libdatadog = require('@datadog/libdatadog')
5
+ const binding = libdatadog.load('crashtracker')
6
+
7
+ const log = require('../log')
8
+ const { URL } = require('url')
9
+ const pkg = require('../../../../package.json')
10
+
11
+ class Crashtracker {
12
+ constructor () {
13
+ this._started = false
14
+ }
15
+
16
+ configure (config) {
17
+ if (!this._started) return
18
+
19
+ try {
20
+ binding.updateConfig(this._getConfig(config))
21
+ binding.updateMetadata(this._getMetadata(config))
22
+ } catch (e) {
23
+ log.error(e)
24
+ }
25
+ }
26
+
27
+ start (config) {
28
+ if (this._started) return this.configure(config)
29
+
30
+ this._started = true
31
+
32
+ try {
33
+ binding.init(
34
+ this._getConfig(config),
35
+ this._getReceiverConfig(config),
36
+ this._getMetadata(config)
37
+ )
38
+ } catch (e) {
39
+ log.error(e)
40
+ }
41
+ }
42
+
43
+ // TODO: Send only configured values when defaults are fixed.
44
+ _getConfig (config) {
45
+ const { hostname = '127.0.0.1', port = 8126 } = config
46
+ const url = config.url || new URL(`http://${hostname}:${port}`)
47
+
48
+ return {
49
+ additional_files: [],
50
+ create_alt_stack: true,
51
+ use_alt_stack: true,
52
+ endpoint: {
53
+ // TODO: Use the string directly when deserialization is fixed.
54
+ url: {
55
+ scheme: url.protocol.slice(0, -1),
56
+ authority: url.protocol === 'unix'
57
+ ? Buffer.from(url.pathname).toString('hex')
58
+ : url.host,
59
+ path_and_query: ''
60
+ },
61
+ timeout_ms: 3000
62
+ },
63
+ timeout_ms: 0,
64
+ // TODO: Use `EnabledWithSymbolsInReceiver` instead for Linux when fixed.
65
+ resolve_frames: 'EnabledWithInprocessSymbols'
66
+ }
67
+ }
68
+
69
+ _getMetadata (config) {
70
+ const tags = Object.keys(config.tags).map(key => `${key}:${config.tags[key]}`)
71
+
72
+ return {
73
+ library_name: pkg.name,
74
+ library_version: pkg.version,
75
+ family: 'nodejs',
76
+ tags: [
77
+ ...tags,
78
+ 'is_crash:true',
79
+ 'language:javascript',
80
+ `library_version:${pkg.version}`,
81
+ 'runtime:nodejs',
82
+ 'severity:crash'
83
+ ]
84
+ }
85
+ }
86
+
87
+ _getReceiverConfig () {
88
+ return {
89
+ args: [],
90
+ env: [],
91
+ path_to_receiver_binary: libdatadog.find('crashtracker-receiver', true),
92
+ stderr_filename: null,
93
+ stdout_filename: null
94
+ }
95
+ }
96
+ }
97
+
98
+ module.exports = new Crashtracker()
@@ -0,0 +1,15 @@
1
+ 'use strict'
2
+
3
+ const { isMainThread } = require('worker_threads')
4
+ const log = require('../log')
5
+
6
+ if (isMainThread) {
7
+ try {
8
+ module.exports = require('./crashtracker')
9
+ } catch (e) {
10
+ log.warn(e.message)
11
+ module.exports = require('./noop')
12
+ }
13
+ } else {
14
+ module.exports = require('./noop')
15
+ }
@@ -0,0 +1,8 @@
1
+ 'use strict'
2
+
3
+ class NoopCrashtracker {
4
+ configure () {}
5
+ start () {}
6
+ }
7
+
8
+ module.exports = new NoopCrashtracker()
@@ -291,7 +291,7 @@ class LLMObs extends NoopLLMObs {
291
291
  }
292
292
 
293
293
  const evaluationTags = {
294
- 'dd-trace.version': tracerVersion,
294
+ 'ddtrace.version': tracerVersion,
295
295
  ml_app: mlApp
296
296
  }
297
297
 
@@ -179,7 +179,7 @@ class LLMObsSpanProcessor {
179
179
  service: this._config.service,
180
180
  source: 'integration',
181
181
  ml_app: mlApp,
182
- 'dd-trace.version': tracerVersion,
182
+ 'ddtrace.version': tracerVersion,
183
183
  error: Number(!!error) || 0,
184
184
  language: 'javascript'
185
185
  }
@@ -6,6 +6,8 @@ const { DROPPED_IO_COLLECTION_ERROR } = require('../../constants/tags')
6
6
  const BaseWriter = require('../base')
7
7
  const logger = require('../../../log')
8
8
 
9
+ const tracerVersion = require('../../../../../../package.json').version
10
+
9
11
  class LLMObsSpanWriter extends BaseWriter {
10
12
  constructor (options) {
11
13
  super({
@@ -32,6 +34,7 @@ class LLMObsSpanWriter extends BaseWriter {
32
34
  makePayload (events) {
33
35
  return {
34
36
  '_dd.stage': 'raw',
37
+ '_dd.tracer_version': tracerVersion,
35
38
  event_type: this._eventType,
36
39
  spans: events
37
40
  }
@@ -4,6 +4,7 @@ const coalesce = require('koalas')
4
4
  const { isTrue } = require('../util')
5
5
  const { debugChannel, infoChannel, warnChannel, errorChannel } = require('./channels')
6
6
  const logWriter = require('./writer')
7
+ const { Log } = require('./log')
7
8
 
8
9
  const memoize = func => {
9
10
  const cache = {}
@@ -18,10 +19,6 @@ const memoize = func => {
18
19
  return memoized
19
20
  }
20
21
 
21
- function processMsg (msg) {
22
- return typeof msg === 'function' ? msg() : msg
23
- }
24
-
25
22
  const config = {
26
23
  enabled: false,
27
24
  logger: undefined,
@@ -52,37 +49,37 @@ const log = {
52
49
  reset () {
53
50
  logWriter.reset()
54
51
  this._deprecate = memoize((code, message) => {
55
- errorChannel.publish(message)
52
+ errorChannel.publish(Log.parse(message))
56
53
  return true
57
54
  })
58
55
 
59
56
  return this
60
57
  },
61
58
 
62
- debug (message) {
59
+ debug (...args) {
63
60
  if (debugChannel.hasSubscribers) {
64
- debugChannel.publish(processMsg(message))
61
+ debugChannel.publish(Log.parse(...args))
65
62
  }
66
63
  return this
67
64
  },
68
65
 
69
- info (message) {
66
+ info (...args) {
70
67
  if (infoChannel.hasSubscribers) {
71
- infoChannel.publish(processMsg(message))
68
+ infoChannel.publish(Log.parse(...args))
72
69
  }
73
70
  return this
74
71
  },
75
72
 
76
- warn (message) {
73
+ warn (...args) {
77
74
  if (warnChannel.hasSubscribers) {
78
- warnChannel.publish(processMsg(message))
75
+ warnChannel.publish(Log.parse(...args))
79
76
  }
80
77
  return this
81
78
  },
82
79
 
83
- error (err) {
80
+ error (...args) {
84
81
  if (errorChannel.hasSubscribers) {
85
- errorChannel.publish(processMsg(err))
82
+ errorChannel.publish(Log.parse(...args))
86
83
  }
87
84
  return this
88
85
  },
@@ -0,0 +1,52 @@
1
+ 'use strict'
2
+
3
+ const { format } = require('util')
4
+
5
+ class Log {
6
+ constructor (message, args, cause, delegate) {
7
+ this.message = message
8
+ this.args = args
9
+ this.cause = cause
10
+ this.delegate = delegate
11
+ }
12
+
13
+ get formatted () {
14
+ const { message, args } = this
15
+
16
+ let formatted = message
17
+ if (message && args && args.length) {
18
+ formatted = format(message, ...args)
19
+ }
20
+ return formatted
21
+ }
22
+
23
+ static parse (...args) {
24
+ let message, cause, delegate
25
+
26
+ const lastArg = args[args.length - 1]
27
+ if (lastArg && typeof lastArg === 'object' && lastArg.stack) { // lastArg instanceof Error?
28
+ cause = args.pop()
29
+ }
30
+
31
+ const firstArg = args.shift()
32
+ if (firstArg) {
33
+ if (typeof firstArg === 'string') {
34
+ message = firstArg
35
+ } else if (typeof firstArg === 'object') {
36
+ message = String(firstArg.message || firstArg)
37
+ } else if (typeof firstArg === 'function') {
38
+ delegate = firstArg
39
+ } else {
40
+ message = String(firstArg)
41
+ }
42
+ } else if (!cause) {
43
+ message = String(firstArg)
44
+ }
45
+
46
+ return new Log(message, args, cause, delegate)
47
+ }
48
+ }
49
+
50
+ module.exports = {
51
+ Log
52
+ }
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { storage } = require('../../../datadog-core')
4
4
  const { LogChannel } = require('./channels')
5
+ const { Log } = require('./log')
5
6
  const defaultLogger = {
6
7
  debug: msg => console.debug(msg), /* eslint-disable-line no-console */
7
8
  info: msg => console.info(msg), /* eslint-disable-line no-console */
@@ -22,7 +23,7 @@ function withNoop (fn) {
22
23
  }
23
24
 
24
25
  function unsubscribeAll () {
25
- logChannel.unsubscribe({ debug, info, warn, error })
26
+ logChannel.unsubscribe({ debug: onDebug, info: onInfo, warn: onWarn, error: onError })
26
27
  }
27
28
 
28
29
  function toggleSubscription (enable, level) {
@@ -30,7 +31,7 @@ function toggleSubscription (enable, level) {
30
31
 
31
32
  if (enable) {
32
33
  logChannel = new LogChannel(level)
33
- logChannel.subscribe({ debug, info, warn, error })
34
+ logChannel.subscribe({ debug: onDebug, info: onInfo, warn: onWarn, error: onError })
34
35
  }
35
36
  }
36
37
 
@@ -51,32 +52,62 @@ function reset () {
51
52
  toggleSubscription(false)
52
53
  }
53
54
 
54
- function error (err) {
55
- if (typeof err !== 'object' || !err) {
56
- err = String(err)
57
- } else if (!err.stack) {
58
- err = String(err.message || err)
55
+ function getErrorLog (err) {
56
+ if (err && typeof err.delegate === 'function') {
57
+ const result = err.delegate()
58
+ return Array.isArray(result) ? Log.parse(...result) : Log.parse(result)
59
+ } else {
60
+ return err
59
61
  }
62
+ }
60
63
 
61
- if (typeof err === 'string') {
62
- err = new Error(err)
63
- }
64
+ function onError (err) {
65
+ const { formatted, cause } = getErrorLog(err)
66
+
67
+ // calling twice logger.error() because Error cause is only available in nodejs v16.9.0
68
+ // TODO: replace it with Error(message, { cause }) when cause has broad support
69
+ if (formatted) withNoop(() => logger.error(new Error(formatted)))
70
+ if (cause) withNoop(() => logger.error(cause))
71
+ }
72
+
73
+ function onWarn (log) {
74
+ const { formatted, cause } = getErrorLog(log)
75
+ if (formatted) withNoop(() => logger.warn(formatted))
76
+ if (cause) withNoop(() => logger.warn(cause))
77
+ }
64
78
 
65
- withNoop(() => logger.error(err))
79
+ function onInfo (log) {
80
+ const { formatted, cause } = getErrorLog(log)
81
+ if (formatted) withNoop(() => logger.info(formatted))
82
+ if (cause) withNoop(() => logger.info(cause))
66
83
  }
67
84
 
68
- function warn (message) {
69
- if (!logger.warn) return debug(message)
70
- withNoop(() => logger.warn(message))
85
+ function onDebug (log) {
86
+ const { formatted, cause } = getErrorLog(log)
87
+ if (formatted) withNoop(() => logger.debug(formatted))
88
+ if (cause) withNoop(() => logger.debug(cause))
71
89
  }
72
90
 
73
- function info (message) {
74
- if (!logger.info) return debug(message)
75
- withNoop(() => logger.info(message))
91
+ function error (...args) {
92
+ onError(Log.parse(...args))
93
+ }
94
+
95
+ function warn (...args) {
96
+ const log = Log.parse(...args)
97
+ if (!logger.warn) return onDebug(log)
98
+
99
+ onWarn(log)
100
+ }
101
+
102
+ function info (...args) {
103
+ const log = Log.parse(...args)
104
+ if (!logger.info) return onDebug(log)
105
+
106
+ onInfo(log)
76
107
  }
77
108
 
78
- function debug (message) {
79
- withNoop(() => logger.debug(message))
109
+ function debug (...args) {
110
+ onDebug(Log.parse(...args))
80
111
  }
81
112
 
82
113
  module.exports = { use, toggle, reset, error, warn, info, debug }
@@ -22,6 +22,7 @@ class NoopSpan {
22
22
  setTag (key, value) { return this }
23
23
  addTags (keyValueMap) { return this }
24
24
  addLink (link) { return this }
25
+ addSpanPointer (ptrKind, ptrDir, ptrHash) { return this }
25
26
  log () { return this }
26
27
  logEvent () {}
27
28
  finish (finishTime) {}
@@ -14,6 +14,7 @@ const { SERVICE_NAME, RESOURCE_NAME } = require('../../../../ext/tags')
14
14
  const kinds = require('../../../../ext/kinds')
15
15
 
16
16
  const SpanContext = require('./span_context')
17
+ const id = require('../id')
17
18
 
18
19
  // The one built into OTel rounds so we lose sub-millisecond precision.
19
20
  function hrTimeToMilliseconds (time) {
@@ -217,6 +218,20 @@ class Span {
217
218
  return this
218
219
  }
219
220
 
221
+ addSpanPointer (ptrKind, ptrDir, ptrHash) {
222
+ const zeroContext = new SpanContext({
223
+ traceId: id('0'),
224
+ spanId: id('0')
225
+ })
226
+ const attributes = {
227
+ 'ptr.kind': ptrKind,
228
+ 'ptr.dir': ptrDir,
229
+ 'ptr.hash': ptrHash,
230
+ 'link.kind': 'span-pointer'
231
+ }
232
+ return this.addLink(zeroContext, attributes)
233
+ }
234
+
220
235
  setStatus ({ code, message }) {
221
236
  if (!this.ended && !this._hasStatus && code) {
222
237
  this._hasStatus = true
@@ -290,50 +290,63 @@ class TextMapPropagator {
290
290
  }
291
291
 
292
292
  _extractSpanContext (carrier) {
293
- let spanContext = null
293
+ let context = null
294
294
  for (const extractor of this._config.tracePropagationStyle.extract) {
295
- // add logic to ensure tracecontext headers takes precedence over other extracted headers
296
- if (spanContext !== null) {
297
- if (this._config.tracePropagationExtractFirst) {
298
- return spanContext
299
- }
300
- if (extractor !== 'tracecontext') {
301
- continue
302
- }
303
- spanContext = this._resolveTraceContextConflicts(
304
- this._extractTraceparentContext(carrier), spanContext, carrier)
305
- break
306
- }
307
-
295
+ let extractedContext = null
308
296
  switch (extractor) {
309
297
  case 'datadog':
310
- spanContext = this._extractDatadogContext(carrier)
298
+ extractedContext = this._extractDatadogContext(carrier)
311
299
  break
312
300
  case 'tracecontext':
313
- spanContext = this._extractTraceparentContext(carrier)
301
+ extractedContext = this._extractTraceparentContext(carrier)
314
302
  break
315
303
  case 'b3' && this
316
304
  ._config
317
305
  .tracePropagationStyle
318
306
  .otelPropagators: // TODO: should match "b3 single header" in next major
319
307
  case 'b3 single header': // TODO: delete in major after singular "b3"
320
- spanContext = this._extractB3SingleContext(carrier)
308
+ extractedContext = this._extractB3SingleContext(carrier)
321
309
  break
322
310
  case 'b3':
323
311
  case 'b3multi':
324
- spanContext = this._extractB3MultiContext(carrier)
312
+ extractedContext = this._extractB3MultiContext(carrier)
325
313
  break
326
314
  default:
327
- log.warn(`Unknown propagation style: ${extractor}`)
315
+ if (extractor !== 'baggage') log.warn(`Unknown propagation style: ${extractor}`)
316
+ }
317
+
318
+ if (extractedContext === null) { // If the current extractor was invalid, continue to the next extractor
319
+ continue
320
+ }
321
+
322
+ if (context === null) {
323
+ context = extractedContext
324
+ if (this._config.tracePropagationExtractFirst) {
325
+ return context
326
+ }
327
+ } else {
328
+ // If extractor is tracecontext, add tracecontext specific information to the context
329
+ if (extractor === 'tracecontext') {
330
+ context = this._resolveTraceContextConflicts(
331
+ this._extractTraceparentContext(carrier), context, carrier)
332
+ }
333
+ if (extractedContext._traceId && extractedContext._spanId &&
334
+ extractedContext.toTraceId(true) !== context.toTraceId(true)) {
335
+ const link = {
336
+ context: extractedContext,
337
+ attributes: { reason: 'terminated_context', context_headers: extractor }
338
+ }
339
+ context._links.push(link)
340
+ }
328
341
  }
329
342
 
330
343
  if (this._config.tracePropagationStyle.extract.includes('baggage') && carrier.baggage) {
331
- spanContext = spanContext || new DatadogSpanContext()
332
- this._extractBaggageItems(carrier, spanContext)
344
+ context = context || new DatadogSpanContext()
345
+ this._extractBaggageItems(carrier, context)
333
346
  }
334
347
  }
335
348
 
336
- return spanContext || this._extractSqsdContext(carrier)
349
+ return context || this._extractSqsdContext(carrier)
337
350
  }
338
351
 
339
352
  _extractDatadogContext (carrier) {