dd-trace 5.108.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 (174) hide show
  1. package/ci/init.js +4 -3
  2. package/index.d.ts +88 -25
  3. package/loader-hook.mjs +88 -2
  4. package/package.json +19 -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/ai.js +43 -48
  8. package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js-context-methods.js +18 -0
  9. package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +111 -0
  10. package/packages/datadog-instrumentations/src/aws-sdk.js +3 -1
  11. package/packages/datadog-instrumentations/src/connect.js +4 -3
  12. package/packages/datadog-instrumentations/src/electron.js +1 -1
  13. package/packages/datadog-instrumentations/src/fastify.js +4 -12
  14. package/packages/datadog-instrumentations/src/graphql.js +17 -6
  15. package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +81 -8
  16. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  17. package/packages/datadog-instrumentations/src/helpers/instrument.js +27 -2
  18. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +65 -0
  19. package/packages/datadog-instrumentations/src/helpers/register.js +6 -4
  20. package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +17 -0
  21. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/aws-durable-execution-sdk-js.js +31 -0
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +18 -2
  24. package/packages/datadog-instrumentations/src/http/client.js +12 -2
  25. package/packages/datadog-instrumentations/src/ioredis.js +0 -1
  26. package/packages/datadog-instrumentations/src/iovalkey.js +1 -2
  27. package/packages/datadog-instrumentations/src/koa.js +3 -2
  28. package/packages/datadog-instrumentations/src/mariadb.js +4 -2
  29. package/packages/datadog-instrumentations/src/mocha/main.js +3 -3
  30. package/packages/datadog-instrumentations/src/next.js +44 -10
  31. package/packages/datadog-instrumentations/src/openai.js +77 -18
  32. package/packages/datadog-instrumentations/src/otel-sdk-trace.js +7 -6
  33. package/packages/datadog-instrumentations/src/playwright.js +1 -1
  34. package/packages/datadog-instrumentations/src/redis.js +0 -1
  35. package/packages/datadog-instrumentations/src/restify.js +4 -3
  36. package/packages/datadog-instrumentations/src/router.js +18 -5
  37. package/packages/datadog-instrumentations/src/selenium.js +1 -2
  38. package/packages/datadog-instrumentations/src/tedious.js +28 -0
  39. package/packages/datadog-instrumentations/src/vitest.js +323 -7
  40. package/packages/datadog-plugin-avsc/src/schema_iterator.js +2 -2
  41. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/checkpoint.js +31 -0
  42. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/client.js +55 -0
  43. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +114 -0
  44. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +128 -0
  45. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/index.js +19 -0
  46. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/trace-checkpoint.js +224 -0
  47. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +43 -0
  48. package/packages/datadog-plugin-aws-sdk/src/base.js +14 -16
  49. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +43 -6
  50. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +100 -37
  51. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +108 -99
  52. package/packages/datadog-plugin-bullmq/src/filter.js +35 -0
  53. package/packages/datadog-plugin-bullmq/src/producer.js +84 -4
  54. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +9 -1
  55. package/packages/datadog-plugin-fs/src/index.js +1 -0
  56. package/packages/datadog-plugin-http/src/client.js +8 -3
  57. package/packages/datadog-plugin-http2/src/client.js +5 -2
  58. package/packages/datadog-plugin-mysql/src/index.js +7 -2
  59. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +2 -2
  60. package/packages/datadog-plugin-redis/src/index.js +1 -2
  61. package/packages/datadog-plugin-undici/src/index.js +5 -2
  62. package/packages/datadog-plugin-vitest/src/index.js +11 -2
  63. package/packages/datadog-webpack/index.js +17 -1
  64. package/packages/datadog-webpack/src/optional-peer-loader.js +17 -0
  65. package/packages/dd-trace/src/aiguard/channels.js +0 -1
  66. package/packages/dd-trace/src/aiguard/index.js +11 -49
  67. package/packages/dd-trace/src/aiguard/integrations/evaluate.js +46 -0
  68. package/packages/dd-trace/src/aiguard/integrations/openai.js +66 -0
  69. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +78 -0
  70. package/packages/{datadog-instrumentations/src/helpers/ai-messages.js → dd-trace/src/aiguard/messages/openai.js} +85 -193
  71. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +185 -0
  72. package/packages/dd-trace/src/aiguard/sdk.js +13 -1
  73. package/packages/dd-trace/src/appsec/api_security/index.js +55 -0
  74. package/packages/dd-trace/src/appsec/api_security/sampler.js +147 -0
  75. package/packages/dd-trace/src/appsec/channels.js +1 -0
  76. package/packages/dd-trace/src/appsec/downstream_requests.js +111 -58
  77. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +54 -12
  78. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +5 -1
  79. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +29 -4
  80. package/packages/dd-trace/src/appsec/index.js +10 -6
  81. package/packages/dd-trace/src/appsec/rasp/ssrf.js +19 -11
  82. package/packages/dd-trace/src/appsec/reporter.js +1 -0
  83. package/packages/dd-trace/src/appsec/telemetry/api_security.js +34 -0
  84. package/packages/dd-trace/src/appsec/telemetry/index.js +26 -0
  85. package/packages/dd-trace/src/ci-visibility/requests/request.js +1 -17
  86. package/packages/dd-trace/src/config/defaults.js +22 -3
  87. package/packages/dd-trace/src/config/generated-config-types.d.ts +538 -1
  88. package/packages/dd-trace/src/config/helper.js +133 -33
  89. package/packages/dd-trace/src/config/index.js +4 -1
  90. package/packages/dd-trace/src/config/parsers.js +19 -1
  91. package/packages/dd-trace/src/config/supported-configurations.json +45 -5
  92. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +49 -35
  93. package/packages/dd-trace/src/debugger/devtools_client/index.js +31 -66
  94. package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +83 -0
  95. package/packages/dd-trace/src/debugger/devtools_client/state.js +1 -0
  96. package/packages/dd-trace/src/debugger/index.js +5 -0
  97. package/packages/dd-trace/src/debugger/probe_sampler.js +112 -0
  98. package/packages/dd-trace/src/debugger/probe_sampler_constants.js +19 -0
  99. package/packages/dd-trace/src/dogstatsd.js +15 -8
  100. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +77 -26
  101. package/packages/dd-trace/src/exporters/agentless/index.js +7 -5
  102. package/packages/dd-trace/src/exporters/agentless/intake.js +43 -0
  103. package/packages/dd-trace/src/exporters/agentless/writer.js +5 -4
  104. package/packages/dd-trace/src/exporters/common/request.js +1 -20
  105. package/packages/dd-trace/src/exporters/common/url.js +34 -0
  106. package/packages/dd-trace/src/exporters/span-stats/writer.js +1 -3
  107. package/packages/dd-trace/src/guardrails/index.js +8 -5
  108. package/packages/dd-trace/src/index.js +6 -4
  109. package/packages/dd-trace/src/llmobs/constants/tags.js +6 -0
  110. package/packages/dd-trace/src/llmobs/index.js +16 -5
  111. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +3 -3
  112. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +5 -1
  113. package/packages/dd-trace/src/llmobs/sdk.js +5 -3
  114. package/packages/dd-trace/src/llmobs/span_processor.js +4 -0
  115. package/packages/dd-trace/src/llmobs/tagger.js +55 -0
  116. package/packages/dd-trace/src/llmobs/telemetry.js +1 -0
  117. package/packages/dd-trace/src/log/index.js +18 -20
  118. package/packages/dd-trace/src/openfeature/flagging_provider.js +3 -1
  119. package/packages/dd-trace/src/opentelemetry/bridge-span-base.js +5 -4
  120. package/packages/dd-trace/src/opentelemetry/metrics/instruments.js +2 -1
  121. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +4 -3
  122. package/packages/dd-trace/src/opentelemetry/metrics/time.js +19 -0
  123. package/packages/dd-trace/src/opentelemetry/otlp/common.proto +2 -2
  124. package/packages/dd-trace/src/opentelemetry/otlp/logs.proto +1 -1
  125. package/packages/dd-trace/src/opentelemetry/otlp/logs_service.proto +1 -1
  126. package/packages/dd-trace/src/opentelemetry/otlp/metrics.proto +1 -1
  127. package/packages/dd-trace/src/opentelemetry/otlp/metrics_service.proto +1 -1
  128. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +11 -4
  129. package/packages/dd-trace/src/opentelemetry/span-helpers.js +29 -13
  130. package/packages/dd-trace/src/opentelemetry/span.js +14 -3
  131. package/packages/dd-trace/src/opentelemetry/span_context.js +1 -0
  132. package/packages/dd-trace/src/opentelemetry/trace/index.js +2 -1
  133. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +3 -2
  134. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +26 -13
  135. package/packages/dd-trace/src/opentracing/propagation/text_map.js +3 -3
  136. package/packages/dd-trace/src/opentracing/span_context.js +17 -0
  137. package/packages/dd-trace/src/plugin_manager.js +7 -1
  138. package/packages/dd-trace/src/plugins/ci_plugin.js +36 -3
  139. package/packages/dd-trace/src/plugins/index.js +3 -0
  140. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +276 -0
  141. package/packages/dd-trace/src/plugins/util/test.js +80 -0
  142. package/packages/dd-trace/src/plugins/util/url.js +68 -1
  143. package/packages/dd-trace/src/plugins/util/web.js +19 -36
  144. package/packages/dd-trace/src/priority_sampler.js +1 -13
  145. package/packages/dd-trace/src/profiling/config.js +129 -198
  146. package/packages/dd-trace/src/profiling/constants.js +4 -7
  147. package/packages/dd-trace/src/profiling/exporter_cli.js +10 -10
  148. package/packages/dd-trace/src/profiling/exporters/agent.js +23 -16
  149. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +45 -14
  150. package/packages/dd-trace/src/profiling/exporters/file.js +16 -5
  151. package/packages/dd-trace/src/profiling/oom.js +71 -0
  152. package/packages/dd-trace/src/profiling/profiler.js +35 -23
  153. package/packages/dd-trace/src/profiling/profilers/events.js +14 -7
  154. package/packages/dd-trace/src/profiling/profilers/space.js +27 -18
  155. package/packages/dd-trace/src/profiling/profilers/wall.js +35 -25
  156. package/packages/dd-trace/src/ritm.js +4 -2
  157. package/packages/dd-trace/src/serverless.js +1 -2
  158. package/packages/dd-trace/src/service-naming/schemas/util.js +36 -5
  159. package/packages/dd-trace/src/service-naming/schemas/v0/serverless.js +12 -0
  160. package/packages/dd-trace/src/service-naming/schemas/v1/serverless.js +12 -0
  161. package/packages/dd-trace/src/service-naming/schemas/v1/web.js +5 -3
  162. package/packages/dd-trace/src/span_format.js +2 -1
  163. package/packages/dd-trace/src/span_processor.js +6 -0
  164. package/packages/dd-trace/src/telemetry/send-data.js +12 -10
  165. package/packages/dd-trace/src/tracer.js +15 -1
  166. package/packages/dd-trace/src/util.js +14 -0
  167. package/register.js +58 -1
  168. package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
  169. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  170. package/vendor/dist/pprof-format/index.js +1 -1
  171. package/vendor/dist/protobufjs/index.js +1 -1
  172. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  173. package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +0 -284
  174. package/packages/dd-trace/src/appsec/api_security_sampler.js +0 -117
