dd-trace 6.9.0 → 6.11.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/ci/vitest-no-worker-init-setup.mjs +112 -10
- package/index.d.ts +150 -3
- package/initialize.mjs +28 -17
- package/openfeature.js +2 -2
- package/package.json +14 -10
- 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/confluentinc-kafka-javascript.js +111 -6
- 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 +634 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +10 -8
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +81 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +83 -1
- package/packages/datadog-instrumentations/src/jest.js +26 -18
- package/packages/datadog-instrumentations/src/mariadb.js +278 -21
- package/packages/datadog-instrumentations/src/mocha/main.js +540 -56
- package/packages/datadog-instrumentations/src/mocha/utils.js +132 -25
- package/packages/datadog-instrumentations/src/mysql.js +45 -15
- package/packages/datadog-instrumentations/src/mysql2.js +119 -18
- package/packages/datadog-instrumentations/src/path-to-regexp.js +67 -1
- package/packages/datadog-instrumentations/src/pg.js +116 -92
- 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-no-worker-init.js +43 -4
- package/packages/datadog-instrumentations/src/vitest-main.js +180 -20
- package/packages/datadog-instrumentations/src/vitest-util.js +64 -5
- package/packages/datadog-instrumentations/src/vitest-worker.js +150 -25
- package/packages/datadog-instrumentations/src/webdriverio.js +31 -17
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/trace-checkpoint.js +12 -6
- 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 +4 -13
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -5
- 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 +82 -30
- 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-google-cloud-pubsub/src/producer.js +31 -17
- package/packages/datadog-plugin-jest/src/index.js +11 -4
- package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +2 -3
- package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -3
- package/packages/datadog-plugin-kafkajs/src/producer.js +3 -2
- package/packages/datadog-plugin-kafkajs/src/utils.js +65 -7
- package/packages/datadog-plugin-mocha/src/index.js +606 -40
- package/packages/datadog-plugin-mysql/src/index.js +37 -0
- package/packages/datadog-plugin-nats/src/index.js +1 -1
- package/packages/datadog-plugin-next/src/index.js +1 -19
- package/packages/datadog-plugin-openai/src/tracing.js +41 -30
- package/packages/datadog-plugin-openai-agents/src/integration.js +279 -69
- package/packages/datadog-plugin-openai-agents/src/processor.js +16 -4
- package/packages/datadog-plugin-pg/src/index.js +1 -0
- package/packages/datadog-plugin-playwright/src/index.js +42 -13
- package/packages/datadog-plugin-vitest/src/index.js +14 -20
- package/packages/datadog-plugin-ws/src/util.js +2 -1
- 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/api_security/normalized-route.js +453 -0
- package/packages/dd-trace/src/appsec/handlers/http-request.js +13 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +9 -12
- package/packages/dd-trace/src/carrier.js +356 -0
- 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 +7 -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 +24 -0
- package/packages/dd-trace/src/datastreams/context.js +6 -1
- package/packages/dd-trace/src/datastreams/index.js +2 -0
- package/packages/dd-trace/src/datastreams/manager.js +14 -3
- package/packages/dd-trace/src/datastreams/pathway.js +23 -22
- package/packages/dd-trace/src/datastreams/processor.js +16 -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/constants/tags.js +5 -0
- 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/index.js +34 -2
- 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/llmobs/span_processor.js +16 -0
- package/packages/dd-trace/src/llmobs/tagger.js +96 -0
- package/packages/dd-trace/src/llmobs/util.js +78 -0
- 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/propagation/text_map.js +126 -120
- package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +4 -8
- 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 +5 -3
- package/packages/dd-trace/src/plugins/ci_plugin.js +6 -1
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/{datadog-plugin-jest/src/util.js → dd-trace/src/plugins/util/jest.js} +5 -65
- package/packages/dd-trace/src/plugins/util/test.js +93 -14
- package/packages/dd-trace/src/plugins/util/web.js +56 -2
- 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
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// OTEP-4947 Thread Local Context Record writer integration.
|
|
4
|
+
//
|
|
5
|
+
// Hooks into the active-span lifecycle (storage:enter, span:finish,
|
|
6
|
+
// span:tags:update channels) and mirrors the active trace ID, span ID
|
|
7
|
+
// and current endpoint into a thread-local record that an
|
|
8
|
+
// out-of-process eBPF reader can discover via the
|
|
9
|
+
// otel_thread_ctx_nodejs_v1 TLS symbol exported by the @datadog/pprof
|
|
10
|
+
// addon.
|
|
11
|
+
//
|
|
12
|
+
// Linux + AsyncContextFrame only. Degrades to a no-op start() on
|
|
13
|
+
// platforms or Node versions where the writer can't operate; the
|
|
14
|
+
// caller is expected to gate activation via the DD_TRACE_OTEL_CTX_ENABLED
|
|
15
|
+
// env var (or future config flag).
|
|
16
|
+
//
|
|
17
|
+
// Covers both the dd-trace API and the OpenTelemetry API when the
|
|
18
|
+
// latter is used through dd-trace-js's TracerProvider: an OTel Span
|
|
19
|
+
// wraps a DatadogSpan (packages/dd-trace/src/opentelemetry/span.js)
|
|
20
|
+
// and the OTel ContextManager activates spans via
|
|
21
|
+
// storage('legacy').run({ span: ddSpan }, ...), so the active span
|
|
22
|
+
// our writer reads from legacy storage is the same DatadogSpan in
|
|
23
|
+
// both cases. The OTel-visible trace/span IDs come from the same
|
|
24
|
+
// _ddContext.toTraceId/toSpanId calls we use here.
|
|
25
|
+
|
|
26
|
+
const { isMainThread, threadId } = require('worker_threads')
|
|
27
|
+
|
|
28
|
+
const { isACFActive } = require('../../datadog-core/src/storage')
|
|
29
|
+
const log = require('./log')
|
|
30
|
+
const {
|
|
31
|
+
enterCh,
|
|
32
|
+
spanFinishCh,
|
|
33
|
+
getActiveSpan,
|
|
34
|
+
ensureChannelsActivated,
|
|
35
|
+
} = require('./storage-channels')
|
|
36
|
+
const {
|
|
37
|
+
finalEndpoint,
|
|
38
|
+
getStartedSpans,
|
|
39
|
+
} = require('./profiling/webspan-utils')
|
|
40
|
+
const webTagsCache = require('./web-tags-cache')
|
|
41
|
+
|
|
42
|
+
// OTEP-4947 duplicates are last-wins, so appending a later endpoint value would
|
|
43
|
+
// correctly overwrite an earlier one — but an out-of-process reader sampling the
|
|
44
|
+
// record mid-request would still see the interim value (e.g. a bare `GET`) as
|
|
45
|
+
// the endpoint for whatever it was sampling. So a record is built without an
|
|
46
|
+
// endpoint until finalEndpoint (profiling/webspan-utils.js) reports the value
|
|
47
|
+
// settled, and gets one appended when webTagsCache announces that moment.
|
|
48
|
+
//
|
|
49
|
+
// Records still waiting on that announcement, grouped by the web-server tag bag
|
|
50
|
+
// they are waiting on. Every span under one request resolves to that same bag
|
|
51
|
+
// (webTagsCache maps a span to its nearest web-server ancestor's tags) and the
|
|
52
|
+
// endpoint belongs to the request rather than to any one span, so a single
|
|
53
|
+
// resolution fills in every record built before it — the request span's own and
|
|
54
|
+
// each descendant's. Descendants are the reason this can't just live on the
|
|
55
|
+
// per-span cache entry: the tags:update that carries the route is published for
|
|
56
|
+
// the ancestor, which has no way to enumerate them.
|
|
57
|
+
//
|
|
58
|
+
// Keyed by the tag bag itself because that is all a descendant has at build
|
|
59
|
+
// time, and the announcement carries the span whose getTags() is that same
|
|
60
|
+
// object. Tag-bag identity is stable for live spans — only spanleak.js replaces
|
|
61
|
+
// a bag, on already-expired spans under manual-GC debug mode — and webTagsCache
|
|
62
|
+
// already depends on it, holding bags the wall profiler dereferences much later.
|
|
63
|
+
//
|
|
64
|
+
// Entries are dropped as soon as the endpoint resolves, or die with the tag bag
|
|
65
|
+
// if it never does.
|
|
66
|
+
const pendingEndpoints = new WeakMap()
|
|
67
|
+
|
|
68
|
+
// Positional attribute layout. The local root span ID stays at index 0 by
|
|
69
|
+
// convention (mirrors libdatadog's libdd-otel-thread-ctx, where
|
|
70
|
+
// `local_root_span_id` is always the first entry in
|
|
71
|
+
// `threadlocal.attribute_key_map`), encoded as a 16-character lowercase
|
|
72
|
+
// hex string. Endpoint, thread name, and thread id follow. Adding more
|
|
73
|
+
// means assigning the next index and updating ATTRIBUTE_KEYS
|
|
74
|
+
// accordingly.
|
|
75
|
+
const LOCAL_ROOT_SPAN_ID_IDX = 0
|
|
76
|
+
const ENDPOINT_IDX = 1
|
|
77
|
+
const THREAD_NAME_IDX = 2
|
|
78
|
+
const THREAD_ID_IDX = 3
|
|
79
|
+
|
|
80
|
+
// The dd-trace-js-supplied subset of the OTEP-4719 attribute_key_map
|
|
81
|
+
// (the implicit `datadog.local_root_span_id` at wire index 0 is
|
|
82
|
+
// prepended by libdatadog when it publishes the process context, so it
|
|
83
|
+
// is NOT listed here). Index N here corresponds to wire key index N+1.
|
|
84
|
+
// Kept in sync with the positional indices above.
|
|
85
|
+
// Also see https://docs.google.com/document/d/1IwjjVJzEChcFPcnVV2N5Kkjg-4_Q4v4Q3ojpxntbdvY/edit?pli=1&tab=t.efaosgjya44c#bookmark=id.700gvw31vb7h
|
|
86
|
+
const ATTRIBUTE_KEYS = [
|
|
87
|
+
'datadog.trace_endpoint',
|
|
88
|
+
'datadog.thread_name',
|
|
89
|
+
'datadog.thread_id',
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
// Stable per-thread values baked into every record. Same shape as the
|
|
93
|
+
// profiler's `eventLoopThreadName` in profiling/profilers/shared.js.
|
|
94
|
+
const THREAD_NAME = (isMainThread ? 'Main' : `Worker #${threadId}`) + ' Event Loop'
|
|
95
|
+
const THREAD_ID = String(threadId)
|
|
96
|
+
|
|
97
|
+
// Cache slot on span objects. One ThreadContext is built per span on first
|
|
98
|
+
// activation and re-installed across every async-context frame that
|
|
99
|
+
// re-enters the span — V8's AsyncContextFrame inherits the JS
|
|
100
|
+
// reference verbatim, and the context's record buffer is mutated in
|
|
101
|
+
// place by appendAttributes, so all frames observe the same record.
|
|
102
|
+
//
|
|
103
|
+
// Fields:
|
|
104
|
+
// context: ThreadContext from @datadog/pprof.otelThreadCtx — built the first
|
|
105
|
+
// time onEnter activates the span, and cleared on span finish, which
|
|
106
|
+
// is how a pendingEndpoints waiter recognizes a record it must no
|
|
107
|
+
// longer append to.
|
|
108
|
+
const CachedSym = Symbol('OtelThreadCtx.cached')
|
|
109
|
+
|
|
110
|
+
let started = false
|
|
111
|
+
let ThreadContext
|
|
112
|
+
let getContext
|
|
113
|
+
let clearContext
|
|
114
|
+
|
|
115
|
+
function getOrBuildContext (span) {
|
|
116
|
+
let cached = span[CachedSym]
|
|
117
|
+
if (cached !== undefined && cached.context !== undefined) return cached.context
|
|
118
|
+
const spanContext = span.context()
|
|
119
|
+
const traceId = Uint8Array.from(Buffer.from(spanContext.toTraceId(true), 'hex'))
|
|
120
|
+
const spanId = Uint8Array.from(Buffer.from(spanContext.toSpanId(true), 'hex'))
|
|
121
|
+
// Local root span: the first entry in the trace's started-spans list, or
|
|
122
|
+
// this span itself when it IS the root. Encoded as 16-char lowercase hex
|
|
123
|
+
// per the libdatadog convention.
|
|
124
|
+
const startedSpans = getStartedSpans(spanContext)
|
|
125
|
+
const rootContext = startedSpans.length ? startedSpans[0].context() : spanContext
|
|
126
|
+
// Only write the endpoint when its value has settled; otherwise leave a hole
|
|
127
|
+
// and wait for webTagsCache to announce that it has.
|
|
128
|
+
const webTags = webTagsCache.getCachedWebTags(span)
|
|
129
|
+
const endpoint = finalEndpoint(webTags)
|
|
130
|
+
const attrs = []
|
|
131
|
+
attrs[LOCAL_ROOT_SPAN_ID_IDX] = rootContext.toSpanId(true)
|
|
132
|
+
if (endpoint !== undefined) attrs[ENDPOINT_IDX] = endpoint
|
|
133
|
+
attrs[THREAD_NAME_IDX] = THREAD_NAME
|
|
134
|
+
attrs[THREAD_ID_IDX] = THREAD_ID
|
|
135
|
+
if (cached === undefined) {
|
|
136
|
+
cached = {}
|
|
137
|
+
span[CachedSym] = cached
|
|
138
|
+
}
|
|
139
|
+
cached.context = new ThreadContext(traceId, spanId, attrs)
|
|
140
|
+
if (endpoint === undefined) awaitEndpoint(webTags, cached)
|
|
141
|
+
return cached.context
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Enlist a record built without an endpoint, so the resolution announcement for
|
|
145
|
+
// its request fills it in.
|
|
146
|
+
function awaitEndpoint (webTags, cached) {
|
|
147
|
+
// No web-server span in this span's ancestry, so no endpoint is coming. Should
|
|
148
|
+
// one appear later, webTagsCache announces that separately — see
|
|
149
|
+
// onWebTagsResolved.
|
|
150
|
+
if (webTags === undefined) return
|
|
151
|
+
const waiting = pendingEndpoints.get(webTags)
|
|
152
|
+
if (waiting === undefined) {
|
|
153
|
+
pendingEndpoints.set(webTags, [cached])
|
|
154
|
+
} else {
|
|
155
|
+
waiting.push(cached)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function appendEndpoint (context, endpoint) {
|
|
160
|
+
// Append in place. The record buffer is shared across every async-context
|
|
161
|
+
// frame holding this context, so the endpoint becomes visible everywhere at
|
|
162
|
+
// once.
|
|
163
|
+
const append = []
|
|
164
|
+
append[ENDPOINT_IDX] = endpoint
|
|
165
|
+
context.appendAttributes(append)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function onEnter () {
|
|
169
|
+
if (!started) return
|
|
170
|
+
const span = getActiveSpan()
|
|
171
|
+
if (!span) {
|
|
172
|
+
clearContext()
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
const context = getOrBuildContext(span)
|
|
176
|
+
// Skip if this CPED already holds the same context. Same allocation-churn
|
|
177
|
+
// fix as the wall profiler in dd-trace-js#8638.
|
|
178
|
+
if (getContext() === context) return
|
|
179
|
+
context.enter()
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function onSpanFinished (span) {
|
|
183
|
+
if (!started) return
|
|
184
|
+
const cached = span[CachedSym]
|
|
185
|
+
if (cached === undefined) return
|
|
186
|
+
span[CachedSym] = undefined
|
|
187
|
+
const context = cached.context
|
|
188
|
+
if (context === undefined) return
|
|
189
|
+
// The span is over, so its record must stop presenting itself as an active
|
|
190
|
+
// thread context to an out-of-process reader. Invalidating the record covers
|
|
191
|
+
// every async-context frame still holding this same context reference —
|
|
192
|
+
// sibling frames, and continuations the span scheduled before it finished.
|
|
193
|
+
// Detaching only the current frame wouldn't: with enterWith-style activation
|
|
194
|
+
// (sticky storage) no storage:enter fires in those inherited frames to
|
|
195
|
+
// overwrite the record, and in ACF mode there is no `before` hook either, so
|
|
196
|
+
// they would keep exposing the finished span's IDs.
|
|
197
|
+
context.invalidate()
|
|
198
|
+
// Marks the record dead for any pendingEndpoints waiter still holding this
|
|
199
|
+
// entry, which must not append to an invalidated record.
|
|
200
|
+
cached.context = undefined
|
|
201
|
+
// Also detach from the current frame when it is the holder, so the record
|
|
202
|
+
// becomes unreachable and collectable instead of lingering as an invalid one
|
|
203
|
+
// for the rest of the frame's life.
|
|
204
|
+
if (getContext() === context) clearContext()
|
|
205
|
+
// Dropping the cache entry means a later storage:enter naming this same
|
|
206
|
+
// finished span builds a fresh, valid record for it — which is deliberate, and
|
|
207
|
+
// not the staleness invalidate() above exists to prevent. The two cases differ
|
|
208
|
+
// in what the tracer told us: an inherited frame is a frame we never heard
|
|
209
|
+
// about again, while an enter is the tracer affirmatively naming the active
|
|
210
|
+
// span, finished or not. Work running under a finished request span still
|
|
211
|
+
// belongs to that request, so a reader should attribute it there; the wall
|
|
212
|
+
// profiler decides the same way, dropping its per-span context on finish and
|
|
213
|
+
// rebuilding it on re-entry (profiling/profilers/wall.js). Clearing instead
|
|
214
|
+
// would strip trace and endpoint attribution off that work entirely.
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// The endpoint of a request has settled: fill in every record built while it was
|
|
218
|
+
// still unresolved. `span` is the web-server span, so its tag bag is the key its
|
|
219
|
+
// descendants enlisted under.
|
|
220
|
+
function onEndpointResolved (span) {
|
|
221
|
+
if (!started) return
|
|
222
|
+
const webTags = span.context().getTags()
|
|
223
|
+
const waiting = pendingEndpoints.get(webTags)
|
|
224
|
+
if (waiting === undefined) return
|
|
225
|
+
const endpoint = finalEndpoint(webTags)
|
|
226
|
+
// webTagsCache only announces settled endpoints, so this is belt and braces.
|
|
227
|
+
if (endpoint === undefined) return
|
|
228
|
+
pendingEndpoints.delete(webTags)
|
|
229
|
+
for (const cached of waiting) {
|
|
230
|
+
if (cached.context !== undefined) appendEndpoint(cached.context, endpoint)
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// A span whose record was built before it looked like a web-server span at all
|
|
235
|
+
// has just been recognized as one, so it has no endpoint and never enlisted for
|
|
236
|
+
// this request. Its ancestry can't have changed, only its own tags, so this is
|
|
237
|
+
// only ever about the announced span's own record.
|
|
238
|
+
function onWebTagsResolved (span) {
|
|
239
|
+
if (!started) return
|
|
240
|
+
const cached = span[CachedSym]
|
|
241
|
+
if (cached === undefined || cached.context === undefined) return
|
|
242
|
+
const webTags = webTagsCache.getCachedWebTags(span)
|
|
243
|
+
const endpoint = finalEndpoint(webTags)
|
|
244
|
+
if (endpoint === undefined) {
|
|
245
|
+
awaitEndpoint(webTags, cached)
|
|
246
|
+
} else {
|
|
247
|
+
appendEndpoint(cached.context, endpoint)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Every otelThreadCtx member the writer calls, checked before it starts. Anything
|
|
252
|
+
// missing would otherwise surface from inside a diagnostic-channel subscriber on
|
|
253
|
+
// a hot path, where an exception lands in application code: a ThreadContext
|
|
254
|
+
// without invalidate() would throw out of onSpanFinished and up through
|
|
255
|
+
// DatadogSpan#finish() the first time an activated span finished.
|
|
256
|
+
//
|
|
257
|
+
// Returns the name of the first missing member, or undefined when the surface is
|
|
258
|
+
// complete.
|
|
259
|
+
function missingApiMember (ns) {
|
|
260
|
+
if (!ns) return 'otelThreadCtx'
|
|
261
|
+
for (const name of ['ThreadContext', 'getContext', 'clearContext', 'getProcessContextAttributes']) {
|
|
262
|
+
if (typeof ns[name] !== 'function') return name
|
|
263
|
+
}
|
|
264
|
+
for (const name of ['appendAttributes', 'enter', 'invalidate']) {
|
|
265
|
+
if (typeof ns.ThreadContext.prototype[name] !== 'function') return `ThreadContext.prototype.${name}`
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Install and detach one throwaway context, to establish that this process can
|
|
270
|
+
// actually do it before any span depends on it.
|
|
271
|
+
//
|
|
272
|
+
// @datadog/pprof decides whether AsyncContextFrame is available by inspecting
|
|
273
|
+
// `process.execArgv`, and throws from enter() when it concludes it isn't. That
|
|
274
|
+
// disagrees with the feature detection behind `isACFActive` whenever the flag
|
|
275
|
+
// reached Node by another route: `NODE_OPTIONS=--experimental-async-context-frame`
|
|
276
|
+
// is accepted on Node 22 and 23 and leaves `execArgv` empty, and a worker thread
|
|
277
|
+
// created with an explicit `execArgv` loses it too. Since our subscribers run
|
|
278
|
+
// inline with `storage.enterWith`, letting that throw would put the exception in
|
|
279
|
+
// application code on the first span activation, so find out here instead, where
|
|
280
|
+
// declining to start is still an option.
|
|
281
|
+
function canInstallContext (ns) {
|
|
282
|
+
try {
|
|
283
|
+
// Zero-filled ids: the record is only readable while it is installed, which
|
|
284
|
+
// is for the length of this function, and an all-zero trace id identifies
|
|
285
|
+
// nothing.
|
|
286
|
+
const probe = new ns.ThreadContext(new Uint8Array(16), new Uint8Array(8))
|
|
287
|
+
probe.enter()
|
|
288
|
+
ns.clearContext()
|
|
289
|
+
return true
|
|
290
|
+
} catch (e) {
|
|
291
|
+
log.warn('OTEP-4947 thread context writer: @datadog/pprof cannot install a thread context', e)
|
|
292
|
+
return false
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function start () {
|
|
297
|
+
if (started) return true
|
|
298
|
+
if (process.platform !== 'linux') {
|
|
299
|
+
log.debug('OTEP-4947 thread context writer: not on Linux, skipping')
|
|
300
|
+
return false
|
|
301
|
+
}
|
|
302
|
+
if (!isACFActive) {
|
|
303
|
+
log.warn(
|
|
304
|
+
'OTEP-4947 thread context writer requires AsyncContextFrame to be active; not enabling'
|
|
305
|
+
)
|
|
306
|
+
return false
|
|
307
|
+
}
|
|
308
|
+
let pprofMod
|
|
309
|
+
try {
|
|
310
|
+
pprofMod = require('@datadog/pprof')
|
|
311
|
+
} catch (e) {
|
|
312
|
+
log.warn('OTEP-4947 thread context writer: @datadog/pprof unavailable', e)
|
|
313
|
+
return false
|
|
314
|
+
}
|
|
315
|
+
const ns = pprofMod.otelThreadCtx
|
|
316
|
+
const missing = missingApiMember(ns)
|
|
317
|
+
if (missing !== undefined) {
|
|
318
|
+
log.warn(
|
|
319
|
+
'OTEP-4947 thread context writer: installed @datadog/pprof does not expose the otelThreadCtx API (missing %s)',
|
|
320
|
+
missing
|
|
321
|
+
)
|
|
322
|
+
return false
|
|
323
|
+
}
|
|
324
|
+
if (!canInstallContext(ns)) return false
|
|
325
|
+
ThreadContext = ns.ThreadContext
|
|
326
|
+
getContext = ns.getContext
|
|
327
|
+
clearContext = ns.clearContext
|
|
328
|
+
|
|
329
|
+
ensureChannelsActivated(isACFActive)
|
|
330
|
+
enterCh.subscribe(onEnter)
|
|
331
|
+
spanFinishCh.subscribe(onSpanFinished)
|
|
332
|
+
// Endpoint updates come from the shared web-tags cache's transition channels
|
|
333
|
+
// rather than from `dd-trace:span:tags:update` directly: the cache already
|
|
334
|
+
// subscribes to that channel and derives both transitions we care about, so
|
|
335
|
+
// this keeps one subscriber on the hot path instead of two, and removes any
|
|
336
|
+
// ordering dependency on the cache having handled an update before we read it.
|
|
337
|
+
webTagsCache.endpointResolvedCh.subscribe(onEndpointResolved)
|
|
338
|
+
webTagsCache.resolvedCh.subscribe(onWebTagsResolved)
|
|
339
|
+
// Turn on the shared web-tags cache's own tagsUpdate subscription for as
|
|
340
|
+
// long as this writer runs — see web-tags-cache.js.
|
|
341
|
+
webTagsCache.activate()
|
|
342
|
+
|
|
343
|
+
started = true
|
|
344
|
+
log.info('OTEP-4947 thread context writer started')
|
|
345
|
+
return true
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Snapshot of the OTEP-4719 process-context attributes describing this
|
|
349
|
+
// writer's on-the-wire record schema — schema-version string, the caller-side
|
|
350
|
+
// attribute key map, and the V8 layout constants a reader needs to walk from
|
|
351
|
+
// our discovery TLS symbol into the record. Returned in the shape libdatadog's
|
|
352
|
+
// napi ThreadLocalMetadata expects:
|
|
353
|
+
//
|
|
354
|
+
// { attributeKeys, schemaVersion, extraAttributes: [{ key, intValue|stringValue }] }
|
|
355
|
+
//
|
|
356
|
+
// Returns undefined unless start() succeeded. Callers should treat that as
|
|
357
|
+
// "no threadlocal block" (equivalent to the flag being off) — otherwise we'd
|
|
358
|
+
// publish process-context metadata advertising a decodable OTEP-4947 stream
|
|
359
|
+
// while no writer is producing records.
|
|
360
|
+
function getThreadLocalMetadata () {
|
|
361
|
+
if (!started) return
|
|
362
|
+
// start() verified @datadog/pprof.otelThreadCtx exposes the full API
|
|
363
|
+
// surface (ThreadContext/getContext/clearContext/getProcessContextAttributes),
|
|
364
|
+
// so this require is a cached lookup and the method is guaranteed present.
|
|
365
|
+
const pca = require('@datadog/pprof').otelThreadCtx
|
|
366
|
+
.getProcessContextAttributes(ATTRIBUTE_KEYS)
|
|
367
|
+
const extraAttributes = []
|
|
368
|
+
for (const [key, value] of Object.entries(pca)) {
|
|
369
|
+
if (key === 'threadlocal.schema_version' || key === 'threadlocal.attribute_key_map') continue
|
|
370
|
+
if (typeof value === 'number' && Number.isInteger(value)) {
|
|
371
|
+
extraAttributes.push({ key, intValue: value })
|
|
372
|
+
} else if (typeof value === 'string') {
|
|
373
|
+
extraAttributes.push({ key, stringValue: value })
|
|
374
|
+
} else {
|
|
375
|
+
throw new TypeError(
|
|
376
|
+
`OTEP-4947 process-context attribute ${JSON.stringify(key)} has unsupported value type: ${typeof value}`
|
|
377
|
+
)
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return {
|
|
381
|
+
attributeKeys: [...pca['threadlocal.attribute_key_map']],
|
|
382
|
+
schemaVersion: pca['threadlocal.schema_version'],
|
|
383
|
+
extraAttributes,
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
module.exports = { start, ATTRIBUTE_KEYS, getThreadLocalMetadata }
|
|
@@ -61,9 +61,9 @@ function maybeEnable (Plugin) {
|
|
|
61
61
|
function getEnabled (Plugin) {
|
|
62
62
|
const envName = `DD_TRACE_${Plugin.id.toUpperCase()}_ENABLED`
|
|
63
63
|
// skipDefault: only an explicitly configured value should drive enablement here. A registered
|
|
64
|
-
// default of `false` (e.g. an
|
|
64
|
+
// default of `false` (e.g. an opt-in plugin like `nats`) must not be read as an explicit
|
|
65
65
|
// "disabled via configuration option" — that path both logs a misleading line and nulls the
|
|
66
|
-
// plugin class, bypassing the
|
|
66
|
+
// plugin class, bypassing the opt-in handled by `loadPlugin`.
|
|
67
67
|
return getValueFromEnvSources(normalizePluginEnvName(envName), true)
|
|
68
68
|
}
|
|
69
69
|
|
|
@@ -102,7 +102,7 @@ module.exports = class PluginManager {
|
|
|
102
102
|
}
|
|
103
103
|
const pluginConfig = this._configsByName[name] || {
|
|
104
104
|
enabled: this._tracerConfig.plugins !== false &&
|
|
105
|
-
(!Plugin.
|
|
105
|
+
(!Plugin.optIn || isTrue(getEnabled(Plugin))),
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
// extracts predetermined configuration from tracer and combines it with plugin-specific config
|
|
@@ -165,6 +165,7 @@ module.exports = class PluginManager {
|
|
|
165
165
|
codeOriginForSpans,
|
|
166
166
|
dbmPropagationMode,
|
|
167
167
|
dsmEnabled,
|
|
168
|
+
DD_TRACE_HTTP_SERVER_ERROR_STATUSES,
|
|
168
169
|
clientIpEnabled,
|
|
169
170
|
clientIpHeader,
|
|
170
171
|
DD_TRACE_MEMCACHED_COMMAND_ENABLED,
|
|
@@ -194,6 +195,7 @@ module.exports = class PluginManager {
|
|
|
194
195
|
site,
|
|
195
196
|
url,
|
|
196
197
|
headers: headerTags || [],
|
|
198
|
+
DD_TRACE_HTTP_SERVER_ERROR_STATUSES,
|
|
197
199
|
clientIpHeader,
|
|
198
200
|
DD_TEST_SESSION_NAME,
|
|
199
201
|
DD_AGENTLESS_LOG_SUBMISSION_ENABLED,
|
|
@@ -728,7 +728,12 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
728
728
|
this._bufferWorkerTrace(missingTestSuite, trace)
|
|
729
729
|
return
|
|
730
730
|
}
|
|
731
|
-
this.tracer._exporter
|
|
731
|
+
const exporter = this.tracer._exporter
|
|
732
|
+
if (exporter.exportTraceWithDeferredTestSuite) {
|
|
733
|
+
exporter.exportTraceWithDeferredTestSuite(trace)
|
|
734
|
+
} else {
|
|
735
|
+
exporter.export(trace)
|
|
736
|
+
}
|
|
732
737
|
}
|
|
733
738
|
|
|
734
739
|
/**
|
|
@@ -46,6 +46,7 @@ const plugins = {
|
|
|
46
46
|
get amqplib () { return require('../../../datadog-plugin-amqplib/src') },
|
|
47
47
|
get avsc () { return require('../../../datadog-plugin-avsc/src') },
|
|
48
48
|
get 'aws-sdk' () { return require('../../../datadog-plugin-aws-sdk/src') },
|
|
49
|
+
get 'browser-bunyan' () { return require('../../../datadog-plugin-browser-bunyan/src') },
|
|
49
50
|
get bullmq () { return require('../../../datadog-plugin-bullmq/src') },
|
|
50
51
|
get bunyan () { return require('../../../datadog-plugin-bunyan/src') },
|
|
51
52
|
get 'cassandra-driver' () { return require('../../../datadog-plugin-cassandra-driver/src') },
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
const { parse } = require('../../../vendor/dist/jest-docblock')
|
|
5
|
-
|
|
6
|
-
const { getTestSuitePath } = require('../../dd-trace/src/plugins/util/test')
|
|
7
|
-
const log = require('../../dd-trace/src/log')
|
|
3
|
+
const { getTestSuitePath, isMarkedAsUnskippable } = require('./test')
|
|
8
4
|
|
|
9
5
|
/**
|
|
10
6
|
* There are two ways to call `test.each` in `jest`:
|
|
@@ -69,65 +65,11 @@ function getJestTestName (test) {
|
|
|
69
65
|
return removeSeedSuffixFromTestName(getRawJestTestName(test))
|
|
70
66
|
}
|
|
71
67
|
|
|
72
|
-
const globalDocblockRegExp = /^\s*(\/\*\*?(.|\r?\n)*?\*\/)/
|
|
73
|
-
const MAX_COMMENTS_CHECKED = 10
|
|
74
|
-
|
|
75
|
-
function isMarkedAsUnskippable (test) {
|
|
76
|
-
let testSource
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
testSource = readFileSync(test.path, 'utf8')
|
|
80
|
-
} catch {
|
|
81
|
-
return false
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const re = globalDocblockRegExp
|
|
85
|
-
re.lastIndex = 0
|
|
86
|
-
let commentsChecked = 0
|
|
87
|
-
|
|
88
|
-
while (testSource.length) {
|
|
89
|
-
const match = re.exec(testSource)
|
|
90
|
-
if (!match) break
|
|
91
|
-
const comment = match[1]
|
|
92
|
-
|
|
93
|
-
let docblocks
|
|
94
|
-
try {
|
|
95
|
-
docblocks = parse(comment)
|
|
96
|
-
} catch {
|
|
97
|
-
// Skip unparsable comment and continue scanning
|
|
98
|
-
if (commentsChecked++ >= MAX_COMMENTS_CHECKED) {
|
|
99
|
-
return false
|
|
100
|
-
}
|
|
101
|
-
continue
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (docblocks?.datadog) {
|
|
105
|
-
try {
|
|
106
|
-
// @ts-expect-error The datadog type is defined by us and may only be a string.
|
|
107
|
-
return JSON.parse(docblocks.datadog).unskippable
|
|
108
|
-
} catch {
|
|
109
|
-
// If the @datadog block comment is present but malformed, we'll run the suite
|
|
110
|
-
log.warn('@datadog block comment is malformed.')
|
|
111
|
-
return true
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (commentsChecked++ >= MAX_COMMENTS_CHECKED) {
|
|
116
|
-
return false
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// To stop as soon as no doc blocks are found, slice the source. That way the
|
|
120
|
-
// regexp works by using the `^` anchor. Without it, it would continue
|
|
121
|
-
// scanning the rest of the file.
|
|
122
|
-
testSource = testSource.slice(match[0].length)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return false
|
|
126
|
-
}
|
|
127
|
-
|
|
128
68
|
function getJestSuitesToRun (skippableSuites, originalTests, rootDir, fallbackRootDir) {
|
|
129
69
|
const unskippableSuites = {}
|
|
130
70
|
const forcedToRunSuites = {}
|
|
71
|
+
let hasUnskippableSuites = false
|
|
72
|
+
let hasForcedToRunSuites = false
|
|
131
73
|
|
|
132
74
|
const skippedSuites = []
|
|
133
75
|
const suitesToRun = []
|
|
@@ -147,11 +89,13 @@ function getJestSuitesToRun (skippableSuites, originalTests, rootDir, fallbackRo
|
|
|
147
89
|
if (isMarkedAsUnskippable(test)) {
|
|
148
90
|
suitesToRun.push(test)
|
|
149
91
|
unskippableSuites[relativePath] = true
|
|
92
|
+
hasUnskippableSuites = true
|
|
150
93
|
if (fallbackRelativePath !== undefined) {
|
|
151
94
|
unskippableSuites[fallbackRelativePath] = true
|
|
152
95
|
}
|
|
153
96
|
if (skippedSuite !== undefined) {
|
|
154
97
|
forcedToRunSuites[relativePath] = true
|
|
98
|
+
hasForcedToRunSuites = true
|
|
155
99
|
if (fallbackRelativePath !== undefined) {
|
|
156
100
|
forcedToRunSuites[fallbackRelativePath] = true
|
|
157
101
|
}
|
|
@@ -165,9 +109,6 @@ function getJestSuitesToRun (skippableSuites, originalTests, rootDir, fallbackRo
|
|
|
165
109
|
}
|
|
166
110
|
}
|
|
167
111
|
|
|
168
|
-
const hasUnskippableSuites = Object.keys(unskippableSuites).length > 0
|
|
169
|
-
const hasForcedToRunSuites = Object.keys(forcedToRunSuites).length > 0
|
|
170
|
-
|
|
171
112
|
if (originalTests.length) {
|
|
172
113
|
// The config object is shared by all tests, so we can just take the first one
|
|
173
114
|
const [test] = originalTests
|
|
@@ -195,6 +136,5 @@ module.exports = {
|
|
|
195
136
|
getJestTestName,
|
|
196
137
|
getRawJestTestName,
|
|
197
138
|
getJestSuitesToRun,
|
|
198
|
-
isMarkedAsUnskippable,
|
|
199
139
|
removeSeedSuffixFromTestName,
|
|
200
140
|
}
|