dd-trace 6.14.0 → 6.16.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 (98) hide show
  1. package/README.md +15 -0
  2. package/ci/test-optimization-validation/framework-adapters/cucumber.js +4 -2
  3. package/ci/test-optimization-validation/package-check.js +4 -1
  4. package/ci/test-optimization-validation/scenarios/ci-wiring.js +1 -1
  5. package/index.d.ts +12 -0
  6. package/next.d.ts +27 -0
  7. package/next.js +3 -0
  8. package/package.json +17 -13
  9. package/packages/datadog-esbuild/index.js +14 -1
  10. package/packages/datadog-esbuild/src/resolver.js +284 -0
  11. package/packages/datadog-esbuild/src/utils.js +163 -119
  12. package/packages/datadog-instrumentations/src/ai.js +39 -97
  13. package/packages/datadog-instrumentations/src/anthropic.js +76 -134
  14. package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +1 -1
  15. package/packages/datadog-instrumentations/src/azure-cosmos.js +1 -1
  16. package/packages/datadog-instrumentations/src/azure-durable-functions.js +23 -9
  17. package/packages/datadog-instrumentations/src/azure-functions.js +48 -0
  18. package/packages/datadog-instrumentations/src/bullmq.js +1 -1
  19. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +1 -1
  20. package/packages/datadog-instrumentations/src/dd-trace-api.js +2 -2
  21. package/packages/datadog-instrumentations/src/graphql.js +1 -1
  22. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +39 -14
  23. package/packages/datadog-instrumentations/src/helpers/instrument.js +25 -8
  24. package/packages/datadog-instrumentations/src/helpers/instrumentation-utils.js +72 -0
  25. package/packages/datadog-instrumentations/src/helpers/register.js +9 -64
  26. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +46 -14
  27. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/azure-durable-functions.js +17 -0
  28. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  29. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +1 -5
  30. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +6 -7
  31. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
  32. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +99 -70
  33. package/packages/datadog-instrumentations/src/jest.js +141 -25
  34. package/packages/datadog-instrumentations/src/langchain.js +1 -1
  35. package/packages/datadog-instrumentations/src/langgraph.js +1 -1
  36. package/packages/datadog-instrumentations/src/mercurius.js +1 -1
  37. package/packages/datadog-instrumentations/src/mocha/main.js +41 -5
  38. package/packages/datadog-instrumentations/src/modelcontextprotocol-sdk.js +1 -1
  39. package/packages/datadog-instrumentations/src/openai-agents.js +1 -1
  40. package/packages/datadog-instrumentations/src/openai.js +54 -76
  41. package/packages/datadog-instrumentations/src/playwright.js +4 -0
  42. package/packages/datadog-instrumentations/src/vitest-worker.js +7 -10
  43. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +7 -1
  44. package/packages/datadog-plugin-azure-cosmos/src/index.js +1 -1
  45. package/packages/datadog-plugin-azure-durable-functions/src/index.js +96 -1
  46. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -24
  47. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +2 -36
  48. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-request-context.js +31 -0
  49. package/packages/datadog-plugin-graphql/src/resolve-error.js +33 -11
  50. package/packages/datadog-turbopack/index.js +212 -0
  51. package/packages/datadog-turbopack/src/constants.js +5 -0
  52. package/packages/datadog-turbopack/src/loader.js +500 -0
  53. package/packages/dd-trace/src/aiguard/index.js +2 -1
  54. package/packages/dd-trace/src/aiguard/integrations/anthropic.js +53 -25
  55. package/packages/dd-trace/src/aiguard/integrations/evaluate.js +49 -25
  56. package/packages/dd-trace/src/aiguard/integrations/index.js +3 -2
  57. package/packages/dd-trace/src/aiguard/integrations/openai.js +59 -30
  58. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +116 -42
  59. package/packages/dd-trace/src/aiguard/messages/utils.js +29 -1
  60. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +25 -0
  61. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +3 -1
  62. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +531 -70
  63. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +2 -2
  64. package/packages/dd-trace/src/appsec/telemetry/api_security.js +1 -3
  65. package/packages/dd-trace/src/appsec/telemetry/common.js +4 -0
  66. package/packages/dd-trace/src/appsec/telemetry/index.js +23 -5
  67. package/packages/dd-trace/src/appsec/telemetry/rasp.js +1 -20
  68. package/packages/dd-trace/src/appsec/telemetry/user.js +1 -3
  69. package/packages/dd-trace/src/appsec/telemetry/waf.js +1 -20
  70. package/packages/dd-trace/src/ci-visibility/requests/request.js +11 -0
  71. package/packages/dd-trace/src/ci-visibility/requests/video-request.js +4 -0
  72. package/packages/dd-trace/src/config/generated-config-types.d.ts +2 -0
  73. package/packages/dd-trace/src/config/helper.js +10 -2
  74. package/packages/dd-trace/src/config/index.js +20 -5
  75. package/packages/dd-trace/src/config/supported-configurations.json +10 -0
  76. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +14 -7
  77. package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +14 -1
  78. package/packages/dd-trace/src/debugger/devtools_client/request-options.js +1 -6
  79. package/packages/dd-trace/src/evp_proxy/direct.js +2 -28
  80. package/packages/dd-trace/src/evp_proxy/discovery.js +3 -4
  81. package/packages/dd-trace/src/evp_proxy/path.js +12 -3
  82. package/packages/dd-trace/src/exporters/agentless/writer.js +3 -1
  83. package/packages/dd-trace/src/exporters/common/docker.js +4 -1
  84. package/packages/dd-trace/src/exporters/common/proxy.js +55 -0
  85. package/packages/dd-trace/src/exporters/common/request.js +11 -3
  86. package/packages/dd-trace/src/opentelemetry/metrics/index.js +1 -1
  87. package/packages/dd-trace/src/opentelemetry/metrics/meter_provider.js +5 -4
  88. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +106 -25
  89. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +12 -1
  90. package/packages/dd-trace/src/opentracing/propagation/text_map.js +8 -2
  91. package/packages/dd-trace/src/plugin_manager.js +9 -3
  92. package/packages/dd-trace/src/plugins/index.js +1 -0
  93. package/packages/dd-trace/src/plugins/util/test.js +4 -1
  94. package/packages/dd-trace/src/priority_sampler.js +4 -0
  95. package/packages/dd-trace/src/sampling_rule.js +3 -1
  96. package/packages/dd-trace/src/span_processor.js +18 -3
  97. package/packages/dd-trace/src/telemetry/send-data.js +5 -4
  98. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/utils.js +0 -10