@@ -14,11 +14,13 @@ function guard (fn) {
14
14
  var initBailout = false
15
15
  var clobberBailout = false
16
16
  var forced = isTrue(process.env.DD_INJECT_FORCE)
17
- var engines = require('../../../../package.json').engines
18
- var versions = engines.node.match(/^>=(\d+) <(\d+)$/)
17
+ var pkg = require('../../../../package.json')
18
+ var engines = pkg.engines
19
+ var versions = engines.node.match(/^>=(\d+)$/)
19
20
  var minMajor = versions[1]
20
- var nextMajor = versions[2]
21
+ var nextMajor = pkg.nodeMaxMajor
21
22
  var version = process.versions.node
23
+ var supportedRange = engines.node + ' <' + nextMajor
22
24
 
23
25
  if (process.env.DD_INJECTION_ENABLED) {
24
26
  // If we're running via single-step install, and we're in the app's
@@ -45,16 +47,17 @@ function guard (fn) {
45
47
  // should not initialize the tracer.
46
48
  if (!clobberBailout && (NODE_MAJOR < minMajor || NODE_MAJOR >= nextMajor)) {
47
49
  initBailout = true
50
+ var runtimeInfo = 'Incompatible runtime Node.js ' + version + ', supported runtimes: Node.js ' + supportedRange
48
51
  telemetry([
49
52
  { name: 'abort', tags: ['reason:incompatible_runtime'] },
50
53
  { name: 'abort.runtime', tags: [] }
51
54
  ], undefined, {
52
55
  result: 'abort',
53
56
  result_class: 'incompatible_runtime',
54
- result_reason: 'Incompatible runtime Node.js ' + version + ', supported runtimes: Node.js ' + engines.node
57
+ result_reason: runtimeInfo
55
58
  })
56
59
  log.info('Aborting application instrumentation due to incompatible_runtime.')
57
- log.info('Found incompatible runtime Node.js %s, Supported runtimes: Node.js %s.', version, engines.node)
60
+ log.info('Found incompatible runtime Node.js %s, Supported runtimes: Node.js %s.', version, supportedRange)
58
61
  if (forced) {
59
62
  log.info('DD_INJECT_FORCE enabled, allowing unsupported runtimes and continuing.')
60
63
  }
@@ -1,14 +1,16 @@
1
1
  'use strict'
2
2
 
3
3
  const { getValueFromEnvSources } = require('./config/helper')
4
- const { isFalse } = require('./util')
5
4
 
6
5
  // Global `jest` is only present in Jest workers.
7
6
  const inJestWorker = typeof jest !== 'undefined'
8
7
 
9
- const ddTraceDisabled = getValueFromEnvSources('DD_TRACE_ENABLED')
10
- ? isFalse(getValueFromEnvSources('DD_TRACE_ENABLED'))
11
- : String(getValueFromEnvSources('OTEL_TRACES_EXPORTER')).toLowerCase() === 'none'
8
+ // skipDefault: distinguish an unset DD_TRACE_ENABLED (fall back to the OTel signal) from an
9
+ // explicit value; the registered default would otherwise mask the OTEL_TRACES_EXPORTER check.
10
+ const ddTraceEnabled = getValueFromEnvSources('DD_TRACE_ENABLED', true)
11
+ const ddTraceDisabled = ddTraceEnabled === undefined
12
+ ? getValueFromEnvSources('OTEL_TRACES_EXPORTER') === 'none'
13
+ : ddTraceEnabled === false
12
14
 
13
15
  module.exports = ddTraceDisabled || inJestWorker
14
16
  ? require('./noop/proxy')
@@ -14,6 +14,12 @@ module.exports = {
14
14
  PROPAGATED_PARENT_ID_KEY: '_dd.p.llmobs_parent_id',
15
15
  PROPAGATED_ML_APP_KEY: '_dd.p.llmobs_ml_app',
16
16
  PARENT_ID_KEY: '_ml_obs.llmobs_parent_id',
17
+ PROPAGATED_SAMPLE_RATE_KEY: '_dd.p.llmobs_sr',
18
+ PROPAGATED_SAMPLING_DECISION_KEY: '_dd.p.llmobs_sd',
19
+ SAMPLE_RATE: '_ml_obs.sample_rate',
20
+ SAMPLING_DECISION: '_ml_obs.sampling_decision',
21
+ SAMPLING_DECISION_SAMPLED: '1',
22
+ SAMPLING_DECISION_DROPPED: '0',
17
23
  TAGS: '_ml_obs.tags',
18
24
  NAME: '_ml_obs.name',
19
25
  TRACE_ID: '_ml_obs.trace_id',
@@ -9,6 +9,10 @@ const {
9
9
  ML_APP,
10
10
  PROPAGATED_ML_APP_KEY,
11
11
  PROPAGATED_PARENT_ID_KEY,
12
+ SAMPLE_RATE,
13
+ SAMPLING_DECISION,
14
+ PROPAGATED_SAMPLE_RATE_KEY,
15
+ PROPAGATED_SAMPLING_DECISION_KEY,
12
16
  } = require('./constants/tags')
13
17
  const { storage } = require('./storage')
14
18
  const telemetry = require('./telemetry')
@@ -68,7 +72,7 @@ function enable (config) {
68
72
  spanFinishCh.subscribe(handleSpanProcess)
69
73
 
70
74
  // distributed tracing for llmobs
71
- injectCh.subscribe(handleLLMObsParentIdInjection)
75
+ injectCh.subscribe(handleLLMObsInjection)
72
76
 
73
77
  setAgentStrategy(config, useAgentless => {
74
78
  if (useAgentless && !(config.apiKey && config.site)) {
@@ -92,7 +96,7 @@ function disable () {
92
96
  if (evalMetricAppendCh.hasSubscribers) evalMetricAppendCh.unsubscribe(handleEvalMetricAppend)
93
97
  if (flushCh.hasSubscribers) flushCh.unsubscribe(handleFlush)
94
98
  if (spanFinishCh.hasSubscribers) spanFinishCh.unsubscribe(handleSpanProcess)
95
- if (injectCh.hasSubscribers) injectCh.unsubscribe(handleLLMObsParentIdInjection)
99
+ if (injectCh.hasSubscribers) injectCh.unsubscribe(handleLLMObsInjection)
96
100
  if (registerUserSpanProcessorCh.hasSubscribers) registerUserSpanProcessorCh.unsubscribe(handleRegisterProcessor)
97
101
 
98
102
  spanWriter?.destroy()
@@ -106,8 +110,8 @@ function disable () {
106
110
  }
107
111
 
108
112
  // since LLMObs traces can extend between services and be the same trace,
109
- // we need to propagate the parent id and mlApp.
110
- function handleLLMObsParentIdInjection ({ carrier }) {
113
+ // we need to propagate the parent id, mlApp, and sampling rate/decision.
114
+ function handleLLMObsInjection ({ carrier }) {
111
115
  // Respect the standard propagator's gate: when trace tag propagation is
112
116
  // disabled, don't write `x-datadog-tags` for LLMObs either.
113
117
  if (globalTracerConfig.DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH === 0) return
@@ -122,7 +126,12 @@ function handleLLMObsParentIdInjection ({ carrier }) {
122
126
  parentContext?._trace?.tags?.[PROPAGATED_ML_APP_KEY] ||
123
127
  globalTracerConfig.llmobs.mlApp
124
128
 
125
- if (!parentId && !mlApp) return
129
+ const sampleRate =
130
+ mlObsSpanTags?.[SAMPLE_RATE] ?? parentContext?._trace?.tags?.[PROPAGATED_SAMPLE_RATE_KEY]
131
+ const samplingDecision =
132
+ mlObsSpanTags?.[SAMPLING_DECISION] ?? parentContext?._trace?.tags?.[PROPAGATED_SAMPLING_DECISION_KEY]
133
+
134
+ if (!parentId && !mlApp && samplingDecision == null) return
126
135
 
127
136
  // `_injectTags` only writes `x-datadog-tags` when the trace has `_dd.p.*`
128
137
  // tags, so it may be undefined here — coalesce before appending.
@@ -130,6 +139,8 @@ function handleLLMObsParentIdInjection ({ carrier }) {
130
139
  let tags = existing || ''
131
140
  if (parentId) tags += `${tags ? ',' : ''}${PROPAGATED_PARENT_ID_KEY}=${parentId}`
132
141
  if (mlApp) tags += `${tags ? ',' : ''}${PROPAGATED_ML_APP_KEY}=${mlApp}`
142
+ if (sampleRate != null) tags += `${tags ? ',' : ''}${PROPAGATED_SAMPLE_RATE_KEY}=${sampleRate}`
143
+ if (samplingDecision != null) tags += `${tags ? ',' : ''}${PROPAGATED_SAMPLING_DECISION_KEY}=${samplingDecision}`
133
144
  if (tags !== existing) carrier['x-datadog-tags'] = tags
134
145
  }
135
146
 
@@ -114,7 +114,7 @@ function extractMetrics (response) {
114
114
  }
115
115
 
116
116
  const totalTokens = tokenUsage.totalTokenCount ||
117
- (tokenUsage.promptTokenCount || 0) + (tokenUsage.candidatesTokenCount || 0)
117
+ (tokenUsage.promptTokenCount || 0) + (tokenUsage.candidatesTokenCount || 0)
118
118
  if (totalTokens) {
119
119
  metrics.totalTokens = totalTokens
120
120
  }
@@ -458,8 +458,8 @@ function formatStreamingOutput (response) {
458
458
 
459
459
  // Skip special cases in streaming (handle them as non-streaming)
460
460
  if (content.parts.some(part => part.functionCall ||
461
- part.executableCode ||
462
- part.codeExecutionResult)) {
461
+ part.executableCode ||
462
+ part.codeExecutionResult)) {
463
463
  messages.push(...formatNonStreamingCandidate(candidate))
464
464
  continue
465
465
  }
@@ -9,7 +9,11 @@ const ANTHROPIC_PROVIDER_NAME = 'anthropic'
9
9
  const BEDROCK_PROVIDER_NAME = 'amazon_bedrock'
10
10
  const OPENAI_PROVIDER_NAME = 'openai'
11
11
 
12
- const SUPPORTED_INTEGRATIONS = new Set(['openai'])
12
+ // Providers that ship their own LLMObs integration. When one of these is also
13
+ // enabled, the LangChain model span is demoted to `workflow` so the provider
14
+ // integration emits the single `llm` span — avoiding two llm spans (and
15
+ // double-counted tokens/cost) for one underlying call.
16
+ const SUPPORTED_INTEGRATIONS = new Set(['openai', 'anthropic'])
13
17
  const LLM_SPAN_TYPES = new Set(['llm', 'chat_model', 'embedding'])
14
18
  const LLM = 'llm'
15
19
  const WORKFLOW = 'workflow'
@@ -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,12 +1,14 @@
1
1
  'use strict'
2
2
 
3
- const { DatadogNodeServerProvider } = require('@datadog/openfeature-node-server')
4
3
  const { channel } = require('dc-polyfill')
4
+ const requireOptionalPeer = require('../../../datadog-instrumentations/src/helpers/require-optional-peer')
5
5
  const log = require('../log')
6
6
  const { EXPOSURE_CHANNEL } = require('./constants/constants')
7
7
  const EvalMetricsHook = require('./eval-metrics-hook')
8
8
  const SpanEnrichmentHook = require('./span-enrichment-hook')
9
9
 
10
+ const { DatadogNodeServerProvider } = requireOptionalPeer('@datadog/openfeature-node-server')
11
+
10
12
  /**
11
13
  * OpenFeature provider that integrates with Datadog's feature flagging system.
12
14
  * Extends DatadogNodeServerProvider to add tracer integration and configuration management.
@@ -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
  /**