dd-trace 5.121.0 → 5.122.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 +0 -17
- package/index.d.ts +108 -3
- package/initialize.mjs +28 -17
- package/openfeature.js +2 -2
- package/package.json +11 -8
- package/packages/datadog-esbuild/index.js +0 -34
- package/packages/datadog-instrumentations/src/anthropic.js +210 -15
- package/packages/datadog-instrumentations/src/aws-sdk.js +9 -1
- package/packages/datadog-instrumentations/src/browser-bunyan.js +11 -0
- package/packages/datadog-instrumentations/src/bunyan.js +3 -16
- package/packages/datadog-instrumentations/src/cucumber.js +26 -11
- package/packages/datadog-instrumentations/src/cypress-config.js +182 -16
- package/packages/datadog-instrumentations/src/cypress-legacy-finalizers.js +30 -0
- package/packages/datadog-instrumentations/src/cypress.js +2 -0
- package/packages/datadog-instrumentations/src/fastify.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/bunyan.js +22 -0
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +9 -4
- package/packages/datadog-instrumentations/src/helpers/register.js +10 -8
- package/packages/datadog-instrumentations/src/jest.js +25 -17
- package/packages/datadog-instrumentations/src/mariadb.js +278 -21
- package/packages/datadog-instrumentations/src/mocha/main.js +539 -55
- package/packages/datadog-instrumentations/src/mocha/utils.js +130 -17
- package/packages/datadog-instrumentations/src/playwright-reporter.js +99 -0
- package/packages/datadog-instrumentations/src/playwright.js +149 -2
- package/packages/datadog-instrumentations/src/router.js +2 -0
- package/packages/datadog-instrumentations/src/vitest-main.js +11 -3
- package/packages/datadog-plugin-aws-sdk/src/base.js +1 -0
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +163 -44
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +2 -8
- package/packages/datadog-plugin-azure-functions/src/index.js +1 -1
- package/packages/datadog-plugin-browser-bunyan/src/index.js +9 -0
- package/packages/datadog-plugin-bunyan/src/index.js +5 -8
- package/packages/datadog-plugin-cucumber/src/index.js +11 -0
- package/packages/datadog-plugin-cypress/src/after-run.js +5 -1
- package/packages/datadog-plugin-cypress/src/after-spec.js +5 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +81 -29
- package/packages/datadog-plugin-cypress/src/finalization.js +76 -0
- package/packages/datadog-plugin-cypress/src/index.js +12 -6
- package/packages/datadog-plugin-cypress/src/plugin.js +16 -3
- package/packages/datadog-plugin-dd-trace-api/src/index.js +4 -0
- package/packages/datadog-plugin-electron/src/index.js +2 -0
- package/packages/datadog-plugin-fs/src/index.js +1 -1
- package/packages/datadog-plugin-jest/src/index.js +6 -0
- package/packages/datadog-plugin-mocha/src/index.js +56 -1
- package/packages/datadog-plugin-nats/src/index.js +1 -1
- package/packages/datadog-plugin-openai/src/tracing.js +41 -30
- package/packages/datadog-plugin-openai-agents/src/integration.js +1 -0
- package/packages/datadog-plugin-playwright/src/index.js +42 -13
- package/packages/datadog-plugin-vitest/src/index.js +6 -0
- package/packages/datadog-webpack/index.js +0 -11
- package/packages/dd-trace/index.js +0 -1
- package/packages/dd-trace/src/agent/info.js +6 -5
- package/packages/dd-trace/src/aiguard/client.js +100 -1
- package/packages/dd-trace/src/aiguard/errors.js +41 -0
- package/packages/dd-trace/src/aiguard/evaluation.js +357 -0
- package/packages/dd-trace/src/aiguard/integrations/anthropic.js +70 -0
- package/packages/dd-trace/src/aiguard/integrations/index.js +4 -0
- package/packages/dd-trace/src/aiguard/messages/anthropic.js +423 -0
- package/packages/dd-trace/src/aiguard/messages/openai.js +1 -24
- package/packages/dd-trace/src/aiguard/messages/utils.js +28 -0
- package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +1 -11
- package/packages/dd-trace/src/aiguard/sdk.js +22 -278
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +9 -12
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +1 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +27 -3
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +22 -6
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +21 -5
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +164 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +22 -6
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +365 -32
- package/packages/dd-trace/src/ci-visibility/exporters/request.js +201 -0
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +5 -3
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -5
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -1
- package/packages/dd-trace/src/ci-visibility/requests/rate-limit.js +39 -0
- package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -6
- package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +6 -2
- package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
- package/packages/dd-trace/src/config/index.js +13 -2
- package/packages/dd-trace/src/config/major-overrides.js +7 -0
- package/packages/dd-trace/src/config/supported-configurations.json +14 -0
- package/packages/dd-trace/src/datastreams/index.js +2 -0
- package/packages/dd-trace/src/datastreams/manager.js +9 -2
- package/packages/dd-trace/src/datastreams/processor.js +15 -13
- package/packages/dd-trace/src/datastreams/size.js +5 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +3 -2
- package/packages/dd-trace/src/exporters/agent/writer.js +49 -9
- package/packages/dd-trace/src/exporters/common/buffering-exporter.js +6 -4
- package/packages/dd-trace/src/exporters/common/writer.js +11 -6
- package/packages/dd-trace/src/llmobs/experiments/client.js +163 -5
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +241 -87
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +280 -20
- package/packages/dd-trace/src/llmobs/experiments/index.js +58 -59
- package/packages/dd-trace/src/llmobs/experiments/noop.js +71 -3
- package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
- package/packages/dd-trace/src/llmobs/experiments/util.js +41 -5
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +12 -3
- package/packages/dd-trace/src/llmobs/plugins/base.js +4 -2
- package/packages/dd-trace/src/noop/proxy.js +3 -4
- package/packages/dd-trace/src/openfeature/flagging_provider.js +18 -2
- package/packages/dd-trace/src/openfeature/remote_config.js +1 -1
- package/packages/dd-trace/src/opentelemetry/context_manager.js +7 -7
- package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +2 -1
- package/packages/dd-trace/src/opentelemetry/logs/logger.js +67 -11
- package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +14 -3
- package/packages/dd-trace/src/opentelemetry/metrics/index.js +25 -12
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +2 -5
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +96 -43
- package/packages/dd-trace/src/opentracing/tracer.js +5 -2
- package/packages/dd-trace/src/otel-thread-ctx.js +387 -0
- package/packages/dd-trace/src/plugin_manager.js +3 -3
- package/packages/dd-trace/src/plugins/ci_plugin.js +6 -1
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/util/jest.js +4 -3
- package/packages/dd-trace/src/plugins/util/test.js +9 -9
- package/packages/dd-trace/src/plugins/util/web.js +3 -0
- package/packages/dd-trace/src/profiling/profiler.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/space.js +1 -3
- package/packages/dd-trace/src/profiling/profilers/wall.js +37 -90
- package/packages/dd-trace/src/profiling/webspan-utils.js +45 -0
- package/packages/dd-trace/src/proxy.js +38 -29
- package/packages/dd-trace/src/ritm.js +9 -2
- package/packages/dd-trace/src/serverless.js +37 -0
- package/packages/dd-trace/src/span_format.js +1 -0
- package/packages/dd-trace/src/span_stats.js +30 -3
- package/packages/dd-trace/src/storage-channels.js +86 -0
- package/packages/dd-trace/src/tracer.js +9 -2
- package/packages/dd-trace/src/tracer_metadata.js +14 -1
- package/packages/dd-trace/src/web-tags-cache.js +132 -0
- package/vendor/dist/@datadog/openfeature-node-server/LICENSE +201 -0
- package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +0 -65
- package/packages/datadog-webpack/src/optional-peer-loader.js +0 -17
- package/packages/dd-trace/src/feature-registry.js +0 -29
- package/packages/dd-trace/src/openfeature/register.js +0 -35
- package/packages/dd-trace/src/openfeature/require-provider.js +0 -18
|
@@ -68,6 +68,8 @@ function getJestTestName (test) {
|
|
|
68
68
|
function getJestSuitesToRun (skippableSuites, originalTests, rootDir, fallbackRootDir) {
|
|
69
69
|
const unskippableSuites = {}
|
|
70
70
|
const forcedToRunSuites = {}
|
|
71
|
+
let hasUnskippableSuites = false
|
|
72
|
+
let hasForcedToRunSuites = false
|
|
71
73
|
|
|
72
74
|
const skippedSuites = []
|
|
73
75
|
const suitesToRun = []
|
|
@@ -87,11 +89,13 @@ function getJestSuitesToRun (skippableSuites, originalTests, rootDir, fallbackRo
|
|
|
87
89
|
if (isMarkedAsUnskippable(test)) {
|
|
88
90
|
suitesToRun.push(test)
|
|
89
91
|
unskippableSuites[relativePath] = true
|
|
92
|
+
hasUnskippableSuites = true
|
|
90
93
|
if (fallbackRelativePath !== undefined) {
|
|
91
94
|
unskippableSuites[fallbackRelativePath] = true
|
|
92
95
|
}
|
|
93
96
|
if (skippedSuite !== undefined) {
|
|
94
97
|
forcedToRunSuites[relativePath] = true
|
|
98
|
+
hasForcedToRunSuites = true
|
|
95
99
|
if (fallbackRelativePath !== undefined) {
|
|
96
100
|
forcedToRunSuites[fallbackRelativePath] = true
|
|
97
101
|
}
|
|
@@ -105,9 +109,6 @@ function getJestSuitesToRun (skippableSuites, originalTests, rootDir, fallbackRo
|
|
|
105
109
|
}
|
|
106
110
|
}
|
|
107
111
|
|
|
108
|
-
const hasUnskippableSuites = Object.keys(unskippableSuites).length > 0
|
|
109
|
-
const hasForcedToRunSuites = Object.keys(forcedToRunSuites).length > 0
|
|
110
|
-
|
|
111
112
|
if (originalTests.length) {
|
|
112
113
|
// The config object is shared by all tests, so we can just take the first one
|
|
113
114
|
const [test] = originalTests
|
|
@@ -851,7 +851,7 @@ function getTestTypeFromFramework (testFramework) {
|
|
|
851
851
|
*/
|
|
852
852
|
function finishAllTraceSpans (span) {
|
|
853
853
|
for (const traceSpan of span.context()._trace.started) {
|
|
854
|
-
if (traceSpan !== span) {
|
|
854
|
+
if (traceSpan !== span && traceSpan._duration === undefined) {
|
|
855
855
|
traceSpan.finish()
|
|
856
856
|
}
|
|
857
857
|
}
|
|
@@ -1439,10 +1439,6 @@ function addSkippedCoverageToMap (skippedCoverage, targetMap) {
|
|
|
1439
1439
|
}
|
|
1440
1440
|
}
|
|
1441
1441
|
|
|
1442
|
-
function hasSkippedCoverage (skippedCoverage) {
|
|
1443
|
-
return skippedCoverage && typeof skippedCoverage === 'object' && Object.keys(skippedCoverage).length > 0
|
|
1444
|
-
}
|
|
1445
|
-
|
|
1446
1442
|
function getTestCoverageLinesPercentage (coverage, skippedCoverage, rootDir) {
|
|
1447
1443
|
const executableLinesByFile = new Map()
|
|
1448
1444
|
const coveredLinesByFile = new Map()
|
|
@@ -1496,10 +1492,10 @@ function applySkippedCoverageToFileCoverage (fileCoverage, skippedBitmap) {
|
|
|
1496
1492
|
* @returns {boolean}
|
|
1497
1493
|
*/
|
|
1498
1494
|
function applySkippedCoverageToCoverage (coverage, skippedCoverage, rootDir) {
|
|
1499
|
-
|
|
1495
|
+
const skippedCoverageByFilename = getSkippedCoverageByFilename(skippedCoverage)
|
|
1496
|
+
if (skippedCoverageByFilename.size === 0) return false
|
|
1500
1497
|
|
|
1501
1498
|
const coverageMap = getCoverageMap(coverage)
|
|
1502
|
-
const skippedCoverageByFilename = getSkippedCoverageByFilename(skippedCoverage)
|
|
1503
1499
|
let matched = false
|
|
1504
1500
|
|
|
1505
1501
|
for (const filename of coverageMap.files()) {
|
|
@@ -1820,6 +1816,7 @@ function getPullRequestBaseBranch (pullRequestBaseBranch) {
|
|
|
1820
1816
|
}
|
|
1821
1817
|
|
|
1822
1818
|
const metrics = {}
|
|
1819
|
+
let hasMetrics = false
|
|
1823
1820
|
for (const candidate of candidateBranches) {
|
|
1824
1821
|
// Find common ancestor
|
|
1825
1822
|
const baseSha = getMergeBase(candidate, sourceBranch)
|
|
@@ -1838,6 +1835,7 @@ function getPullRequestBaseBranch (pullRequestBaseBranch) {
|
|
|
1838
1835
|
ahead,
|
|
1839
1836
|
baseSha,
|
|
1840
1837
|
}
|
|
1838
|
+
hasMetrics = true
|
|
1841
1839
|
}
|
|
1842
1840
|
|
|
1843
1841
|
function isDefaultBranch (branch) {
|
|
@@ -1846,7 +1844,7 @@ function getPullRequestBaseBranch (pullRequestBaseBranch) {
|
|
|
1846
1844
|
)
|
|
1847
1845
|
}
|
|
1848
1846
|
|
|
1849
|
-
if (
|
|
1847
|
+
if (!hasMetrics) {
|
|
1850
1848
|
return null
|
|
1851
1849
|
}
|
|
1852
1850
|
// Find branch with smallest "ahead" value, preferring default branch on tie
|
|
@@ -1872,6 +1870,7 @@ function getPullRequestDiff (baseCommit, targetCommit) {
|
|
|
1872
1870
|
function getModifiedFilesFromDiff (diff) {
|
|
1873
1871
|
if (!diff) return null
|
|
1874
1872
|
const result = {}
|
|
1873
|
+
let hasModifiedFiles = false
|
|
1875
1874
|
|
|
1876
1875
|
const filesRegex = /^diff --git a\/(?<file>.+) b\/(?<file2>.+)$/g
|
|
1877
1876
|
const linesRegex = /^@@ -\d+(,\d+)? \+(?<start>\d+)(,(?<count>\d+))? @@/g
|
|
@@ -1886,6 +1885,7 @@ function getModifiedFilesFromDiff (diff) {
|
|
|
1886
1885
|
if (fileMatch && fileMatch.groups.file) {
|
|
1887
1886
|
currentFile = fileMatch.groups.file
|
|
1888
1887
|
result[currentFile] = []
|
|
1888
|
+
hasModifiedFiles = true
|
|
1889
1889
|
continue
|
|
1890
1890
|
}
|
|
1891
1891
|
|
|
@@ -1904,7 +1904,7 @@ function getModifiedFilesFromDiff (diff) {
|
|
|
1904
1904
|
linesRegex.lastIndex = 0
|
|
1905
1905
|
}
|
|
1906
1906
|
|
|
1907
|
-
if (
|
|
1907
|
+
if (!hasModifiedFiles) {
|
|
1908
1908
|
return null
|
|
1909
1909
|
}
|
|
1910
1910
|
return result
|
|
@@ -376,6 +376,9 @@ function addAllowHeaders (req, res, headers) {
|
|
|
376
376
|
const allowHeaders = splitHeader(headers['access-control-allow-headers'])
|
|
377
377
|
const requestHeaders = splitHeader(req.headers['access-control-request-headers'])
|
|
378
378
|
const contextHeaders = [
|
|
379
|
+
'baggage',
|
|
380
|
+
'traceparent',
|
|
381
|
+
'tracestate',
|
|
379
382
|
'x-datadog-origin',
|
|
380
383
|
'x-datadog-parent-id',
|
|
381
384
|
'x-datadog-sampled', // Deprecated, but still accept it in case it's sent.
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const dc = require('dc-polyfill')
|
|
4
|
-
|
|
5
|
-
const { storage } = require('../../../../datadog-core')
|
|
6
3
|
const log = require('../../log')
|
|
7
4
|
const runtimeMetrics = require('../../runtime_metrics')
|
|
8
5
|
const telemetryMetrics = require('../../telemetry/metrics')
|
|
9
|
-
const {
|
|
6
|
+
const { endpointNameFromTags, finalEndpoint, getStartedSpans } = require('../webspan-utils')
|
|
10
7
|
const { SAMPLING_INTERVAL } = require('../constants')
|
|
8
|
+
const {
|
|
9
|
+
enterCh,
|
|
10
|
+
beforeCh,
|
|
11
|
+
spanFinishCh,
|
|
12
|
+
getActiveSpan,
|
|
13
|
+
ensureChannelsActivated,
|
|
14
|
+
} = require('../../storage-channels')
|
|
15
|
+
const webTagsCache = require('../../web-tags-cache')
|
|
11
16
|
|
|
12
17
|
const {
|
|
13
18
|
END_TIMESTAMP_LABEL,
|
|
@@ -21,21 +26,12 @@ const TRACE_ENDPOINT_LABEL = 'trace endpoint'
|
|
|
21
26
|
|
|
22
27
|
/** @typedef {import('../../config/config-base')} TracerConfig */
|
|
23
28
|
|
|
24
|
-
let beforeCh
|
|
25
|
-
const enterCh = dc.channel('dd-trace:storage:enter')
|
|
26
|
-
const spanFinishCh = dc.channel('dd-trace:span:finish')
|
|
27
|
-
const tagsUpdateCh = dc.channel('dd-trace:span:tags:update')
|
|
28
29
|
const profilerTelemetryMetrics = telemetryMetrics.manager.namespace('profilers')
|
|
29
30
|
|
|
30
31
|
const ProfilingContext = Symbol('NativeWallProfiler.ProfilingContext')
|
|
31
32
|
|
|
32
33
|
let kSampleCount
|
|
33
34
|
|
|
34
|
-
function getActiveSpan () {
|
|
35
|
-
const store = storage('legacy').getStore()
|
|
36
|
-
return store && store.span
|
|
37
|
-
}
|
|
38
|
-
|
|
39
35
|
function toBigInt (spanId) {
|
|
40
36
|
return spanId !== null && typeof spanId === 'object' ? spanId.toBigInt() : spanId
|
|
41
37
|
}
|
|
@@ -53,58 +49,13 @@ function updateContext (context) {
|
|
|
53
49
|
}
|
|
54
50
|
if (context.webTags !== undefined && context.endpoint === undefined) {
|
|
55
51
|
// endpoint may not be determined yet, but keep it as fallback
|
|
56
|
-
// if tags are not available anymore during serialization
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
function ensureChannelsActivated (asyncContextFrameEnabled) {
|
|
63
|
-
if (channelsActivated) return
|
|
64
|
-
|
|
65
|
-
const shimmer = require('../../../../datadog-shimmer')
|
|
66
|
-
|
|
67
|
-
// We need to instrument enterWith() on the legacy storage — that's the storage
|
|
68
|
-
// carrying span data and the only one the profiler cares about.
|
|
69
|
-
const legacyStorage = storage('legacy')
|
|
70
|
-
let inRun = false
|
|
71
|
-
shimmer.wrap(legacyStorage, 'enterWith', function (original) {
|
|
72
|
-
return function (store) {
|
|
73
|
-
const retVal = original.call(this, store)
|
|
74
|
-
if (!inRun) enterCh.publish()
|
|
75
|
-
return retVal
|
|
76
|
-
}
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
// When not using AsyncContextFrame, we need additional instrumentation.
|
|
80
|
-
if (!asyncContextFrameEnabled) {
|
|
81
|
-
// We need async_hooks.createHook to create a "before" callback.
|
|
82
|
-
const { createHook } = require('async_hooks')
|
|
83
|
-
beforeCh = dc.channel('dd-trace:storage:before')
|
|
84
|
-
createHook({ before: () => beforeCh.publish() }).enable()
|
|
85
|
-
|
|
86
|
-
// In ACF-based implementation run() delegates to enterWith() so it doesn't
|
|
87
|
-
// need to be separately instrumented. in non-ACF implementation run()
|
|
88
|
-
// doesn't delegate to enterWith(), so separate instrumentation is necessary.
|
|
89
|
-
shimmer.wrap(legacyStorage, 'run', function (original) {
|
|
90
|
-
return function (store, callback, ...args) {
|
|
91
|
-
const wrappedCb = shimmer.wrapFunction(callback, cb => function (...args) {
|
|
92
|
-
inRun = false
|
|
93
|
-
enterCh.publish()
|
|
94
|
-
const retVal = cb.apply(this, args)
|
|
95
|
-
inRun = true
|
|
96
|
-
return retVal
|
|
97
|
-
})
|
|
98
|
-
inRun = true
|
|
99
|
-
const retVal = original.call(this, store, wrappedCb, ...args)
|
|
100
|
-
enterCh.publish()
|
|
101
|
-
inRun = false
|
|
102
|
-
return retVal
|
|
103
|
-
}
|
|
104
|
-
})
|
|
52
|
+
// if tags are not available anymore during serialization. Only snapshot a
|
|
53
|
+
// settled value: routing tags land over the course of the request, and this
|
|
54
|
+
// field is never refreshed once set, so capturing an interim placeholder
|
|
55
|
+
// (a bare `GET`) would pin the fallback to it. Staying undefined until then
|
|
56
|
+
// just means the next sample rotation tries again.
|
|
57
|
+
context.endpoint = finalEndpoint(context.webTags)
|
|
105
58
|
}
|
|
106
|
-
|
|
107
|
-
channelsActivated = true
|
|
108
59
|
}
|
|
109
60
|
|
|
110
61
|
class NativeWallProfiler {
|
|
@@ -216,7 +167,13 @@ class NativeWallProfiler {
|
|
|
216
167
|
enterCh.subscribe(this.#boundEnter)
|
|
217
168
|
spanFinishCh.subscribe(this.#boundSpanFinished)
|
|
218
169
|
if (this.#endpointCollectionEnabled) {
|
|
219
|
-
|
|
170
|
+
// Web-tags cache publishes once per span at the moment its
|
|
171
|
+
// walk-result transitions from undefined to a real value —
|
|
172
|
+
// exactly when we need to refresh the ProfilingContext snapshot.
|
|
173
|
+
webTagsCache.resolvedCh.subscribe(this.#boundSpanTagsUpdated)
|
|
174
|
+
// Turn on the cache's own tagsUpdate subscription — it's
|
|
175
|
+
// ref-counted, so this composes with any other active consumer.
|
|
176
|
+
webTagsCache.activate()
|
|
220
177
|
}
|
|
221
178
|
}
|
|
222
179
|
}
|
|
@@ -299,32 +256,20 @@ class NativeWallProfiler {
|
|
|
299
256
|
let profilingContext = span[ProfilingContext]
|
|
300
257
|
if (profilingContext === undefined) {
|
|
301
258
|
const context = span.context()
|
|
302
|
-
const startedSpans = getStartedSpans(context)
|
|
303
259
|
|
|
304
260
|
let spanId
|
|
305
261
|
let rootSpanId
|
|
306
262
|
if (this.#codeHotspotsEnabled) {
|
|
263
|
+
const startedSpans = getStartedSpans(context)
|
|
307
264
|
spanId = context._spanId
|
|
308
265
|
rootSpanId = startedSpans.length ? startedSpans[0].context()._spanId : context._spanId
|
|
309
266
|
}
|
|
310
267
|
|
|
311
|
-
|
|
312
|
-
if
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
} else {
|
|
317
|
-
// Get parent's context's web tags
|
|
318
|
-
const parentId = context._parentId
|
|
319
|
-
for (let i = startedSpans.length; --i >= 0;) {
|
|
320
|
-
const ispan = startedSpans[i]
|
|
321
|
-
if (ispan.context()._spanId === parentId) {
|
|
322
|
-
webTags = this.#getProfilingContext(ispan).webTags
|
|
323
|
-
break
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
268
|
+
// webTags is snapshotted into the sample context at getProfilingContext
|
|
269
|
+
// time; if the answer turns out to be undefined and the span later gets
|
|
270
|
+
// web-server tags, #spanTagsUpdated refreshes this field via the shared
|
|
271
|
+
// cache (see web-tags-cache.js).
|
|
272
|
+
const webTags = this.#endpointCollectionEnabled ? webTagsCache.getCachedWebTags(span) : undefined
|
|
328
273
|
|
|
329
274
|
profilingContext = { spanId, rootSpanId, webTags }
|
|
330
275
|
span[ProfilingContext] = profilingContext
|
|
@@ -345,14 +290,15 @@ class NativeWallProfiler {
|
|
|
345
290
|
}
|
|
346
291
|
}
|
|
347
292
|
|
|
293
|
+
// Invoked (via webTagsCache.resolvedCh) once per span at the moment the
|
|
294
|
+
// shared cache promotes a previously-undefined webTags answer into a
|
|
295
|
+
// real value. Refresh the ProfilingContext snapshot so future samples
|
|
296
|
+
// pick it up.
|
|
348
297
|
#spanTagsUpdated (span) {
|
|
349
298
|
if (!this.#started) return
|
|
350
299
|
const profilingContext = span[ProfilingContext]
|
|
351
|
-
if (profilingContext === undefined
|
|
352
|
-
|
|
353
|
-
if (isWebServerSpan(tags)) {
|
|
354
|
-
profilingContext.webTags = tags
|
|
355
|
-
}
|
|
300
|
+
if (profilingContext === undefined) return
|
|
301
|
+
profilingContext.webTags = webTagsCache.getCachedWebTags(span)
|
|
356
302
|
}
|
|
357
303
|
|
|
358
304
|
#reportV8bug (maybeBug) {
|
|
@@ -401,7 +347,8 @@ class NativeWallProfiler {
|
|
|
401
347
|
enterCh.unsubscribe(this.#boundEnter)
|
|
402
348
|
spanFinishCh.unsubscribe(this.#boundSpanFinished)
|
|
403
349
|
if (this.#endpointCollectionEnabled) {
|
|
404
|
-
|
|
350
|
+
webTagsCache.resolvedCh.unsubscribe(this.#boundSpanTagsUpdated)
|
|
351
|
+
webTagsCache.deactivate()
|
|
405
352
|
}
|
|
406
353
|
this.#profilerState = undefined
|
|
407
354
|
}
|
|
@@ -470,7 +417,7 @@ class NativeWallProfiler {
|
|
|
470
417
|
if (rootSpanId !== undefined) {
|
|
471
418
|
labels[LOCAL_ROOT_SPAN_ID_LABEL] = toBigInt(rootSpanId)
|
|
472
419
|
}
|
|
473
|
-
if (webTags !== undefined
|
|
420
|
+
if (webTags !== undefined) {
|
|
474
421
|
labels[TRACE_ENDPOINT_LABEL] = endpointNameFromTags(webTags)
|
|
475
422
|
} else if (endpoint) {
|
|
476
423
|
// fallback to endpoint computed when sample was taken
|
|
@@ -14,6 +14,50 @@ function endpointNameFromTags (tags) {
|
|
|
14
14
|
].filter(Boolean).join(' ')
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
// The endpoint name a web-server span's tag bag yields changes over the span's
|
|
18
|
+
// lifetime: plugins set `http.method` when the request arrives and only add
|
|
19
|
+
// `http.route` (and often `resource.name`) once framework routing has resolved
|
|
20
|
+
// the URL. Returns the endpoint name once its value looks stable, or undefined
|
|
21
|
+
// while it is still an interim placeholder.
|
|
22
|
+
//
|
|
23
|
+
// Consumers that read the tag bag lazily (the wall profiler resolves endpoint
|
|
24
|
+
// labels at serialization time) see the settled value for free and only need
|
|
25
|
+
// this for fallbacks captured mid-request. Consumers that have to materialize
|
|
26
|
+
// the value early — the OTEP-4947 thread-context writer writes it into a record
|
|
27
|
+
// an out-of-process reader samples at any moment — need it to decide when
|
|
28
|
+
// publishing is safe.
|
|
29
|
+
//
|
|
30
|
+
// The test is on the computed value rather than on which tags are present,
|
|
31
|
+
// because endpointNameFromTags prefers `resource.name` over
|
|
32
|
+
// `http.method` + `http.route` and some plugins seed `resource.name` with the
|
|
33
|
+
// bare request method before routing resolves: datadog-plugin-next starts its
|
|
34
|
+
// request span with `resource.name: req.method` and only replaces it with
|
|
35
|
+
// `${req.method} ${page}` once the page is known. A value equal to the bare
|
|
36
|
+
// method is therefore a placeholder no matter which tags produced it.
|
|
37
|
+
function finalEndpoint (tags) {
|
|
38
|
+
if (tags == null) return
|
|
39
|
+
// Presence gate first: nothing but these two can contribute routing
|
|
40
|
+
// information, and checking them keeps the common not-yet-routed case free of
|
|
41
|
+
// the string building inside endpointNameFromTags.
|
|
42
|
+
if (tags[RESOURCE_NAME] == null && tags[HTTP_ROUTE] == null) return
|
|
43
|
+
const endpoint = endpointNameFromTags(tags)
|
|
44
|
+
if (!endpoint || endpoint === tags[HTTP_METHOD]) return
|
|
45
|
+
return endpoint
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// The trace's started-spans list, whose first entry is the local root span by
|
|
49
|
+
// repo-wide convention — priority_sampler, span_format, the wall profiler's
|
|
50
|
+
// local-root-span-id label, the event plugins and the OTEP-4947 writer all read
|
|
51
|
+
// it that way, and web-tags-cache walks it to find a span's parent.
|
|
52
|
+
//
|
|
53
|
+
// Partial flush weakens that convention for every one of them alike:
|
|
54
|
+
// span_processor's _erase() replaces the list with the still-active spans only,
|
|
55
|
+
// so once a trace crosses DD_TRACE_PARTIAL_FLUSH_MIN_SPANS a local root that has
|
|
56
|
+
// already finished drops out, and the first entry becomes merely the oldest span
|
|
57
|
+
// still running — while a finished web-server ancestor stops being reachable for
|
|
58
|
+
// the parent-chain walk. Correcting it needs a local-root reference the tracer
|
|
59
|
+
// core keeps across flushes; until there is one, consumers share the same skew
|
|
60
|
+
// on large traces rather than each compensating differently.
|
|
17
61
|
function getStartedSpans (context) {
|
|
18
62
|
return context._trace.started
|
|
19
63
|
}
|
|
@@ -21,5 +65,6 @@ function getStartedSpans (context) {
|
|
|
21
65
|
module.exports = {
|
|
22
66
|
isWebServerSpan,
|
|
23
67
|
endpointNameFromTags,
|
|
68
|
+
finalEndpoint,
|
|
24
69
|
getStartedSpans,
|
|
25
70
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const NoopProxy = require('./noop/proxy')
|
|
4
|
-
const { features } = require('./feature-registry')
|
|
5
4
|
const DatadogTracer = require('./tracer')
|
|
6
5
|
const getConfig = require('./config')
|
|
7
6
|
const { getEnvironmentVariable } = require('./config/helper')
|
|
@@ -38,9 +37,9 @@ const OFFLINE_VALIDATION_EXPORTERS = new Set([
|
|
|
38
37
|
'playwright_worker',
|
|
39
38
|
'vitest_worker',
|
|
40
39
|
])
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
const
|
|
40
|
+
const OPENFEATURE_STATE_NOOP = 0
|
|
41
|
+
const OPENFEATURE_STATE_LAZY = 1
|
|
42
|
+
const OPENFEATURE_STATE_ACTIVE = 2
|
|
44
43
|
|
|
45
44
|
class LazyModule {
|
|
46
45
|
constructor (provider) {
|
|
@@ -91,8 +90,7 @@ function defineLazily (obj, property, getClass, ...args) {
|
|
|
91
90
|
}
|
|
92
91
|
|
|
93
92
|
class Tracer extends NoopProxy {
|
|
94
|
-
|
|
95
|
-
#featureStates
|
|
93
|
+
#openfeatureState = OPENFEATURE_STATE_NOOP
|
|
96
94
|
|
|
97
95
|
constructor () {
|
|
98
96
|
super()
|
|
@@ -115,12 +113,9 @@ class Tracer extends NoopProxy {
|
|
|
115
113
|
aiguard: new LazyModule(() => require('./aiguard')),
|
|
116
114
|
iast: new LazyModule(() => require('./appsec/iast')),
|
|
117
115
|
llmobs: new LazyModule(() => require('./llmobs')),
|
|
116
|
+
openfeature: new LazyModule(() => require('./openfeature')),
|
|
118
117
|
rewriter: new LazyModule(() => require('./appsec/iast/taint-tracking/rewriter')),
|
|
119
118
|
}
|
|
120
|
-
|
|
121
|
-
for (const feature of Object.values(features)) {
|
|
122
|
-
this._modules[feature.name] = new LazyModule(feature.factory)
|
|
123
|
-
}
|
|
124
119
|
}
|
|
125
120
|
|
|
126
121
|
/**
|
|
@@ -213,9 +208,10 @@ class Tracer extends NoopProxy {
|
|
|
213
208
|
DynamicInstrumentation.start(config, rc)
|
|
214
209
|
}
|
|
215
210
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
211
|
+
const openfeatureRemoteConfig = require('./openfeature/remote_config')
|
|
212
|
+
const subscribeOpenfeatureToRemoteConfig = config.featureFlags.DD_FEATURE_FLAGS_ENABLED &&
|
|
213
|
+
config.featureFlags.DD_FEATURE_FLAGS_CONFIGURATION_SOURCE === 'remote_config'
|
|
214
|
+
openfeatureRemoteConfig.enable(rc, () => this.openfeature, subscribeOpenfeatureToRemoteConfig)
|
|
219
215
|
}
|
|
220
216
|
|
|
221
217
|
if (config.profiling.DD_PROFILING_ENABLED === 'true') {
|
|
@@ -233,6 +229,17 @@ class Tracer extends NoopProxy {
|
|
|
233
229
|
}
|
|
234
230
|
}
|
|
235
231
|
|
|
232
|
+
// Experimental: mirror the active trace ID, span ID and endpoint into
|
|
233
|
+
// an OTEP-4947 thread-local context record an out-of-process eBPF
|
|
234
|
+
// reader can consume.
|
|
235
|
+
if (config.DD_TRACE_OTEL_CTX_ENABLED) {
|
|
236
|
+
try {
|
|
237
|
+
require('./otel-thread-ctx').start()
|
|
238
|
+
} catch (e) {
|
|
239
|
+
log.error('Error starting OTEP-4947 thread context writer', e)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
236
243
|
// OTel logs/metrics pipelines must be initialized BEFORE runtimeMetrics.start so that
|
|
237
244
|
// when the OTLP runtime metrics module calls metrics.getMeterProvider(), it gets the
|
|
238
245
|
// real provider, otherwise instruments register on the noop provider and never export.
|
|
@@ -305,28 +312,32 @@ class Tracer extends NoopProxy {
|
|
|
305
312
|
}
|
|
306
313
|
|
|
307
314
|
/**
|
|
308
|
-
*
|
|
315
|
+
* `tracer.openfeature` is only reachable through this proxy. SSI itself (auto-injecting
|
|
316
|
+
* the tracer, resolving the vendored provider regardless of the customer's own
|
|
317
|
+
* `node_modules` layout) is unaffected -- that is what this PR fixes. The remaining gap is
|
|
318
|
+
* narrower: there is no `dd-trace-api` handoff for `openfeature` yet (see
|
|
319
|
+
* `packages/datadog-plugin-dd-trace-api`), so an application that calls into the tracer
|
|
320
|
+
* exclusively through the `dd-trace-api` shim -- instead of `require('dd-trace')` -- cannot
|
|
321
|
+
* reach the flagging provider through that shim.
|
|
322
|
+
*
|
|
309
323
|
* @param {import('./config/config-base')} config
|
|
310
324
|
*/
|
|
311
|
-
#
|
|
312
|
-
|
|
313
|
-
|
|
325
|
+
#enableOpenfeature (config) {
|
|
326
|
+
if (this.#openfeatureState !== OPENFEATURE_STATE_NOOP) return
|
|
327
|
+
this.#openfeatureState = OPENFEATURE_STATE_LAZY
|
|
314
328
|
|
|
315
|
-
|
|
316
|
-
states[feature.name] = FEATURE_STATE_LAZY
|
|
317
|
-
|
|
318
|
-
Reflect.defineProperty(this, feature.name, {
|
|
329
|
+
Reflect.defineProperty(this, 'openfeature', {
|
|
319
330
|
get: () => {
|
|
320
|
-
const
|
|
321
|
-
const provider = new
|
|
331
|
+
const FlaggingProvider = require('./openfeature/flagging_provider')
|
|
332
|
+
const provider = new FlaggingProvider(this._tracer, config)
|
|
322
333
|
|
|
323
|
-
this._modules
|
|
324
|
-
Reflect.defineProperty(this,
|
|
334
|
+
this._modules.openfeature.enable(config)
|
|
335
|
+
Reflect.defineProperty(this, 'openfeature', {
|
|
325
336
|
value: provider,
|
|
326
337
|
configurable: true,
|
|
327
338
|
enumerable: true,
|
|
328
339
|
})
|
|
329
|
-
|
|
340
|
+
this.#openfeatureState = OPENFEATURE_STATE_ACTIVE
|
|
330
341
|
return provider
|
|
331
342
|
},
|
|
332
343
|
configurable: true,
|
|
@@ -373,9 +384,7 @@ class Tracer extends NoopProxy {
|
|
|
373
384
|
this._modules.llmobs.disable()
|
|
374
385
|
}
|
|
375
386
|
|
|
376
|
-
|
|
377
|
-
if (feature.isEnabled(config)) this.#enableFeature(feature, config)
|
|
378
|
-
}
|
|
387
|
+
if (config.featureFlags.DD_FEATURE_FLAGS_ENABLED) this.#enableOpenfeature(config)
|
|
379
388
|
|
|
380
389
|
if (this._tracingInitialized) {
|
|
381
390
|
this._tracer.configure(config)
|
|
@@ -20,6 +20,7 @@ const origRequire = Module.prototype.require
|
|
|
20
20
|
module.exports = Hook
|
|
21
21
|
|
|
22
22
|
let moduleHooks = Object.create(null)
|
|
23
|
+
let hookedModuleCount = 0
|
|
23
24
|
let cache = Object.create(null)
|
|
24
25
|
let patching = Object.create(null)
|
|
25
26
|
let patchedRequire = null
|
|
@@ -64,6 +65,7 @@ function Hook (modules, options, onrequire) {
|
|
|
64
65
|
hooks.push(onrequire)
|
|
65
66
|
} else {
|
|
66
67
|
moduleHooks[mod] = [onrequire]
|
|
68
|
+
hookedModuleCount++
|
|
67
69
|
}
|
|
68
70
|
}
|
|
69
71
|
}
|
|
@@ -205,6 +207,7 @@ Hook.reset = function () {
|
|
|
205
207
|
patching = Object.create(null)
|
|
206
208
|
cache = Object.create(null)
|
|
207
209
|
moduleHooks = Object.create(null)
|
|
210
|
+
hookedModuleCount = 0
|
|
208
211
|
}
|
|
209
212
|
|
|
210
213
|
function findProjectRoot (startDir) {
|
|
@@ -221,16 +224,20 @@ function findProjectRoot (startDir) {
|
|
|
221
224
|
|
|
222
225
|
Hook.prototype.unhook = function () {
|
|
223
226
|
for (const mod of this.modules) {
|
|
224
|
-
const
|
|
227
|
+
const registeredHooks = moduleHooks[mod]
|
|
228
|
+
if (registeredHooks === undefined) continue
|
|
229
|
+
|
|
230
|
+
const hooks = registeredHooks.filter(hook => hook !== this.onrequire)
|
|
225
231
|
|
|
226
232
|
if (hooks.length > 0) {
|
|
227
233
|
moduleHooks[mod] = hooks
|
|
228
234
|
} else {
|
|
229
235
|
delete moduleHooks[mod]
|
|
236
|
+
hookedModuleCount--
|
|
230
237
|
}
|
|
231
238
|
}
|
|
232
239
|
|
|
233
|
-
if (
|
|
240
|
+
if (hookedModuleCount === 0) {
|
|
234
241
|
Hook.reset()
|
|
235
242
|
}
|
|
236
243
|
}
|
|
@@ -42,7 +42,44 @@ function isInServerlessEnvironment () {
|
|
|
42
42
|
return inAWSLambda || isGCPFunction || isAzureFunction
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Gets tags describing the serverless platform where the tracer is running.
|
|
47
|
+
*
|
|
48
|
+
* @returns {string[]|undefined}
|
|
49
|
+
*/
|
|
50
|
+
function getServerlessPlatformTags () {
|
|
51
|
+
if (getEnvironmentVariable('VERCEL') === '1') {
|
|
52
|
+
return getVercelPlatformTags()
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @returns {string[]|undefined}
|
|
58
|
+
*/
|
|
59
|
+
function getVercelPlatformTags () {
|
|
60
|
+
let tags
|
|
61
|
+
const projectId = getEnvironmentVariable('VERCEL_PROJECT_ID')
|
|
62
|
+
if (projectId) {
|
|
63
|
+
tags = ['vercel.project_id', projectId]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const environment = getEnvironmentVariable('VERCEL_ENV')
|
|
67
|
+
if (environment) {
|
|
68
|
+
tags ??= []
|
|
69
|
+
tags.push('vercel.environment', environment)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const region = getEnvironmentVariable('VERCEL_REGION')
|
|
73
|
+
if (region) {
|
|
74
|
+
tags ??= []
|
|
75
|
+
tags.push('vercel.region', region)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return tags
|
|
79
|
+
}
|
|
80
|
+
|
|
45
81
|
module.exports = {
|
|
82
|
+
getServerlessPlatformTags,
|
|
46
83
|
getIsGCPFunction,
|
|
47
84
|
getIsAzureFunction,
|
|
48
85
|
enableGCPPubSubPushSubscription,
|