dd-trace 5.109.0 → 5.110.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 (128) hide show
  1. package/ci/init.js +4 -3
  2. package/index.d.ts +66 -24
  3. package/loader-hook.mjs +88 -2
  4. package/package.json +18 -14
  5. package/packages/datadog-esbuild/index.js +26 -0
  6. package/packages/datadog-esbuild/src/utils.js +46 -3
  7. package/packages/datadog-instrumentations/src/connect.js +4 -3
  8. package/packages/datadog-instrumentations/src/fastify.js +4 -12
  9. package/packages/datadog-instrumentations/src/graphql.js +17 -6
  10. package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +81 -8
  11. package/packages/datadog-instrumentations/src/helpers/instrument.js +27 -2
  12. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +65 -0
  13. package/packages/datadog-instrumentations/src/helpers/register.js +6 -4
  14. package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +17 -0
  15. package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +18 -2
  16. package/packages/datadog-instrumentations/src/koa.js +3 -2
  17. package/packages/datadog-instrumentations/src/mariadb.js +4 -2
  18. package/packages/datadog-instrumentations/src/mocha/main.js +3 -3
  19. package/packages/datadog-instrumentations/src/next.js +10 -10
  20. package/packages/datadog-instrumentations/src/otel-sdk-trace.js +7 -6
  21. package/packages/datadog-instrumentations/src/playwright.js +1 -1
  22. package/packages/datadog-instrumentations/src/restify.js +4 -3
  23. package/packages/datadog-instrumentations/src/router.js +18 -5
  24. package/packages/datadog-instrumentations/src/selenium.js +1 -2
  25. package/packages/datadog-instrumentations/src/tedious.js +28 -0
  26. package/packages/datadog-instrumentations/src/vitest.js +264 -7
  27. package/packages/datadog-plugin-avsc/src/schema_iterator.js +2 -2
  28. package/packages/datadog-plugin-aws-sdk/src/base.js +13 -9
  29. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +43 -6
  30. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +83 -91
  31. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +9 -1
  32. package/packages/datadog-plugin-http/src/client.js +8 -3
  33. package/packages/datadog-plugin-http2/src/client.js +5 -2
  34. package/packages/datadog-plugin-mysql/src/index.js +7 -2
  35. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +2 -2
  36. package/packages/datadog-plugin-undici/src/index.js +5 -2
  37. package/packages/datadog-plugin-vitest/src/index.js +7 -1
  38. package/packages/datadog-webpack/index.js +17 -1
  39. package/packages/datadog-webpack/src/optional-peer-loader.js +17 -0
  40. package/packages/dd-trace/src/aiguard/sdk.js +13 -1
  41. package/packages/dd-trace/src/appsec/api_security/index.js +55 -0
  42. package/packages/dd-trace/src/appsec/api_security/sampler.js +147 -0
  43. package/packages/dd-trace/src/appsec/index.js +10 -6
  44. package/packages/dd-trace/src/appsec/reporter.js +1 -0
  45. package/packages/dd-trace/src/appsec/telemetry/api_security.js +34 -0
  46. package/packages/dd-trace/src/appsec/telemetry/index.js +26 -0
  47. package/packages/dd-trace/src/ci-visibility/requests/request.js +1 -17
  48. package/packages/dd-trace/src/config/defaults.js +22 -3
  49. package/packages/dd-trace/src/config/generated-config-types.d.ts +535 -1
  50. package/packages/dd-trace/src/config/helper.js +133 -33
  51. package/packages/dd-trace/src/config/index.js +4 -1
  52. package/packages/dd-trace/src/config/parsers.js +19 -1
  53. package/packages/dd-trace/src/config/supported-configurations.json +21 -3
  54. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +47 -35
  55. package/packages/dd-trace/src/debugger/devtools_client/index.js +31 -66
  56. package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +83 -0
  57. package/packages/dd-trace/src/debugger/devtools_client/state.js +1 -0
  58. package/packages/dd-trace/src/debugger/index.js +5 -0
  59. package/packages/dd-trace/src/debugger/probe_sampler.js +112 -0
  60. package/packages/dd-trace/src/debugger/probe_sampler_constants.js +19 -0
  61. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +77 -26
  62. package/packages/dd-trace/src/exporters/common/request.js +1 -20
  63. package/packages/dd-trace/src/exporters/common/url.js +34 -0
  64. package/packages/dd-trace/src/exporters/span-stats/writer.js +1 -3
  65. package/packages/dd-trace/src/guardrails/index.js +8 -5
  66. package/packages/dd-trace/src/index.js +6 -4
  67. package/packages/dd-trace/src/llmobs/constants/tags.js +6 -0
  68. package/packages/dd-trace/src/llmobs/index.js +16 -5
  69. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +3 -3
  70. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +5 -1
  71. package/packages/dd-trace/src/llmobs/sdk.js +5 -3
  72. package/packages/dd-trace/src/llmobs/span_processor.js +4 -0
  73. package/packages/dd-trace/src/llmobs/tagger.js +55 -0
  74. package/packages/dd-trace/src/llmobs/telemetry.js +1 -0
  75. package/packages/dd-trace/src/log/index.js +18 -20
  76. package/packages/dd-trace/src/openfeature/flagging_provider.js +2 -7
  77. package/packages/dd-trace/src/opentelemetry/bridge-span-base.js +5 -4
  78. package/packages/dd-trace/src/opentelemetry/metrics/instruments.js +2 -1
  79. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +4 -3
  80. package/packages/dd-trace/src/opentelemetry/metrics/time.js +19 -0
  81. package/packages/dd-trace/src/opentelemetry/otlp/common.proto +2 -2
  82. package/packages/dd-trace/src/opentelemetry/otlp/logs.proto +1 -1
  83. package/packages/dd-trace/src/opentelemetry/otlp/logs_service.proto +1 -1
  84. package/packages/dd-trace/src/opentelemetry/otlp/metrics.proto +1 -1
  85. package/packages/dd-trace/src/opentelemetry/otlp/metrics_service.proto +1 -1
  86. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +11 -4
  87. package/packages/dd-trace/src/opentelemetry/span-helpers.js +29 -13
  88. package/packages/dd-trace/src/opentelemetry/span.js +14 -3
  89. package/packages/dd-trace/src/opentelemetry/span_context.js +1 -0
  90. package/packages/dd-trace/src/opentelemetry/trace/index.js +2 -1
  91. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +3 -2
  92. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +26 -13
  93. package/packages/dd-trace/src/opentracing/propagation/text_map.js +3 -3
  94. package/packages/dd-trace/src/opentracing/span_context.js +17 -0
  95. package/packages/dd-trace/src/plugin_manager.js +7 -1
  96. package/packages/dd-trace/src/plugins/ci_plugin.js +9 -1
  97. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +276 -0
  98. package/packages/dd-trace/src/plugins/util/test.js +80 -0
  99. package/packages/dd-trace/src/plugins/util/url.js +68 -1
  100. package/packages/dd-trace/src/plugins/util/web.js +19 -36
  101. package/packages/dd-trace/src/priority_sampler.js +1 -13
  102. package/packages/dd-trace/src/profiling/config.js +129 -198
  103. package/packages/dd-trace/src/profiling/constants.js +4 -7
  104. package/packages/dd-trace/src/profiling/exporter_cli.js +10 -10
  105. package/packages/dd-trace/src/profiling/exporters/agent.js +23 -16
  106. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +45 -14
  107. package/packages/dd-trace/src/profiling/exporters/file.js +16 -5
  108. package/packages/dd-trace/src/profiling/oom.js +71 -0
  109. package/packages/dd-trace/src/profiling/profiler.js +35 -23
  110. package/packages/dd-trace/src/profiling/profilers/events.js +14 -7
  111. package/packages/dd-trace/src/profiling/profilers/space.js +27 -18
  112. package/packages/dd-trace/src/profiling/profilers/wall.js +35 -25
  113. package/packages/dd-trace/src/ritm.js +4 -2
  114. package/packages/dd-trace/src/serverless.js +1 -2
  115. package/packages/dd-trace/src/service-naming/schemas/util.js +36 -5
  116. package/packages/dd-trace/src/service-naming/schemas/v1/web.js +5 -3
  117. package/packages/dd-trace/src/span_format.js +2 -1
  118. package/packages/dd-trace/src/span_processor.js +6 -0
  119. package/packages/dd-trace/src/telemetry/send-data.js +12 -10
  120. package/packages/dd-trace/src/tracer.js +15 -1
  121. package/packages/dd-trace/src/util.js +14 -0
  122. package/register.js +58 -1
  123. package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
  124. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  125. package/vendor/dist/pprof-format/index.js +1 -1
  126. package/vendor/dist/protobufjs/index.js +1 -1
  127. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  128. package/packages/dd-trace/src/appsec/api_security_sampler.js +0 -117
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { channel } = require('dc-polyfill')
4
4
 
