dd-trace 6.20.0 → 6.21.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/index.d.ts +91 -3
- package/package.json +6 -7
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +57 -4
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
- package/packages/datadog-instrumentations/src/jest.js +119 -1
- package/packages/datadog-instrumentations/src/mocha/utils.js +124 -36
- package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +61 -34
- package/packages/datadog-instrumentations/src/mocha/worker.js +6 -3
- package/packages/datadog-instrumentations/src/playwright.js +3 -0
- package/packages/datadog-instrumentations/src/vitest-main.js +0 -2
- package/packages/datadog-instrumentations/src/webdriverio.js +115 -15
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +17 -0
- package/packages/datadog-plugin-mocha/src/index.js +216 -29
- package/packages/datadog-plugin-mocha/src/webdriverio-video.js +449 -0
- package/packages/dd-trace/src/config/supported-configurations.json +2 -2
- package/packages/dd-trace/src/debugger/constants.js +1 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +5 -3
- package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +17 -12
- package/packages/dd-trace/src/debugger/devtools_client/send.js +6 -2
- package/packages/dd-trace/src/debugger/devtools_client/worker-error.js +21 -0
- package/packages/dd-trace/src/debugger/index.js +24 -2
- package/packages/dd-trace/src/debugger/probe_sampler.js +108 -11
- package/packages/dd-trace/src/exporters/common/request.js +1 -0
- package/packages/dd-trace/src/exporters/common/url.js +1 -1
- package/packages/dd-trace/src/id.js +1 -4
- package/packages/dd-trace/src/llmobs/agent-manifest.js +296 -0
- package/packages/dd-trace/src/llmobs/constants/tags.js +1 -0
- package/packages/dd-trace/src/llmobs/sdk.js +23 -6
- package/packages/dd-trace/src/llmobs/span_processor.js +17 -6
- package/packages/dd-trace/src/llmobs/tagger.js +54 -1
- package/packages/dd-trace/src/llmobs/writers/base.js +3 -1
- package/packages/dd-trace/src/llmobs/writers/util.js +3 -1
- package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +30 -1
- package/packages/dd-trace/src/openfeature/configuration_source.js +17 -2
- package/packages/dd-trace/src/openfeature/debug-logging-hook.js +25 -0
- package/packages/dd-trace/src/openfeature/flagging_provider.js +29 -0
- package/packages/dd-trace/src/openfeature/remote_config.js +9 -0
- package/packages/dd-trace/src/openfeature/writers/util.js +43 -4
- package/packages/dd-trace/src/opentelemetry/resource-attributes.js +1 -1
- package/packages/dd-trace/src/otel-thread-ctx.js +2 -2
- package/packages/dd-trace/src/plugins/util/url.js +6 -1
- package/packages/dd-trace/src/profiling/profilers/wall.js +4 -10
- package/packages/dd-trace/src/proxy.js +2 -0
- package/packages/dd-trace/src/serverless.js +5 -0
- package/packages/dd-trace/src/storage-channels.js +112 -46
|
@@ -18,6 +18,7 @@ const {
|
|
|
18
18
|
OUTPUT_VALUE,
|
|
19
19
|
METADATA,
|
|
20
20
|
COST_TAGS,
|
|
21
|
+
AGENT_MANIFEST,
|
|
21
22
|
METRICS,
|
|
22
23
|
TOOL_DEFINITIONS,
|
|
23
24
|
PARENT_ID_KEY,
|
|
@@ -53,6 +54,7 @@ const {
|
|
|
53
54
|
TRACE_ID,
|
|
54
55
|
PROPAGATED_TRACE_ID_KEY,
|
|
55
56
|
} = require('./constants/tags')
|
|
57
|
+
const { buildAgentDeclaration } = require('./agent-manifest')
|
|
56
58
|
const { storage } = require('./storage')
|
|
57
59
|
const {
|
|
58
60
|
findGenAIAncestorSpanId,
|
|
@@ -187,6 +189,11 @@ class LLMObsTagger {
|
|
|
187
189
|
this.tagCostTags(span, annotationContext.costTags, 'annotation_context')
|
|
188
190
|
}
|
|
189
191
|
|
|
192
|
+
const agentDeclarations = /** @type {import('./agent-manifest').AgentDeclaration[] | undefined} */ (
|
|
193
|
+
storage.getStore()?.agentDeclarations
|
|
194
|
+
)
|
|
195
|
+
if (agentDeclarations) this.#applyAgentDeclarations(span, agentDeclarations)
|
|
196
|
+
|
|
190
197
|
// apply annotation context name
|
|
191
198
|
const annotationContextName = annotationContext?.name
|
|
192
199
|
if (annotationContextName) this._setTag(span, NAME, annotationContextName)
|
|
@@ -204,6 +211,43 @@ class LLMObsTagger {
|
|
|
204
211
|
}
|
|
205
212
|
}
|
|
206
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Applies the agents declared by the enclosing annotation contexts, outermost first, to every span in the block.
|
|
216
|
+
* The manifest is emitted only if the span is an agent when it finishes, since some integrations promote a span
|
|
217
|
+
* to an agent after registration.
|
|
218
|
+
*
|
|
219
|
+
* @param {import('../opentracing/span')} span
|
|
220
|
+
* @param {import('./agent-manifest').AgentDeclaration[]} declarations
|
|
221
|
+
*/
|
|
222
|
+
#applyAgentDeclarations (span, declarations) {
|
|
223
|
+
for (const declaration of declarations) {
|
|
224
|
+
this.#tagAgentManifestFields(span, declaration.manifest)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Tags the agent a caller declared on an agent span, merged onto what earlier annotations declared.
|
|
230
|
+
*
|
|
231
|
+
* @param {import('../opentracing/span')} span
|
|
232
|
+
* @param {unknown} agent
|
|
233
|
+
*/
|
|
234
|
+
tagAgent (span, agent) {
|
|
235
|
+
const declaration = buildAgentDeclaration(agent)
|
|
236
|
+
if (declaration) this.#tagAgentManifestFields(span, declaration.manifest)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* @param {import('../opentracing/span')} span
|
|
241
|
+
* @param {import('./agent-manifest').AgentManifestFields} manifest
|
|
242
|
+
*/
|
|
243
|
+
#tagAgentManifestFields (span, manifest) {
|
|
244
|
+
// A shallow update, matching dd-trace-py: each declaration is already validated, so a later field replaces an
|
|
245
|
+
// earlier one whole (`model_settings` included) and an unset or invalid one leaves it in place. Declarations are
|
|
246
|
+
// never mutated, so the first one is stored without a copy.
|
|
247
|
+
const existing = registry.get(span)?.[AGENT_MANIFEST]
|
|
248
|
+
this._setTag(span, AGENT_MANIFEST, existing ? { ...existing, ...manifest } : manifest)
|
|
249
|
+
}
|
|
250
|
+
|
|
207
251
|
#tagSamplingDecision (span, parent) {
|
|
208
252
|
const traceTags = span.context()._trace.tags
|
|
209
253
|
const parentTags = registry.get(parent)
|
|
@@ -337,7 +381,16 @@ class LLMObsTagger {
|
|
|
337
381
|
if (currentTags) {
|
|
338
382
|
Object.assign(currentTags, tags)
|
|
339
383
|
} else {
|
|
340
|
-
|
|
384
|
+
// Copied so a later annotation cannot write into a caller's object shared by an annotation context. The copy
|
|
385
|
+
// reads caller getters and Proxy traps during span registration, so it must not throw into the application.
|
|
386
|
+
let copy
|
|
387
|
+
try {
|
|
388
|
+
copy = { ...tags }
|
|
389
|
+
} catch {
|
|
390
|
+
log.warn('Dropping span tags that could not be read.')
|
|
391
|
+
return
|
|
392
|
+
}
|
|
393
|
+
this._setTag(span, TAGS, copy)
|
|
341
394
|
}
|
|
342
395
|
}
|
|
343
396
|
|
|
@@ -4,7 +4,7 @@ const { URL, format } = require('node:url')
|
|
|
4
4
|
const path = require('node:path')
|
|
5
5
|
const request = require('../../exporters/common/request')
|
|
6
6
|
const { getEnvironmentVariable } = require('../../config/helper')
|
|
7
|
-
const { createServerlessDeliveryTracker } = require('../../serverless')
|
|
7
|
+
const { createServerlessDeliveryTracker, IS_STANDARD_AWS_LAMBDA } = require('../../serverless')
|
|
8
8
|
|
|
9
9
|
const logger = require('../../log')
|
|
10
10
|
|
|
@@ -118,6 +118,8 @@ class BaseLLMObsWriter {
|
|
|
118
118
|
|
|
119
119
|
buffer.size += eventSize
|
|
120
120
|
buffer.events.push(event)
|
|
121
|
+
// The standard Lambda sandbox can freeze before the periodic flush runs.
|
|
122
|
+
if (IS_STANDARD_AWS_LAMBDA) this.flush()
|
|
121
123
|
return true
|
|
122
124
|
}
|
|
123
125
|
|
|
@@ -24,7 +24,9 @@ function setAgentStrategy (config, setWritersAgentlessValue) {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
const endpoints = agentInfo.endpoints
|
|
27
|
-
|
|
27
|
+
// The Lambda extension lists full proxy paths (e.g. /evp_proxy/v2/api/v2/llmobs), not the bare prefix.
|
|
28
|
+
const hasEndpoint = Array.isArray(endpoints) &&
|
|
29
|
+
endpoints.some(endpoint => typeof endpoint === 'string' && endpoint.startsWith(EVP_PROXY_AGENT_BASE_PATH))
|
|
28
30
|
setWritersAgentlessValue(!hasEndpoint)
|
|
29
31
|
})
|
|
30
32
|
}
|
|
@@ -21,6 +21,7 @@ const RETRY_JITTER = 0.2
|
|
|
21
21
|
* @property {number} pollIntervalMs
|
|
22
22
|
* @property {number} requestTimeoutMs
|
|
23
23
|
* @property {string | undefined} apiKey
|
|
24
|
+
* @property {string | undefined} env
|
|
24
25
|
*/
|
|
25
26
|
|
|
26
27
|
/**
|
|
@@ -106,9 +107,23 @@ class AgentlessConfigurationSource {
|
|
|
106
107
|
*/
|
|
107
108
|
async #pollOnce (abortController) {
|
|
108
109
|
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
110
|
+
log.debug('Feature Flags: agentless configuration poll attempt %d/%d', attempt, MAX_ATTEMPTS)
|
|
109
111
|
const response = await this.#request(abortController.signal)
|
|
110
112
|
if (this.#abortController !== abortController) return
|
|
111
113
|
|
|
114
|
+
if (response.statusCode === 304) {
|
|
115
|
+
// 304 means the cached configuration is still valid, not a failure.
|
|
116
|
+
log.debug('Feature Flags: agentless configuration poll attempt %d/%d returned HTTP 304 (unchanged)',
|
|
117
|
+
attempt, MAX_ATTEMPTS)
|
|
118
|
+
} else if (response.statusCode === undefined) {
|
|
119
|
+
log.debug('Feature Flags: agentless configuration poll attempt %d/%d failed: %s',
|
|
120
|
+
attempt, MAX_ATTEMPTS, errorMessage(response.error))
|
|
121
|
+
} else {
|
|
122
|
+
// Avoids errorMessage(response.error), which embeds the full request URL.
|
|
123
|
+
log.debug('Feature Flags: agentless configuration poll attempt %d/%d returned HTTP %d',
|
|
124
|
+
attempt, MAX_ATTEMPTS, response.statusCode)
|
|
125
|
+
}
|
|
126
|
+
|
|
112
127
|
const retryable = response.statusCode === undefined || isRetryableStatus(response.statusCode)
|
|
113
128
|
if (!retryable) {
|
|
114
129
|
this.#apply(response)
|
|
@@ -205,6 +220,13 @@ class AgentlessConfigurationSource {
|
|
|
205
220
|
const etag = response.headers?.etag
|
|
206
221
|
const value = Array.isArray(etag) ? etag[0] : etag
|
|
207
222
|
this.#etag = value?.trim() || undefined
|
|
223
|
+
|
|
224
|
+
// eslint-disable-next-line eslint-rules/eslint-log-printf-style
|
|
225
|
+
log.debug(() => {
|
|
226
|
+
const flagCount = Object.keys(configuration.flags ?? {}).length
|
|
227
|
+
return 'Feature Flags: agentless configuration applied successfully ' +
|
|
228
|
+
`(${flagCount} flag(s), env=${this.#config.env ?? 'unset'})`
|
|
229
|
+
})
|
|
208
230
|
}
|
|
209
231
|
|
|
210
232
|
/**
|
|
@@ -270,10 +292,17 @@ function parseConfiguration (body) {
|
|
|
270
292
|
}
|
|
271
293
|
|
|
272
294
|
/**
|
|
295
|
+
* Formats a request error, including the underlying network error code
|
|
296
|
+
* (e.g. ENOTFOUND, ECONNREFUSED, ETIMEDOUT) when available, since "request
|
|
297
|
+
* failed" alone does not tell a customer whether the problem is DNS, a
|
|
298
|
+
* refused connection, a timeout, or something else.
|
|
299
|
+
*
|
|
273
300
|
* @param {unknown} error
|
|
274
301
|
*/
|
|
275
302
|
function errorMessage (error) {
|
|
276
|
-
|
|
303
|
+
if (!(error instanceof Error)) return String(error ?? 'request was not sent')
|
|
304
|
+
const code = /** @type {{ code?: string }} */ (error).code
|
|
305
|
+
return code ? `${error.message} (${code})` : error.message
|
|
277
306
|
}
|
|
278
307
|
|
|
279
308
|
/**
|
|
@@ -23,7 +23,10 @@ function create (config, applyConfiguration) {
|
|
|
23
23
|
DD_FEATURE_FLAGS_ENABLED: enabled,
|
|
24
24
|
} = config.featureFlags
|
|
25
25
|
|
|
26
|
-
if (!enabled
|
|
26
|
+
if (!enabled) return
|
|
27
|
+
|
|
28
|
+
if (source !== 'agentless') {
|
|
29
|
+
log.debug('Feature Flags: configuration source is %s, not agentless; skipping agentless setup', source)
|
|
27
30
|
return
|
|
28
31
|
}
|
|
29
32
|
|
|
@@ -35,11 +38,14 @@ function create (config, applyConfiguration) {
|
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
const AgentlessConfigurationSource = require('./agentless_configuration_source')
|
|
41
|
+
const resolvedEndpoint = endpoint(config, baseUrl)
|
|
42
|
+
log.debug('Feature Flags: starting agentless configuration source at %s', redactedUrl(resolvedEndpoint))
|
|
38
43
|
return new AgentlessConfigurationSource({
|
|
39
|
-
endpoint:
|
|
44
|
+
endpoint: resolvedEndpoint,
|
|
40
45
|
pollIntervalMs: Math.min(pollIntervalSeconds, MAX_POLL_INTERVAL_SECONDS) * 1000,
|
|
41
46
|
requestTimeoutMs: requestTimeoutSeconds * 1000,
|
|
42
47
|
apiKey: hasCustomEndpoint ? undefined : config.DD_API_KEY,
|
|
48
|
+
env: config.env,
|
|
43
49
|
}, applyConfiguration)
|
|
44
50
|
} catch (error) {
|
|
45
51
|
log.error('Unable to configure Feature Flagging configuration source', error)
|
|
@@ -85,6 +91,15 @@ function endpoint (config, configuredBaseUrl) {
|
|
|
85
91
|
return url
|
|
86
92
|
}
|
|
87
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Drops everything but the origin; a custom base URL may embed a signed token in its path.
|
|
96
|
+
*
|
|
97
|
+
* @param {URL} url
|
|
98
|
+
*/
|
|
99
|
+
function redactedUrl (url) {
|
|
100
|
+
return `${url.protocol}//${url.host}`
|
|
101
|
+
}
|
|
102
|
+
|
|
88
103
|
module.exports = {
|
|
89
104
|
create,
|
|
90
105
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const log = require('../log')
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Logs full evaluation details under DD_TRACE_DEBUG, matching EvalMetricsHook's use of `finally`.
|
|
7
|
+
*/
|
|
8
|
+
class DebugLoggingHook {
|
|
9
|
+
/**
|
|
10
|
+
* @param {{ flagKey: string }} hookContext - Hook context containing the flag key
|
|
11
|
+
* @param {object} evaluationDetails - Full evaluation details
|
|
12
|
+
*/
|
|
13
|
+
finally (hookContext, evaluationDetails) {
|
|
14
|
+
try {
|
|
15
|
+
log.debug('Feature Flags: evaluated %s: %o', hookContext?.flagKey, evaluationDetails)
|
|
16
|
+
} catch (error) {
|
|
17
|
+
try {
|
|
18
|
+
// Defense in depth; the OpenFeature SDK already guards finally hooks.
|
|
19
|
+
log.warn('DebugLoggingHook: error in finally hook: %s', error.message)
|
|
20
|
+
} catch { /* logging failure must not crash evaluation */ }
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = DebugLoggingHook
|
|
@@ -4,8 +4,10 @@ const { channel } = require('dc-polyfill')
|
|
|
4
4
|
|
|
5
5
|
const { DatadogNodeServerProvider } = require('../../../../vendor/dist/@datadog/openfeature-node-server')
|
|
6
6
|
const log = require('../log')
|
|
7
|
+
const { debugChannel } = require('../log/channels')
|
|
7
8
|
const configurationSource = require('./configuration_source')
|
|
8
9
|
const { EXPOSURE_CHANNEL } = require('./constants/constants')
|
|
10
|
+
const DebugLoggingHook = require('./debug-logging-hook')
|
|
9
11
|
const EvalMetricsHook = require('./eval-metrics-hook')
|
|
10
12
|
const SpanEnrichmentHook = require('./span-enrichment-hook')
|
|
11
13
|
const FlagEvalEVPHook = require('./writers/flag-eval-evp-hook')
|
|
@@ -36,6 +38,12 @@ class FlaggingProvider extends DatadogNodeServerProvider {
|
|
|
36
38
|
|
|
37
39
|
if (config.DD_METRICS_OTEL_ENABLED === true) {
|
|
38
40
|
this.hooks.push(new EvalMetricsHook(config))
|
|
41
|
+
} else {
|
|
42
|
+
log.debug('Feature Flags: evaluation metrics disabled; set %s=true to enable', 'DD_METRICS_OTEL_ENABLED')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (config.debug && debugChannel.hasSubscribers) {
|
|
46
|
+
this.hooks.push(new DebugLoggingHook())
|
|
39
47
|
}
|
|
40
48
|
|
|
41
49
|
if (config.featureFlags.DD_EXPERIMENTAL_FLAGGING_PROVIDER_SPAN_ENRICHMENT_ENABLED) {
|
|
@@ -64,6 +72,8 @@ class FlaggingProvider extends DatadogNodeServerProvider {
|
|
|
64
72
|
* @returns {Promise<void>}
|
|
65
73
|
*/
|
|
66
74
|
initialize (context) {
|
|
75
|
+
log.debug('Feature Flags: waiting for provider initialization...')
|
|
76
|
+
|
|
67
77
|
const promise = super.initialize(context)
|
|
68
78
|
|
|
69
79
|
// `DatadogNodeServerProvider#initialize` starts a timer that is never unref'd, which would
|
|
@@ -72,6 +82,25 @@ class FlaggingProvider extends DatadogNodeServerProvider {
|
|
|
72
82
|
// TODO: remove once `@datadog/openfeature-node-server` unrefs this timer itself.
|
|
73
83
|
this.initController?.timeoutId?.unref?.()
|
|
74
84
|
|
|
85
|
+
// Only observes the outcome for logging; `promise` itself is returned unmodified below.
|
|
86
|
+
// Guarded so a logger failure can never leave the derived promise unhandled.
|
|
87
|
+
promise.then(
|
|
88
|
+
() => {
|
|
89
|
+
try {
|
|
90
|
+
log.debug('Feature Flags: provider initialized successfully')
|
|
91
|
+
} catch { /* logging failure must not crash the process */ }
|
|
92
|
+
},
|
|
93
|
+
(error) => {
|
|
94
|
+
try {
|
|
95
|
+
// errorWithoutTelemetry avoids inflating telemetry volume for every routine init timeout.
|
|
96
|
+
log.errorWithoutTelemetry(
|
|
97
|
+
'Feature Flags: provider failed to initialize: %s',
|
|
98
|
+
error instanceof Error ? error.message : String(error)
|
|
99
|
+
)
|
|
100
|
+
} catch { /* logging failure must not crash the process */ }
|
|
101
|
+
}
|
|
102
|
+
)
|
|
103
|
+
|
|
75
104
|
return promise
|
|
76
105
|
}
|
|
77
106
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const log = require('../log')
|
|
3
4
|
const RemoteConfigCapabilities = require('../remote_config/capabilities')
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -12,6 +13,8 @@ const RemoteConfigCapabilities = require('../remote_config/capabilities')
|
|
|
12
13
|
function enable (rc, getOpenfeatureProxy, subscribe) {
|
|
13
14
|
if (!subscribe) return
|
|
14
15
|
|
|
16
|
+
log.debug('Feature Flags: starting remote_config configuration source (Agent Remote Configuration)')
|
|
17
|
+
|
|
15
18
|
rc.updateCapabilities(RemoteConfigCapabilities.FFE_FLAG_CONFIGURATION_RULES, true)
|
|
16
19
|
|
|
17
20
|
/**
|
|
@@ -21,7 +24,13 @@ function enable (rc, getOpenfeatureProxy, subscribe) {
|
|
|
21
24
|
const updateConfiguration = (action, conf) => {
|
|
22
25
|
if (action === 'apply' || action === 'modify') {
|
|
23
26
|
getOpenfeatureProxy().setConfiguration(conf)
|
|
27
|
+
// eslint-disable-next-line eslint-rules/eslint-log-printf-style
|
|
28
|
+
log.debug(() => {
|
|
29
|
+
const flagCount = Object.keys(conf?.flags ?? {}).length
|
|
30
|
+
return `Feature Flags: remote_config configuration ${action} applied successfully (${flagCount} flag(s))`
|
|
31
|
+
})
|
|
24
32
|
} else if (action === 'unapply') {
|
|
33
|
+
log.debug('Feature Flags: remote_config configuration removed')
|
|
25
34
|
getOpenfeatureProxy().setConfiguration(undefined)
|
|
26
35
|
}
|
|
27
36
|
}
|
|
@@ -16,6 +16,18 @@ const ROUTE_DISCOVERY_COOLDOWN_MS = 60_000
|
|
|
16
16
|
const REQUIRED_LOCAL_HEADERS = [EVP_ORIGIN_HEADER_NAME, EVP_ORIGIN_VERSION_HEADER_NAME]
|
|
17
17
|
const STOP_NOOP = () => {}
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {(enabled: boolean, route?: import('./flag-evaluations').FlagEvaluationRoute) => void} DeliverySubscriber
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {object} DeliveryStrategy
|
|
24
|
+
* @property {Set<{notify: DeliverySubscriber}>} subscribers
|
|
25
|
+
* @property {() => void} stop
|
|
26
|
+
* @property {[boolean, import('./flag-evaluations').FlagEvaluationRoute?]} [result]
|
|
27
|
+
*/
|
|
28
|
+
/** @type {WeakMap<import('../../config/config-base'), DeliveryStrategy>} */
|
|
29
|
+
const deliveryStrategies = new WeakMap()
|
|
30
|
+
|
|
19
31
|
let missingRouteWarningLogged = false
|
|
20
32
|
|
|
21
33
|
/**
|
|
@@ -167,18 +179,45 @@ function setAgentlessStrategy (config, setWriterEnabledValue) {
|
|
|
167
179
|
}
|
|
168
180
|
|
|
169
181
|
/**
|
|
170
|
-
*
|
|
182
|
+
* Subscribes to the delivery strategy owned by this tracer configuration.
|
|
183
|
+
* The provider and exposure module receive the same configuration object from
|
|
184
|
+
* the tracer. Share their route transitions, but never share credentials across
|
|
185
|
+
* distinct configurations. The last subscriber owns discovery cleanup.
|
|
171
186
|
*
|
|
172
187
|
* @param {import('../../config/config-base')} config - Tracer configuration object
|
|
173
188
|
* @param {(enabled: boolean, route?: import('./flag-evaluations').FlagEvaluationRoute) => void} setWriterEnabledValue
|
|
174
189
|
* Callback to set the writer enabled state
|
|
175
190
|
*/
|
|
176
191
|
function setEventDeliveryStrategy (config, setWriterEnabledValue) {
|
|
177
|
-
|
|
178
|
-
|
|
192
|
+
let strategy = deliveryStrategies.get(config)
|
|
193
|
+
const subscriber = { notify: setWriterEnabledValue }
|
|
194
|
+
|
|
195
|
+
if (strategy) {
|
|
196
|
+
strategy.subscribers.add(subscriber)
|
|
197
|
+
if (strategy.result) setWriterEnabledValue(...strategy.result)
|
|
198
|
+
} else {
|
|
199
|
+
strategy = { subscribers: new Set([subscriber]), stop: STOP_NOOP }
|
|
200
|
+
deliveryStrategies.set(config, strategy)
|
|
201
|
+
/** @type {DeliverySubscriber} */
|
|
202
|
+
const notify = (enabled, route) => {
|
|
203
|
+
// A Remote Config discovery already in flight cannot be canceled. Ignore
|
|
204
|
+
// its result after shutdown, including when the same config is reused.
|
|
205
|
+
if (deliveryStrategies.get(config) !== strategy) return
|
|
206
|
+
strategy.result = route === undefined ? [enabled] : [enabled, route]
|
|
207
|
+
for (const subscriber of strategy.subscribers) {
|
|
208
|
+
subscriber.notify(...strategy.result)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
strategy.stop = config.featureFlags?.DD_FEATURE_FLAGS_CONFIGURATION_SOURCE === 'agentless'
|
|
212
|
+
? setAgentlessStrategy(config, notify)
|
|
213
|
+
: setAgentStrategy(config, notify)
|
|
179
214
|
}
|
|
180
215
|
|
|
181
|
-
return
|
|
216
|
+
return () => {
|
|
217
|
+
if (!strategy.subscribers.delete(subscriber) || strategy.subscribers.size !== 0) return
|
|
218
|
+
deliveryStrategies.delete(config)
|
|
219
|
+
strategy.stop()
|
|
220
|
+
}
|
|
182
221
|
}
|
|
183
222
|
|
|
184
223
|
module.exports = {
|
|
@@ -32,7 +32,7 @@ const {
|
|
|
32
32
|
enterCh,
|
|
33
33
|
spanFinishCh,
|
|
34
34
|
getActiveSpan,
|
|
35
|
-
|
|
35
|
+
acquireChannels,
|
|
36
36
|
} = require('./storage-channels')
|
|
37
37
|
const {
|
|
38
38
|
finalEndpoint,
|
|
@@ -371,7 +371,7 @@ function start () {
|
|
|
371
371
|
getContext = ns.getContext
|
|
372
372
|
clearContext = ns.clearContext
|
|
373
373
|
|
|
374
|
-
|
|
374
|
+
acquireChannels(false)
|
|
375
375
|
enterCh.subscribe(onEnter)
|
|
376
376
|
spanFinishCh.subscribe(onSpanFinished)
|
|
377
377
|
// Endpoint updates come from the shared web-tags cache's transition channels
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { URL } = require('url')
|
|
4
4
|
|
|
5
|
+
const { defaults } = require('../../config/defaults')
|
|
5
6
|
const log = require('../../log')
|
|
6
7
|
|
|
7
8
|
const HTTP2_HEADER_AUTHORITY = ':authority'
|
|
@@ -55,12 +56,15 @@ function obfuscateQs (config, url) {
|
|
|
55
56
|
|
|
56
57
|
let qs = url.slice(i + 1)
|
|
57
58
|
|
|
58
|
-
|
|
59
|
+
// Group 1 of the default regex is the delimiter in front of a JWT. A user-provided regex may capture the secret.
|
|
60
|
+
const replacement = queryStringObfuscation === defaultQsObfuscator ? '$1<redacted>' : '<redacted>'
|
|
61
|
+
qs = qs.replace(queryStringObfuscation, replacement)
|
|
59
62
|
|
|
60
63
|
return `${path}?${qs}`
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
const qsObfuscatorCache = new Map()
|
|
67
|
+
let defaultQsObfuscator
|
|
64
68
|
|
|
65
69
|
/**
|
|
66
70
|
* Compile the configured query-string obfuscator (a regex string, or a boolean)
|
|
@@ -85,6 +89,7 @@ function getQsObfuscator (config) {
|
|
|
85
89
|
} else if (obfuscator !== '.*') { // '.*' optimizes to a full redact (true)
|
|
86
90
|
try {
|
|
87
91
|
compiled = new RegExp(obfuscator, 'gi')
|
|
92
|
+
if (obfuscator === defaults.DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP) defaultQsObfuscator = compiled
|
|
88
93
|
} catch (err) {
|
|
89
94
|
log.error('Error getting qs obfuscator', err)
|
|
90
95
|
}
|
|
@@ -10,7 +10,8 @@ const {
|
|
|
10
10
|
beforeCh,
|
|
11
11
|
spanFinishCh,
|
|
12
12
|
getActiveSpan,
|
|
13
|
-
|
|
13
|
+
acquireChannels,
|
|
14
|
+
releaseChannels,
|
|
14
15
|
} = require('../../storage-channels')
|
|
15
16
|
const webTagsCache = require('../../web-tags-cache')
|
|
16
17
|
|
|
@@ -128,14 +129,6 @@ class NativeWallProfiler {
|
|
|
128
129
|
this.#pprof = require('@datadog/pprof')
|
|
129
130
|
kSampleCount = this.#pprof.time.constants.kSampleCount
|
|
130
131
|
|
|
131
|
-
// pprof otherwise crashes in worker threads
|
|
132
|
-
if (!process._startProfilerIdleNotifier) {
|
|
133
|
-
process._startProfilerIdleNotifier = () => {}
|
|
134
|
-
}
|
|
135
|
-
if (!process._stopProfilerIdleNotifier) {
|
|
136
|
-
process._stopProfilerIdleNotifier = () => {}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
132
|
this.#pprof.time.start({
|
|
140
133
|
collectCpuTime: this.#cpuProfilingEnabled,
|
|
141
134
|
columnNumbers: 'emit',
|
|
@@ -157,7 +150,7 @@ class NativeWallProfiler {
|
|
|
157
150
|
this.#profilerState = this.#pprof.time.getState()
|
|
158
151
|
this.#lastSampleCount = 0
|
|
159
152
|
|
|
160
|
-
|
|
153
|
+
acquireChannels(!this.#asyncContextFrameEnabled)
|
|
161
154
|
|
|
162
155
|
if (this.#asyncContextFrameEnabled) {
|
|
163
156
|
this.#setupTelemetryMetrics()
|
|
@@ -346,6 +339,7 @@ class NativeWallProfiler {
|
|
|
346
339
|
}
|
|
347
340
|
enterCh.unsubscribe(this.#boundEnter)
|
|
348
341
|
spanFinishCh.unsubscribe(this.#boundSpanFinished)
|
|
342
|
+
releaseChannels(!this.#asyncContextFrameEnabled)
|
|
349
343
|
if (this.#endpointCollectionEnabled) {
|
|
350
344
|
webTagsCache.resolvedCh.unsubscribe(this.#boundSpanTagsUpdated)
|
|
351
345
|
webTagsCache.deactivate()
|
|
@@ -6,6 +6,7 @@ const NoopProxy = require('./noop/proxy')
|
|
|
6
6
|
const DatadogTracer = require('./tracer')
|
|
7
7
|
const getConfig = require('./config')
|
|
8
8
|
const { getEnvironmentVariable } = require('./config/helper')
|
|
9
|
+
const { reseed } = require('./id')
|
|
9
10
|
const runtimeMetrics = require('./runtime_metrics')
|
|
10
11
|
const log = require('./log')
|
|
11
12
|
const { setStartupLogPluginManager, startupLog } = require('./startup-log')
|
|
@@ -353,6 +354,7 @@ class Tracer extends NoopProxy {
|
|
|
353
354
|
if (request.method === 'POST' && request.url === '/aws/lambda-microvms/runtime/v1/run') {
|
|
354
355
|
ch.unsubscribe(onHttpRequest)
|
|
355
356
|
drainUuidPool()
|
|
357
|
+
reseed()
|
|
356
358
|
channel('datadog:identity:update').publish(config)
|
|
357
359
|
if (this._tracingInitialized) {
|
|
358
360
|
const storeTracerMetadata = require('./tracer_metadata')
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
const { getEnvironmentVariable, getValueFromEnvSources } = require('./config/helper')
|
|
4
4
|
|
|
5
5
|
const IS_AWS_LAMBDA_MICROVM = getEnvironmentVariable('AWS_LAMBDA_MICROVM_IMAGE_ARN') !== undefined
|
|
6
|
+
// Standard Lambda, whose sandbox freezes between invocations. Excludes Lambda Lite (native-http),
|
|
7
|
+
// Managed Instances (lambda-managed-instances) and MicroVMs.
|
|
8
|
+
const IS_STANDARD_AWS_LAMBDA = !IS_AWS_LAMBDA_MICROVM && ['on-demand', 'provisioned-concurrency', 'snap-start']
|
|
9
|
+
.includes(getEnvironmentVariable('AWS_LAMBDA_INITIALIZATION_TYPE'))
|
|
6
10
|
const isVercelAtStartup = getEnvironmentVariable('VERCEL') === '1'
|
|
7
11
|
|
|
8
12
|
function getIsGCPFunction () {
|
|
@@ -104,6 +108,7 @@ module.exports = {
|
|
|
104
108
|
getIsFlexConsumptionAzureFunction,
|
|
105
109
|
initializeServerlessTelemetry,
|
|
106
110
|
IS_AWS_LAMBDA_MICROVM,
|
|
111
|
+
IS_STANDARD_AWS_LAMBDA,
|
|
107
112
|
// true only for a Node that bundles its own OpenSSL, whose CSPRNG keeps the snapshot's DRBG
|
|
108
113
|
// state across a MicroVM clone resume
|
|
109
114
|
NODE_BUNDLES_OPENSSL: process.config.variables.node_shared_openssl === false,
|