dd-trace 6.10.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/index.d.ts +108 -3
- package/initialize.mjs +28 -17
- package/openfeature.js +2 -2
- package/package.json +11 -8
- package/packages/datadog-esbuild/index.js +0 -34
- package/packages/datadog-instrumentations/src/anthropic.js +210 -15
- package/packages/datadog-instrumentations/src/aws-sdk.js +9 -1
- package/packages/datadog-instrumentations/src/browser-bunyan.js +11 -0
- package/packages/datadog-instrumentations/src/bunyan.js +3 -16
- package/packages/datadog-instrumentations/src/cucumber.js +26 -11
- package/packages/datadog-instrumentations/src/cypress-config.js +182 -16
- package/packages/datadog-instrumentations/src/cypress-legacy-finalizers.js +30 -0
- package/packages/datadog-instrumentations/src/cypress.js +2 -0
- package/packages/datadog-instrumentations/src/fastify.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/bunyan.js +22 -0
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +9 -4
- package/packages/datadog-instrumentations/src/helpers/register.js +10 -8
- package/packages/datadog-instrumentations/src/jest.js +25 -17
- package/packages/datadog-instrumentations/src/mariadb.js +278 -21
- package/packages/datadog-instrumentations/src/mocha/main.js +539 -55
- package/packages/datadog-instrumentations/src/mocha/utils.js +130 -17
- package/packages/datadog-instrumentations/src/playwright-reporter.js +99 -0
- package/packages/datadog-instrumentations/src/playwright.js +149 -2
- package/packages/datadog-instrumentations/src/router.js +2 -0
- package/packages/datadog-instrumentations/src/vitest-main.js +11 -3
- package/packages/datadog-plugin-aws-sdk/src/base.js +1 -0
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +163 -44
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +2 -8
- package/packages/datadog-plugin-azure-functions/src/index.js +1 -1
- package/packages/datadog-plugin-browser-bunyan/src/index.js +9 -0
- package/packages/datadog-plugin-bunyan/src/index.js +5 -8
- package/packages/datadog-plugin-cucumber/src/index.js +11 -0
- package/packages/datadog-plugin-cypress/src/after-run.js +5 -1
- package/packages/datadog-plugin-cypress/src/after-spec.js +5 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +81 -29
- package/packages/datadog-plugin-cypress/src/finalization.js +76 -0
- package/packages/datadog-plugin-cypress/src/index.js +12 -6
- package/packages/datadog-plugin-cypress/src/plugin.js +16 -3
- package/packages/datadog-plugin-dd-trace-api/src/index.js +4 -0
- package/packages/datadog-plugin-electron/src/index.js +2 -0
- package/packages/datadog-plugin-fs/src/index.js +1 -1
- package/packages/datadog-plugin-jest/src/index.js +6 -0
- package/packages/datadog-plugin-mocha/src/index.js +56 -1
- package/packages/datadog-plugin-nats/src/index.js +1 -1
- package/packages/datadog-plugin-openai/src/tracing.js +41 -30
- package/packages/datadog-plugin-openai-agents/src/integration.js +1 -0
- package/packages/datadog-plugin-playwright/src/index.js +42 -13
- package/packages/datadog-plugin-vitest/src/index.js +6 -0
- package/packages/datadog-webpack/index.js +0 -11
- package/packages/dd-trace/index.js +0 -1
- package/packages/dd-trace/src/agent/info.js +6 -5
- package/packages/dd-trace/src/aiguard/client.js +100 -1
- package/packages/dd-trace/src/aiguard/errors.js +41 -0
- package/packages/dd-trace/src/aiguard/evaluation.js +357 -0
- package/packages/dd-trace/src/aiguard/integrations/anthropic.js +70 -0
- package/packages/dd-trace/src/aiguard/integrations/index.js +4 -0
- package/packages/dd-trace/src/aiguard/messages/anthropic.js +423 -0
- package/packages/dd-trace/src/aiguard/messages/openai.js +1 -24
- package/packages/dd-trace/src/aiguard/messages/utils.js +28 -0
- package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +1 -11
- package/packages/dd-trace/src/aiguard/sdk.js +22 -278
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +9 -12
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +1 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +27 -3
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +22 -6
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +21 -5
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +164 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +22 -6
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +365 -32
- package/packages/dd-trace/src/ci-visibility/exporters/request.js +201 -0
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +5 -3
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -5
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -1
- package/packages/dd-trace/src/ci-visibility/requests/rate-limit.js +39 -0
- package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -6
- package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +6 -2
- package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
- package/packages/dd-trace/src/config/index.js +13 -2
- package/packages/dd-trace/src/config/major-overrides.js +7 -0
- package/packages/dd-trace/src/config/supported-configurations.json +14 -0
- package/packages/dd-trace/src/datastreams/index.js +2 -0
- package/packages/dd-trace/src/datastreams/manager.js +9 -2
- package/packages/dd-trace/src/datastreams/processor.js +15 -13
- package/packages/dd-trace/src/datastreams/size.js +5 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +3 -2
- package/packages/dd-trace/src/exporters/agent/writer.js +49 -9
- package/packages/dd-trace/src/exporters/common/buffering-exporter.js +6 -4
- package/packages/dd-trace/src/exporters/common/writer.js +11 -6
- package/packages/dd-trace/src/llmobs/experiments/client.js +163 -5
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +241 -87
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +280 -20
- package/packages/dd-trace/src/llmobs/experiments/index.js +58 -59
- package/packages/dd-trace/src/llmobs/experiments/noop.js +71 -3
- package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
- package/packages/dd-trace/src/llmobs/experiments/util.js +41 -5
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +12 -3
- package/packages/dd-trace/src/llmobs/plugins/base.js +4 -2
- package/packages/dd-trace/src/noop/proxy.js +3 -4
- package/packages/dd-trace/src/openfeature/flagging_provider.js +18 -2
- package/packages/dd-trace/src/openfeature/remote_config.js +1 -1
- package/packages/dd-trace/src/opentelemetry/context_manager.js +7 -7
- package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +2 -1
- package/packages/dd-trace/src/opentelemetry/logs/logger.js +67 -11
- package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +14 -3
- package/packages/dd-trace/src/opentelemetry/metrics/index.js +25 -12
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +2 -5
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +96 -43
- package/packages/dd-trace/src/opentracing/tracer.js +5 -2
- package/packages/dd-trace/src/otel-thread-ctx.js +387 -0
- package/packages/dd-trace/src/plugin_manager.js +3 -3
- package/packages/dd-trace/src/plugins/ci_plugin.js +6 -1
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/util/jest.js +4 -3
- package/packages/dd-trace/src/plugins/util/test.js +9 -9
- package/packages/dd-trace/src/plugins/util/web.js +3 -0
- package/packages/dd-trace/src/profiling/profiler.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/space.js +1 -3
- package/packages/dd-trace/src/profiling/profilers/wall.js +37 -90
- package/packages/dd-trace/src/profiling/webspan-utils.js +45 -0
- package/packages/dd-trace/src/proxy.js +38 -29
- package/packages/dd-trace/src/ritm.js +9 -2
- package/packages/dd-trace/src/serverless.js +37 -0
- package/packages/dd-trace/src/span_format.js +1 -0
- package/packages/dd-trace/src/span_stats.js +30 -3
- package/packages/dd-trace/src/storage-channels.js +86 -0
- package/packages/dd-trace/src/tracer.js +9 -2
- package/packages/dd-trace/src/tracer_metadata.js +14 -1
- package/packages/dd-trace/src/web-tags-cache.js +132 -0
- package/vendor/dist/@datadog/openfeature-node-server/LICENSE +201 -0
- package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +0 -65
- package/packages/datadog-webpack/src/optional-peer-loader.js +0 -17
- package/packages/dd-trace/src/feature-registry.js +0 -29
- package/packages/dd-trace/src/openfeature/register.js +0 -35
- package/packages/dd-trace/src/openfeature/require-provider.js +0 -18
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const log = require('../../log')
|
|
4
|
+
const { ExternalExperiment } = require('./experiment')
|
|
5
|
+
|
|
6
|
+
const NOOP_EXPERIMENT_ID = '00000000-0000-0000-0000-000000000000'
|
|
7
|
+
const NOOP_SPAN_ID = '0000000000000000'
|
|
8
|
+
const NOOP_TRACE_ID = '00000000000000000000000000000000'
|
|
4
9
|
|
|
5
10
|
class NoopDataset {
|
|
6
11
|
#name
|
|
12
|
+
#description
|
|
7
13
|
#records
|
|
8
14
|
|
|
9
15
|
constructor (name = '', options = {}) {
|
|
10
16
|
this.#name = name
|
|
17
|
+
this.#description = typeof options === 'string' ? options : (options.description ?? '')
|
|
11
18
|
this.#records = (typeof options === 'string' ? [] : (options.records ?? [])).map(record => ({
|
|
12
19
|
id: record.id ?? null,
|
|
13
20
|
input: record.inputData,
|
|
@@ -21,6 +28,20 @@ class NoopDataset {
|
|
|
21
28
|
return this
|
|
22
29
|
}
|
|
23
30
|
|
|
31
|
+
update (index, fields) {
|
|
32
|
+
const record = this.#records[index]
|
|
33
|
+
if (record == null) return this
|
|
34
|
+
if (Object.hasOwn(fields, 'input')) record.input = fields.input
|
|
35
|
+
if (Object.hasOwn(fields, 'expectedOutput')) record.expectedOutput = fields.expectedOutput ?? null
|
|
36
|
+
if (Object.hasOwn(fields, 'metadata')) record.metadata = fields.metadata ?? {}
|
|
37
|
+
return this
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
delete (index) {
|
|
41
|
+
this.#records.splice(index, 1)
|
|
42
|
+
return this
|
|
43
|
+
}
|
|
44
|
+
|
|
24
45
|
push () {
|
|
25
46
|
return Promise.resolve({ pushedCount: 0, totalCount: 0 })
|
|
26
47
|
}
|
|
@@ -29,6 +50,10 @@ class NoopDataset {
|
|
|
29
50
|
return this.#name
|
|
30
51
|
}
|
|
31
52
|
|
|
53
|
+
description () {
|
|
54
|
+
return this.#description
|
|
55
|
+
}
|
|
56
|
+
|
|
32
57
|
id () {
|
|
33
58
|
return null
|
|
34
59
|
}
|
|
@@ -60,9 +85,11 @@ class NoopDataset {
|
|
|
60
85
|
|
|
61
86
|
class NoopExperiment {
|
|
62
87
|
#name
|
|
88
|
+
#external
|
|
63
89
|
|
|
64
|
-
constructor (name = '') {
|
|
90
|
+
constructor (name = '', external = false) {
|
|
65
91
|
this.#name = name
|
|
92
|
+
this.#external = external
|
|
66
93
|
}
|
|
67
94
|
|
|
68
95
|
name () {
|
|
@@ -70,7 +97,7 @@ class NoopExperiment {
|
|
|
70
97
|
}
|
|
71
98
|
|
|
72
99
|
experimentId () {
|
|
73
|
-
return null
|
|
100
|
+
return this.#external ? NOOP_EXPERIMENT_ID : null
|
|
74
101
|
}
|
|
75
102
|
|
|
76
103
|
url () {
|
|
@@ -80,6 +107,32 @@ class NoopExperiment {
|
|
|
80
107
|
run () {
|
|
81
108
|
return Promise.resolve({ experimentId: null, rows: [], url: null })
|
|
82
109
|
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @returns {Promise<{experimentId: string, spanId: string, traceId: string, url: null}>}
|
|
113
|
+
*/
|
|
114
|
+
submitSpan () {
|
|
115
|
+
return Promise.resolve({
|
|
116
|
+
experimentId: NOOP_EXPERIMENT_ID,
|
|
117
|
+
spanId: NOOP_SPAN_ID,
|
|
118
|
+
traceId: NOOP_TRACE_ID,
|
|
119
|
+
url: null,
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @returns {Promise<void>}
|
|
125
|
+
*/
|
|
126
|
+
submitEvaluationMetrics () {
|
|
127
|
+
return Promise.resolve()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* @returns {Promise<void>}
|
|
132
|
+
*/
|
|
133
|
+
close () {
|
|
134
|
+
return Promise.resolve()
|
|
135
|
+
}
|
|
83
136
|
}
|
|
84
137
|
|
|
85
138
|
// No-op Experiments used when LLM Observability is disabled or the API/APP keys
|
|
@@ -87,9 +140,11 @@ class NoopExperiment {
|
|
|
87
140
|
// throwing, so intentionally disabled experiments remain graceful.
|
|
88
141
|
class NoopExperiments {
|
|
89
142
|
#reason
|
|
143
|
+
#startExperiment
|
|
90
144
|
|
|
91
|
-
constructor (reason) {
|
|
145
|
+
constructor (reason, options = {}) {
|
|
92
146
|
this.#reason = reason || 'LLMObs experiments are not available'
|
|
147
|
+
this.#startExperiment = options.startExperiment
|
|
93
148
|
}
|
|
94
149
|
|
|
95
150
|
#warn () {
|
|
@@ -110,6 +165,19 @@ class NoopExperiments {
|
|
|
110
165
|
this.#warn()
|
|
111
166
|
return new NoopExperiment(options.name)
|
|
112
167
|
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* @param {object} options
|
|
171
|
+
* @returns {Promise<ExternalExperiment>}
|
|
172
|
+
*/
|
|
173
|
+
startExperiment (options = {}) {
|
|
174
|
+
if (this.#startExperiment !== undefined && options.projectName) {
|
|
175
|
+
return this.#startExperiment(options)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
this.#warn()
|
|
179
|
+
return Promise.resolve(new ExternalExperiment(new NoopExperiment(options.name, true)))
|
|
180
|
+
}
|
|
113
181
|
}
|
|
114
182
|
|
|
115
183
|
module.exports = NoopExperiments
|
|
@@ -13,6 +13,7 @@ class Row {
|
|
|
13
13
|
this.expectedOutput = fields.expectedOutput
|
|
14
14
|
this.errorType = fields.errorType
|
|
15
15
|
this.errorMessage = fields.errorMessage
|
|
16
|
+
this.errorStack = fields.errorStack
|
|
16
17
|
this.evaluations = fields.evaluations
|
|
17
18
|
this.evaluationErrors = fields.evaluationErrors
|
|
18
19
|
}
|
|
@@ -178,12 +178,12 @@ function buildTags (userTags, autoTags) {
|
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
/**
|
|
181
|
-
* @param {Record<string, unknown> | undefined}
|
|
182
|
-
* @param {Record<string, unknown>}
|
|
181
|
+
* @param {Record<string, unknown> | undefined} baseTags
|
|
182
|
+
* @param {Record<string, unknown> | undefined} overrideTags
|
|
183
183
|
* @returns {Record<string, unknown>}
|
|
184
184
|
*/
|
|
185
|
-
function
|
|
186
|
-
return
|
|
185
|
+
function mergeTags (baseTags, overrideTags) {
|
|
186
|
+
return { ...baseTags, ...overrideTags }
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
/**
|
|
@@ -195,6 +195,40 @@ function sleep (ms) {
|
|
|
195
195
|
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
/**
|
|
199
|
+
* @param {unknown} value
|
|
200
|
+
* @param {number} fallback
|
|
201
|
+
* @returns {number}
|
|
202
|
+
*/
|
|
203
|
+
function timestampMs (value, fallback = Date.now()) {
|
|
204
|
+
if (value === null || value === undefined) return fallback
|
|
205
|
+
if (value instanceof Date) {
|
|
206
|
+
const timestamp = value.getTime()
|
|
207
|
+
return Number.isFinite(timestamp) ? timestamp : fallback
|
|
208
|
+
}
|
|
209
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value
|
|
210
|
+
const parsed = Date.parse(String(value))
|
|
211
|
+
return Number.isFinite(parsed) ? parsed : fallback
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* @param {{ durationMs?: unknown, completedAt?: unknown }} row
|
|
216
|
+
* @param {number} startMs
|
|
217
|
+
* @returns {number}
|
|
218
|
+
*/
|
|
219
|
+
function durationNs (row, startMs) {
|
|
220
|
+
if (typeof row.durationMs === 'number' && Number.isFinite(row.durationMs)) {
|
|
221
|
+
return Math.max(0, Math.round(row.durationMs * 1e6))
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (row.completedAt !== undefined) {
|
|
225
|
+
const completedMs = timestampMs(row.completedAt, startMs)
|
|
226
|
+
return Math.max(0, Math.round((completedMs - startMs) * 1e6))
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return 0
|
|
230
|
+
}
|
|
231
|
+
|
|
198
232
|
/**
|
|
199
233
|
* @param {Record<string, unknown> | undefined} recordMetadata
|
|
200
234
|
* @param {Record<string, unknown>} config
|
|
@@ -207,14 +241,16 @@ function buildSpanMetadata (recordMetadata, config) {
|
|
|
207
241
|
}
|
|
208
242
|
|
|
209
243
|
module.exports = {
|
|
210
|
-
buildExperimentTagObject,
|
|
211
244
|
buildSpanMetadata,
|
|
212
245
|
buildTags,
|
|
246
|
+
durationNs,
|
|
213
247
|
hasEntries,
|
|
214
248
|
inferMetricType,
|
|
249
|
+
mergeTags,
|
|
215
250
|
normalizeEvaluators,
|
|
216
251
|
normalizeJsonMetricValue,
|
|
217
252
|
sleep,
|
|
218
253
|
stringify,
|
|
254
|
+
timestampMs,
|
|
219
255
|
validateEvaluatorName,
|
|
220
256
|
}
|
|
@@ -228,6 +228,7 @@ function getJsonStringValue (str, defaultValue) {
|
|
|
228
228
|
function getModelMetadata (tags) {
|
|
229
229
|
/** @type {Record<string, unknown>} */
|
|
230
230
|
const modelMetadata = {}
|
|
231
|
+
let hasModelMetadata = false
|
|
231
232
|
for (const tag of Object.keys(tags)) {
|
|
232
233
|
const isModelMetadata = tag.startsWith(VERCEL_AI_MODEL_METADATA_PREFIX)
|
|
233
234
|
if (isModelMetadata) {
|
|
@@ -235,6 +236,7 @@ function getModelMetadata (tags) {
|
|
|
235
236
|
const metadataKey = lastCommaPosition === -1 ? tag : tag.slice(lastCommaPosition + 1)
|
|
236
237
|
if (metadataKey && MODEL_METADATA_KEYS.has(metadataKey)) {
|
|
237
238
|
modelMetadata[metadataKey] = tags[tag]
|
|
239
|
+
hasModelMetadata = true
|
|
238
240
|
}
|
|
239
241
|
} else {
|
|
240
242
|
const isTelemetryMetadata = tag.startsWith(VERCEL_AI_TELEMETRY_METADATA_PREFIX)
|
|
@@ -242,12 +244,13 @@ function getModelMetadata (tags) {
|
|
|
242
244
|
const metadataKey = tag.slice(VERCEL_AI_TELEMETRY_METADATA_PREFIX.length)
|
|
243
245
|
if (metadataKey) {
|
|
244
246
|
modelMetadata[metadataKey] = tags[tag]
|
|
247
|
+
hasModelMetadata = true
|
|
245
248
|
}
|
|
246
249
|
}
|
|
247
250
|
}
|
|
248
251
|
}
|
|
249
252
|
|
|
250
|
-
return
|
|
253
|
+
return hasModelMetadata ? modelMetadata : null
|
|
251
254
|
}
|
|
252
255
|
|
|
253
256
|
/**
|
|
@@ -259,6 +262,7 @@ function getModelMetadata (tags) {
|
|
|
259
262
|
function getGenerationMetadata (tags) {
|
|
260
263
|
/** @type {Record<string, unknown>} */
|
|
261
264
|
const metadata = {}
|
|
265
|
+
let hasMetadata = false
|
|
262
266
|
|
|
263
267
|
for (const tag of Object.keys(tags)) {
|
|
264
268
|
const isGenerationMetadata = tag.startsWith(VERCEL_AI_GENERATION_METADATA_PREFIX)
|
|
@@ -270,18 +274,20 @@ function getGenerationMetadata (tags) {
|
|
|
270
274
|
|
|
271
275
|
const settingValue = tags[tag]
|
|
272
276
|
metadata[settingKey] = settingValue
|
|
277
|
+
hasMetadata = true
|
|
273
278
|
} else {
|
|
274
279
|
const isTelemetryMetadata = tag.startsWith(VERCEL_AI_TELEMETRY_METADATA_PREFIX)
|
|
275
280
|
if (isTelemetryMetadata) {
|
|
276
281
|
const metadataKey = tag.slice(VERCEL_AI_TELEMETRY_METADATA_PREFIX.length)
|
|
277
282
|
if (metadataKey) {
|
|
278
283
|
metadata[metadataKey] = tags[tag]
|
|
284
|
+
hasMetadata = true
|
|
279
285
|
}
|
|
280
286
|
}
|
|
281
287
|
}
|
|
282
288
|
}
|
|
283
289
|
|
|
284
|
-
return
|
|
290
|
+
return hasMetadata ? metadata : null
|
|
285
291
|
}
|
|
286
292
|
|
|
287
293
|
/**
|
|
@@ -307,6 +313,7 @@ function getGenerationMetadataFromEvent (event) {
|
|
|
307
313
|
metadata[transformedKey] = value
|
|
308
314
|
}
|
|
309
315
|
|
|
316
|
+
// eslint-disable-next-line no-restricted-syntax -- manual tracking would duplicate Object.assign semantics
|
|
310
317
|
return Object.keys(metadata).length ? metadata : null
|
|
311
318
|
}
|
|
312
319
|
|
|
@@ -432,6 +439,7 @@ function getLlmObsSpanName (operation, functionId) {
|
|
|
432
439
|
*/
|
|
433
440
|
function getTelemetryMetadata (tags) {
|
|
434
441
|
const metadata = {}
|
|
442
|
+
let hasMetadata = false
|
|
435
443
|
|
|
436
444
|
for (const tag of Object.keys(tags)) {
|
|
437
445
|
if (!tag.startsWith(VERCEL_AI_TELEMETRY_METADATA_PREFIX)) continue
|
|
@@ -439,10 +447,11 @@ function getTelemetryMetadata (tags) {
|
|
|
439
447
|
const metadataKey = tag.slice(VERCEL_AI_TELEMETRY_METADATA_PREFIX.length)
|
|
440
448
|
if (metadataKey) {
|
|
441
449
|
metadata[metadataKey] = tags[tag]
|
|
450
|
+
hasMetadata = true
|
|
442
451
|
}
|
|
443
452
|
}
|
|
444
453
|
|
|
445
|
-
return
|
|
454
|
+
return hasMetadata ? metadata : null
|
|
446
455
|
}
|
|
447
456
|
|
|
448
457
|
module.exports = {
|
|
@@ -84,9 +84,11 @@ class LLMObsPlugin extends TracingPlugin {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
configure (config) {
|
|
87
|
-
// we do not want to enable any LLMObs plugins if it is disabled on the tracer
|
|
87
|
+
// we do not want to enable any LLMObs plugins if it is disabled on the tracer, or if the
|
|
88
|
+
// integration opted out via `tracer.use(<name>, { llmobs: false })`. Opting out only disables
|
|
89
|
+
// the LLMObs layer: the integration keeps emitting APM spans and propagating trace context.
|
|
88
90
|
const llmobsEnabled = this._tracerConfig.llmobs.DD_LLMOBS_ENABLED
|
|
89
|
-
if (llmobsEnabled === false) {
|
|
91
|
+
if (llmobsEnabled === false || config?.llmobs === false) {
|
|
90
92
|
config = typeof config === 'boolean' ? false : { ...config, enabled: false } // override to false
|
|
91
93
|
}
|
|
92
94
|
super.configure(config)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { features } = require('../feature-registry')
|
|
4
3
|
const NoopAppsecSdk = require('../appsec/sdk/noop')
|
|
5
4
|
const NoopLLMObsSDK = require('../llmobs/noop')
|
|
6
5
|
const NoopAIGuardSDK = require('../aiguard/noop')
|
|
6
|
+
const NoopFlaggingProvider = require('../openfeature/noop')
|
|
7
7
|
const NoopDogStatsDClient = require('./dogstatsd')
|
|
8
8
|
const NoopTracer = require('./tracer')
|
|
9
9
|
|
|
@@ -12,6 +12,7 @@ const noopAppsec = new NoopAppsecSdk()
|
|
|
12
12
|
const noopDogStatsDClient = new NoopDogStatsDClient()
|
|
13
13
|
const noopLLMObs = new NoopLLMObsSDK(noop)
|
|
14
14
|
const noopAIGuard = new NoopAIGuardSDK()
|
|
15
|
+
const noopFlaggingProviderInstance = new NoopFlaggingProvider()
|
|
15
16
|
const noopProfiling = {
|
|
16
17
|
setCustomLabelKeys () {},
|
|
17
18
|
runWithLabels (labels, fn) { return fn() },
|
|
@@ -25,9 +26,7 @@ class NoopProxy {
|
|
|
25
26
|
this.dogstatsd = noopDogStatsDClient
|
|
26
27
|
this.llmobs = noopLLMObs
|
|
27
28
|
this.aiguard = noopAIGuard
|
|
28
|
-
|
|
29
|
-
this[name] = noop
|
|
30
|
-
}
|
|
29
|
+
this.openfeature = noopFlaggingProviderInstance
|
|
31
30
|
this.setBaggageItem = (key, value) => {}
|
|
32
31
|
this.getBaggageItem = (key) => {}
|
|
33
32
|
this.getAllBaggageItems = () => {}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { channel } = require('dc-polyfill')
|
|
4
|
+
|
|
5
|
+
const { DatadogNodeServerProvider } = require('../../../../vendor/dist/@datadog/openfeature-node-server')
|
|
4
6
|
const log = require('../log')
|
|
5
7
|
const configurationSource = require('./configuration_source')
|
|
6
8
|
const { EXPOSURE_CHANNEL } = require('./constants/constants')
|
|
7
9
|
const EvalMetricsHook = require('./eval-metrics-hook')
|
|
8
10
|
const SpanEnrichmentHook = require('./span-enrichment-hook')
|
|
9
11
|
|
|
10
|
-
const { DatadogNodeServerProvider } = require('./require-provider')
|
|
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.
|
|
@@ -48,6 +48,22 @@ class FlaggingProvider extends DatadogNodeServerProvider {
|
|
|
48
48
|
this.#configurationSource?.start()
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* @param {import('@openfeature/core').EvaluationContext} [context]
|
|
53
|
+
* @returns {Promise<void>}
|
|
54
|
+
*/
|
|
55
|
+
initialize (context) {
|
|
56
|
+
const promise = super.initialize(context)
|
|
57
|
+
|
|
58
|
+
// `DatadogNodeServerProvider#initialize` starts a timer that is never unref'd, which would
|
|
59
|
+
// otherwise keep an idle process (a short script, a serverless handler) alive for up to
|
|
60
|
+
// `initializationTimeoutMs` while waiting for configuration to arrive.
|
|
61
|
+
// TODO: remove once `@datadog/openfeature-node-server` unrefs this timer itself.
|
|
62
|
+
this.initController?.timeoutId?.unref?.()
|
|
63
|
+
|
|
64
|
+
return promise
|
|
65
|
+
}
|
|
66
|
+
|
|
51
67
|
/**
|
|
52
68
|
* Called when the provider is shut down.
|
|
53
69
|
* Cleans up resources including channel subscriptions.
|
|
@@ -6,7 +6,7 @@ const RemoteConfigCapabilities = require('../remote_config/capabilities')
|
|
|
6
6
|
* Configures remote config handlers for openfeature feature flagging
|
|
7
7
|
*
|
|
8
8
|
* @param {import('../remote_config')} rc - RemoteConfig instance
|
|
9
|
-
* @param {() => import('./flagging_provider')} getOpenfeatureProxy
|
|
9
|
+
* @param {() => InstanceType<ReturnType<typeof import('./flagging_provider')>>} getOpenfeatureProxy
|
|
10
10
|
* @param {boolean} subscribe - Whether Agent Remote Config owns UFC delivery
|
|
11
11
|
*/
|
|
12
12
|
function enable (rc, getOpenfeatureProxy, subscribe) {
|
|
@@ -21,14 +21,14 @@ class ContextManager {
|
|
|
21
21
|
const storedSpan = store ? trace.getSpan(store) : null
|
|
22
22
|
|
|
23
23
|
// Convert DD baggage to OTel format
|
|
24
|
-
|
|
25
|
-
const
|
|
24
|
+
let entries
|
|
25
|
+
for (const [key, value] of Object.entries(getAllBaggageItems())) {
|
|
26
|
+
entries ??= {}
|
|
27
|
+
entries[key] = { value }
|
|
28
|
+
}
|
|
29
|
+
|
|
26
30
|
let otelBaggages
|
|
27
|
-
if (
|
|
28
|
-
const entries = {}
|
|
29
|
-
for (const [key, value] of Object.entries(baggages)) {
|
|
30
|
-
entries[key] = { value }
|
|
31
|
-
}
|
|
31
|
+
if (entries !== undefined) {
|
|
32
32
|
otelBaggages = propagation.createBaggage(entries)
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -42,7 +42,8 @@ class BatchLogRecordProcessor {
|
|
|
42
42
|
*/
|
|
43
43
|
onEmit (logRecord, instrumentationScope) {
|
|
44
44
|
// Store the log record (already enriched by Logger.emit)
|
|
45
|
-
|
|
45
|
+
logRecord.instrumentationScope = instrumentationScope
|
|
46
|
+
this.#logRecords.push(logRecord)
|
|
46
47
|
|
|
47
48
|
if (this.#logRecords.length >= this.#maxExportBatchSize) {
|
|
48
49
|
this.#export()
|
|
@@ -1,8 +1,65 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { context } = require('@opentelemetry/api')
|
|
4
|
-
const {
|
|
4
|
+
const {
|
|
5
|
+
millisToHrTime,
|
|
6
|
+
sanitizeAttributes,
|
|
7
|
+
timeInputToHrTime,
|
|
8
|
+
} = require('../../../../../vendor/dist/@opentelemetry/core')
|
|
5
9
|
const { VERSION: packageVersion } = require('../../../../../version')
|
|
10
|
+
const log = require('../../log')
|
|
11
|
+
|
|
12
|
+
const MAX_DATE_MILLISECONDS = 8.64e15
|
|
13
|
+
const NANOSECONDS_PER_SECOND = 1e9
|
|
14
|
+
const MAX_UINT64_SECONDS = 18_446_744_073
|
|
15
|
+
const MAX_UINT64_NANOSECONDS = 709_551_615
|
|
16
|
+
let invalidTimestampWarningLogged = false
|
|
17
|
+
|
|
18
|
+
function isValidHrTime (hrTime) {
|
|
19
|
+
return Array.isArray(hrTime) &&
|
|
20
|
+
hrTime.length === 2 &&
|
|
21
|
+
Number.isInteger(hrTime[0]) &&
|
|
22
|
+
Number.isInteger(hrTime[1]) &&
|
|
23
|
+
hrTime[0] >= 0 &&
|
|
24
|
+
hrTime[1] >= 0 &&
|
|
25
|
+
hrTime[1] < NANOSECONDS_PER_SECOND &&
|
|
26
|
+
(hrTime[0] < MAX_UINT64_SECONDS ||
|
|
27
|
+
(hrTime[0] === MAX_UINT64_SECONDS && hrTime[1] <= MAX_UINT64_NANOSECONDS))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function toHrTime (timestamp) {
|
|
31
|
+
if (typeof timestamp === 'number') {
|
|
32
|
+
if (!Number.isFinite(timestamp)) return
|
|
33
|
+
|
|
34
|
+
// Older versions documented numeric timestamps as Unix nanoseconds.
|
|
35
|
+
if (Math.abs(timestamp) > MAX_DATE_MILLISECONDS) {
|
|
36
|
+
const seconds = Math.trunc(timestamp / NANOSECONDS_PER_SECOND)
|
|
37
|
+
const hrTime = [seconds, timestamp - seconds * NANOSECONDS_PER_SECOND]
|
|
38
|
+
return isValidHrTime(hrTime) ? hrTime : undefined
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const hrTime = timeInputToHrTime(timestamp)
|
|
44
|
+
return isValidHrTime(hrTime) ? hrTime : undefined
|
|
45
|
+
} catch {
|
|
46
|
+
// Invalid timestamps fall back to the current time in normalizeTimestamp.
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function normalizeTimestamp (timestamp) {
|
|
51
|
+
if (timestamp !== undefined) {
|
|
52
|
+
const hrTime = toHrTime(timestamp)
|
|
53
|
+
if (hrTime) return hrTime
|
|
54
|
+
|
|
55
|
+
if (!invalidTimestampWarningLogged) {
|
|
56
|
+
invalidTimestampWarningLogged = true
|
|
57
|
+
log.warn('Invalid OpenTelemetry log timestamp; using the current time instead')
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return millisToHrTime(Date.now())
|
|
62
|
+
}
|
|
6
63
|
|
|
7
64
|
/**
|
|
8
65
|
* @typedef {import('@opentelemetry/api-logs').LogRecord} LogRecord
|
|
@@ -62,21 +119,20 @@ class Logger {
|
|
|
62
119
|
return
|
|
63
120
|
}
|
|
64
121
|
|
|
65
|
-
|
|
66
|
-
logRecord
|
|
122
|
+
const record = {
|
|
123
|
+
...logRecord,
|
|
124
|
+
timestamp: normalizeTimestamp(logRecord.timestamp),
|
|
125
|
+
context: logRecord.context || context.active(),
|
|
67
126
|
}
|
|
68
127
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
logRecord.timestamp = Number(process.hrtime.bigint())
|
|
128
|
+
if (logRecord.observedTimestamp !== undefined) {
|
|
129
|
+
record.observedTimestamp = normalizeTimestamp(logRecord.observedTimestamp)
|
|
72
130
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
// Store span context in the log record context for trace correlation
|
|
76
|
-
logRecord.context = context.active()
|
|
131
|
+
if (logRecord.attributes) {
|
|
132
|
+
record.attributes = sanitizeAttributes(logRecord.attributes)
|
|
77
133
|
}
|
|
78
134
|
|
|
79
|
-
this.loggerProvider.processor.onEmit(
|
|
135
|
+
this.loggerProvider.processor.onEmit(record, this.#instrumentationScope)
|
|
80
136
|
}
|
|
81
137
|
}
|
|
82
138
|
|
|
@@ -38,6 +38,17 @@ const SEVERITY_MAP = {
|
|
|
38
38
|
[SeverityNumber.FATAL4]: 'SEVERITY_NUMBER_FATAL4',
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
const NANOSECONDS_PER_SECOND = 1_000_000_000n
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Converts an OpenTelemetry HrTime to an exact OTLP uint64 decimal string.
|
|
45
|
+
* @param {import('@opentelemetry/api').HrTime} hrTime
|
|
46
|
+
* @returns {string}
|
|
47
|
+
*/
|
|
48
|
+
function encodeHrTime (hrTime) {
|
|
49
|
+
return (BigInt(hrTime[0]) * NANOSECONDS_PER_SECOND + BigInt(hrTime[1])).toString()
|
|
50
|
+
}
|
|
51
|
+
|
|
41
52
|
/**
|
|
42
53
|
* OtlpTransformer transforms log records to OTLP format.
|
|
43
54
|
*
|
|
@@ -138,13 +149,13 @@ class OtlpTransformer extends OtlpTransformerBase {
|
|
|
138
149
|
const spanContext = this.#extractSpanContext(logRecord.context)
|
|
139
150
|
|
|
140
151
|
const result = {
|
|
141
|
-
timeUnixNano: logRecord.timestamp,
|
|
152
|
+
timeUnixNano: encodeHrTime(logRecord.timestamp),
|
|
142
153
|
body: this.#transformBody(logRecord.body),
|
|
143
154
|
}
|
|
144
155
|
|
|
145
156
|
// Add optional fields only if they are set
|
|
146
|
-
if (logRecord.observedTimestamp) {
|
|
147
|
-
result.observedTimeUnixNano = logRecord.observedTimestamp
|
|
157
|
+
if (logRecord.observedTimestamp !== undefined) {
|
|
158
|
+
result.observedTimeUnixNano = encodeHrTime(logRecord.observedTimestamp)
|
|
148
159
|
}
|
|
149
160
|
|
|
150
161
|
if (logRecord.severityNumber !== undefined) {
|
|
@@ -10,6 +10,8 @@ const MeterProvider = require('./meter_provider')
|
|
|
10
10
|
const PeriodicMetricReader = require('./periodic_metric_reader')
|
|
11
11
|
const OtlpHttpMetricExporter = require('./otlp_http_metric_exporter')
|
|
12
12
|
|
|
13
|
+
const RESERVED_TRACER_TAGS = new Set(['service', 'env', 'version', 'runtime_id', 'runtime-id'])
|
|
14
|
+
|
|
13
15
|
/**
|
|
14
16
|
* @typedef {import('../../config')} Config
|
|
15
17
|
*/
|
|
@@ -78,7 +80,16 @@ function initializeOpenTelemetryMetrics (config) {
|
|
|
78
80
|
metrics.setGlobalMeterProvider(meterProvider)
|
|
79
81
|
}
|
|
80
82
|
|
|
81
|
-
|
|
83
|
+
/**
|
|
84
|
+
* @param {Record<string, unknown>} tags
|
|
85
|
+
* @param {object} [options]
|
|
86
|
+
* @param {boolean} [options.reportHostname]
|
|
87
|
+
* @param {string} [options.service]
|
|
88
|
+
* @param {string} [options.env]
|
|
89
|
+
* @param {string} [options.serviceVersion]
|
|
90
|
+
* @returns {import('@opentelemetry/api').Attributes}
|
|
91
|
+
*/
|
|
92
|
+
function buildResourceAttributes (tags, { reportHostname, service, env, serviceVersion } = {}) {
|
|
82
93
|
const attrs = {
|
|
83
94
|
'telemetry.sdk.name': 'datadog',
|
|
84
95
|
'telemetry.sdk.language': 'nodejs',
|
|
@@ -89,14 +100,19 @@ function buildResourceAttributes (tags, { reportHostname, otelSemanticsEnabled,
|
|
|
89
100
|
if (env) attrs['deployment.environment.name'] = env
|
|
90
101
|
if (reportHostname) attrs['host.name'] = os.hostname()
|
|
91
102
|
|
|
92
|
-
if (
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
103
|
+
if (tags['runtime-id']) attrs['datadog.runtime_id'] = tags['runtime-id']
|
|
104
|
+
const tracerTags = []
|
|
105
|
+
for (const [key, value] of Object.entries(tags)) {
|
|
106
|
+
const valueType = typeof value
|
|
107
|
+
const supported = valueType === 'string' || valueType === 'boolean' ||
|
|
108
|
+
(valueType === 'number' && Number.isFinite(value))
|
|
109
|
+
if (!RESERVED_TRACER_TAGS.has(key) && supported) tracerTags.push(`${key}:${value}`)
|
|
110
|
+
}
|
|
111
|
+
if (tracerTags.length) attrs['datadog.tracer_tags'] = tracerTags
|
|
112
|
+
// Mirrors the legacy v0.6/stats ProcessTags shape (buildProcessTags().tagsArray); keep both in sync.
|
|
113
|
+
const processTagsArray = processTags.tagsArray
|
|
114
|
+
if (processTagsArray.length) {
|
|
115
|
+
attrs['datadog.process_tags'] = processTagsArray
|
|
100
116
|
}
|
|
101
117
|
return attrs
|
|
102
118
|
}
|
|
@@ -106,7 +122,6 @@ function createOtlpSpanStatsExporter (config) {
|
|
|
106
122
|
const protocol = config.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL || 'http/json'
|
|
107
123
|
const resourceAttributes = buildResourceAttributes(config.tags, {
|
|
108
124
|
reportHostname: config.reportHostname,
|
|
109
|
-
otelSemanticsEnabled: config.DD_TRACE_OTEL_SEMANTICS_ENABLED,
|
|
110
125
|
service: config.service,
|
|
111
126
|
env: config.env,
|
|
112
127
|
serviceVersion: config.version,
|
|
@@ -115,8 +130,6 @@ function createOtlpSpanStatsExporter (config) {
|
|
|
115
130
|
config.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT,
|
|
116
131
|
protocol,
|
|
117
132
|
resourceAttributes,
|
|
118
|
-
config.DD_TRACE_OTEL_SEMANTICS_ENABLED,
|
|
119
|
-
config.service,
|
|
120
133
|
config.OTEL_EXPORTER_OTLP_METRICS_HEADERS,
|
|
121
134
|
config.OTEL_EXPORTER_OTLP_METRICS_TIMEOUT
|
|
122
135
|
)
|
|
@@ -11,15 +11,12 @@ class OtlpStatsExporter extends OtlpHttpExporterBase {
|
|
|
11
11
|
* @param {string} url
|
|
12
12
|
* @param {string} protocol
|
|
13
13
|
* @param {import('@opentelemetry/api').Attributes} resourceAttributes
|
|
14
|
-
* @param {boolean} [otelSemanticsEnabled]
|
|
15
|
-
* @param {string} [defaultService]
|
|
16
14
|
* @param {Record<string, string>} [headers]
|
|
17
15
|
* @param {number} [timeout]
|
|
18
16
|
*/
|
|
19
|
-
constructor (url, protocol, resourceAttributes,
|
|
20
|
-
headers, timeout = 10_000) {
|
|
17
|
+
constructor (url, protocol, resourceAttributes, headers, timeout = 10_000) {
|
|
21
18
|
super(url, headers, timeout, protocol, 'span-stats')
|
|
22
|
-
this.#transformer = new OtlpStatsTransformer(resourceAttributes, protocol
|
|
19
|
+
this.#transformer = new OtlpStatsTransformer(resourceAttributes, protocol)
|
|
23
20
|
}
|
|
24
21
|
|
|
25
22
|
/**
|