5
- const { isError, isTrue } = require('../util')
5
+ const { isError } = require('../util')
6
6
  const tracerVersion = require('../../../../package.json').version
7
7
  const logger = require('../log')
8
8
  const { getValueFromEnvSources } = require('../config/helper')
@@ -65,9 +65,11 @@ class LLMObs extends NoopLLMObs {
65
65
 
66
66
  logger.debug('Enabling LLMObs')
67
67
 
68
- const DD_LLMOBS_ENABLED = getValueFromEnvSources('DD_LLMOBS_ENABLED')
68
+ // skipDefault: only an explicit DD_LLMOBS_ENABLED=false blocks enable(); an unset value
69
+ // (its default is false) must still allow this programmatic opt-in.
70
+ const DD_LLMOBS_ENABLED = getValueFromEnvSources('DD_LLMOBS_ENABLED', true)
69
71
 
70
- if (DD_LLMOBS_ENABLED != null && !isTrue(DD_LLMOBS_ENABLED)) {
72
+ if (DD_LLMOBS_ENABLED === false) {
71
73
  logger.debug('LLMObs.enable() called when DD_LLMOBS_ENABLED is false. No action taken.')
72
74
  return
73
75
  }
@@ -33,6 +33,8 @@ const {
33
33
  ROUTING_API_KEY,
34
34
  ROUTING_SITE,
35
35
  LLMOBS_SUBMITTED_TAG_KEY,
36
+ SAMPLE_RATE,
37
+ SAMPLING_DECISION,
36
38
  } = require('./constants/tags')
37
39
  const { UNSERIALIZABLE_VALUE_TEXT } = require('./constants/text')
38
40
  const telemetry = require('./telemetry')
@@ -248,6 +250,8 @@ class LLMObsSpanProcessor {
248
250
  _dd: {
249
251
  span_id: span.context().toSpanId(),
250
252
  trace_id: span.context().toTraceId(true),
253
+ sample_rate: mlObsTags[SAMPLE_RATE],
254
+ sampling_decision: mlObsTags[SAMPLING_DECISION],
251
255
  },
252
256
  }
253
257
 
@@ -1,6 +1,8 @@
1
1
  'use strict'
2
2
 
3
3
  const log = require('../log')
4
+ const Sampler = require('../sampler')
5
+ const { formatKnuthRate } = require('../util')
4
6
  const {
5
7
  MODEL_NAME,
6
8
  MODEL_PROVIDER,
@@ -41,6 +43,12 @@ const {
41
43
  ROUTING_SITE,
42
44
  PROMPT_TRACKING_INSTRUMENTATION_METHOD,
43
45
  INSTRUMENTATION_METHOD_ANNOTATED,
46
+ SAMPLE_RATE,
47
+ SAMPLING_DECISION,
48
+ SAMPLING_DECISION_SAMPLED,
49
+ SAMPLING_DECISION_DROPPED,
50
+ PROPAGATED_SAMPLE_RATE_KEY,
51
+ PROPAGATED_SAMPLING_DECISION_KEY,
44
52
  } = require('./constants/tags')
45
53
  const { storage } = require('./storage')
46
54
  const { findGenAIAncestorSpanId, validateCostTags, writeBridgeTags, validateToolDefinitions } = require('./util')
@@ -53,12 +61,31 @@ class LLMObsTagger {
53
61
  /** @type {import('../config/config-base')} */
54
62
  #config
55
63
 
64
+ /** @type {import('../sampler') | null} */
65
+ #sampler = null
66
+
56
67
  constructor (config, softFail = false) {
57
68
  this.#config = config
58
69
 
59
70
  this.softFail = softFail
60
71
  }
61
72
 
73
+ /**
74
+ * The sampler reads its rate from `config.llmobs.sampleRate`, which can change
75
+ * at runtime (e.g. via remote config). Rebuild the sampler whenever the rate
76
+ * changes so decisions reflect the current config, while reusing the existing
77
+ * sampler when it hasn't.
78
+ *
79
+ * @returns {import('../sampler')}
80
+ */
81
+ #getSampler () {
82
+ const rate = this.#config.llmobs?.sampleRate ?? 1
83
+ if (this.#sampler === null || rate !== this.#sampler.rate()) {
84
+ this.#sampler = new Sampler(rate)
85
+ }
86
+ return this.#sampler
87
+ }
88
+
62
89
  static get tagMap () {
63
90
  return registry
64
91
  }
@@ -124,6 +151,8 @@ class LLMObsTagger {
124
151
  ROOT_PARENT_ID
125
152
  this._setTag(span, PARENT_ID_KEY, parentId)
126
153
 
154
+ this.#tagSamplingDecision(span, parent)
155
+
127
156
  // apply annotation context
128
157
  const annotationContext = storage.getStore()?.annotationContext
129
158
 
@@ -153,6 +182,32 @@ class LLMObsTagger {
153
182
  }
154
183
  }
155
184
 
185
+ #tagSamplingDecision (span, parent) {
186
+ const traceTags = span.context()._trace.tags
187
+ const parentTags = registry.get(parent)
188
+
189
+ let sampleRate, samplingDecision
190
+ if (parentTags) {
191
+ // Local LLMObs parent: inherit its decision.
192
+ sampleRate = parentTags[SAMPLE_RATE]
193
+ samplingDecision = parentTags[SAMPLING_DECISION]
194
+ } else if (traceTags[PROPAGATED_PARENT_ID_KEY]) {
195
+ // Distributed LLMObs parent: inherit whatever was propagated. This may be
196
+ // absent if the upstream service predates sampling propagation, in which
197
+ // case we make no decision here rather than starting a divergent one.
198
+ sampleRate = traceTags[PROPAGATED_SAMPLE_RATE_KEY]
199
+ samplingDecision = traceTags[PROPAGATED_SAMPLING_DECISION_KEY]
200
+ } else {
201
+ // Root span: make the trace's one sampling decision.
202
+ const sampler = this.#getSampler()
203
+ sampleRate = formatKnuthRate(sampler.rate())
204
+ samplingDecision = sampler.isSampled(span) ? SAMPLING_DECISION_SAMPLED : SAMPLING_DECISION_DROPPED
205
+ }
206
+
207
+ if (sampleRate != null) this._setTag(span, SAMPLE_RATE, sampleRate)
208
+ if (samplingDecision != null) this._setTag(span, SAMPLING_DECISION, samplingDecision)
209
+ }
210
+
156
211
  // TODO: similarly for the following `tag` methods,
157
212
  // how can we transition from a span weakmap to core API functionality
158
213
  tagLLMIO (span, inputData, outputData) {
@@ -87,6 +87,7 @@ function recordLLMObsEnabled (startTime, config, value = 1) {
87
87
  site: config.site,
88
88
  auto: Number(autoEnabled),
89
89
  ml_app: config.llmobs.mlApp,
90
+ sample_rate: config.llmobs.sampleRate,
90
91
  }
91
92
  llmobsMetrics.count('product_enabled', tags).inc(value)
92
93
  llmobsMetrics.distribution('init_time', tags).track(initTimeMs)
@@ -2,19 +2,16 @@
2
2
 
3
3
  const { inspect } = require('util')
4
4
 
5
- const { defaults } = require('../config/defaults')
6
- const { isTrue } = require('../util')
7
5
  const { getValueFromEnvSources } = require('../config/helper')
6
+ // Eager require restores the original startup module-load order: pulling `config/defaults` here
7
+ // also installs the instrumented `dns` it transitively loads before the tracer/agent connects.
8
+ // `config/defaults` defers its own `dns` require until after it exports, so this no longer hits
9
+ // the `config/defaults` <-> log parse cycle that motivated the lazy require below.
10
+ const { defaults } = require('../config/defaults')
8
11
  const { traceChannel, debugChannel, infoChannel, warnChannel, errorChannel } = require('./channels')
9
12
  const logWriter = require('./writer')
10
13
  const { Log, LogConfig, NoTransmitError } = require('./log')
11
14
 
12
- const config = {
13
- enabled: defaults.DD_TRACE_DEBUG,
14
- logger: undefined,
15
- logLevel: defaults.logLevel,
16
- }
17
-
18
15
  // In most places where we know we want to mute a log we use log.error() directly
19
16
  const NO_TRANSMIT = new LogConfig(false)
20
17
 
@@ -77,19 +74,17 @@ const log = {
77
74
  },
78
75
 
79
76
  configure (options) {
80
- config.logger = options.logger
81
- config.logLevel = options.logLevel ??
82
- getValueFromEnvSources('DD_TRACE_LOG_LEVEL') ??
83
- config.logLevel
84
- config.enabled = isTrue(
85
- getValueFromEnvSources('DD_TRACE_DEBUG') ??
77
+ const logger = options.logger
78
+ const logLevel = options.logLevel ??
79
+ getValueFromEnvSources('DD_TRACE_LOG_LEVEL', true) ??
80
+ defaults?.logLevel
81
+ const enabled = getValueFromEnvSources('DD_TRACE_DEBUG', true) ??
86
82
  // TODO: Handle this by adding a log buffer so that configure may be called with the actual configurations.
87
83
  // eslint-disable-next-line eslint-rules/eslint-process-env
88
- (process.env.OTEL_LOG_LEVEL === 'debug' || config.enabled)
89
- )
90
- logWriter.configure(config.enabled, config.logLevel, options.logger)
84
+ (process.env.OTEL_LOG_LEVEL === 'debug' || defaults?.DD_TRACE_DEBUG)
85
+ logWriter.configure(enabled, logLevel, logger)
91
86
 
92
- return config.enabled
87
+ return enabled
93
88
  },
94
89
  }
95
90
 
@@ -113,6 +108,9 @@ function getErrorLog (err) {
113
108
  return err
114
109
  }
115
110
 
116
- log.configure({})
117
-
111
+ // Assign before the bootstrap configure() call: an invalid DD_TRACE_LOG_LEVEL
112
+ // makes config/defaults re-require this module to warn, which must observe the
113
+ // fully built log object rather than a half-initialized one.
118
114
  module.exports = log
115
+
116
+ log.configure({})
@@ -1,18 +1,13 @@
1
1
  'use strict'
2
2
 
3
3
  const { channel } = require('dc-polyfill')
4
+ const requireOptionalPeer = require('../../../datadog-instrumentations/src/helpers/require-optional-peer')
4
5
  const log = require('../log')
5
6
  const { EXPOSURE_CHANNEL } = require('./constants/constants')
6
7
  const EvalMetricsHook = require('./eval-metrics-hook')
7
8
  const SpanEnrichmentHook = require('./span-enrichment-hook')
8
9
 
9
- // Bundler-opaque require for the optional peer chain
10
- // `@datadog/openfeature-node-server` -> `@openfeature/server-sdk` ->
11
- // `@openfeature/core`. Same shape as `helpers/rewriter/compiler.js`.
12
- // Refs: https://github.com/DataDog/dd-trace-js/issues/8635
13
- // eslint-disable-next-line camelcase, no-undef
14
- const runtimeRequire = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require
15
- const { DatadogNodeServerProvider } = runtimeRequire(['@datadog/openfeature', 'node', 'server'].join('-'))
10
+ const { DatadogNodeServerProvider } = requireOptionalPeer('@datadog/openfeature-node-server')
16
11
 
17
12
  /**
18
13
  * OpenFeature provider that integrates with Datadog's feature flagging system.
@@ -30,6 +30,7 @@ class BridgeSpanBase {
30
30
  */
31
31
  constructor (ddSpan) {
32
32
  this._ddSpan = ddSpan
33
+ this._otelTraceSemanticsEnabled = false
33
34
  }
34
35
 
35
36
  get ended () {
@@ -45,7 +46,7 @@ class BridgeSpanBase {
45
46
  * @param {import('@opentelemetry/api').AttributeValue} value
46
47
  */
47
48
  setAttribute (key, value) {
48
- setOtelAttribute(this._ddSpan, key, value)
49
+ setOtelAttribute(this._ddSpan, key, value, this._otelTraceSemanticsEnabled)
49
50
  return this
50
51
  }
51
52
 
@@ -53,7 +54,7 @@ class BridgeSpanBase {
53
54
  * @param {import('@opentelemetry/api').Attributes} attributes
54
55
  */
55
56
  setAttributes (attributes) {
56
- setOtelAttributes(this._ddSpan, attributes)
57
+ setOtelAttributes(this._ddSpan, attributes, this._otelTraceSemanticsEnabled)
57
58
  return this
58
59
  }
59
60
 
@@ -91,14 +92,14 @@ class BridgeSpanBase {
91
92
  * @param {import('@opentelemetry/api').TimeInput} [timeInput]
92
93
  */
93
94
  recordException (exception, timeInput) {
94
- recordException(this._ddSpan, exception, timeInput)
95
+ recordException(this._ddSpan, exception, timeInput, this._otelTraceSemanticsEnabled)
95
96
  }
96
97
 
97
98
  /**
98
99
  * @param {import('@opentelemetry/api').SpanStatus} status
99
100
  */
100
101
  setStatus (status) {
101
- this.#statusCode = applyOtelStatus(this._ddSpan, this.#statusCode, status)
102
+ this.#statusCode = applyOtelStatus(this._ddSpan, this.#statusCode, status, this._otelTraceSemanticsEnabled)
102
103
  return this
103
104
  }
104
105
  }
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { sanitizeAttributes } = require('../../../../../vendor/dist/@opentelemetry/core')
4
4
  const { METRIC_TYPES } = require('./constants')
5
+ const { nowUnixNano } = require('./time')
5
6
 
6
7
  /**
7
8
  * @typedef {import('@opentelemetry/api').Attributes} Attributes
@@ -62,7 +63,7 @@ class Instrument {
62
63
  type,
63
64
  value,
64
65
  attributes: sanitizeAttributes(attributes),
65
- timestamp: Number(process.hrtime.bigint()),
66
+ timestamp: nowUnixNano(),
66
67
  }
67
68
  }
68
69
  }
@@ -6,6 +6,7 @@ const {
6
6
  METRIC_TYPES, TEMPORALITY, DEFAULT_HISTOGRAM_BUCKETS, DEFAULT_MAX_MEASUREMENT_QUEUE_SIZE,
7
7
  } = require('./constants')
8
8
  const { ObservableInstrument } = require('./instruments')
9
+ const { nowUnixNano } = require('./time')
9
10
 
10
11
  /**
11
12
  * @typedef {import('@opentelemetry/api').Attributes} Attributes
@@ -309,7 +310,7 @@ class PeriodicMetricReader {
309
310
  *
310
311
  */
311
312
  class MetricAggregator {
312
- #startTime = Number(process.hrtime.bigint())
313
+ #startTime = nowUnixNano()
313
314
  #temporalityPreference
314
315
  #maxBatchedQueueSize
315
316
 
@@ -444,8 +445,8 @@ class MetricAggregator {
444
445
  */
445
446
  #isDeltaType (type) {
446
447
  return type === METRIC_TYPES.COUNTER ||
447
- type === METRIC_TYPES.OBSERVABLECOUNTER ||
448
- type === METRIC_TYPES.HISTOGRAM
448
+ type === METRIC_TYPES.OBSERVABLECOUNTER ||
449
+ type === METRIC_TYPES.HISTOGRAM
449
450
  }
450
451
 
451
452
  /**
@@ -0,0 +1,19 @@
1
+ 'use strict'
2
+
3
+ const { timeOrigin } = performance
4
+
5
+ /**
6
+ * Current wall-clock time as UNIX epoch nanoseconds, the unit OTLP requires for
7
+ * `timeUnixNano` / `startTimeUnixNano`. `process.hrtime.bigint()` is a monotonic
8
+ * clock with an arbitrary origin, so its values decode as 1970 timestamps that
9
+ * the Datadog Agent silently drops. Anchoring on `performance.timeOrigin` puts
10
+ * the value on the epoch while `performance.now()` keeps it monotonic, so a
11
+ * backward wall-clock jump can't make `timeUnixNano` precede `startTimeUnixNano`.
12
+ *
13
+ * @returns {number} Nanoseconds since the UNIX epoch
14
+ */
15
+ function nowUnixNano () {
16
+ return Math.round((timeOrigin + performance.now()) * 1e6)
17
+ }
18
+
19
+ module.exports = { nowUnixNano }
@@ -68,7 +68,7 @@ message KeyValue {
68
68
  }
69
69
 
70
70
  // InstrumentationScope is a message representing the instrumentation scope information
71
- // such as the fully qualified name and version.
71
+ // such as the fully qualified name and version.
72
72
  message InstrumentationScope {
73
73
  // An empty instrumentation scope name means the name is unknown.
74
74
  string name = 1;
@@ -113,4 +113,4 @@ message EntityRef {
113
113
  // These attribute keys are not part of entity's identity.
114
114
  // These keys MUST exist in the containing {message}.attributes.
115
115
  repeated string description_keys = 4;
116
- }
116
+ }
@@ -223,4 +223,4 @@ message LogRecord {
223
223
  //
224
224
  // [Optional].
225
225
  string event_name = 12;
226
- }
226
+ }
@@ -75,4 +75,4 @@ message ExportLogsPartialSuccess {
75
75
  // error_message is an optional field. An error_message with an empty value
76
76
  // is equivalent to it not being set.
77
77
  string error_message = 2;
78
- }
78
+ }
@@ -717,4 +717,4 @@ message Exemplar {
717
717
  // trace_id may be missing if the measurement is not recorded inside a trace
718
718
  // or if the trace is not sampled.
719
719
  bytes trace_id = 5;
720
- }
720
+ }
@@ -75,4 +75,4 @@ message ExportMetricsPartialSuccess {
75
75
  // error_message is an optional field. An error_message with an empty value
76
76
  // is equivalent to it not being set.
77
77
  string error_message = 2;
78
- }
78
+ }
@@ -1,7 +1,8 @@
1
1
  'use strict'
2
2
 
3
- const http = require('http')
4
- const { URL } = require('url')
3
+ const http = require('node:http')
4
+ const https = require('node:https')
5
+ const { URL } = require('node:url')
5
6
  const log = require('../../log')
6
7
  const telemetryMetrics = require('../../telemetry/metrics')
7
8
 
@@ -16,6 +17,8 @@ const tracerMetrics = telemetryMetrics.manager.namespace('tracers')
16
17
  * @class OtlpHttpExporterBase
17
18
  */
18
19
  class OtlpHttpExporterBase {
20
+ #transport = https
21
+
19
22
  /**
20
23
  * Creates a new OtlpHttpExporterBase instance.
21
24
  *
@@ -45,7 +48,7 @@ class OtlpHttpExporterBase {
45
48
  this.setUrl(url)
46
49
 
47
50
  this.telemetryTags = [
48
- 'protocol:http',
51
+ `protocol:${this.#transport === https ? 'https' : 'http'}`,
49
52
  `encoding:${isJson ? 'json' : 'protobuf'}`,
50
53
  ]
51
54
  }
@@ -81,7 +84,7 @@ class OtlpHttpExporterBase {
81
84
  },
82
85
  }
83
86
 
84
- const req = http.request(options, (res) => {
87
+ const req = this.#transport.request(options, (res) => {
85
88
  let data = ''
86
89
 
87
90
  res.on('data', (chunk) => {
@@ -124,6 +127,10 @@ class OtlpHttpExporterBase {
124
127
  this.options.hostname = parsedUrl.hostname
125
128
  this.options.port = parsedUrl.port
126
129
  this.options.path = parsedUrl.pathname + parsedUrl.search
130
+ this.#transport = parsedUrl.protocol === 'http:' ? http : https
131
+ if (this.telemetryTags !== undefined) {
132
+ this.telemetryTags[0] = `protocol:${this.#transport === https ? 'https' : 'http'}`
133
+ }
127
134
  }
128
135
 
129
136
  /**
@@ -137,14 +137,17 @@ function normalizeLinkContext (context) {
137
137
  * Mirrors `http.response.status_code` onto `http.status_code` (DD's special tag used by APM
138
138
  * trace metrics and client-side stats); both names end up on the span.
139
139
  *
140
+ * When OTel trace semantics are enabled, the behavior is disabled.
141
+ *
140
142
  * @param {import('../opentracing/span')} ddSpan
141
143
  * @param {string} key
142
144
  * @param {unknown} value
145
+ * @param {boolean} [otelTraceSemanticsEnabled]
143
146
  */
144
- function setOtelAttribute (ddSpan, key, value) {
147
+ function setOtelAttribute (ddSpan, key, value, otelTraceSemanticsEnabled) {
145
148
  if (!isWritable(ddSpan)) return
146
149
 
147
- if (key === 'http.response.status_code') {
150
+ if (!otelTraceSemanticsEnabled && key === 'http.response.status_code') {
148
151
  ddSpan.setTag('http.status_code', String(value))
149
152
  }
150
153
 
@@ -155,14 +158,17 @@ function setOtelAttribute (ddSpan, key, value) {
155
158
  * Same `http.status_code` mirror as `setOtelAttribute`; does not mutate the caller's
156
159
  * `attributes` object.
157
160
  *
161
+ * When OTel trace semantics are enabled, the behavior is disabled.
162
+ *
158
163
  * @param {import('../opentracing/span')} ddSpan
159
164
  * @param {Record<string, unknown>} attributes
165
+ * @param {boolean} [otelTraceSemanticsEnabled]
160
166
  */
161
- function setOtelAttributes (ddSpan, attributes) {
167
+ function setOtelAttributes (ddSpan, attributes, otelTraceSemanticsEnabled) {
162
168
  if (!isWritable(ddSpan)) return
163
169
 
164
170
  ddSpan.addTags(attributes)
165
- if ('http.response.status_code' in attributes) {
171
+ if (!otelTraceSemanticsEnabled && 'http.response.status_code' in attributes) {
166
172
  ddSpan.setTag('http.status_code', String(attributes['http.response.status_code']))
167
173
  }
168
174
  }
@@ -223,16 +229,19 @@ function addOtelEvent (ddSpan, name, attributesOrStartTime, startTime) {
223
229
  * @param {import('../opentracing/span')} ddSpan
224
230
  * @param {ExceptionLike} exception
225
231
  * @param {TimeInput} [timeInput]
232
+ * @param {boolean} [otelTraceSemanticsEnabled]
226
233
  */
227
- function recordException (ddSpan, exception, timeInput) {
234
+ function recordException (ddSpan, exception, timeInput, otelTraceSemanticsEnabled) {
228
235
  if (!isWritable(ddSpan)) return
229
236
 
230
- ddSpan.addTags({
231
- [ERROR_TYPE]: exception.name,
232
- [ERROR_MESSAGE]: exception.message,
233
- [ERROR_STACK]: exception.stack,
234
- [IGNORE_OTEL_ERROR]: ddSpan.context().getTag(IGNORE_OTEL_ERROR) ?? true,
235
- })
237
+ if (!otelTraceSemanticsEnabled) {
238
+ ddSpan.addTags({
239
+ [ERROR_TYPE]: exception.name,
240
+ [ERROR_MESSAGE]: exception.message,
241
+ [ERROR_STACK]: exception.stack,
242
+ [IGNORE_OTEL_ERROR]: ddSpan.context().getTag(IGNORE_OTEL_ERROR) ?? true,
243
+ })
244
+ }
236
245
 
237
246
  const attributes = {}
238
247
  if (exception.message) attributes['exception.message'] = exception.message
@@ -251,13 +260,20 @@ function recordException (ddSpan, exception, timeInput) {
251
260
  * @param {import('../opentracing/span')} ddSpan
252
261
  * @param {number} currentCode 0 = UNSET, 1 = OK, 2 = ERROR.
253
262
  * @param {{ code?: number, message?: string }} [status]
263
+ * @param {boolean} [otelTraceSemanticsEnabled]
254
264
  * @returns {number} The new status code to track on the caller.
255
265
  */
256
- function applyOtelStatus (ddSpan, currentCode, status) {
266
+ function applyOtelStatus (ddSpan, currentCode, status, otelTraceSemanticsEnabled) {
257
267
  if (!isWritable(ddSpan)) return currentCode
258
268
 
259
269
  const code = status?.code
260
- if (!code || currentCode === 1) return currentCode
270
+ if (!code || currentCode === 1) {
271
+ if (otelTraceSemanticsEnabled) {
272
+ ddSpan.context().deleteTag(ERROR_MESSAGE)
273
+ ddSpan.context().deleteTag(IGNORE_OTEL_ERROR)
274
+ }
275
+ return currentCode
276
+ }
261
277
 
262
278
  if (code === 2) {
263
279
  ddSpan.addTags({
@@ -15,7 +15,7 @@ const kinds = require('../../../../ext/kinds')
15
15
  const id = require('../id')
16
16
  const BridgeSpanBase = require('./bridge-span-base')
17
17
  const SpanContext = require('./span_context')
18
- const { setOtelOperationName } = require('./span-helpers')
18
+ const { setOtelOperationName, setOtelResource } = require('./span-helpers')
19
19
 
20
20
  const spanKindNames = {
21
21
  [api.SpanKind.INTERNAL]: kinds.INTERNAL,
@@ -118,6 +118,8 @@ function spanNameMapper (spanName, kind, attributes) {
118
118
  * surface; the underlying DD span carries the lifecycle.
119
119
  */
120
120
  class Span extends BridgeSpanBase {
121
+ #otelName
122
+
121
123
  /**
122
124
  * @param {import('./tracer')} parentTracer
123
125
  * @param {import('@opentelemetry/api').Context} context
@@ -159,12 +161,15 @@ class Span extends BridgeSpanBase {
159
161
 
160
162
  super(ddSpan)
161
163
 
164
+ this._otelTraceSemanticsEnabled = _tracer._config.DD_TRACE_OTEL_SEMANTICS_ENABLED
165
+
162
166
  if (attributes) {
163
167
  this.setAttributes(attributes)
164
168
  }
165
169
 
166
170
  this._parentTracer = parentTracer
167
171
  this._context = context
172
+ this.#otelName = spanName || this._ddSpan.context()._name
168
173
 
169
174
  // NOTE: Need to grab the value before setting it on the span because the
170
175
  // math for computing opentracing timestamps is apparently lossy...
@@ -192,7 +197,7 @@ class Span extends BridgeSpanBase {
192
197
  }
193
198
 
194
199
  get name () {
195
- return this._ddSpan.context()._name
200
+ return this.#otelName
196
201
  }
197
202
 
198
203
  spanContext () {
@@ -223,7 +228,13 @@ class Span extends BridgeSpanBase {
223
228
  * @param {string} name
224
229
  */
225
230
  updateName (name) {
226
- setOtelOperationName(this._ddSpan, name)
231
+ if (this.ended) return this
232
+ this.#otelName = name
233
+ if (this._otelTraceSemanticsEnabled) {
234
+ setOtelResource(this._ddSpan, name)
235
+ } else {
236
+ setOtelOperationName(this._ddSpan, name)
237
+ }
227
238
  return this
228
239
  }
229
240
 
@@ -32,6 +32,7 @@ class SpanContext {
32
32
  }
33
33
 
34
34
  get traceFlags () {
35
+ this._ddContext._ensureSamplingPriority()
35
36
  return this._ddContext._sampling.priority >= AUTO_KEEP ? 1 : 0
36
37
  }
37
38
 
@@ -59,7 +59,8 @@ function createOtlpTraceExporter (config) {
59
59
  config.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
60
60
  config.OTEL_EXPORTER_OTLP_TRACES_HEADERS,
61
61
  config.OTEL_EXPORTER_OTLP_TRACES_TIMEOUT,
62
- buildResourceAttributes(config)
62
+ buildResourceAttributes(config),
63
+ config.DD_TRACE_OTEL_SEMANTICS_ENABLED
63
64
  )
64
65
  }
65
66
 
@@ -36,10 +36,11 @@ class OtlpHttpTraceExporter extends OtlpHttpExporterBase {
36
36
  * corresponding `OTEL_EXPORTER_OTLP_*_HEADERS` env by the MAP parser.
37
37
  * @param {number} timeout - Request timeout in milliseconds
38
38
  * @param {import('@opentelemetry/api').Attributes} resourceAttributes - Resource attributes
39
+ * @param {boolean} otelTraceSemanticsEnabled - When true, do not emit Datadog-only attributes as span attributes
39
40
  */
40
- constructor (url, headers, timeout, resourceAttributes) {
41
+ constructor (url, headers, timeout, resourceAttributes, otelTraceSemanticsEnabled) {
41
42
  super(url, headers, timeout, 'http/json', 'traces')
42
- this.#transformer = new OtlpTraceTransformer(resourceAttributes)
43
+ this.#transformer = new OtlpTraceTransformer(resourceAttributes, otelTraceSemanticsEnabled)
43
44
  }
44
45
 
45
46
  /**