dd-trace 6.20.1 → 6.22.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/ci/diagnose.js +9 -14
- package/ci/init.js +1 -2
- package/ci/test-optimization-validation/ci-discovery.js +9 -4
- package/ci/test-optimization-validation/executable.js +1 -5
- package/ci/test-optimization-validation/framework-adapters/cypress.js +1 -4
- package/ci/test-optimization-validation/framework-adapters/vitest.js +1 -1
- package/ci/test-optimization-validation/generated-files.js +6 -4
- package/ci/test-optimization-validation/generated-test-contract.js +7 -3
- package/ci/test-optimization-validation/manifest-scaffold.js +7 -2
- package/ci/test-optimization-validation/manifest-schema.js +6 -7
- package/ci/test-optimization-validation/report-writer.js +6 -2
- package/ci/test-optimization-validation/runner-command.js +2 -4
- package/ci/test-optimization-validation/runner-contract.js +1 -2
- package/ci/test-optimization-validation/scenarios/auto-test-retries.js +1 -15
- package/ci/vitest-no-worker-init-setup.mjs +10 -7
- package/index.d.ts +116 -17
- package/initialize.mjs +1 -3
- package/openfeature/rules-based.d.ts +11 -0
- package/openfeature/rules-based.js +7 -0
- package/package.json +13 -11
- package/packages/datadog-core/src/storage.js +16 -0
- package/packages/datadog-esbuild/index.js +18 -12
- package/packages/datadog-instrumentations/src/aerospike.js +3 -3
- package/packages/datadog-instrumentations/src/ai.js +3 -3
- package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +0 -6
- package/packages/datadog-instrumentations/src/aws-sdk.js +21 -13
- package/packages/datadog-instrumentations/src/cassandra-driver.js +25 -20
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +9 -23
- package/packages/datadog-instrumentations/src/cucumber.js +48 -33
- package/packages/datadog-instrumentations/src/elasticsearch.js +1 -5
- package/packages/datadog-instrumentations/src/electron.js +6 -6
- package/packages/datadog-instrumentations/src/fastify.js +13 -4
- package/packages/datadog-instrumentations/src/fs.js +2 -2
- package/packages/datadog-instrumentations/src/google-cloud-pubsub.js +9 -5
- package/packages/datadog-instrumentations/src/grpc/client.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/bundler-register.js +2 -2
- package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +1 -2
- package/packages/datadog-instrumentations/src/helpers/instrument.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/register.js +36 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +87 -6
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +5 -6
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/postgres.js +48 -31
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +64 -11
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms/postgres.js +42 -4
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +7 -10
- package/packages/datadog-instrumentations/src/ioredis.js +7 -5
- package/packages/datadog-instrumentations/src/iovalkey.js +7 -5
- package/packages/datadog-instrumentations/src/jest.js +122 -11
- package/packages/datadog-instrumentations/src/knex.js +9 -5
- package/packages/datadog-instrumentations/src/mariadb-bundle.js +3 -3
- package/packages/datadog-instrumentations/src/mocha/common.js +20 -8
- package/packages/datadog-instrumentations/src/mocha/main.js +140 -43
- package/packages/datadog-instrumentations/src/mocha/utils.js +127 -38
- package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +61 -34
- package/packages/datadog-instrumentations/src/mocha/worker.js +7 -5
- package/packages/datadog-instrumentations/src/mongodb-core.js +3 -3
- package/packages/datadog-instrumentations/src/mongoose.js +3 -3
- package/packages/datadog-instrumentations/src/nova-sonic/audio.js +173 -0
- package/packages/datadog-instrumentations/src/nova-sonic/index.js +209 -0
- package/packages/datadog-instrumentations/src/nova-sonic/session.js +483 -0
- package/packages/datadog-instrumentations/src/openai-realtime/index.js +18 -6
- package/packages/datadog-instrumentations/src/openai-realtime/session.js +1 -1
- package/packages/datadog-instrumentations/src/openai.js +7 -0
- package/packages/datadog-instrumentations/src/pino.js +5 -6
- package/packages/datadog-instrumentations/src/playwright.js +3 -0
- package/packages/datadog-instrumentations/src/redis.js +27 -13
- package/packages/datadog-instrumentations/src/router.js +30 -0
- package/packages/datadog-instrumentations/src/rum-browser-scripts.js +2 -6
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +7 -5
- package/packages/datadog-instrumentations/src/vitest-main.js +9 -17
- package/packages/datadog-instrumentations/src/vitest-util.js +2 -2
- package/packages/datadog-instrumentations/src/vitest-worker.js +1 -2
- package/packages/datadog-instrumentations/src/vitest.js +2 -2
- package/packages/datadog-instrumentations/src/webdriverio.js +118 -21
- package/packages/datadog-instrumentations/src/ws.js +1 -4
- package/packages/datadog-plugin-amqplib/src/client.js +1 -2
- package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -5
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -2
- package/packages/datadog-plugin-aws-sdk/src/base.js +5 -8
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/index.js +18 -0
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/sonic.js +45 -0
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +9 -3
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +2 -5
- package/packages/datadog-plugin-azure-durable-functions/src/index.js +5 -4
- package/packages/datadog-plugin-azure-functions/src/index.js +5 -3
- package/packages/datadog-plugin-claude-agent-sdk/src/index.js +0 -2
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +18 -5
- package/packages/datadog-plugin-cypress/src/index.js +6 -6
- package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -2
- package/packages/datadog-plugin-cypress/src/support.js +1 -4
- package/packages/datadog-plugin-express/src/code_origin.js +1 -2
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -2
- package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +1 -2
- package/packages/datadog-plugin-graphql/src/execute.js +5 -5
- package/packages/datadog-plugin-grpc/src/util.js +1 -2
- package/packages/datadog-plugin-kafkajs/src/producer.js +1 -2
- package/packages/datadog-plugin-mocha/src/index.js +223 -39
- package/packages/datadog-plugin-mocha/src/webdriverio-video.js +453 -0
- package/packages/datadog-plugin-mongodb-core/src/query.js +8 -5
- package/packages/datadog-plugin-next/src/index.js +1 -2
- package/packages/datadog-plugin-openai/src/tracing.js +5 -2
- package/packages/datadog-plugin-openai-agents/src/integration.js +6 -2
- package/packages/datadog-plugin-openai-agents/src/processor.js +2 -4
- package/packages/datadog-plugin-postgres/src/index.js +24 -0
- package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -5
- package/packages/datadog-plugin-rhea/src/consumer.js +1 -4
- package/packages/datadog-plugin-vitest/src/index.js +5 -2
- package/packages/datadog-shimmer/src/shimmer.js +5 -4
- package/packages/datadog-webpack/index.js +55 -43
- package/packages/datadog-webpack/src/loader.js +56 -8
- package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -4
- package/packages/dd-trace/src/aiguard/messages/utils.js +1 -2
- package/packages/dd-trace/src/aiguard/redaction.js +8 -2
- package/packages/dd-trace/src/appsec/api_security/normalized-route.js +3 -2
- package/packages/dd-trace/src/appsec/api_security/sampler.js +2 -6
- package/packages/dd-trace/src/appsec/downstream_requests.js +1 -9
- package/packages/dd-trace/src/appsec/handlers/http-request.js +3 -9
- package/packages/dd-trace/src/appsec/handlers/http-response.js +7 -2
- package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +4 -3
- package/packages/dd-trace/src/appsec/iast/analyzers/set-cookies-header-interceptor.js +1 -4
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +7 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +1 -3
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +1 -3
- package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +1 -6
- package/packages/dd-trace/src/appsec/lambda.js +1 -3
- package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +37 -27
- package/packages/dd-trace/src/appsec/rasp/lfi.js +9 -5
- package/packages/dd-trace/src/appsec/reporter.js +21 -1
- package/packages/dd-trace/src/appsec/stack_trace.js +1 -2
- package/packages/dd-trace/src/ci-visibility/dynamic-atr-retries.js +3 -6
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +13 -7
- package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +1 -2
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +2 -8
- package/packages/dd-trace/src/ci-visibility/resolve-runfile-path.js +54 -0
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +5 -8
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +2 -53
- package/packages/dd-trace/src/config/config-types.d.ts +2 -2
- package/packages/dd-trace/src/config/defaults.js +5 -5
- package/packages/dd-trace/src/config/generated-config-types.d.ts +8 -0
- package/packages/dd-trace/src/config/helper.js +34 -3
- package/packages/dd-trace/src/config/index.js +10 -9
- package/packages/dd-trace/src/config/normalize-service.js +1 -1
- package/packages/dd-trace/src/config/parsers.js +2 -2
- package/packages/dd-trace/src/config/supported-configurations.json +35 -0
- package/packages/dd-trace/src/datastreams/processor.js +3 -2
- package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +1 -4
- 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 +7 -2
- package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -0
- package/packages/dd-trace/src/debugger/devtools_client/worker-error.js +21 -0
- package/packages/dd-trace/src/debugger/guardrail-metrics.js +2 -2
- 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/dogstatsd.js +1 -1
- package/packages/dd-trace/src/encode/tags-processors.js +2 -2
- package/packages/dd-trace/src/exporters/common/request.js +2 -5
- package/packages/dd-trace/src/exporters/common/url.js +1 -1
- package/packages/dd-trace/src/id.js +15 -5
- package/packages/dd-trace/src/lambda/handler.js +179 -19
- package/packages/dd-trace/src/lambda/index.js +15 -1
- package/packages/dd-trace/src/lambda/runtime/patch.js +34 -7
- package/packages/dd-trace/src/llmobs/agent-manifest.js +296 -0
- package/packages/dd-trace/src/llmobs/audio-utils.js +3 -2
- package/packages/dd-trace/src/llmobs/constants/tags.js +13 -0
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +15 -0
- package/packages/dd-trace/src/llmobs/plugins/genai/util.js +1 -2
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +1 -3
- package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/nova-sonic.js +92 -0
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +56 -2
- package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +38 -0
- package/packages/dd-trace/src/llmobs/prompts/manager.js +5 -7
- package/packages/dd-trace/src/llmobs/prompts/prompt.js +1 -1
- package/packages/dd-trace/src/llmobs/sdk.js +23 -6
- package/packages/dd-trace/src/llmobs/span_processor.js +18 -8
- package/packages/dd-trace/src/llmobs/tagger.js +56 -3
- package/packages/dd-trace/src/llmobs/util.js +1 -2
- package/packages/dd-trace/src/llmobs/writers/base.js +7 -6
- package/packages/dd-trace/src/llmobs/writers/util.js +3 -1
- package/packages/dd-trace/src/log/log.js +1 -5
- 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/evaluation-telemetry-opt-out-hook.js +51 -0
- package/packages/dd-trace/src/openfeature/flagging_provider.js +59 -3
- 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/logs/otlp_transformer.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -2
- package/packages/dd-trace/src/opentelemetry/otlp/common.proto +1 -1
- package/packages/dd-trace/src/opentelemetry/resource-attributes.js +1 -1
- package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +6 -23
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +53 -13
- package/packages/dd-trace/src/otel-sampling.js +4 -5
- package/packages/dd-trace/src/otel-thread-ctx.js +81 -15
- package/packages/dd-trace/src/plugins/database.js +7 -16
- package/packages/dd-trace/src/plugins/util/git.js +9 -25
- package/packages/dd-trace/src/plugins/util/inferred_proxy.js +1 -5
- package/packages/dd-trace/src/plugins/util/test.js +48 -4
- package/packages/dd-trace/src/priority_sampler.js +1 -2
- package/packages/dd-trace/src/profiling/profiler.js +4 -6
- package/packages/dd-trace/src/profiling/profilers/wall.js +4 -10
- package/packages/dd-trace/src/proxy.js +3 -2
- package/packages/dd-trace/src/ritm.js +4 -2
- package/packages/dd-trace/src/serverless.js +10 -0
- package/packages/dd-trace/src/span_processor.js +13 -17
- package/packages/dd-trace/src/storage-channels.js +112 -46
- package/packages/dd-trace/src/telemetry/endpoints.js +1 -3
- package/packages/dd-trace/src/util.js +29 -0
- package/packages/dd-trace/src/web-tags-cache.js +6 -2
- package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
- package/vendor/dist/@datadog/openfeature-node-server/rules-based/LICENSE +201 -0
- package/vendor/dist/@datadog/openfeature-node-server/rules-based/index.d.ts +925 -0
- package/vendor/dist/@datadog/openfeature-node-server/rules-based/index.js +1 -0
- package/vendor/dist/@datadog/sketches-js/index.js +1 -1
- package/vendor/dist/protobufjs/index.js +1 -1
- package/vendor/dist/protobufjs/minimal/index.js +1 -1
- package/vendor/dist/shell-quote/index.js +1 -1
|
@@ -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
|
}
|
|
@@ -17,11 +17,7 @@ class Log {
|
|
|
17
17
|
get formatted () {
|
|
18
18
|
const { message, args } = this
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
if (message && args && args.length) {
|
|
22
|
-
formatted = format(message, ...args)
|
|
23
|
-
}
|
|
24
|
-
return formatted
|
|
20
|
+
return message && args && args.length ? format(message, ...args) : message
|
|
25
21
|
}
|
|
26
22
|
|
|
27
23
|
static parse (...args) {
|
|
@@ -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
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const PARAMETER = 'dd_evaluation_telemetry_opt_out'
|
|
4
|
+
|
|
5
|
+
/** Gates an owned finally-only telemetry hook without changing evaluation or exposure handling. */
|
|
6
|
+
class EvaluationTelemetryOptOutHook {
|
|
7
|
+
#delegate
|
|
8
|
+
#decisions = new WeakMap()
|
|
9
|
+
|
|
10
|
+
/** @param {import('@openfeature/server-sdk').Hook} delegate */
|
|
11
|
+
constructor (delegate) {
|
|
12
|
+
this.#delegate = delegate
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** @param {import('@openfeature/core').HookContext} hookContext */
|
|
16
|
+
before (hookContext) {
|
|
17
|
+
// Hook context identity is stable across stages, including each MultiProvider branch.
|
|
18
|
+
this.#decisions.set(hookContext, isOptedOut(hookContext.context))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** @param {import('@openfeature/core').HookContext} hookContext */
|
|
22
|
+
error (hookContext) {
|
|
23
|
+
// A customer before hook can fail before this hook runs. Capture opt-out before customer error hooks change it.
|
|
24
|
+
if (!this.#decisions.has(hookContext)) {
|
|
25
|
+
this.#decisions.set(hookContext, isOptedOut(hookContext.context))
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {import('@openfeature/core').HookContext} hookContext
|
|
31
|
+
* @param {import('@openfeature/core').EvaluationDetails<import('@openfeature/core').FlagValue>} evaluationDetails
|
|
32
|
+
* @param {import('@openfeature/core').HookHints} [hints]
|
|
33
|
+
*/
|
|
34
|
+
finally (hookContext, evaluationDetails, hints) {
|
|
35
|
+
const optedOut = this.#decisions.get(hookContext) === true || isOptedOut(hookContext.context)
|
|
36
|
+
this.#decisions.delete(hookContext)
|
|
37
|
+
if (!optedOut) return this.#delegate.finally?.(hookContext, evaluationDetails, hints)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** @param {import('@openfeature/core').EvaluationContext} context */
|
|
42
|
+
function isOptedOut (context) {
|
|
43
|
+
try {
|
|
44
|
+
return context?.[PARAMETER] === true
|
|
45
|
+
} catch {
|
|
46
|
+
// An unreadable attribute is not boolean true and must not change the evaluation result.
|
|
47
|
+
return false
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = EvaluationTelemetryOptOutHook
|
|
@@ -4,9 +4,12 @@ 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')
|
|
12
|
+
const EvaluationTelemetryOptOutHook = require('./evaluation-telemetry-opt-out-hook')
|
|
10
13
|
const SpanEnrichmentHook = require('./span-enrichment-hook')
|
|
11
14
|
const FlagEvalEVPHook = require('./writers/flag-eval-evp-hook')
|
|
12
15
|
|
|
@@ -15,6 +18,9 @@ const FlagEvalEVPHook = require('./writers/flag-eval-evp-hook')
|
|
|
15
18
|
* Extends DatadogNodeServerProvider to add tracer integration and configuration management.
|
|
16
19
|
*/
|
|
17
20
|
class FlaggingProvider extends DatadogNodeServerProvider {
|
|
21
|
+
// Track this wrapper's logging lifecycle without reading the upstream provider's private state.
|
|
22
|
+
#waitingForConfiguration = false
|
|
23
|
+
|
|
18
24
|
/** @type {SpanEnrichmentHook | undefined} */
|
|
19
25
|
#spanEnrichmentHook
|
|
20
26
|
|
|
@@ -34,14 +40,25 @@ class FlaggingProvider extends DatadogNodeServerProvider {
|
|
|
34
40
|
initializationTimeoutMs: config.featureFlags.DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS,
|
|
35
41
|
})
|
|
36
42
|
|
|
43
|
+
const enableOptOut =
|
|
44
|
+
config.featureFlags.DD_FEATURE_FLAGS_EVALUATION_TELEMETRY_PER_SUBJECT_OPT_OUT_ENABLED === true
|
|
45
|
+
/** @param {import('@openfeature/server-sdk').Hook} hook */
|
|
46
|
+
const telemetryHook = hook => enableOptOut ? new EvaluationTelemetryOptOutHook(hook) : hook
|
|
47
|
+
|
|
37
48
|
if (config.DD_METRICS_OTEL_ENABLED === true) {
|
|
38
|
-
this.hooks.push(new EvalMetricsHook(config))
|
|
49
|
+
this.hooks.push(telemetryHook(new EvalMetricsHook(config)))
|
|
50
|
+
} else {
|
|
51
|
+
log.debug('Feature Flags: evaluation metrics disabled; set %s=true to enable', 'DD_METRICS_OTEL_ENABLED')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (config.debug && debugChannel.hasSubscribers) {
|
|
55
|
+
this.hooks.push(new DebugLoggingHook())
|
|
39
56
|
}
|
|
40
57
|
|
|
41
58
|
if (config.featureFlags.DD_EXPERIMENTAL_FLAGGING_PROVIDER_SPAN_ENRICHMENT_ENABLED) {
|
|
42
59
|
this.#spanEnrichmentHook = new SpanEnrichmentHook(tracer)
|
|
43
60
|
// @ts-expect-error The upstream constructor always initializes its optional hooks property.
|
|
44
|
-
this.hooks.push(this.#spanEnrichmentHook)
|
|
61
|
+
this.hooks.push(telemetryHook(this.#spanEnrichmentHook))
|
|
45
62
|
log.info('%s span enrichment enabled', this.constructor.name)
|
|
46
63
|
} else {
|
|
47
64
|
log.info('%s span enrichment disabled', this.constructor.name)
|
|
@@ -52,7 +69,7 @@ class FlaggingProvider extends DatadogNodeServerProvider {
|
|
|
52
69
|
|
|
53
70
|
if (config.featureFlags?.DD_FLAGGING_EVALUATION_COUNTS_ENABLED !== false) {
|
|
54
71
|
this.#flagEvalEVPHook = new FlagEvalEVPHook(config)
|
|
55
|
-
this.hooks.push(this.#flagEvalEVPHook)
|
|
72
|
+
this.hooks.push(telemetryHook(this.#flagEvalEVPHook))
|
|
56
73
|
}
|
|
57
74
|
|
|
58
75
|
this.#configurationSource = configurationSource.create(config, this.setConfiguration.bind(this))
|
|
@@ -64,6 +81,9 @@ class FlaggingProvider extends DatadogNodeServerProvider {
|
|
|
64
81
|
* @returns {Promise<void>}
|
|
65
82
|
*/
|
|
66
83
|
initialize (context) {
|
|
84
|
+
log.debug('Feature Flags: waiting for provider initialization...')
|
|
85
|
+
|
|
86
|
+
this.#waitingForConfiguration = !this.getConfiguration()
|
|
67
87
|
const promise = super.initialize(context)
|
|
68
88
|
|
|
69
89
|
// `DatadogNodeServerProvider#initialize` starts a timer that is never unref'd, which would
|
|
@@ -72,9 +92,45 @@ class FlaggingProvider extends DatadogNodeServerProvider {
|
|
|
72
92
|
// TODO: remove once `@datadog/openfeature-node-server` unrefs this timer itself.
|
|
73
93
|
this.initController?.timeoutId?.unref?.()
|
|
74
94
|
|
|
95
|
+
if (this.getConfiguration()) this.#logInitialConfiguration()
|
|
96
|
+
|
|
75
97
|
return promise
|
|
76
98
|
}
|
|
77
99
|
|
|
100
|
+
/**
|
|
101
|
+
* @param {Parameters<DatadogNodeServerProvider['setConfiguration']>[0]} configuration
|
|
102
|
+
*/
|
|
103
|
+
setConfiguration (configuration) {
|
|
104
|
+
const wasWaiting = this.#waitingForConfiguration
|
|
105
|
+
super.setConfiguration(configuration)
|
|
106
|
+
this.#waitingForConfiguration = false
|
|
107
|
+
if (wasWaiting && configuration != null) this.#logInitialConfiguration()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* @param {unknown} error
|
|
112
|
+
*/
|
|
113
|
+
setError (error) {
|
|
114
|
+
const wasWaiting = this.#waitingForConfiguration
|
|
115
|
+
super.setError(error)
|
|
116
|
+
this.#waitingForConfiguration = false
|
|
117
|
+
if (!wasWaiting) return
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
// Avoid inflating telemetry volume for routine initialization timeouts.
|
|
121
|
+
log.errorWithoutTelemetry(
|
|
122
|
+
'Feature Flags: provider failed to initialize: %s',
|
|
123
|
+
error instanceof Error ? error.message : String(error)
|
|
124
|
+
)
|
|
125
|
+
} catch { /* logging failure must not affect initialization */ }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
#logInitialConfiguration () {
|
|
129
|
+
try {
|
|
130
|
+
log.debug('Feature Flags: initial configuration available')
|
|
131
|
+
} catch { /* logging failure must not affect initialization */ }
|
|
132
|
+
}
|
|
133
|
+
|
|
78
134
|
/**
|
|
79
135
|
* Called when the provider is shut down.
|
|
80
136
|
* Cleans up resources including channel subscriptions.
|
|
@@ -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 = {
|
|
@@ -52,7 +52,7 @@ function encodeHrTime (hrTime) {
|
|
|
52
52
|
* OtlpTransformer transforms log records to OTLP format.
|
|
53
53
|
*
|
|
54
54
|
* This implementation follows the OTLP Logs v1.7.0 Data Model specification:
|
|
55
|
-
* https://opentelemetry.
|
|
55
|
+
* https://github.com/open-telemetry/opentelemetry-proto/blob/v1.7.0/opentelemetry/proto/logs/v1/logs.proto
|
|
56
56
|
*
|
|
57
57
|
* @class OtlpTransformer
|
|
58
58
|
* @augments OtlpTransformerBase
|
|
@@ -18,7 +18,7 @@ const AGGREGATION_TEMPORALITY_CUMULATIVE = protoAggregationTemporality.values.AG
|
|
|
18
18
|
* OtlpTransformer transforms metrics to OTLP format.
|
|
19
19
|
*
|
|
20
20
|
* This implementation follows the OTLP Metrics v1.7.0 Data Model specification:
|
|
21
|
-
* https://opentelemetry.
|
|
21
|
+
* https://github.com/open-telemetry/opentelemetry-proto/blob/v1.7.0/opentelemetry/proto/metrics/v1/metrics.proto
|
|
22
22
|
*
|
|
23
23
|
* @class OtlpTransformer
|
|
24
24
|
* @augments OtlpTransformerBase
|
|
@@ -162,8 +162,7 @@ class PeriodicMetricReader {
|
|
|
162
162
|
addBatchObservableCallback (callback, observables) {
|
|
163
163
|
if (typeof callback !== 'function') return
|
|
164
164
|
const instruments = new Set(observables?.filter(isObservableInstrument))
|
|
165
|
-
if (instruments.size === 0) return
|
|
166
|
-
if (this.#findBatchCallback(callback, instruments) !== -1) return
|
|
165
|
+
if (instruments.size === 0 || this.#findBatchCallback(callback, instruments) !== -1) return
|
|
167
166
|
this.#batchCallbacks.push({ callback, instruments })
|
|
168
167
|
}
|
|
169
168
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Vendored from: https://github.com/open-telemetry/opentelemetry-proto/blob/v1.7.0/opentelemetry/proto/
|
|
1
|
+
// Vendored from: https://github.com/open-telemetry/opentelemetry-proto/blob/v1.7.0/opentelemetry/proto/common/v1/common.proto
|
|
2
2
|
// Copyright 2019, OpenTelemetry Authors
|
|
3
3
|
//
|
|
4
4
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -84,7 +84,7 @@ const DD_ERROR_META_KEYS = new Set(['error.message'])
|
|
|
84
84
|
* OtlpTraceTransformer transforms DD-formatted spans to OTLP trace JSON format.
|
|
85
85
|
*
|
|
86
86
|
* This implementation follows the OTLP trace data model:
|
|
87
|
-
* https://opentelemetry.
|
|
87
|
+
* https://github.com/open-telemetry/opentelemetry-proto/blob/v1.7.0/opentelemetry/proto/trace/v1/trace.proto
|
|
88
88
|
*
|
|
89
89
|
* It receives DD-formatted spans (from span_format.js) and produces
|
|
90
90
|
* an ExportTraceServiceRequest serialized as JSON (http/json protocol only).
|
|
@@ -168,8 +168,8 @@ class OtlpTraceTransformer extends OtlpTransformerBase {
|
|
|
168
168
|
|
|
169
169
|
return {
|
|
170
170
|
traceId: span.trace_id.toTraceIdHex(traceIdHigh).padStart(32, '0'),
|
|
171
|
-
spanId: this.#
|
|
172
|
-
parentSpanId:
|
|
171
|
+
spanId: this.#spanIdToHex(span.span_id),
|
|
172
|
+
parentSpanId: parentId && !parentId.equals(ZERO_ID) ? this.#spanIdToHex(parentId) : undefined,
|
|
173
173
|
traceState: span.trace_state,
|
|
174
174
|
name: span.resource,
|
|
175
175
|
kind: this.#mapSpanKind(span.meta?.['span.kind']),
|
|
@@ -338,26 +338,9 @@ class OtlpTraceTransformer extends OtlpTransformerBase {
|
|
|
338
338
|
}
|
|
339
339
|
}
|
|
340
340
|
|
|
341
|
-
/**
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
* Per the OTLP http/json spec, trace-ids and span-ids must be hex-encoded strings.
|
|
345
|
-
*
|
|
346
|
-
* @param {Identifier} identifier - DD Identifier
|
|
347
|
-
* @param {number} targetLength - Target byte length (16 for trace ID, 8 for span ID)
|
|
348
|
-
*/
|
|
349
|
-
#idToBytes (identifier, targetLength) {
|
|
350
|
-
const buffer = identifier.toBuffer()
|
|
351
|
-
if (buffer.length === targetLength) {
|
|
352
|
-
return Buffer.from(buffer).toString('hex')
|
|
353
|
-
}
|
|
354
|
-
if (buffer.length > targetLength) {
|
|
355
|
-
return Buffer.from(buffer.slice(buffer.length - targetLength)).toString('hex')
|
|
356
|
-
}
|
|
357
|
-
// Pad with leading zeros to reach target length.
|
|
358
|
-
const result = Buffer.alloc(targetLength)
|
|
359
|
-
Buffer.from(buffer).copy(result, targetLength - buffer.length)
|
|
360
|
-
return result.toString('hex')
|
|
341
|
+
/** @param {Identifier} identifier */
|
|
342
|
+
#spanIdToHex (identifier) {
|
|
343
|
+
return identifier.toString(16).slice(-16)
|
|
361
344
|
}
|
|
362
345
|
|
|
363
346
|
/**
|
|
@@ -38,7 +38,7 @@ const id = require('../../id')
|
|
|
38
38
|
const DatadogSpanContext = require('../span_context')
|
|
39
39
|
const log = require('../../log')
|
|
40
40
|
const tags = require('../../../../../ext/tags')
|
|
41
|
-
const {
|
|
41
|
+
const { getValidatedConfiguredEnvName } = require('../../config/helper')
|
|
42
42
|
const { setAllBaggageItems, getAllBaggageItems, removeAllBaggageItems } = require('../../baggage')
|
|
43
43
|
const { hasTraceSourcePropagationTag } = require('../../standalone/tracesource')
|
|
44
44
|
const telemetryMetrics = require('../../telemetry/metrics')
|
|
@@ -134,8 +134,7 @@ function isOptionalDatadogTraceStateField (key, injection) {
|
|
|
134
134
|
* @returns {DatadogSpanContext | undefined}
|
|
135
135
|
*/
|
|
136
136
|
function extractGenericContext (traceId, spanId, radix) {
|
|
137
|
-
if (!traceId || invalidSegment.test(traceId)) return
|
|
138
|
-
if (!spanId) return
|
|
137
|
+
if (!traceId || invalidSegment.test(traceId) || !spanId) return
|
|
139
138
|
|
|
140
139
|
return new DatadogSpanContext({
|
|
141
140
|
traceId: id(traceId, radix),
|
|
@@ -305,13 +304,25 @@ class TextMapPropagator {
|
|
|
305
304
|
/** @type {string[] | undefined} Source array that `#baggageTagKeysSet` was built from. */
|
|
306
305
|
#baggageTagKeysSetSource
|
|
307
306
|
|
|
308
|
-
/** @type {import('../../config')} */
|
|
307
|
+
/** @type {ReturnType<import('../../config')>} */
|
|
309
308
|
#config
|
|
310
309
|
|
|
311
310
|
/** @type {typeof extractB3SingleContext | typeof extractB3MultiContext} */
|
|
312
311
|
#extractB3Context
|
|
313
312
|
|
|
314
|
-
/**
|
|
313
|
+
/** Whether the `b3` injection style resolves to single-header propagation. */
|
|
314
|
+
#b3InjectIsSingleHeader
|
|
315
|
+
|
|
316
|
+
/** Cached origin used to resolve the v5 `b3` injection style. */
|
|
317
|
+
#b3InjectOrigin
|
|
318
|
+
|
|
319
|
+
/** Cached state of the v5 experimental B3 setting. */
|
|
320
|
+
#b3ExperimentalEnabled
|
|
321
|
+
|
|
322
|
+
/** Cached origin of the shared v5 propagation style. */
|
|
323
|
+
#b3SharedOrigin
|
|
324
|
+
|
|
325
|
+
/** @param {ReturnType<import('../../config')>} config */
|
|
315
326
|
constructor (config) {
|
|
316
327
|
this.#config = config
|
|
317
328
|
|
|
@@ -319,16 +330,44 @@ class TextMapPropagator {
|
|
|
319
330
|
// expect single, legacy `DD_TRACE_PROPAGATION_STYLE` callers expect multi.
|
|
320
331
|
/* istanbul ignore else: v5 fallback */
|
|
321
332
|
if (DD_MAJOR >= 6) {
|
|
333
|
+
this.#b3InjectIsSingleHeader = true
|
|
322
334
|
this.#extractB3Context = extractB3SingleContext
|
|
323
335
|
} else {
|
|
324
|
-
const envName =
|
|
336
|
+
const envName = getValidatedConfiguredEnvName('DD_TRACE_PROPAGATION_STYLE')
|
|
325
337
|
// eslint-disable-next-line eslint-rules/eslint-env-aliases
|
|
326
338
|
this.#extractB3Context = envName === 'OTEL_PROPAGATORS'
|
|
327
339
|
? extractB3SingleContext
|
|
328
340
|
: extractB3MultiContext
|
|
341
|
+
|
|
342
|
+
this.#refreshB3InjectionStyle()
|
|
329
343
|
}
|
|
330
344
|
}
|
|
331
345
|
|
|
346
|
+
#refreshB3InjectionStyle () {
|
|
347
|
+
const origin = this.#config.getOrigin?.('tracePropagationStyle.inject') ?? 'default'
|
|
348
|
+
const experimentalEnabled = this.#config.experimental?.b3 === true
|
|
349
|
+
const sharedOrigin = origin === 'calculated'
|
|
350
|
+
? this.#config.getOrigin?.('DD_TRACE_PROPAGATION_STYLE') ?? 'default'
|
|
351
|
+
: undefined
|
|
352
|
+
if (origin === this.#b3InjectOrigin &&
|
|
353
|
+
experimentalEnabled === this.#b3ExperimentalEnabled &&
|
|
354
|
+
sharedOrigin === this.#b3SharedOrigin) return
|
|
355
|
+
|
|
356
|
+
let envName
|
|
357
|
+
if (origin === 'calculated' && !experimentalEnabled && sharedOrigin !== 'remote_config') {
|
|
358
|
+
envName = getValidatedConfiguredEnvName('DD_TRACE_PROPAGATION_STYLE')
|
|
359
|
+
} else if (origin === 'env_var' ||
|
|
360
|
+
origin === 'local_stable_config' ||
|
|
361
|
+
origin === 'fleet_stable_config') {
|
|
362
|
+
envName = getValidatedConfiguredEnvName('DD_TRACE_PROPAGATION_STYLE_INJECT')
|
|
363
|
+
}
|
|
364
|
+
// eslint-disable-next-line eslint-rules/eslint-env-aliases
|
|
365
|
+
this.#b3InjectIsSingleHeader = envName === 'OTEL_PROPAGATORS'
|
|
366
|
+
this.#b3InjectOrigin = origin
|
|
367
|
+
this.#b3ExperimentalEnabled = experimentalEnabled
|
|
368
|
+
this.#b3SharedOrigin = sharedOrigin
|
|
369
|
+
}
|
|
370
|
+
|
|
332
371
|
/**
|
|
333
372
|
* Returns a `Set` view of `#config.baggageTagKeys` that is rebuilt only
|
|
334
373
|
* when the source array reference changes. Avoids an `O(n)` `Set` alloc
|
|
@@ -574,9 +613,8 @@ class TextMapPropagator {
|
|
|
574
613
|
* @returns {Record<string, string> | undefined}
|
|
575
614
|
*/
|
|
576
615
|
#injectB3MultipleHeaders (spanContext, carrier) {
|
|
577
|
-
// v5 also accepts the legacy `'b3'` spelling for multi; v6+ routes `'b3'` to single-header.
|
|
578
616
|
const hasB3multi = this.#hasPropagationStyle('inject', 'b3multi') ||
|
|
579
|
-
(
|
|
617
|
+
(this.#hasPropagationStyle('inject', 'b3') && !this.#usesB3SingleHeaderInjection())
|
|
580
618
|
if (!hasB3multi) return
|
|
581
619
|
|
|
582
620
|
carrier ??= {}
|
|
@@ -603,7 +641,7 @@ class TextMapPropagator {
|
|
|
603
641
|
#injectB3SingleHeader (spanContext, carrier) {
|
|
604
642
|
// v6+ keeps `'b3 single header'` as a back-compat alias for callers that bypass parser normalisation.
|
|
605
643
|
const hasB3SingleHeader = this.#hasPropagationStyle('inject', 'b3 single header') ||
|
|
606
|
-
(
|
|
644
|
+
(this.#hasPropagationStyle('inject', 'b3') && this.#usesB3SingleHeaderInjection())
|
|
607
645
|
if (!hasB3SingleHeader) return
|
|
608
646
|
|
|
609
647
|
carrier ??= {}
|
|
@@ -620,6 +658,11 @@ class TextMapPropagator {
|
|
|
620
658
|
return carrier
|
|
621
659
|
}
|
|
622
660
|
|
|
661
|
+
#usesB3SingleHeaderInjection () {
|
|
662
|
+
if (DD_MAJOR < 6) this.#refreshB3InjectionStyle()
|
|
663
|
+
return this.#b3InjectIsSingleHeader
|
|
664
|
+
}
|
|
665
|
+
|
|
623
666
|
/**
|
|
624
667
|
* @param {DatadogSpanContext} spanContext
|
|
625
668
|
* @param {Record<string, string> | undefined} carrier
|
|
@@ -982,11 +1025,8 @@ class TextMapPropagator {
|
|
|
982
1025
|
const traceparent = { version }
|
|
983
1026
|
// W3C Trace Context §3.3.1.1: multiple tracestate fields MUST be combined per RFC 7230 §3.2.2.
|
|
984
1027
|
const tracestate = TraceState.fromString(readTracestate(carrier))
|
|
985
|
-
if (invalidSegment.test(traceId)) return
|
|
986
|
-
if (invalidSegment.test(spanId)) return
|
|
987
|
-
|
|
988
1028
|
// Version ff is considered invalid
|
|
989
|
-
if (version === 'ff') return
|
|
1029
|
+
if (invalidSegment.test(traceId) || invalidSegment.test(spanId) || version === 'ff') return
|
|
990
1030
|
|
|
991
1031
|
// Version 00 should have no tail, but future versions may
|
|
992
1032
|
if (tail && version === '00') return
|