dd-trace 6.3.0 → 6.5.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.electron.md +7 -0
- package/README.md +17 -0
- package/ci/diagnose.js +2100 -0
- package/ci/init.js +23 -2
- package/ci/runbook.md +198 -0
- package/ci/test-optimization-validation/approval-artifacts.js +143 -0
- package/ci/test-optimization-validation/approval.js +299 -0
- package/ci/test-optimization-validation/artifact-id.js +20 -0
- package/ci/test-optimization-validation/bounded-json.js +121 -0
- package/ci/test-optimization-validation/ci-command-candidate.js +83 -0
- package/ci/test-optimization-validation/ci-discovery.js +181 -0
- package/ci/test-optimization-validation/ci-remediation.js +210 -0
- package/ci/test-optimization-validation/cli.js +903 -0
- package/ci/test-optimization-validation/command-blocker.js +81 -0
- package/ci/test-optimization-validation/command-output-policy.js +206 -0
- package/ci/test-optimization-validation/command-runner.js +707 -0
- package/ci/test-optimization-validation/command-suitability.js +471 -0
- package/ci/test-optimization-validation/executable-approval.js +48 -0
- package/ci/test-optimization-validation/executable.js +480 -0
- package/ci/test-optimization-validation/generated-file-policy.js +63 -0
- package/ci/test-optimization-validation/generated-files.js +351 -0
- package/ci/test-optimization-validation/generated-verifier.js +196 -0
- package/ci/test-optimization-validation/init-probe-preload.js +163 -0
- package/ci/test-optimization-validation/init-probe.js +246 -0
- package/ci/test-optimization-validation/late-initialization.js +63 -0
- package/ci/test-optimization-validation/local-command.js +163 -0
- package/ci/test-optimization-validation/manifest-loader.js +133 -0
- package/ci/test-optimization-validation/manifest-scaffold.js +738 -0
- package/ci/test-optimization-validation/manifest-schema.js +862 -0
- package/ci/test-optimization-validation/offline-fixtures.js +327 -0
- package/ci/test-optimization-validation/offline-output.js +406 -0
- package/ci/test-optimization-validation/payload-normalizer.js +72 -0
- package/ci/test-optimization-validation/plan-writer.js +1120 -0
- package/ci/test-optimization-validation/preflight-runner.js +114 -0
- package/ci/test-optimization-validation/redaction.js +331 -0
- package/ci/test-optimization-validation/report-writer.js +1508 -0
- package/ci/test-optimization-validation/safe-files.js +203 -0
- package/ci/test-optimization-validation/scenarios/auto-test-retries.js +159 -0
- package/ci/test-optimization-validation/scenarios/basic-reporting.js +657 -0
- package/ci/test-optimization-validation/scenarios/ci-wiring.js +780 -0
- package/ci/test-optimization-validation/scenarios/early-flake-detection.js +141 -0
- package/ci/test-optimization-validation/scenarios/helpers.js +539 -0
- package/ci/test-optimization-validation/scenarios/test-management.js +218 -0
- package/ci/test-optimization-validation/setup-runner.js +97 -0
- package/ci/test-optimization-validation/static-diagnosis.js +159 -0
- package/ci/test-optimization-validation/test-output.js +129 -0
- package/ci/test-optimization-validation-manifest.schema.json +1 -0
- package/ci/validate-test-optimization.js +3 -0
- package/ext/exporters.js +1 -0
- package/index.d.ts +121 -8
- package/index.electron.js +3 -0
- package/init.js +18 -0
- package/initialize.mjs +1 -1
- package/loader-hook.mjs +28 -18
- package/openfeature.d.ts +1 -0
- package/openfeature.js +4 -0
- package/package.json +15 -8
- package/packages/datadog-instrumentations/src/ai.js +37 -26
- package/packages/datadog-instrumentations/src/child_process.js +1 -1
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +1 -1
- package/packages/datadog-instrumentations/src/cucumber-worker-threads.js +9 -3
- package/packages/datadog-instrumentations/src/cucumber.js +24 -10
- package/packages/datadog-instrumentations/src/cypress-config.js +398 -52
- package/packages/datadog-instrumentations/src/express.js +20 -2
- package/packages/datadog-instrumentations/src/fastify.js +7 -11
- package/packages/datadog-instrumentations/src/grpc/server.js +18 -0
- package/packages/datadog-instrumentations/src/helpers/hook.js +27 -6
- package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +8 -8
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +28 -2
- package/packages/datadog-instrumentations/src/helpers/router-helper.js +8 -8
- package/packages/datadog-instrumentations/src/http2/server.js +143 -17
- package/packages/datadog-instrumentations/src/jest.js +36 -4
- package/packages/datadog-instrumentations/src/mariadb.js +1 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +0 -2
- package/packages/datadog-instrumentations/src/mocha/utils.js +0 -4
- package/packages/datadog-instrumentations/src/mongodb.js +3 -3
- package/packages/datadog-instrumentations/src/mongoose.js +3 -3
- package/packages/datadog-instrumentations/src/mquery.js +2 -2
- package/packages/datadog-instrumentations/src/mysql.js +1 -1
- package/packages/datadog-instrumentations/src/next.js +24 -0
- package/packages/datadog-instrumentations/src/nyc.js +0 -2
- package/packages/datadog-instrumentations/src/oracledb.js +2 -2
- package/packages/datadog-instrumentations/src/pg.js +2 -2
- package/packages/datadog-instrumentations/src/playwright.js +159 -22
- package/packages/datadog-instrumentations/src/process.js +3 -0
- package/packages/datadog-instrumentations/src/router.js +191 -106
- package/packages/datadog-instrumentations/src/selenium.js +52 -26
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +2 -2
- package/packages/datadog-instrumentations/src/vitest-main.js +0 -2
- package/packages/datadog-instrumentations/src/ws.js +2 -1
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +2 -2
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +2 -1
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +41 -16
- package/packages/datadog-plugin-aws-sdk/src/services/sns.js +21 -21
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +14 -8
- package/packages/datadog-plugin-aws-sdk/src/util.js +2 -24
- package/packages/datadog-plugin-cucumber/src/index.js +1 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +24 -62
- package/packages/datadog-plugin-cypress/src/index.js +6 -1
- package/packages/datadog-plugin-cypress/src/support.js +92 -26
- package/packages/datadog-plugin-electron/src/ipc.js +1 -1
- package/packages/datadog-plugin-graphql/src/execute.js +177 -59
- package/packages/datadog-plugin-graphql/src/parse.js +22 -1
- package/packages/datadog-plugin-graphql/src/request.js +32 -1
- package/packages/datadog-plugin-graphql/src/utils.js +42 -0
- package/packages/datadog-plugin-graphql/src/validate.js +15 -3
- package/packages/datadog-plugin-http2/src/client.js +1 -1
- package/packages/datadog-plugin-http2/src/server.js +38 -6
- package/packages/datadog-plugin-jest/src/util.js +21 -6
- package/packages/datadog-plugin-langchain/src/handlers/embedding.js +2 -1
- package/packages/datadog-plugin-langchain/src/handlers/language_models.js +2 -1
- package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
- package/packages/datadog-plugin-playwright/src/index.js +177 -64
- package/packages/datadog-plugin-router/src/index.js +11 -2
- package/packages/datadog-plugin-selenium/src/index.js +5 -36
- package/packages/datadog-plugin-vitest/src/index.js +4 -2
- package/packages/datadog-plugin-ws/src/close.js +2 -1
- package/packages/datadog-plugin-ws/src/producer.js +2 -1
- package/packages/datadog-plugin-ws/src/receiver.js +2 -1
- package/packages/datadog-plugin-ws/src/server.js +2 -1
- package/packages/dd-trace/index.electron.js +3 -0
- package/packages/dd-trace/index.js +2 -37
- package/packages/dd-trace/src/aiguard/sdk.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +2 -2
- package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +3 -1
- package/packages/dd-trace/src/azure_metadata.js +2 -1
- package/packages/dd-trace/src/bootstrap.js +39 -0
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +93 -28
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/index.js +160 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +288 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/payload-projection.js +84 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +369 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +60 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +55 -5
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +22 -6
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +5 -0
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +62 -7
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +9 -2
- package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +6 -2
- package/packages/dd-trace/src/ci-visibility/rum.js +7 -0
- package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +4 -0
- package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +1 -1
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +137 -9
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +173 -9
- package/packages/dd-trace/src/ci-visibility/test-screenshot.js +90 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +20 -0
- package/packages/dd-trace/src/config/helper.js +1 -0
- package/packages/dd-trace/src/config/index.js +10 -0
- package/packages/dd-trace/src/config/supported-configurations.json +83 -0
- package/packages/dd-trace/src/constants.js +7 -0
- package/packages/dd-trace/src/datastreams/pathway.js +6 -4
- package/packages/dd-trace/src/exporter.js +2 -0
- package/packages/dd-trace/src/exporters/common/client-library-headers.js +21 -0
- package/packages/dd-trace/src/exporters/common/request.js +59 -30
- package/packages/dd-trace/src/exporters/common/url.js +15 -1
- package/packages/dd-trace/src/feature-registry.js +29 -0
- package/packages/dd-trace/src/llmobs/constants/tags.js +1 -0
- package/packages/dd-trace/src/llmobs/experiments/client.js +113 -0
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +154 -0
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +283 -0
- package/packages/dd-trace/src/llmobs/experiments/index.js +152 -0
- package/packages/dd-trace/src/llmobs/experiments/noop.js +30 -0
- package/packages/dd-trace/src/llmobs/experiments/result.js +34 -0
- package/packages/dd-trace/src/llmobs/index.js +10 -2
- package/packages/dd-trace/src/llmobs/noop.js +6 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +20 -3
- package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +2 -2
- package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/openai/constants.js +8 -0
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +48 -14
- package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +43 -0
- package/packages/dd-trace/src/llmobs/sdk.js +17 -0
- package/packages/dd-trace/src/llmobs/tagger.js +79 -7
- package/packages/dd-trace/src/llmobs/telemetry.js +2 -1
- package/packages/dd-trace/src/llmobs/util.js +32 -0
- package/packages/dd-trace/src/noop/proxy.js +5 -4
- package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +322 -0
- package/packages/dd-trace/src/openfeature/configuration_source.js +90 -0
- package/packages/dd-trace/src/openfeature/flagging_provider.js +14 -23
- package/packages/dd-trace/src/openfeature/index.js +0 -2
- package/packages/dd-trace/src/openfeature/noop.js +1 -28
- package/packages/dd-trace/src/openfeature/register.js +35 -0
- package/packages/dd-trace/src/openfeature/remote_config.js +15 -18
- package/packages/dd-trace/src/openfeature/require-provider.js +18 -0
- package/packages/dd-trace/src/opentelemetry/span-ending-hook.js +10 -0
- package/packages/dd-trace/src/opentelemetry/span.js +5 -0
- package/packages/dd-trace/src/opentracing/propagation/log.js +11 -2
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +80 -14
- package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +10 -1
- package/packages/dd-trace/src/opentracing/tracer.js +7 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +20 -6
- package/packages/dd-trace/src/plugins/util/git.js +3 -2
- package/packages/dd-trace/src/plugins/util/inferred_proxy.js +29 -5
- package/packages/dd-trace/src/plugins/util/test.js +65 -8
- package/packages/dd-trace/src/plugins/util/web.js +12 -0
- package/packages/dd-trace/src/proxy.js +99 -10
- package/packages/dd-trace/src/span_processor.js +5 -0
- package/packages/dd-trace/src/standalone/index.js +0 -36
- package/packages/dd-trace/src/telemetry/send-data.js +2 -2
- package/packages/dd-trace/src/util.js +26 -0
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
- 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
- package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +0 -17
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/* eslint-disable no-await-in-loop -- Polls and retries must remain sequential. */
|
|
4
|
+
|
|
5
|
+
const { setTimeout: sleep } = require('node:timers/promises')
|
|
6
|
+
|
|
7
|
+
const request = require('../exporters/common/request')
|
|
8
|
+
const { getClientLibraryHeaders } = require('../exporters/common/client-library-headers')
|
|
9
|
+
const log = require('../log')
|
|
10
|
+
|
|
11
|
+
const MAX_ATTEMPTS = 3
|
|
12
|
+
const FIRST_RETRY_MIN_MS = 2000
|
|
13
|
+
const FIRST_RETRY_MAX_MS = 10_000
|
|
14
|
+
const SECOND_RETRY_MIN_MS = 5000
|
|
15
|
+
const SECOND_RETRY_MAX_MS = 30_000
|
|
16
|
+
const RETRY_JITTER = 0.2
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {object} AgentlessSourceConfig
|
|
20
|
+
* @property {URL} endpoint
|
|
21
|
+
* @property {number} pollIntervalMs
|
|
22
|
+
* @property {number} requestTimeoutMs
|
|
23
|
+
* @property {string} apiKey
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {import('@datadog/openfeature-node-server').UniversalFlagConfigurationV1} UniversalFlagConfiguration
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {object} PollResponse
|
|
32
|
+
* @property {Error | null | undefined} error
|
|
33
|
+
* @property {string | undefined} body
|
|
34
|
+
* @property {number | undefined} statusCode
|
|
35
|
+
* @property {import('node:http').IncomingHttpHeaders | undefined} headers
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
class AgentlessConfigurationSource {
|
|
39
|
+
/** @type {AbortController | undefined} */
|
|
40
|
+
#abortController
|
|
41
|
+
|
|
42
|
+
/** @type {(configuration: UniversalFlagConfiguration) => void} */
|
|
43
|
+
#applyConfiguration
|
|
44
|
+
|
|
45
|
+
#applicationFailureLogged = false
|
|
46
|
+
|
|
47
|
+
/** @type {AgentlessSourceConfig} */
|
|
48
|
+
#config
|
|
49
|
+
|
|
50
|
+
/** @type {string | undefined} */
|
|
51
|
+
#etag
|
|
52
|
+
|
|
53
|
+
/** @type {Set<string>} */
|
|
54
|
+
#failureWarnings = new Set()
|
|
55
|
+
|
|
56
|
+
#malformedPayloadLogged = false
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @param {AgentlessSourceConfig} config
|
|
60
|
+
* @param {(configuration: UniversalFlagConfiguration) => void} applyConfiguration
|
|
61
|
+
*/
|
|
62
|
+
constructor (config, applyConfiguration) {
|
|
63
|
+
this.#config = config
|
|
64
|
+
this.#applyConfiguration = applyConfiguration
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @returns {void}
|
|
69
|
+
*/
|
|
70
|
+
start () {
|
|
71
|
+
if (this.#abortController) return
|
|
72
|
+
|
|
73
|
+
const abortController = new AbortController()
|
|
74
|
+
this.#abortController = abortController
|
|
75
|
+
this.#poll(abortController)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @returns {void}
|
|
80
|
+
*/
|
|
81
|
+
stop () {
|
|
82
|
+
this.#abortController?.abort()
|
|
83
|
+
this.#abortController = undefined
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @param {AbortController} abortController
|
|
88
|
+
* @returns {Promise<void>}
|
|
89
|
+
*/
|
|
90
|
+
async #poll (abortController) {
|
|
91
|
+
try {
|
|
92
|
+
do {
|
|
93
|
+
await this.#pollOnce(abortController)
|
|
94
|
+
} while (
|
|
95
|
+
this.#abortController === abortController &&
|
|
96
|
+
await wait(this.#config.pollIntervalMs, abortController.signal)
|
|
97
|
+
)
|
|
98
|
+
} catch (error) {
|
|
99
|
+
if (this.#abortController === abortController) {
|
|
100
|
+
this.#warnFailure(undefined, error, 1)
|
|
101
|
+
}
|
|
102
|
+
} finally {
|
|
103
|
+
if (this.#abortController === abortController) {
|
|
104
|
+
this.#abortController = undefined
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @param {AbortController} abortController
|
|
111
|
+
* @returns {Promise<void>}
|
|
112
|
+
*/
|
|
113
|
+
async #pollOnce (abortController) {
|
|
114
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
115
|
+
const response = await this.#request(abortController.signal)
|
|
116
|
+
if (this.#abortController !== abortController) return
|
|
117
|
+
|
|
118
|
+
const retryable = response.statusCode === undefined || isRetryableStatus(response.statusCode)
|
|
119
|
+
if (!retryable) {
|
|
120
|
+
this.#apply(response)
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (attempt === MAX_ATTEMPTS) {
|
|
125
|
+
this.#warnFailure(response.statusCode, response.error, attempt)
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const delay = retryDelay(this.#config.pollIntervalMs, attempt, Math.random())
|
|
130
|
+
if (!await wait(delay, abortController.signal)) return
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @param {AbortSignal} signal
|
|
136
|
+
* @returns {Promise<PollResponse>}
|
|
137
|
+
*/
|
|
138
|
+
#request (signal) {
|
|
139
|
+
const headers = getClientLibraryHeaders()
|
|
140
|
+
headers['Accept-Encoding'] = 'gzip'
|
|
141
|
+
headers['DD-API-KEY'] = this.#config.apiKey
|
|
142
|
+
if (this.#etag) headers['If-None-Match'] = this.#etag
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* @param {(response: PollResponse) => void} resolve
|
|
146
|
+
*/
|
|
147
|
+
const execute = (resolve) => {
|
|
148
|
+
/**
|
|
149
|
+
* @param {Error | null} error
|
|
150
|
+
* @param {string | import('node:http').IncomingMessage | null | undefined} body
|
|
151
|
+
* @param {number | undefined} statusCode
|
|
152
|
+
* @param {import('node:http').IncomingHttpHeaders | undefined} responseHeaders
|
|
153
|
+
*/
|
|
154
|
+
const onResponse = (error, body, statusCode, responseHeaders) => {
|
|
155
|
+
resolve({
|
|
156
|
+
error,
|
|
157
|
+
body: typeof body === 'string' ? body : undefined,
|
|
158
|
+
statusCode,
|
|
159
|
+
headers: responseHeaders,
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
request('', {
|
|
164
|
+
url: this.#config.endpoint,
|
|
165
|
+
method: 'GET',
|
|
166
|
+
headers,
|
|
167
|
+
retry: false,
|
|
168
|
+
signal,
|
|
169
|
+
timeout: this.#config.requestTimeoutMs,
|
|
170
|
+
}, onResponse)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return new Promise(execute)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* @param {PollResponse} response
|
|
178
|
+
* @returns {void}
|
|
179
|
+
*/
|
|
180
|
+
#apply (response) {
|
|
181
|
+
const statusCode = response.statusCode
|
|
182
|
+
if (statusCode === 304) return
|
|
183
|
+
|
|
184
|
+
if (statusCode === 401 || statusCode === 403) {
|
|
185
|
+
this.#warnFailure(statusCode, undefined, 1)
|
|
186
|
+
return
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (statusCode !== 200) return
|
|
190
|
+
|
|
191
|
+
let configuration
|
|
192
|
+
try {
|
|
193
|
+
configuration = parseConfiguration(response.body)
|
|
194
|
+
} catch {
|
|
195
|
+
if (!this.#malformedPayloadLogged) {
|
|
196
|
+
this.#malformedPayloadLogged = true
|
|
197
|
+
log.error('Feature Flagging agentless endpoint returned malformed UFC payload')
|
|
198
|
+
}
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
this.#applyConfiguration(configuration)
|
|
204
|
+
} catch (error) {
|
|
205
|
+
if (!this.#applicationFailureLogged) {
|
|
206
|
+
this.#applicationFailureLogged = true
|
|
207
|
+
log.warn('Feature Flagging agentless UFC payload could not be applied: %s', errorMessage(error))
|
|
208
|
+
}
|
|
209
|
+
return
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const etag = response.headers?.etag
|
|
213
|
+
const value = Array.isArray(etag) ? etag[0] : etag
|
|
214
|
+
this.#etag = value?.trim() || undefined
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* @param {number | undefined} statusCode
|
|
219
|
+
* @param {unknown} error
|
|
220
|
+
* @param {number} attempts
|
|
221
|
+
* @returns {void}
|
|
222
|
+
*/
|
|
223
|
+
#warnFailure (statusCode, error, attempts) {
|
|
224
|
+
const category = statusCode === 401 || statusCode === 403
|
|
225
|
+
? 'authentication'
|
|
226
|
+
: statusCode ? 'http' : 'request'
|
|
227
|
+
if (this.#failureWarnings.has(category)) return
|
|
228
|
+
this.#failureWarnings.add(category)
|
|
229
|
+
|
|
230
|
+
if (statusCode === 401 || statusCode === 403) {
|
|
231
|
+
log.warn(
|
|
232
|
+
'Feature Flagging agentless endpoint returned HTTP %d; verify DD_API_KEY is configured and valid',
|
|
233
|
+
statusCode
|
|
234
|
+
)
|
|
235
|
+
} else if (statusCode) {
|
|
236
|
+
log.warn('Feature Flagging agentless endpoint returned HTTP %d after %d attempts', statusCode, attempts)
|
|
237
|
+
} else if (attempts > 1) {
|
|
238
|
+
log.warn('Feature Flagging agentless request failed after %d attempts: %s', attempts, errorMessage(error))
|
|
239
|
+
} else {
|
|
240
|
+
log.warn('Feature Flagging agentless request failed: %s', errorMessage(error))
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* @param {number} delay
|
|
247
|
+
* @param {AbortSignal} signal
|
|
248
|
+
* @returns {Promise<boolean>}
|
|
249
|
+
*/
|
|
250
|
+
async function wait (delay, signal) {
|
|
251
|
+
try {
|
|
252
|
+
await sleep(delay, undefined, { ref: false, signal })
|
|
253
|
+
return true
|
|
254
|
+
} catch (error) {
|
|
255
|
+
if (error?.name === 'AbortError') return false
|
|
256
|
+
throw error
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* @param {string | undefined} body
|
|
262
|
+
* @returns {UniversalFlagConfiguration}
|
|
263
|
+
*/
|
|
264
|
+
function parseConfiguration (body) {
|
|
265
|
+
const { data } = JSON.parse(body)
|
|
266
|
+
if (data?.type !== 'universal-flag-configuration') {
|
|
267
|
+
throw new Error('Expected a JSON:API Universal Flag Configuration resource')
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const { attributes } = data
|
|
271
|
+
if (typeof attributes?.format !== 'string' ||
|
|
272
|
+
typeof attributes.createdAt !== 'string' ||
|
|
273
|
+
typeof attributes.environment?.name !== 'string' ||
|
|
274
|
+
!attributes.flags ||
|
|
275
|
+
typeof attributes.flags !== 'object' ||
|
|
276
|
+
Array.isArray(attributes.flags)) {
|
|
277
|
+
throw new Error('Expected a Universal Flag Configuration v1 object')
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return attributes
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* @param {unknown} error
|
|
285
|
+
* @returns {string}
|
|
286
|
+
*/
|
|
287
|
+
function errorMessage (error) {
|
|
288
|
+
return error instanceof Error ? error.message : String(error ?? 'request was not sent')
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* @param {number | undefined} status
|
|
293
|
+
* @returns {boolean}
|
|
294
|
+
*/
|
|
295
|
+
function isRetryableStatus (status) {
|
|
296
|
+
return status === 408 || status === 429 || (status >= 500 && status <= 599)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* @param {number} pollIntervalMs
|
|
301
|
+
* @param {number} attempt
|
|
302
|
+
* @param {number} random
|
|
303
|
+
* @returns {number}
|
|
304
|
+
*/
|
|
305
|
+
function retryDelay (pollIntervalMs, attempt, random) {
|
|
306
|
+
const base = attempt === 1
|
|
307
|
+
? clamp(pollIntervalMs / 6, FIRST_RETRY_MIN_MS, FIRST_RETRY_MAX_MS)
|
|
308
|
+
: clamp(pollIntervalMs / 3, SECOND_RETRY_MIN_MS, SECOND_RETRY_MAX_MS)
|
|
309
|
+
return Math.max(1, Math.round(base * (1 - RETRY_JITTER + random * RETRY_JITTER * 2)))
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* @param {number} value
|
|
314
|
+
* @param {number} minimum
|
|
315
|
+
* @param {number} maximum
|
|
316
|
+
* @returns {number}
|
|
317
|
+
*/
|
|
318
|
+
function clamp (value, minimum, maximum) {
|
|
319
|
+
return Math.max(minimum, Math.min(maximum, value))
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
module.exports = AgentlessConfigurationSource
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { isLoopbackHost } = require('../exporters/common/url')
|
|
4
|
+
const log = require('../log')
|
|
5
|
+
|
|
6
|
+
const DEFAULT_AGENTLESS_PATH = '/api/v2/feature-flagging/config/rules-based/server'
|
|
7
|
+
const MAX_POLL_INTERVAL_SECONDS = 60 * 60
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {import('@datadog/openfeature-node-server').UniversalFlagConfigurationV1} UniversalFlagConfiguration
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {import('../config/config-base')} config
|
|
15
|
+
* @param {(configuration: UniversalFlagConfiguration) => void} applyConfiguration
|
|
16
|
+
*/
|
|
17
|
+
function create (config, applyConfiguration) {
|
|
18
|
+
const {
|
|
19
|
+
DD_FEATURE_FLAGS_CONFIGURATION_SOURCE: source,
|
|
20
|
+
DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL: baseUrl,
|
|
21
|
+
DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS: pollIntervalSeconds,
|
|
22
|
+
DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_REQUEST_TIMEOUT_SECONDS: requestTimeoutSeconds,
|
|
23
|
+
DD_FEATURE_FLAGS_ENABLED: enabled,
|
|
24
|
+
} = config.featureFlags
|
|
25
|
+
|
|
26
|
+
if (!enabled || source !== 'agentless') {
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
if (!config.DD_API_KEY) {
|
|
32
|
+
throw new Error('DD_API_KEY is required for Feature Flagging agentless delivery')
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const AgentlessConfigurationSource = require('./agentless_configuration_source')
|
|
36
|
+
return new AgentlessConfigurationSource({
|
|
37
|
+
endpoint: endpoint(config, baseUrl),
|
|
38
|
+
pollIntervalMs: Math.min(pollIntervalSeconds, MAX_POLL_INTERVAL_SECONDS) * 1000,
|
|
39
|
+
requestTimeoutMs: requestTimeoutSeconds * 1000,
|
|
40
|
+
apiKey: config.DD_API_KEY,
|
|
41
|
+
}, applyConfiguration)
|
|
42
|
+
} catch (error) {
|
|
43
|
+
log.error('Unable to configure Feature Flagging configuration source', error)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Builds the agentless rules-based server endpoint.
|
|
49
|
+
*
|
|
50
|
+
* A configured URL with a non-root path is treated as the exact endpoint. A
|
|
51
|
+
* configured origin (or root URL) receives the standard rules-based server
|
|
52
|
+
* path.
|
|
53
|
+
*
|
|
54
|
+
* @param {import('../config/config-base')} config - Tracer configuration.
|
|
55
|
+
* @param {string | undefined} configuredBaseUrl - Optional endpoint or origin override.
|
|
56
|
+
* @returns {URL} Agentless endpoint.
|
|
57
|
+
*/
|
|
58
|
+
function endpoint (config, configuredBaseUrl) {
|
|
59
|
+
const configured = configuredBaseUrl?.trim()
|
|
60
|
+
|
|
61
|
+
if (!configured) {
|
|
62
|
+
const url = new URL(`https://ufc-server.ff-cdn.${config.site.toLowerCase()}${DEFAULT_AGENTLESS_PATH}`)
|
|
63
|
+
if (config.env) url.searchParams.set('dd_env', config.env)
|
|
64
|
+
return url
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let url
|
|
68
|
+
try {
|
|
69
|
+
url = new URL(configured)
|
|
70
|
+
} catch {
|
|
71
|
+
throw new Error('Invalid Feature Flagging agentless URL')
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (url.protocol !== 'https:' && url.protocol !== 'http:') {
|
|
75
|
+
throw new Error('Feature Flagging agentless URL must use HTTP or HTTPS')
|
|
76
|
+
}
|
|
77
|
+
if (url.protocol === 'http:' && !isLoopbackHost(url.hostname)) {
|
|
78
|
+
throw new Error('Feature Flagging agentless URL must use HTTPS unless it targets loopback')
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (url.pathname === '' || url.pathname === '/') {
|
|
82
|
+
url.pathname = DEFAULT_AGENTLESS_PATH
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return url
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = {
|
|
89
|
+
create,
|
|
90
|
+
}
|
|
@@ -1,40 +1,40 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { channel } = require('dc-polyfill')
|
|
4
|
-
const requireOptionalPeer = require('../../../datadog-instrumentations/src/helpers/require-optional-peer')
|
|
5
4
|
const log = require('../log')
|
|
5
|
+
const configurationSource = require('./configuration_source')
|
|
6
6
|
const { EXPOSURE_CHANNEL } = require('./constants/constants')
|
|
7
7
|
const EvalMetricsHook = require('./eval-metrics-hook')
|
|
8
8
|
const SpanEnrichmentHook = require('./span-enrichment-hook')
|
|
9
9
|
|
|
10
|
-
const { DatadogNodeServerProvider } =
|
|
10
|
+
const { DatadogNodeServerProvider } = require('./require-provider')
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* OpenFeature provider that integrates with Datadog's feature flagging system.
|
|
14
14
|
* Extends DatadogNodeServerProvider to add tracer integration and configuration management.
|
|
15
15
|
*/
|
|
16
16
|
class FlaggingProvider extends DatadogNodeServerProvider {
|
|
17
|
-
/** @type {SpanEnrichmentHook
|
|
17
|
+
/** @type {SpanEnrichmentHook | undefined} */
|
|
18
18
|
#spanEnrichmentHook
|
|
19
19
|
|
|
20
|
+
/** @type {{ start: Function, stop: Function } | undefined} */
|
|
21
|
+
#configurationSource
|
|
22
|
+
|
|
20
23
|
/**
|
|
21
24
|
* @param {import('../tracer')} tracer - Datadog tracer instance
|
|
22
|
-
* @param {import('../config')} config - Tracer configuration object
|
|
25
|
+
* @param {import('../config/config-base')} config - Tracer configuration object
|
|
23
26
|
*/
|
|
24
27
|
constructor (tracer, config) {
|
|
25
|
-
// Call parent constructor with required options and timeout
|
|
26
28
|
super({
|
|
27
29
|
exposureChannel: channel(EXPOSURE_CHANNEL),
|
|
28
30
|
initializationTimeoutMs: config.experimental.flaggingProvider.initializationTimeoutMs,
|
|
29
31
|
})
|
|
30
32
|
|
|
31
|
-
this._tracer = tracer
|
|
32
|
-
this._config = config
|
|
33
|
-
|
|
34
33
|
this.hooks.push(new EvalMetricsHook(config))
|
|
35
34
|
|
|
36
35
|
if (config.experimental.flaggingProvider.spanEnrichment?.enabled) {
|
|
37
36
|
this.#spanEnrichmentHook = new SpanEnrichmentHook(tracer)
|
|
37
|
+
// @ts-expect-error The upstream constructor always initializes its optional hooks property.
|
|
38
38
|
this.hooks.push(this.#spanEnrichmentHook)
|
|
39
39
|
log.info('%s span enrichment enabled', this.constructor.name)
|
|
40
40
|
} else {
|
|
@@ -43,6 +43,9 @@ class FlaggingProvider extends DatadogNodeServerProvider {
|
|
|
43
43
|
|
|
44
44
|
log.debug('%s created with timeout: %dms', this.constructor.name,
|
|
45
45
|
config.experimental.flaggingProvider.initializationTimeoutMs)
|
|
46
|
+
|
|
47
|
+
this.#configurationSource = configurationSource.create(config, this.setConfiguration.bind(this))
|
|
48
|
+
this.#configurationSource?.start()
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
/**
|
|
@@ -50,22 +53,10 @@ class FlaggingProvider extends DatadogNodeServerProvider {
|
|
|
50
53
|
* Cleans up resources including channel subscriptions.
|
|
51
54
|
*/
|
|
52
55
|
onClose () {
|
|
56
|
+
this.#configurationSource?.stop()
|
|
57
|
+
this.#configurationSource = undefined
|
|
53
58
|
this.#spanEnrichmentHook?.destroy()
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Internal method to update flag configuration from Remote Config.
|
|
58
|
-
* This method is called automatically when Remote Config delivers UFC updates.
|
|
59
|
-
*
|
|
60
|
-
* @internal
|
|
61
|
-
* @param {import('@datadog/openfeature-node-server').UniversalFlagConfigurationV1} ufc
|
|
62
|
-
* - Universal Flag Configuration object
|
|
63
|
-
*/
|
|
64
|
-
_setConfiguration (ufc) {
|
|
65
|
-
if (typeof this.setConfiguration === 'function') {
|
|
66
|
-
this.setConfiguration(ufc)
|
|
67
|
-
}
|
|
68
|
-
log.debug('%s provider configuration updated', this.constructor.name)
|
|
59
|
+
this.#spanEnrichmentHook = undefined
|
|
69
60
|
}
|
|
70
61
|
}
|
|
71
62
|
|
|
@@ -20,12 +20,7 @@ function resolveDefault (defaultValue) {
|
|
|
20
20
|
* https://openfeature.dev/docs/reference/concepts/provider/
|
|
21
21
|
*/
|
|
22
22
|
class NoopFlaggingProvider {
|
|
23
|
-
|
|
24
|
-
* @param {object} [noopTracer] - Optional noop tracer instance
|
|
25
|
-
*/
|
|
26
|
-
constructor (noopTracer) {
|
|
27
|
-
this._tracer = noopTracer
|
|
28
|
-
this._config = {}
|
|
23
|
+
constructor () {
|
|
29
24
|
this.metadata = { name: 'NoopFlaggingProvider' }
|
|
30
25
|
this.status = 'NOT_READY'
|
|
31
26
|
this.runsOn = 'server'
|
|
@@ -78,28 +73,6 @@ class NoopFlaggingProvider {
|
|
|
78
73
|
resolveObjectEvaluation (flagKey, defaultValue, context, logger) {
|
|
79
74
|
return resolveDefault(defaultValue)
|
|
80
75
|
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* @returns {object} Current configuration
|
|
84
|
-
*/
|
|
85
|
-
getConfiguration () {
|
|
86
|
-
return this._config
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* @param {object} config - Configuration to set
|
|
91
|
-
*/
|
|
92
|
-
setConfiguration (config) {
|
|
93
|
-
this._config = config
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* @internal
|
|
98
|
-
* @param {object} ufc - Universal Flag Configuration object
|
|
99
|
-
*/
|
|
100
|
-
_setConfiguration (ufc) {
|
|
101
|
-
this.setConfiguration(ufc)
|
|
102
|
-
}
|
|
103
76
|
}
|
|
104
77
|
|
|
105
78
|
module.exports = NoopFlaggingProvider
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { registerFeature } = require('../feature-registry')
|
|
4
|
+
|
|
5
|
+
const noop = new (require('./noop'))()
|
|
6
|
+
|
|
7
|
+
/** @typedef {import('../proxy') & { openfeature: object }} OpenFeatureProxy */
|
|
8
|
+
|
|
9
|
+
registerFeature({
|
|
10
|
+
name: 'openfeature',
|
|
11
|
+
noop,
|
|
12
|
+
factory: () => require('./index'),
|
|
13
|
+
provider: () => require('./flagging_provider'),
|
|
14
|
+
|
|
15
|
+
/** @param {import('../config/config-base')} config */
|
|
16
|
+
isEnabled (config) {
|
|
17
|
+
return config.featureFlags.DD_FEATURE_FLAGS_ENABLED
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {import('../remote_config')} rc - RemoteConfig instance
|
|
22
|
+
* @param {import('../config/config-base')} config
|
|
23
|
+
* @param {OpenFeatureProxy} proxy
|
|
24
|
+
*/
|
|
25
|
+
remoteConfig (rc, config, proxy) {
|
|
26
|
+
const openfeatureRemoteConfig = require('./remote_config')
|
|
27
|
+
const subscribe = config.featureFlags.DD_FEATURE_FLAGS_ENABLED &&
|
|
28
|
+
config.featureFlags.DD_FEATURE_FLAGS_CONFIGURATION_SOURCE === 'remote_config'
|
|
29
|
+
openfeatureRemoteConfig.enable(
|
|
30
|
+
rc,
|
|
31
|
+
() => proxy.openfeature,
|
|
32
|
+
subscribe
|
|
33
|
+
)
|
|
34
|
+
},
|
|
35
|
+
})
|
|
@@ -5,30 +5,27 @@ const RemoteConfigCapabilities = require('../remote_config/capabilities')
|
|
|
5
5
|
/**
|
|
6
6
|
* Configures remote config handlers for openfeature feature flagging
|
|
7
7
|
*
|
|
8
|
-
* @param {
|
|
9
|
-
* @param {
|
|
10
|
-
* @param {
|
|
8
|
+
* @param {import('../remote_config')} rc - RemoteConfig instance
|
|
9
|
+
* @param {() => import('./flagging_provider')} getOpenfeatureProxy
|
|
10
|
+
* @param {boolean} subscribe - Whether Agent Remote Config owns UFC delivery
|
|
11
11
|
*/
|
|
12
|
-
function enable (rc,
|
|
13
|
-
|
|
14
|
-
// This indicates the library supports this capability via remote config
|
|
15
|
-
rc.updateCapabilities(RemoteConfigCapabilities.FFE_FLAG_CONFIGURATION_RULES, true)
|
|
12
|
+
function enable (rc, getOpenfeatureProxy, subscribe) {
|
|
13
|
+
if (!subscribe) return
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
if (!config.experimental.flaggingProvider.enabled) return
|
|
15
|
+
rc.updateCapabilities(RemoteConfigCapabilities.FFE_FLAG_CONFIGURATION_RULES, true)
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
/**
|
|
18
|
+
* @param {string} action
|
|
19
|
+
* @param {import('@datadog/openfeature-node-server').UniversalFlagConfigurationV1} conf
|
|
20
|
+
*/
|
|
21
|
+
const updateConfiguration = (action, conf) => {
|
|
22
22
|
if (action === 'apply' || action === 'modify') {
|
|
23
|
-
|
|
24
|
-
getOpenfeatureProxy()._setConfiguration(conf)
|
|
23
|
+
getOpenfeatureProxy().setConfiguration(conf)
|
|
25
24
|
} else if (action === 'unapply') {
|
|
26
|
-
|
|
27
|
-
// consistent with Go and Python which also set config to null on RC deletion.
|
|
28
|
-
// The evaluator returns PROVIDER_NOT_READY when config is null/undefined.
|
|
29
|
-
getOpenfeatureProxy()._setConfiguration(null)
|
|
25
|
+
getOpenfeatureProxy().setConfiguration(undefined)
|
|
30
26
|
}
|
|
31
|
-
}
|
|
27
|
+
}
|
|
28
|
+
rc.setProductHandler('FFE_FLAGS', updateConfiguration)
|
|
32
29
|
}
|
|
33
30
|
|
|
34
31
|
module.exports = {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/** @type {(request: string) => typeof import('@datadog/openfeature-node-server')} */
|
|
4
|
+
let requireOptionalPeer
|
|
5
|
+
|
|
6
|
+
// @ts-expect-error webpack exposes this escape hatch as a free variable.
|
|
7
|
+
// eslint-disable-next-line camelcase
|
|
8
|
+
if (typeof __non_webpack_require__ === 'function') {
|
|
9
|
+
// eslint-disable-next-line camelcase, no-undef
|
|
10
|
+
requireOptionalPeer = __non_webpack_require__
|
|
11
|
+
} else {
|
|
12
|
+
// nft recognizes createRequire through a binding named `module`.
|
|
13
|
+
const module = require('node:module')
|
|
14
|
+
const runtimeRequire = module.createRequire(__filename)
|
|
15
|
+
requireOptionalPeer = runtimeRequire
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = requireOptionalPeer('@datadog/openfeature-node-server')
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Pre-finish hook for OTel-bridge spans. `Span.end()` runs it before `finish()` formats and
|
|
4
|
+
// (synchronously, for the last span in a trace) exports the trace, so a framework instrumentation
|
|
5
|
+
// can rewrite the operation name / resource while the DD span is still unfinished; `onEnd` only sees
|
|
6
|
+
// the already-built payload. Its own dependency-free module so an instrumentation can register
|
|
7
|
+
// without loading the OTel bridge, and a plain holder so the caller gates on `hook` existing.
|
|
8
|
+
|
|
9
|
+
/** @type {{ hook: ((ddSpan: import('../opentracing/span')) => void) | undefined }} */
|
|
10
|
+
module.exports = { hook: undefined }
|
|
@@ -15,6 +15,7 @@ const kinds = require('../../../../ext/kinds')
|
|
|
15
15
|
const id = require('../id')
|
|
16
16
|
const BridgeSpanBase = require('./bridge-span-base')
|
|
17
17
|
const SpanContext = require('./span_context')
|
|
18
|
+
const spanEndingHook = require('./span-ending-hook')
|
|
18
19
|
const { setOtelOperationName, setOtelResource } = require('./span-helpers')
|
|
19
20
|
|
|
20
21
|
const spanKindNames = {
|
|
@@ -255,6 +256,10 @@ class Span extends BridgeSpanBase {
|
|
|
255
256
|
const hrEndTime = timeInputToHrTime(timeInput || (performance.now() + timeOrigin))
|
|
256
257
|
const endTime = hrTimeToMilliseconds(hrEndTime)
|
|
257
258
|
|
|
259
|
+
// Must run before `finish()`, while the DD span is still unfinished. See span-ending-hook.js.
|
|
260
|
+
if (spanEndingHook.hook !== undefined) {
|
|
261
|
+
spanEndingHook.hook(this._ddSpan)
|
|
262
|
+
}
|
|
258
263
|
this._ddSpan.finish(endTime)
|
|
259
264
|
this._spanProcessor.onEnd(this)
|
|
260
265
|
}
|