dd-trace 6.15.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.
- package/README.md +15 -0
- package/ci/test-optimization-validation/framework-adapters/cucumber.js +4 -2
- package/ci/test-optimization-validation/package-check.js +4 -1
- package/ci/test-optimization-validation/scenarios/ci-wiring.js +1 -1
- package/index.d.ts +4 -0
- package/next.d.ts +27 -0
- package/next.js +3 -0
- package/package.json +15 -11
- package/packages/datadog-esbuild/index.js +14 -1
- package/packages/datadog-esbuild/src/resolver.js +284 -0
- package/packages/datadog-esbuild/src/utils.js +163 -119
- package/packages/datadog-instrumentations/src/ai.js +39 -97
- package/packages/datadog-instrumentations/src/anthropic.js +76 -134
- package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +1 -1
- package/packages/datadog-instrumentations/src/azure-cosmos.js +1 -1
- package/packages/datadog-instrumentations/src/azure-durable-functions.js +23 -9
- package/packages/datadog-instrumentations/src/azure-functions.js +48 -0
- package/packages/datadog-instrumentations/src/bullmq.js +1 -1
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +1 -1
- package/packages/datadog-instrumentations/src/dd-trace-api.js +2 -2
- package/packages/datadog-instrumentations/src/graphql.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/bundler-register.js +39 -14
- package/packages/datadog-instrumentations/src/helpers/instrument.js +25 -8
- package/packages/datadog-instrumentations/src/helpers/instrumentation-utils.js +72 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +9 -64
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +46 -14
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/azure-durable-functions.js +17 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +6 -7
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +99 -70
- package/packages/datadog-instrumentations/src/langchain.js +1 -1
- package/packages/datadog-instrumentations/src/langgraph.js +1 -1
- package/packages/datadog-instrumentations/src/mercurius.js +1 -1
- package/packages/datadog-instrumentations/src/modelcontextprotocol-sdk.js +1 -1
- package/packages/datadog-instrumentations/src/openai-agents.js +1 -1
- package/packages/datadog-instrumentations/src/openai.js +54 -76
- package/packages/datadog-instrumentations/src/vitest-worker.js +7 -10
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +7 -1
- package/packages/datadog-plugin-azure-cosmos/src/index.js +1 -1
- package/packages/datadog-plugin-azure-durable-functions/src/index.js +96 -1
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -24
- package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +2 -36
- package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-request-context.js +31 -0
- package/packages/datadog-plugin-graphql/src/resolve-error.js +33 -11
- package/packages/datadog-turbopack/index.js +212 -0
- package/packages/datadog-turbopack/src/constants.js +5 -0
- package/packages/datadog-turbopack/src/loader.js +500 -0
- package/packages/dd-trace/src/aiguard/index.js +2 -1
- package/packages/dd-trace/src/aiguard/integrations/anthropic.js +53 -25
- package/packages/dd-trace/src/aiguard/integrations/evaluate.js +49 -25
- package/packages/dd-trace/src/aiguard/integrations/index.js +3 -2
- package/packages/dd-trace/src/aiguard/integrations/openai.js +59 -30
- package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +116 -42
- package/packages/dd-trace/src/aiguard/messages/utils.js +29 -1
- package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +25 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +2 -2
- package/packages/dd-trace/src/appsec/telemetry/api_security.js +1 -3
- package/packages/dd-trace/src/appsec/telemetry/common.js +4 -0
- package/packages/dd-trace/src/appsec/telemetry/index.js +23 -5
- package/packages/dd-trace/src/appsec/telemetry/rasp.js +1 -20
- package/packages/dd-trace/src/appsec/telemetry/user.js +1 -3
- package/packages/dd-trace/src/appsec/telemetry/waf.js +1 -20
- package/packages/dd-trace/src/config/generated-config-types.d.ts +2 -0
- package/packages/dd-trace/src/config/helper.js +10 -2
- package/packages/dd-trace/src/config/index.js +20 -5
- package/packages/dd-trace/src/config/supported-configurations.json +8 -0
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +14 -7
- package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +14 -1
- package/packages/dd-trace/src/evp_proxy/discovery.js +3 -4
- package/packages/dd-trace/src/evp_proxy/path.js +12 -3
- package/packages/dd-trace/src/exporters/common/docker.js +4 -1
- package/packages/dd-trace/src/exporters/common/proxy.js +3 -0
- package/packages/dd-trace/src/opentelemetry/metrics/index.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/meter_provider.js +5 -4
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +106 -25
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +7 -1
- package/packages/dd-trace/src/plugin_manager.js +9 -3
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/util/test.js +4 -1
- package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/utils.js +0 -10
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
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
|
|
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
|
}
|
|
@@ -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
|
-
!
|
|
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
|
-
|
|
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",
|
|
@@ -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 {
|
|
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
|
|
82
|
+
`${probe.nsBetweenSampling}n, ${isSnapshotProducingProbe(probe)})`
|
|
70
83
|
|
|
71
84
|
if (probe.condition === undefined) {
|
|
72
85
|
return `$dd_sampled = ${sample} || $dd_sampled`
|
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
|
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 }
|
|
@@ -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(
|
|
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 = ''
|
|
@@ -44,6 +44,9 @@ function getHttpsProxyAgent (url, directAgent) {
|
|
|
44
44
|
? { keepAlive: directAgent.keepAlive, maxSockets: directAgent.maxSockets }
|
|
45
45
|
: undefined
|
|
46
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:'
|
|
47
50
|
agents.set(proxyUrl, agent)
|
|
48
51
|
}
|
|
49
52
|
return agent
|
|
@@ -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 =>
|
|
69
|
+
registerTelemetryFlusher(done => reader.forceFlush(done))
|
|
70
70
|
|
|
71
71
|
registerResourceAttributeRefresh(exporter, () => buildGeneralResourceAttributes(config))
|
|
72
72
|
}
|
|
@@ -51,11 +51,12 @@ class MeterProvider {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
|
-
* @param {
|
|
54
|
+
* @param {(error: Error | null) => void} [done] Called after shutdown completes
|
|
55
|
+
* @returns {void}
|
|
55
56
|
*/
|
|
56
|
-
|
|
57
|
-
if (this.reader) this.reader.
|
|
58
|
-
else done?.()
|
|
57
|
+
shutdown (done) {
|
|
58
|
+
if (this.reader) this.reader.shutdown(done)
|
|
59
|
+
else done?.(null)
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
|
|
@@ -8,6 +8,19 @@ const {
|
|
|
8
8
|
const { ObservableInstrument } = require('./instruments')
|
|
9
9
|
const { nowUnixNano } = require('./time')
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* @param {(error: Error | null) => void} [done]
|
|
13
|
+
* @param {Error | null} [error]
|
|
14
|
+
*/
|
|
15
|
+
function callDone (done, error) {
|
|
16
|
+
if (!done) return
|
|
17
|
+
try {
|
|
18
|
+
done(error ?? null)
|
|
19
|
+
} catch (callbackError) {
|
|
20
|
+
log.error('Error completing OTLP metrics lifecycle callback:', callbackError)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
11
24
|
/**
|
|
12
25
|
* @typedef {import('@opentelemetry/api').Attributes} Attributes
|
|
13
26
|
* @typedef {import('@opentelemetry/core').InstrumentationScope} InstrumentationScope
|
|
@@ -105,6 +118,10 @@ class PeriodicMetricReader {
|
|
|
105
118
|
#exportInterval
|
|
106
119
|
#aggregator
|
|
107
120
|
#batchCallbacks = []
|
|
121
|
+
#exportQueue = []
|
|
122
|
+
#isExporting = false
|
|
123
|
+
#shutdownCallbacks = []
|
|
124
|
+
#shutdownComplete = false
|
|
108
125
|
|
|
109
126
|
/**
|
|
110
127
|
* Creates a new PeriodicMetricReader instance.
|
|
@@ -197,47 +214,36 @@ class PeriodicMetricReader {
|
|
|
197
214
|
|
|
198
215
|
/**
|
|
199
216
|
* Forces an immediate collection and export of all metrics.
|
|
200
|
-
* @param {
|
|
217
|
+
* @param {(error: Error | null) => void} [done] Called after the metric export completes
|
|
218
|
+
* @returns {void}
|
|
201
219
|
*/
|
|
202
220
|
forceFlush (done) {
|
|
203
221
|
if (this.#isShutdown) {
|
|
204
222
|
log.warn('PeriodicMetricReader is shutdown. %d measurement(s) were dropped', this.#droppedCount)
|
|
205
|
-
|
|
223
|
+
if (this.#shutdownComplete) callDone(done)
|
|
224
|
+
else if (done) this.#shutdownCallbacks.push(done)
|
|
206
225
|
return
|
|
207
226
|
}
|
|
208
|
-
|
|
209
|
-
const complete = () => {
|
|
210
|
-
if (--pending === 0) done?.()
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Snapshot requests already active before starting this flush's export.
|
|
214
|
-
try {
|
|
215
|
-
if (typeof this.exporter.flush === 'function') this.exporter.flush(complete)
|
|
216
|
-
else complete()
|
|
217
|
-
} catch (error) {
|
|
218
|
-
log.error('Error flushing OTLP metrics:', error)
|
|
219
|
-
complete()
|
|
220
|
-
}
|
|
221
|
-
try {
|
|
222
|
-
this.#collectAndExport(complete)
|
|
223
|
-
} catch (error) {
|
|
224
|
-
log.error('Error exporting OTLP metrics:', error)
|
|
225
|
-
complete()
|
|
226
|
-
}
|
|
227
|
+
this.#enqueueExport(true, done)
|
|
227
228
|
}
|
|
228
229
|
|
|
229
230
|
/**
|
|
230
231
|
* Shuts down the reader and stops periodic collection.
|
|
232
|
+
* @param {(error: Error | null) => void} [done] Called after the final export and exporter shutdown complete
|
|
231
233
|
* @returns {void}
|
|
232
234
|
*/
|
|
233
|
-
shutdown () {
|
|
235
|
+
shutdown (done) {
|
|
234
236
|
if (this.#isShutdown) {
|
|
235
237
|
log.warn('PeriodicMetricReader is already shutdown')
|
|
238
|
+
if (this.#shutdownComplete) callDone(done)
|
|
239
|
+
else if (done) this.#shutdownCallbacks.push(done)
|
|
236
240
|
return
|
|
237
241
|
}
|
|
242
|
+
if (done) this.#shutdownCallbacks.push(done)
|
|
243
|
+
|
|
238
244
|
this.#isShutdown = true
|
|
239
245
|
this.#clearTimer()
|
|
240
|
-
this
|
|
246
|
+
this.#enqueueExport(true, error => this.#shutdownExporter(error))
|
|
241
247
|
}
|
|
242
248
|
|
|
243
249
|
/**
|
|
@@ -248,7 +254,7 @@ class PeriodicMetricReader {
|
|
|
248
254
|
if (this.#timer) return
|
|
249
255
|
|
|
250
256
|
this.#timer = setInterval(() => {
|
|
251
|
-
this.#
|
|
257
|
+
if (!this.#isExporting && this.#exportQueue.length === 0) this.#enqueueExport(false)
|
|
252
258
|
}, this.#exportInterval)
|
|
253
259
|
this.#timer.unref?.()
|
|
254
260
|
}
|
|
@@ -264,6 +270,75 @@ class PeriodicMetricReader {
|
|
|
264
270
|
}
|
|
265
271
|
}
|
|
266
272
|
|
|
273
|
+
/**
|
|
274
|
+
* @param {boolean} flushExporter Whether to flush the exporter after export
|
|
275
|
+
* @param {(error: Error | null) => void} [done] Called when the queued export completes
|
|
276
|
+
* @returns {void}
|
|
277
|
+
*/
|
|
278
|
+
#enqueueExport (flushExporter, done) {
|
|
279
|
+
this.#exportQueue.push({ flushExporter, done })
|
|
280
|
+
this.#drainExportQueue()
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** @returns {void} */
|
|
284
|
+
#drainExportQueue () {
|
|
285
|
+
if (this.#isExporting || this.#exportQueue.length === 0) return
|
|
286
|
+
|
|
287
|
+
this.#isExporting = true
|
|
288
|
+
const { flushExporter, done } = this.#exportQueue.shift()
|
|
289
|
+
let completed = false
|
|
290
|
+
const complete = error => {
|
|
291
|
+
if (completed) return
|
|
292
|
+
completed = true
|
|
293
|
+
this.#isExporting = false
|
|
294
|
+
queueMicrotask(() => this.#drainExportQueue())
|
|
295
|
+
callDone(done, error)
|
|
296
|
+
}
|
|
297
|
+
let exportCompleted = false
|
|
298
|
+
const afterExport = error => {
|
|
299
|
+
if (exportCompleted) return
|
|
300
|
+
exportCompleted = true
|
|
301
|
+
if (!flushExporter || typeof this.exporter.flush !== 'function') return complete(error)
|
|
302
|
+
|
|
303
|
+
try {
|
|
304
|
+
this.exporter.flush(flushError => complete(error || flushError))
|
|
305
|
+
} catch (flushError) {
|
|
306
|
+
log.error('Error flushing OTLP metrics:', flushError)
|
|
307
|
+
complete(error || flushError)
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
this.#collectAndExport(afterExport)
|
|
313
|
+
} catch (error) {
|
|
314
|
+
log.error('Error exporting OTLP metrics:', error)
|
|
315
|
+
afterExport(error)
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* @param {Error} [exportError] Error from the final export
|
|
321
|
+
* @returns {void}
|
|
322
|
+
*/
|
|
323
|
+
#shutdownExporter (exportError) {
|
|
324
|
+
let completed = false
|
|
325
|
+
const complete = shutdownError => {
|
|
326
|
+
if (completed) return
|
|
327
|
+
completed = true
|
|
328
|
+
this.#shutdownComplete = true
|
|
329
|
+
const callbacks = this.#shutdownCallbacks.splice(0)
|
|
330
|
+
for (const callback of callbacks) callDone(callback, exportError || shutdownError)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
try {
|
|
334
|
+
if (typeof this.exporter.shutdown === 'function') this.exporter.shutdown(complete)
|
|
335
|
+
else complete()
|
|
336
|
+
} catch (error) {
|
|
337
|
+
log.error('Error shutting down OTLP metrics exporter:', error)
|
|
338
|
+
complete(error)
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
267
342
|
/**
|
|
268
343
|
* Collects measurements and exports metrics.
|
|
269
344
|
*
|
|
@@ -322,7 +397,13 @@ class PeriodicMetricReader {
|
|
|
322
397
|
this.#lastExportedState
|
|
323
398
|
)
|
|
324
399
|
|
|
325
|
-
this.exporter.export(metrics,
|
|
400
|
+
this.exporter.export(metrics, result => {
|
|
401
|
+
if (result?.code === 1) {
|
|
402
|
+
callback?.(result.error || new Error('OTLP metrics export failed'))
|
|
403
|
+
} else {
|
|
404
|
+
callback?.()
|
|
405
|
+
}
|
|
406
|
+
})
|
|
326
407
|
}
|
|
327
408
|
}
|
|
328
409
|
|
|
@@ -177,7 +177,13 @@ class OtlpHttpExporterBase {
|
|
|
177
177
|
this.telemetryTags[0] = `protocol:${this.#transport === https ? 'https' : 'http'}`
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
-
|
|
180
|
+
/**
|
|
181
|
+
* @param {() => void} [done]
|
|
182
|
+
* @returns {void}
|
|
183
|
+
*/
|
|
184
|
+
shutdown (done) {
|
|
185
|
+
done?.()
|
|
186
|
+
}
|
|
181
187
|
}
|
|
182
188
|
|
|
183
189
|
module.exports = OtlpHttpExporterBase
|
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
const { channel } = require('dc-polyfill')
|
|
4
4
|
|
|
5
|
-
const {
|
|
5
|
+
const {
|
|
6
|
+
getEnvironmentVariable,
|
|
7
|
+
getValueFromEnvSources,
|
|
8
|
+
isSupportedConfiguration,
|
|
9
|
+
} = require('./config/helper')
|
|
6
10
|
const { isFalse, isTrue, normalizePluginEnvName } = require('./util')
|
|
7
11
|
const plugins = require('./plugins')
|
|
8
12
|
const log = require('./log')
|
|
@@ -59,12 +63,14 @@ function maybeEnable (Plugin) {
|
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
function getEnabled (Plugin) {
|
|
62
|
-
const envName = `DD_TRACE_${Plugin.id.toUpperCase()}_ENABLED`
|
|
66
|
+
const envName = normalizePluginEnvName(`DD_TRACE_${Plugin.id.toUpperCase()}_ENABLED`)
|
|
67
|
+
if (!isSupportedConfiguration(envName)) return
|
|
68
|
+
|
|
63
69
|
// skipDefault: only an explicitly configured value should drive enablement here. A registered
|
|
64
70
|
// default of `false` (e.g. an opt-in plugin like `nats`) must not be read as an explicit
|
|
65
71
|
// "disabled via configuration option" — that path both logs a misleading line and nulls the
|
|
66
72
|
// plugin class, bypassing the opt-in handled by `loadPlugin`.
|
|
67
|
-
return getValueFromEnvSources(
|
|
73
|
+
return getValueFromEnvSources(envName, true)
|
|
68
74
|
}
|
|
69
75
|
|
|
70
76
|
// TODO this must always be a singleton.
|
|
@@ -19,6 +19,7 @@ const plugins = {
|
|
|
19
19
|
get '@google-cloud/pubsub' () { return require('../../../datadog-plugin-google-cloud-pubsub/src') },
|
|
20
20
|
get '@google-cloud/vertexai' () { return require('../../../datadog-plugin-google-cloud-vertexai/src') },
|
|
21
21
|
get '@google/genai' () { return require('../../../datadog-plugin-google-genai/src') },
|
|
22
|
+
get '@graphql-tools/executor' () { return require('../../../datadog-plugin-graphql/src') },
|
|
22
23
|
get '@grpc/grpc-js' () { return require('../../../datadog-plugin-grpc/src') },
|
|
23
24
|
get '@hapi/hapi' () { return require('../../../datadog-plugin-hapi/src') },
|
|
24
25
|
get '@happy-dom/jest-environment' () { return require('../../../datadog-plugin-jest/src') },
|
|
@@ -1135,7 +1135,10 @@ function getCodeOwnersPatternRegex (pattern) {
|
|
|
1135
1135
|
}
|
|
1136
1136
|
|
|
1137
1137
|
const directoryOnly = pattern.endsWith('/')
|
|
1138
|
-
|
|
1138
|
+
let normalizedPattern = pattern.replace(/^\/+/, '')
|
|
1139
|
+
let normalizedPatternEnd = normalizedPattern.length
|
|
1140
|
+
while (normalizedPatternEnd > 0 && normalizedPattern[normalizedPatternEnd - 1] === '/') normalizedPatternEnd--
|
|
1141
|
+
normalizedPattern = normalizedPattern.slice(0, normalizedPatternEnd)
|
|
1139
1142
|
const anchored = pattern.startsWith('/') || normalizedPattern.includes('/')
|
|
1140
1143
|
|
|
1141
1144
|
if (!normalizedPattern) {
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
function splitModel (model) {
|
|
4
|
-
if (!model) return { modelName: undefined, modelProvider: 'anthropic' }
|
|
5
|
-
const idx = model.indexOf('/')
|
|
6
|
-
if (idx === -1) return { modelName: model, modelProvider: 'anthropic' }
|
|
7
|
-
return { modelName: model.slice(idx + 1), modelProvider: model.slice(0, idx) }
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
module.exports = { splitModel }
|