@@ -5,14 +5,13 @@ const {
5
5
  incrementApiSecRequestNoSchema,
6
6
  incrementApiSecRequestSchema,
7
7
  } = require('./api_security')
8
- const { DD_TELEMETRY_REQUEST_METRICS } = require('./common')
8
+ const { appsecMetrics, DD_TELEMETRY_REQUEST_METRICS, getVersionsTags } = require('./common')
9
9
  const { incrementMissingUserId, incrementMissingUserLogin, incrementSdkEvent } = require('./user')
10
10
  const {
11
11
  addRaspRequestMetrics,
12
12
  trackRaspMetrics,
13
13
  trackRaspRuleMatch,
14
14
  trackRaspRuleSkipped,
15
- incrementRaspDurationMetrics,
16
15
  } = require('./rasp')
17
16
  const {
18
17
  addWafRequestMetrics,
@@ -21,7 +20,6 @@ const {
21
20
  incrementWafUpdates,
22
21
  incrementWafConfigErrors,
23
22
  incrementWafRequests,
24
- incrementWafDurationMetrics,
25
23
  } = require('./waf')
26
24
 
27
25
  const metricsStoreMap = new WeakMap()
@@ -150,8 +148,28 @@ function incrementRequestDurationMetrics (req) {
150
148
  const store = getStore(req)
151
149
  const requestMetrics = store[DD_TELEMETRY_REQUEST_METRICS]
152
150
 
153
- incrementWafDurationMetrics(requestMetrics)
154
- incrementRaspDurationMetrics(requestMetrics)
151
+ if (!requestMetrics.duration && !requestMetrics.durationExt &&
152
+ !requestMetrics.raspDuration && !requestMetrics.raspDurationExt) return
153
+
154
+ const { duration, durationExt, raspDuration, raspDurationExt, wafVersion, rulesVersion } = requestMetrics
155
+
156
+ const versionsTags = getVersionsTags(wafVersion, rulesVersion)
157
+
158
+ if (duration) {
159
+ appsecMetrics.distribution('waf.duration', versionsTags).track(duration)
160
+ }
161
+
162
+ if (durationExt) {
163
+ appsecMetrics.distribution('waf.duration_ext', versionsTags).track(durationExt)
164
+ }
165
+
166
+ if (raspDuration) {
167
+ appsecMetrics.distribution('rasp.duration', versionsTags).track(raspDuration)
168
+ }
169
+
170
+ if (raspDurationExt) {
171
+ appsecMetrics.distribution('rasp.duration_ext', versionsTags).track(raspDurationExt)
172
+ }
155
173
  }
156
174
 
157
175
  function incrementMissingUserLoginMetric (framework, eventType) {
@@ -1,9 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const telemetryMetrics = require('../../telemetry/metrics')
4
- const { DD_TELEMETRY_REQUEST_METRICS, getVersionsTags } = require('./common')
5
-
6
- const appsecMetrics = telemetryMetrics.manager.namespace('appsec')
3
+ const { appsecMetrics, DD_TELEMETRY_REQUEST_METRICS, getVersionsTags } = require('./common')
7
4
 
8
5
  const BLOCKING_STATUS = {
9
6
  FAILURE: 'failure',
@@ -77,21 +74,6 @@ function trackRaspRuleMatch (store, raspRule, blockTriggered, blocked) {
77
74
  appsecMetrics.count('rasp.rule.match', tags).inc(1)
78
75
  }
79
76
 
80
- function incrementRaspDurationMetrics (requestMetrics) {
81
- const { raspDuration, raspDurationExt, wafVersion, rulesVersion } = requestMetrics
82
- if (!raspDuration && !raspDurationExt) return
83
-
84
- const versionsTags = getVersionsTags(wafVersion, rulesVersion)
85
-
86
- if (raspDuration) {
87
- appsecMetrics.distribution('rasp.duration', versionsTags).track(raspDuration)
88
- }
89
-
90
- if (raspDurationExt) {
91
- appsecMetrics.distribution('rasp.duration_ext', versionsTags).track(raspDurationExt)
92
- }
93
- }
94
-
95
77
  function trackRaspRuleSkipped (raspRule, reason) {
96
78
  const tags = { reason, rule_type: raspRule.type }
97
79
 
@@ -115,5 +97,4 @@ module.exports = {
115
97
  trackRaspMetrics,
116
98
  trackRaspRuleMatch,
117
99
  trackRaspRuleSkipped,
118
- incrementRaspDurationMetrics,
119
100
  }
@@ -1,8 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const telemetryMetrics = require('../../telemetry/metrics')
4
-
5
- const appsecMetrics = telemetryMetrics.manager.namespace('appsec')
3
+ const { appsecMetrics } = require('./common')
6
4
 
7
5
  function incrementMissingUserLogin (framework, eventType) {
8
6
  appsecMetrics.count('instrum.user_auth.missing_user_login', {
@@ -1,9 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const telemetryMetrics = require('../../telemetry/metrics')
4
- const { tags, getVersionsTags, DD_TELEMETRY_REQUEST_METRICS } = require('./common')
5
-
6
- const appsecMetrics = telemetryMetrics.manager.namespace('appsec')
3
+ const { appsecMetrics, tags, getVersionsTags, DD_TELEMETRY_REQUEST_METRICS } = require('./common')
7
4
 
8
5
  const DD_TELEMETRY_WAF_RESULT_TAGS = Symbol('_dd.appsec.telemetry.waf.result.tags')
9
6
 
@@ -126,21 +123,6 @@ function incrementWafRequests (store) {
126
123
  }
127
124
  }
128
125
 
129
- function incrementWafDurationMetrics (requestMetrics) {
130
- const { duration, durationExt, wafVersion, rulesVersion } = requestMetrics
131
- if (!duration && !durationExt) return
132
-
133
- const versionsTags = getVersionsTags(wafVersion, rulesVersion)
134
-
135
- if (duration) {
136
- appsecMetrics.distribution('waf.duration', versionsTags).track(duration)
137
- }
138
-
139
- if (durationExt) {
140
- appsecMetrics.distribution('waf.duration_ext', versionsTags).track(durationExt)
141
- }
142
- }
143
-
144
126
  function incrementTruncatedMetrics (metrics, truncationReason) {
145
127
  const truncationTags = { truncation_reason: truncationReason }
146
128
  appsecMetrics.count('waf.input_truncated', truncationTags).inc(1)
@@ -163,5 +145,4 @@ module.exports = {
163
145
  incrementWafUpdates,
164
146
  incrementWafConfigErrors,
165
147
  incrementWafRequests,
166
- incrementWafDurationMetrics,
167
148
  }
@@ -13,6 +13,7 @@ const {
13
13
  singleJitteredDelay,
14
14
  } = require('../../exporters/common/retry')
15
15
  const { parseUrl } = require('../../exporters/common/url')
16
+ const { getHttpsProxyAgent } = require('../../exporters/common/proxy')
16
17
  const { getRateLimitResetDelay } = require('./rate-limit')
17
18
 
18
19
  const legacyStorage = storage('legacy')
@@ -58,6 +59,16 @@ function request (data, options, callback) {
58
59
  opts.agent = isSecure ? httpsAgent : httpAgent
59
60
  }
60
61
 
62
+ const hasApiKey = headers['dd-api-key'] !== undefined || headers['DD-API-KEY'] !== undefined
63
+ if (hasApiKey && isSecure) {
64
+ try {
65
+ opts.agent = getHttpsProxyAgent(opts, opts.agent)
66
+ } catch (error) {
67
+ callback(error)
68
+ return
69
+ }
70
+ }
71
+
61
72
  let hasRetried = false
62
73
  let firstStatusCode = null
63
74
 
@@ -6,6 +6,7 @@ const https = require('node:https')
6
6
  const { storage } = require('../../../../datadog-core')
7
7
  const log = require('../../log')
8
8
  const { markEndpointReached } = require('../../exporters/common/retry')
9
+ const { getHttpsProxyAgent } = require('../../exporters/common/proxy')
9
10
  const { isLoopbackHost, parseUrl } = require('../../exporters/common/url')
10
11
 
11
12
  const legacyStorage = storage('legacy')
@@ -129,6 +130,9 @@ function getRequestOptions (options) {
129
130
  delete requestOptions.headers['dd-api-key']
130
131
  delete requestOptions.headers['DD-API-KEY']
131
132
  }
133
+ if (hasApiKey && requestOptions.protocol === 'https:') {
134
+ requestOptions.agent = getHttpsProxyAgent(requestOptions, requestOptions.agent)
135
+ }
132
136
 
133
137
  delete requestOptions.deadline
134
138
  delete requestOptions.retry
@@ -68,6 +68,7 @@ export interface GeneratedConfig {
68
68
  DD_AGENTLESS_ENABLED: boolean;
69
69
  DD_AGENTLESS_LOG_SUBMISSION_ENABLED: boolean;
70
70
  DD_AGENTLESS_LOG_SUBMISSION_URL: string | undefined;
71
+ DD_AI_GUARD_ANALYZE_STREAM_RESPONSES_ENABLED: boolean;
71
72
  DD_API_KEY: string | undefined;
72
73
  DD_APM_FLUSH_DEADLINE_MILLISECONDS: number;
73
74
  DD_APP_KEY: string | undefined;
@@ -615,6 +616,7 @@ export interface GeneratedEnvVarConfig {
615
616
  DD_AGENTLESS_ENABLED: boolean;
616
617
  DD_AGENTLESS_LOG_SUBMISSION_ENABLED: boolean;
617
618
  DD_AGENTLESS_LOG_SUBMISSION_URL: string | undefined;
619
+ DD_AI_GUARD_ANALYZE_STREAM_RESPONSES_ENABLED: boolean;
618
620
  DD_AI_GUARD_BLOCK: boolean;
619
621
  DD_AI_GUARD_ENABLED: boolean;
620
622
  DD_AI_GUARD_ENDPOINT: string | undefined;
@@ -143,10 +143,17 @@ function getEnvNameFromSource (name, source) {
143
143
  }
144
144
  }
145
145
 
146
+ /**
147
+ * @param {string} name
148
+ * @returns {boolean}
149
+ */
150
+ function isSupportedConfiguration (name) {
151
+ return supportedConfigurations[name] !== undefined || aliasToCanonical[name] !== undefined
152
+ }
153
+
146
154
  function validateAccess (name) {
147
155
  if ((name.startsWith('DD_') || name.startsWith('OTEL_')) &&
148
- !supportedConfigurations[name] &&
149
- !aliasToCanonical[name]) {
156
+ !isSupportedConfiguration(name)) {
150
157
  throw new Error(`Missing ${name} env/configuration in "supported-configurations.json" file.`)
151
158
  }
152
159
  }
@@ -284,6 +291,7 @@ function getValueFromEnvSources (name, skipDefault) {
284
291
 
285
292
  module.exports = {
286
293
  getValueFromEnvSources,
294
+ isSupportedConfiguration,
287
295
 
288
296
  /**
289
297
  * Expose raw stable config maps and warnings for consumers that need
@@ -24,6 +24,7 @@ const {
24
24
  } = require('../serverless')
25
25
  const { ORIGIN_KEY, DATADOG_MINI_AGENT_PATH } = require('../constants')
26
26
  const { appendRules } = require('../payload-tagging/config')
27
+ const { createSiteUrl } = require('../exporters/common/url')
27
28
  const ConfigBase = require('./config-base')
28
29
  const {
29
30
  getEnvironmentVariable,
@@ -495,9 +496,6 @@ class Config extends ConfigBase {
495
496
 
496
497
  const agentlessTracingEnabled = this.DD_AGENTLESS_ENABLED ||
497
498
  isTrue(getEnvironmentVariable('_DD_APM_TRACING_AGENTLESS_ENABLED'))
498
- if (agentlessTracingEnabled && !this.isCiVisibility) {
499
- setAndTrack(this, 'OTEL_TRACES_EXPORTER', 'none')
500
- }
501
499
 
502
500
  // Apply the OTel sampler when the user opted into OTel traces or explicitly set the sampler.
503
501
  // OTEL_TRACES_SAMPLER has `default: parentbased_always_on` (per OTel spec), so opt-in users
@@ -699,16 +697,33 @@ class Config extends ConfigBase {
699
697
 
700
698
  // TODO: This could likely be moved to the base class and allow easier GRPC handling
701
699
  // Default OTLP endpoints follow the configured agent host so users who point DD at a custom
702
- // agent (DD_AGENT_HOST / DD_TRACE_AGENT_URL) also reach OTLP on that host.
703
- const defaultOtlpBase = this.OTEL_EXPORTER_OTLP_ENDPOINT?.replace(/\/$/, '') ?? `http://${agentHostname}:4318`
700
+ // agent (DD_AGENT_HOST / DD_TRACE_AGENT_URL) also reach OTLP on that host. In agentless mode
701
+ // there is no agent to relay through, so OTLP goes straight to the per-site intake instead.
702
+ const otlpAgentlessOrigin = agentlessTracingEnabled && !this.OTEL_EXPORTER_OTLP_ENDPOINT
703
+ ? createSiteUrl(this.site, 'otlp')?.origin
704
+ : undefined
705
+ const defaultOtlpBase = otlpAgentlessOrigin ??
706
+ this.OTEL_EXPORTER_OTLP_ENDPOINT?.replace(/\/$/, '') ?? `http://${agentHostname}:4318`
707
+
708
+ const assignOtlpHeaderApiKey = (configName) => {
709
+ if (otlpAgentlessOrigin) {
710
+ this[configName] ??= { ...this.OTEL_EXPORTER_OTLP_HEADERS }
711
+ this[configName]['dd-api-key'] = this.DD_API_KEY
712
+ setAndTrack(this, configName, this[configName])
713
+ }
714
+ }
715
+
704
716
  if (!this.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT) {
705
717
  setAndTrack(this, 'OTEL_EXPORTER_OTLP_LOGS_ENDPOINT', `${defaultOtlpBase}/v1/logs`)
718
+ assignOtlpHeaderApiKey('OTEL_EXPORTER_OTLP_LOGS_HEADERS')
706
719
  }
707
720
  if (!this.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT) {
708
721
  setAndTrack(this, 'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT', `${defaultOtlpBase}/v1/metrics`)
722
+ assignOtlpHeaderApiKey('OTEL_EXPORTER_OTLP_METRICS_HEADERS')
709
723
  }
710
724
  if (!this.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT) {
711
725
  setAndTrack(this, 'OTEL_EXPORTER_OTLP_TRACES_ENDPOINT', `${defaultOtlpBase}/v1/traces`)
726
+ assignOtlpHeaderApiKey('OTEL_EXPORTER_OTLP_TRACES_HEADERS')
712
727
  }
713
728
 
714
729
  const autoTraceMetrics = this.OTEL_TRACES_EXPORTER === 'otlp' && this.DD_METRICS_OTEL_ENABLED === true
@@ -53,6 +53,14 @@
53
53
  ]
54
54
  }
55
55
  ],
56
+ "DD_AI_GUARD_ANALYZE_STREAM_RESPONSES_ENABLED": [
57
+ {
58
+ "implementation": "A",
59
+ "type": "boolean",
60
+ "default": "false",
61
+ "description": "Buffer Vercel AI streamed responses for After Model evaluation before replaying them."
62
+ }
63
+ ],
56
64
  "DD_AI_GUARD_BLOCK": [
57
65
  {
58
66
  "implementation": "C",
@@ -744,6 +752,7 @@
744
752
  "configurationNames": [
745
753
  "dogstatsd.port"
746
754
  ],
755
+ "allowed": "[1-9]\\d{0,3}|[1-5]\\d{4}|6[0-4]\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5]",
747
756
  "default": "8125"
748
757
  }
749
758
  ],
@@ -2028,6 +2037,7 @@
2028
2037
  "configurationNames": [
2029
2038
  "port"
2030
2039
  ],
2040
+ "allowed": "[1-9]\\d{0,3}|[1-5]\\d{4}|6[0-4]\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5]",
2031
2041
  "default": "8126"
2032
2042
  }
2033
2043
  ],
@@ -6,7 +6,11 @@ const { getGeneratedPosition } = require('./source-maps')
6
6
  const session = require('./session')
7
7
  const { compile, compileSegments, templateRequiresEvaluation } = require('./condition')
8
8
  const { MAX_SNAPSHOTS_PER_SECOND_PER_PROBE, MAX_NON_SNAPSHOTS_PER_SECOND_PER_PROBE } = require('./defaults')
9
- const { compileBreakpointCondition, getRemoveProbeExpression } = require('./probe_sampler')
9
+ const {
10
+ compileBreakpointCondition,
11
+ getRemoveProbeExpression,
12
+ isSnapshotProducingProbe,
13
+ } = require('./probe_sampler')
10
14
  const {
11
15
  DEFAULT_MAX_REFERENCE_DEPTH,
12
16
  DEFAULT_MAX_COLLECTION_SIZE,
@@ -76,12 +80,6 @@ async function addBreakpoint (probe) {
76
80
  }
77
81
  delete probe.segments
78
82
 
79
- // Optimize for fast calculations when probe is hit
80
- const snapshotsPerSecond = probe.sampling?.snapshotsPerSecond ?? (probe.captureSnapshot
81
- ? MAX_SNAPSHOTS_PER_SECOND_PER_PROBE
82
- : MAX_NON_SNAPSHOTS_PER_SECOND_PER_PROBE)
83
- probe.nsBetweenSampling = BigInt(Math.trunc(1 / snapshotsPerSecond * 1e9))
84
-
85
83
  // Warning: The code below relies on undocumented behavior of the inspector!
86
84
  // It expects that `await session.post('Debugger.enable')` will wait for all loaded scripts to be emitted as
87
85
  // `Debugger.scriptParsed` events. If this ever changes, we will have a race condition!
@@ -156,6 +154,15 @@ async function addBreakpoint (probe) {
156
154
  }
157
155
  }
158
156
 
157
+ // Must be calculated after `compiledCaptureExpressions` has been resolved, since capture-expression probes produce
158
+ // snapshots and therefore default to the snapshot rate.
159
+ //
160
+ // Optimize for fast calculations when probe is hit
161
+ const snapshotsPerSecond = probe.sampling?.snapshotsPerSecond ?? (isSnapshotProducingProbe(probe)
162
+ ? MAX_SNAPSHOTS_PER_SECOND_PER_PROBE
163
+ : MAX_NON_SNAPSHOTS_PER_SECOND_PER_PROBE)
164
+ probe.nsBetweenSampling = BigInt(Math.trunc(1 / snapshotsPerSecond * 1e9))
165
+
159
166
  const locationKey = generateLocationKey(scriptId, lineNumber, columnNumber)
160
167
  const breakpoint = locationToBreakpoint.get(locationKey)
161
168
 
@@ -8,6 +8,19 @@ const SAMPLER_EXPRESSION = `globalThis[Symbol.for(${JSON.stringify(DD_TRACE_SYMB
8
8
  module.exports = {
9
9
  compileBreakpointCondition,
10
10
  getRemoveProbeExpression,
11
+ isSnapshotProducingProbe,
12
+ }
13
+
14
+ /**
15
+ * Determine whether a probe produces snapshots. Probes that capture the full local state and probes that only capture
16
+ * specific expressions both emit snapshot payloads, so they share the snapshot sampling defaults and count towards the
17
+ * global snapshot rate limit.
18
+ *
19
+ * @param {{ captureSnapshot?: boolean, compiledCaptureExpressions?: object[] }} probe - The probe to inspect.
20
+ * @returns {boolean}
21
+ */
22
+ function isSnapshotProducingProbe (probe) {
23
+ return probe.captureSnapshot === true || probe.compiledCaptureExpressions !== undefined
11
24
  }
12
25
 
13
26
  /**
@@ -66,7 +79,7 @@ function compileBreakpointCondition (probes) {
66
79
  */
67
80
  function compileProbeCondition (probe) {
68
81
  const sample = `$dd_sampler.makeSampleDecision(${probe.samplingIndex}, ${JSON.stringify(probe.id)}, ` +
69
- `${probe.nsBetweenSampling}n, ${probe.captureSnapshot === true || probe.compiledCaptureExpressions !== undefined})`
82
+ `${probe.nsBetweenSampling}n, ${isSnapshotProducingProbe(probe)})`
70
83
 
71
84
  if (probe.condition === undefined) {
72
85
  return `$dd_sampled = ${sample} || $dd_sampled`
@@ -1,7 +1,5 @@
1
1
  'use strict'
2
2
 
3
- const { getHttpsProxyAgent } = require('../../evp_proxy/direct')
4
-
5
3
  /**
6
4
  * @param {ReturnType<import('../config')>} config - Debugger configuration
7
5
  * @param {string} path - Request path
@@ -10,8 +8,7 @@ const { getHttpsProxyAgent } = require('../../evp_proxy/direct')
10
8
  * method: 'POST',
11
9
  * url: string | URL,
12
10
  * path: string,
13
- * headers: Record<string, string>,
14
- * agent?: import('node:https').Agent
11
+ * headers: Record<string, string>
15
12
  * }}
16
13
  */
17
14
  module.exports = function getRequestOptions (config, path, headers) {
@@ -25,8 +22,6 @@ module.exports = function getRequestOptions (config, path, headers) {
25
22
  if (config.agentless) {
26
23
  if (config.apiKey !== undefined) headers['DD-API-KEY'] = config.apiKey
27
24
  headers['DD-EVP-ORIGIN'] = 'agent-debugger'
28
- const agent = getHttpsProxyAgent(config.url.href)
29
- if (agent !== undefined) options.agent = agent
30
25
  }
31
26
 
32
27
  return options
@@ -1,19 +1,13 @@
1
1
  'use strict'
2
2
 
3
- const { HttpsProxyAgent } = require('../../../../vendor/dist/https-proxy-agent')
4
- const { getProxyForUrl } = require('../../../../vendor/dist/proxy-from-env')
5
3
  const { createSiteUrl } = require('../exporters/common/url')
6
4
  const log = require('../log')
7
5
 
8
- /** @type {Map<string, import('node:https').Agent>} */
9
- const proxyAgents = new Map()
10
-
11
6
  /**
12
7
  * @typedef {object} DirectEVPRoute
13
8
  * @property {URL} url - Direct intake URL
14
9
  * @property {string} basePath - Direct intake base path
15
10
  * @property {object} headers - Direct intake authentication headers
16
- * @property {import('node:https').Agent} [agent] - Optional HTTPS proxy agent
17
11
  */
18
12
 
19
13
  /**
@@ -33,36 +27,16 @@ function createDirectEVPRoute (config, intake) {
33
27
  const url = createSiteUrl(config.site, intake)
34
28
  if (url === undefined) throw new Error('Invalid direct EVP intake URL')
35
29
 
36
- const agent = getHttpsProxyAgent(url.href)
37
-
38
- const route = {
30
+ return {
39
31
  url,
40
32
  basePath: '',
41
33
  headers: {
42
34
  'DD-API-KEY': apiKey,
43
35
  },
44
36
  }
45
- if (agent) route.agent = agent
46
- return route
47
37
  } catch (error) {
48
38
  log.debug('Unable to configure direct EVP intake: %s', error.message)
49
39
  }
50
40
  }
51
41
 
52
- /**
53
- * @param {string} url
54
- * @returns {import('node:https').Agent|undefined}
55
- */
56
- function getHttpsProxyAgent (url) {
57
- const proxyUrl = getProxyForUrl(url)
58
- if (!proxyUrl) return
59
-
60
- let agent = proxyAgents.get(proxyUrl)
61
- if (agent === undefined) {
62
- agent = new HttpsProxyAgent(proxyUrl)
63
- proxyAgents.set(proxyUrl, agent)
64
- }
65
- return agent
66
- }
67
-
68
- module.exports = { createDirectEVPRoute, getHttpsProxyAgent }
42
+ module.exports = { createDirectEVPRoute }
@@ -2,8 +2,7 @@
2
2
 
3
3
  const { fetchAgentInfo } = require('../agent/info')
4
4
  const log = require('../log')
5
-
6
- const TRAILING_SLASHES = /\/+$/
5
+ const { stripTrailingSlashes } = require('./path')
7
6
 
8
7
  /**
9
8
  * Receiver discovery contract
@@ -81,14 +80,14 @@ function selectEVPProxyPath (agentInfo, { supportedPaths, requiredHeaders = [] }
81
80
  const advertisedPaths = new Set()
82
81
  for (const endpoint of agentInfo.endpoints) {
83
82
  if (typeof endpoint === 'string') {
84
- advertisedPaths.add(endpoint.replace(TRAILING_SLASHES, ''))
83
+ advertisedPaths.add(stripTrailingSlashes(endpoint))
85
84
  }
86
85
  }
87
86
 
88
87
  for (const supportedPath of supportedPaths) {
89
88
  if (typeof supportedPath !== 'string') continue
90
89
 
91
- const normalizedPath = supportedPath.replace(TRAILING_SLASHES, '')
90
+ const normalizedPath = stripTrailingSlashes(supportedPath)
92
91
  if (advertisedPaths.has(normalizedPath)) {
93
92
  return normalizedPath
94
93
  }
@@ -1,7 +1,16 @@
1
1
  'use strict'
2
2
 
3
3
  const LEADING_SLASHES = /^\/+/
4
- const TRAILING_SLASHES = /\/+$/
4
+
5
+ /**
6
+ * @param {string} value
7
+ * @returns {string}
8
+ */
9
+ function stripTrailingSlashes (value) {
10
+ let end = value.length
11
+ while (end > 0 && value[end - 1] === '/') end--
12
+ return end === value.length ? value : value.slice(0, end)
13
+ }
5
14
 
6
15
  /**
7
16
  * Joins a caller-supplied EVP proxy path and product endpoint.
@@ -13,10 +22,10 @@ const TRAILING_SLASHES = /\/+$/
13
22
  * @returns {string} Joined request path
14
23
  */
15
24
  function joinEVPProxyPath (basePath, endpoint) {
16
- const normalizedBasePath = basePath.replace(TRAILING_SLASHES, '')
25
+ const normalizedBasePath = stripTrailingSlashes(basePath)
17
26
  const normalizedEndpoint = endpoint.replace(LEADING_SLASHES, '')
18
27
 
19
28
  return `${normalizedBasePath}/${normalizedEndpoint}`
20
29
  }
21
30
 
22
- module.exports = { joinEVPProxyPath }
31
+ module.exports = { joinEVPProxyPath, stripTrailingSlashes }
@@ -8,6 +8,7 @@ const log = require('../../log')
8
8
  const tracerVersion = require('../../../../../package.json').version
9
9
 
10
10
  const { canSendApiKey } = require('../common/url')
11
+ const { getHttpsProxyAgent } = require('../common/proxy')
11
12
  const BaseWriter = require('../common/writer')
12
13
  const { AgentEncoder } = require('../../encode/0.4')
13
14
  const { computeIntakeUrl, INTAKE_PATH } = require('./intake')
@@ -153,6 +154,7 @@ class AgentlessWriter extends BaseWriter {
153
154
 
154
155
  this.#closeExporter()
155
156
  const config = getConfig()
157
+ const agent = this._url.protocol === 'https:' ? getHttpsProxyAgent(this._url) : undefined
156
158
  this.#exporter = createAgentlessExporter({
157
159
  endpoint: this.#endpoint(),
158
160
  apiKey,
@@ -165,7 +167,7 @@ class AgentlessWriter extends BaseWriter {
165
167
  tracerVersion,
166
168
  languageVersion: process.version,
167
169
  languageInterpreter: process.versions.bun ? 'JavaScriptCore' : 'v8',
168
- })
170
+ }, { agent })
169
171
  this.#exporterApiKey = apiKey
170
172
  this.#exporterEnv = env
171
173
  this.#exporterRuntimeId = runtimeID
@@ -12,7 +12,10 @@ const uuidSource =
12
12
  const containerSource = '[0-9a-f]{64}'
13
13
  const taskSource = String.raw`[0-9a-f]{32}-\d+`
14
14
  const lineReg = /^(\d+):([^:]*):(.+)$/m
15
- const entityReg = new RegExp(String.raw`.*(${uuidSource}|${containerSource}|${taskSource})(?:\.scope)?$`, 'm')
15
+ const entityReg = new RegExp(
16
+ String.raw`(?:^|/)[^/\r\n\u2028\u2029]*?(${uuidSource}|${containerSource}|${taskSource})(?:\.scope)?$`,
17
+ 'm'
18
+ )
16
19
 
17
20
  let inode = 0
18
21
  let cgroup = ''
@@ -0,0 +1,55 @@
1
+ 'use strict'
2
+
3
+ const { isIPv6 } = require('node:net')
4
+
5
+ let defaultAgentKey
6
+ let getProxyForUrl
7
+ let proxyAgents
8
+
9
+ /**
10
+ * Selects a proxy agent for an HTTPS target while preserving the direct agent's pool boundaries.
11
+ *
12
+ * @param {string|URL|object} url
13
+ * @param {import('node:http').Agent|false} [directAgent]
14
+ * @returns {import('node:http').Agent|false|undefined}
15
+ */
16
+ function getHttpsProxyAgent (url, directAgent) {
17
+ getProxyForUrl ??= require('../../../../../vendor/dist/proxy-from-env').getProxyForUrl
18
+
19
+ const host = typeof url === 'string' ? undefined : url.host ?? url.hostname
20
+ const isUnbracketedIPv6 = typeof host === 'string' && host.includes(':') && isIPv6(host)
21
+ const target = typeof url === 'string'
22
+ ? url
23
+ : { protocol: url.protocol, host: isUnbracketedIPv6 ? `[${host}]` : host, port: url.port }
24
+ const proxyUrl = getProxyForUrl(target)
25
+ if (!proxyUrl) return directAgent
26
+
27
+ directAgent ??= require('node:https').globalAgent
28
+
29
+ if (proxyAgents === undefined) {
30
+ defaultAgentKey = {}
31
+ proxyAgents = new WeakMap()
32
+ }
33
+ const cacheKey = directAgent || defaultAgentKey
34
+ let agents = proxyAgents.get(cacheKey)
35
+ if (agents === undefined) {
36
+ agents = new Map()
37
+ proxyAgents.set(cacheKey, agents)
38
+ }
39
+
40
+ let agent = agents.get(proxyUrl)
41
+ if (agent === undefined) {
42
+ const { HttpsProxyAgent } = require('../../../../../vendor/dist/https-proxy-agent')
43
+ const options = directAgent
44
+ ? { keepAlive: directAgent.keepAlive, maxSockets: directAgent.maxSockets }
45
+ : undefined
46
+ agent = new HttpsProxyAgent(proxyUrl, options)
47
+ // AgentBase normally infers this from the node:https call stack. Interceptors such as MSW
48
+ // construct http.ClientRequest directly, so identify the target protocol explicitly.
49
+ agent.protocol = 'https:'
50
+ agents.set(proxyUrl, agent)
51
+ }
52
+ return agent
53
+ }
54
+
55
+ module.exports = { getHttpsProxyAgent }
@@ -13,6 +13,7 @@ const log = require('../../log')
13
13
  const { canSendApiKey, parseUrl } = require('./url')
14
14
  const docker = require('./docker')
15
15
  const { httpAgent, httpsAgent } = require('./agents')
16
+ const { getHttpsProxyAgent } = require('./proxy')
16
17
  const {
17
18
  getMaxAttempts,
18
19
  getRetryDelay,
@@ -94,11 +95,18 @@ function request (data, options, callback) {
94
95
 
95
96
  docker.inject(options.headers)
96
97
 
97
- const connectionOptions = {
98
- ...options,
99
- agent: options.agent ?? (isSecure ? httpsAgent : httpAgent),
98
+ let agent = options.agent ?? (isSecure ? httpsAgent : httpAgent)
99
+ if (hasApiKey && isSecure) {
100
+ try {
101
+ agent = getHttpsProxyAgent(options, agent)
102
+ } catch (error) {
103
+ callback(error)
104
+ return
105
+ }
100
106
  }
101
107
 
108
+ const connectionOptions = { ...options, agent }
109
+
102
110
  /**
103
111
  * @param {import('node:http').IncomingMessage} res
104
112
  * @param {(error: Error|null, result?: string|null, statusCode?: number,
@@ -66,7 +66,7 @@ function initializeOpenTelemetryMetrics (config) {
66
66
  metrics.setGlobalMeterProvider(meterProvider)
67
67
 
68
68
  // Include the final metric collection and export in lifecycle retention.
69
- registerTelemetryFlusher(done => meterProvider.forceFlush(done))
69
+ registerTelemetryFlusher(done => reader.forceFlush(done))
70
70
 
71
71
  registerResourceAttributeRefresh(exporter, () => buildGeneralResourceAttributes(config))
72
72
  }