dd-trace 6.7.0 → 6.8.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/test-optimization-validation/command-output-policy.js +5 -5
- package/ci/test-optimization-validation/framework-adapters/playwright.js +5 -5
- package/ci/test-optimization-validation/generated-files.js +9 -9
- package/ci/test-optimization-validation/generated-test-contract.js +1 -3
- package/ci/test-optimization-validation/manifest-scaffold.js +1 -2
- package/ci/test-optimization-validation/offline-output.js +6 -6
- package/ci/test-optimization-validation/runner-command.js +1 -1
- package/ci/test-optimization-validation/safe-files.js +4 -4
- package/ci/test-optimization-validation/scenarios/basic-reporting.js +1 -1
- package/ci/test-optimization-validation/scenarios/test-management.js +2 -2
- package/ci/vitest-no-worker-init-setup.mjs +348 -51
- package/index.d.ts +77 -13
- package/package.json +9 -3
- package/packages/datadog-core/src/utils/src/kebabcase.js +8 -2
- package/packages/datadog-esbuild/index.js +4 -2
- package/packages/datadog-esbuild/src/utils.js +1 -1
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +4 -4
- package/packages/datadog-instrumentations/src/cucumber.js +9 -6
- package/packages/datadog-instrumentations/src/helpers/extract-package-and-module-path.js +10 -0
- package/packages/datadog-instrumentations/src/helpers/hook.js +19 -2
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +9 -3
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +57 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +50 -18
- package/packages/datadog-instrumentations/src/helpers/router-helper.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/shared-utils.js +39 -0
- package/packages/datadog-instrumentations/src/http/client.js +1 -3
- package/packages/datadog-instrumentations/src/jest.js +8 -14
- package/packages/datadog-instrumentations/src/mocha/common.js +1 -3
- package/packages/datadog-instrumentations/src/mocha/main.js +2 -42
- package/packages/datadog-instrumentations/src/mocha/utils.js +68 -2
- package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +19 -0
- package/packages/datadog-instrumentations/src/mocha/worker.js +446 -42
- package/packages/datadog-instrumentations/src/moleculer/client.js +1 -1
- package/packages/datadog-instrumentations/src/playwright.js +3 -2
- package/packages/datadog-instrumentations/src/rhea.js +1 -1
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +525 -56
- package/packages/datadog-instrumentations/src/vitest-main.js +90 -10
- package/packages/datadog-instrumentations/src/webdriverio.js +914 -0
- package/packages/datadog-plugin-aerospike/src/index.js +1 -3
- package/packages/datadog-plugin-amqplib/src/client.js +1 -1
- package/packages/datadog-plugin-amqplib/src/producer.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +2 -2
- package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +2 -2
- package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -3
- package/packages/datadog-plugin-azure-service-bus/src/producer.js +5 -3
- package/packages/datadog-plugin-bullmq/src/producer.js +3 -3
- package/packages/datadog-plugin-child_process/src/index.js +3 -3
- package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +5 -8
- package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -1
- package/packages/datadog-plugin-fastify/src/tracing.js +1 -1
- package/packages/datadog-plugin-fs/src/index.js +0 -4
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +14 -7
- package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +19 -10
- package/packages/datadog-plugin-graphql/src/execute.js +1 -0
- package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -1
- package/packages/datadog-plugin-http/src/client.js +2 -1
- package/packages/datadog-plugin-jest/src/index.js +2 -3
- package/packages/datadog-plugin-jest/src/util.js +2 -1
- package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
- package/packages/datadog-plugin-mocha/src/index.js +25 -6
- package/packages/datadog-plugin-prisma/src/index.js +5 -1
- package/packages/datadog-plugin-rhea/src/consumer.js +16 -13
- package/packages/datadog-plugin-rhea/src/producer.js +1 -1
- package/packages/datadog-plugin-undici/src/index.js +2 -1
- package/packages/datadog-plugin-vitest/src/index.js +84 -11
- package/packages/datadog-webpack/index.js +2 -2
- package/packages/dd-trace/src/appsec/downstream_requests.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-base-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +1 -3
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +2 -2
- package/packages/dd-trace/src/appsec/sdk/track_event.js +1 -1
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +7 -6
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +2 -2
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +2 -1
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +4 -0
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/webdriverio.js +26 -0
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -1
- package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -3
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +1 -1
- package/packages/dd-trace/src/config/defaults.js +14 -6
- package/packages/dd-trace/src/config/generated-config-types.d.ts +2 -2
- package/packages/dd-trace/src/config/index.js +15 -0
- package/packages/dd-trace/src/config/parsers.js +19 -1
- package/packages/dd-trace/src/config/supported-configurations.json +5 -6
- package/packages/dd-trace/src/datastreams/encoding.js +3 -4
- package/packages/dd-trace/src/debugger/devtools_client/log.js +2 -1
- package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +1 -4
- package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -2
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +2 -2
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +16 -2
- package/packages/dd-trace/src/encode/tags-processors.js +2 -2
- package/packages/dd-trace/src/exporters/common/url.js +3 -5
- package/packages/dd-trace/src/exporters/electron/index.js +4 -1
- package/packages/dd-trace/src/external-logger/src/index.js +1 -2
- package/packages/dd-trace/src/flare/index.js +2 -2
- package/packages/dd-trace/src/id.js +1 -1
- package/packages/dd-trace/src/llmobs/constants/tags.js +1 -1
- package/packages/dd-trace/src/llmobs/experiments/client.js +4 -2
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +66 -14
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +289 -95
- package/packages/dd-trace/src/llmobs/experiments/index.js +51 -12
- package/packages/dd-trace/src/llmobs/experiments/noop.js +95 -10
- package/packages/dd-trace/src/llmobs/experiments/result.js +13 -2
- package/packages/dd-trace/src/llmobs/experiments/util.js +218 -0
- package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
- package/packages/dd-trace/src/llmobs/sdk.js +4 -11
- package/packages/dd-trace/src/llmobs/span_processor.js +9 -7
- package/packages/dd-trace/src/llmobs/tagger.js +1 -2
- package/packages/dd-trace/src/llmobs/writers/spans.js +12 -6
- package/packages/dd-trace/src/noop/proxy.js +2 -2
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +2 -1
- package/packages/dd-trace/src/opentracing/span.js +1 -1
- package/packages/dd-trace/src/opentracing/span_context.js +1 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +105 -20
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/plugin.js +1 -1
- package/packages/dd-trace/src/plugins/tracing.js +1 -0
- package/packages/dd-trace/src/plugins/util/ci.js +7 -6
- package/packages/dd-trace/src/plugins/util/git.js +1 -1
- package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +1 -1
- package/packages/dd-trace/src/plugins/util/llm.js +2 -2
- package/packages/dd-trace/src/plugins/util/tags.js +2 -0
- package/packages/dd-trace/src/plugins/util/test.js +38 -12
- package/packages/dd-trace/src/plugins/util/url.js +5 -5
- package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -1
- package/packages/dd-trace/src/plugins/util/web.js +1 -1
- package/packages/dd-trace/src/priority_sampler.js +6 -3
- package/packages/dd-trace/src/profiling/profilers/poisson.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/shared.js +2 -2
- package/packages/dd-trace/src/ritm.js +5 -20
- package/packages/dd-trace/src/startup-log.js +3 -0
- package/packages/dd-trace/src/telemetry/logs/log-collector.js +3 -1
- package/packages/dd-trace/src/util.js +16 -0
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +12 -12
- package/vendor/dist/pprof-format/index.js +1 -1
- package/vendor/dist/source-map/index.js +1 -1
|
@@ -1,8 +1,90 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const log = require('../../log')
|
|
4
|
+
|
|
5
|
+
class NoopDataset {
|
|
6
|
+
#name
|
|
7
|
+
#records
|
|
8
|
+
|
|
9
|
+
constructor (name = '', options = {}) {
|
|
10
|
+
this.#name = name
|
|
11
|
+
this.#records = (typeof options === 'string' ? [] : (options.records ?? [])).map(record => ({
|
|
12
|
+
id: record.id ?? null,
|
|
13
|
+
input: record.inputData,
|
|
14
|
+
expectedOutput: record.expectedOutput ?? null,
|
|
15
|
+
metadata: record.metadata ?? {},
|
|
16
|
+
}))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
addRecord (input, expectedOutput, metadata) {
|
|
20
|
+
this.#records.push({ id: null, input, expectedOutput: expectedOutput ?? null, metadata: metadata ?? {} })
|
|
21
|
+
return this
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
push () {
|
|
25
|
+
return Promise.resolve({ pushedCount: 0, totalCount: 0 })
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
name () {
|
|
29
|
+
return this.#name
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
id () {
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
projectId () {
|
|
37
|
+
return null
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
version () {
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
latestVersion () {
|
|
45
|
+
return null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
records () {
|
|
49
|
+
return [...this.#records]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
recordIds () {
|
|
53
|
+
return []
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
url () {
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
class NoopExperiment {
|
|
62
|
+
#name
|
|
63
|
+
|
|
64
|
+
constructor (name = '') {
|
|
65
|
+
this.#name = name
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
name () {
|
|
69
|
+
return this.#name
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
experimentId () {
|
|
73
|
+
return null
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
url () {
|
|
77
|
+
return null
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
run () {
|
|
81
|
+
return Promise.resolve({ experimentId: null, rows: [], url: null })
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
3
85
|
// No-op Experiments used when LLM Observability is disabled or the API/APP keys
|
|
4
|
-
// are not configured.
|
|
5
|
-
//
|
|
86
|
+
// are not configured. Operations warn and return inert objects rather than
|
|
87
|
+
// throwing, so intentionally disabled experiments remain graceful.
|
|
6
88
|
class NoopExperiments {
|
|
7
89
|
#reason
|
|
8
90
|
|
|
@@ -10,20 +92,23 @@ class NoopExperiments {
|
|
|
10
92
|
this.#reason = reason || 'LLMObs experiments are not available'
|
|
11
93
|
}
|
|
12
94
|
|
|
13
|
-
#
|
|
14
|
-
|
|
95
|
+
#warn () {
|
|
96
|
+
log.warn('LLMObs experiments unavailable: %s', this.#reason)
|
|
15
97
|
}
|
|
16
98
|
|
|
17
|
-
createDataset () {
|
|
18
|
-
|
|
99
|
+
createDataset (name, options = {}) {
|
|
100
|
+
this.#warn()
|
|
101
|
+
return new NoopDataset(name, options)
|
|
19
102
|
}
|
|
20
103
|
|
|
21
|
-
pullDataset () {
|
|
22
|
-
|
|
104
|
+
pullDataset (name) {
|
|
105
|
+
this.#warn()
|
|
106
|
+
return Promise.resolve(new NoopDataset(name))
|
|
23
107
|
}
|
|
24
108
|
|
|
25
|
-
experiment () {
|
|
26
|
-
|
|
109
|
+
experiment (options = {}) {
|
|
110
|
+
this.#warn()
|
|
111
|
+
return new NoopExperiment(options.name)
|
|
27
112
|
}
|
|
28
113
|
}
|
|
29
114
|
|
|
@@ -22,13 +22,24 @@ class Row {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
class ExperimentRun {
|
|
26
|
+
constructor (fields) {
|
|
27
|
+
this.runId = fields.runId
|
|
28
|
+
this.runIteration = fields.runIteration
|
|
29
|
+
this.rows = fields.rows
|
|
30
|
+
this.summaryEvaluations = fields.summaryEvaluations
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
25
34
|
// Returned by Experiment.run().
|
|
26
35
|
class ExperimentResult {
|
|
27
|
-
constructor (experimentId, rows, url) {
|
|
36
|
+
constructor (experimentId, rows, url, runs = [], summaryEvaluations = {}) {
|
|
28
37
|
this.experimentId = experimentId
|
|
29
38
|
this.rows = rows
|
|
30
39
|
this.url = url
|
|
40
|
+
this.runs = runs
|
|
41
|
+
this.summaryEvaluations = summaryEvaluations
|
|
31
42
|
}
|
|
32
43
|
}
|
|
33
44
|
|
|
34
|
-
module.exports = { Row, ExperimentResult }
|
|
45
|
+
module.exports = { Row, ExperimentRun, ExperimentResult }
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const log = require('../../log')
|
|
4
|
+
|
|
5
|
+
// Matches the backend and dd-trace-py evaluator metric label contract.
|
|
6
|
+
const EVALUATOR_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {null | string | number | boolean | NormalizedJsonValue[] | { [key: string]: NormalizedJsonValue }}
|
|
10
|
+
* NormalizedJsonValue
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {object | null | undefined} value
|
|
15
|
+
* @returns {boolean}
|
|
16
|
+
*/
|
|
17
|
+
function hasEntries (value) {
|
|
18
|
+
if (!value) return false
|
|
19
|
+
for (const key of Object.keys(value)) {
|
|
20
|
+
if (Object.hasOwn(value, key)) return true
|
|
21
|
+
}
|
|
22
|
+
return false
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {string} name
|
|
27
|
+
*/
|
|
28
|
+
function validateEvaluatorName (name) {
|
|
29
|
+
if (typeof name !== 'string') throw new TypeError('Evaluator name must be a string')
|
|
30
|
+
if (name.length === 0) throw new Error('Evaluator name cannot be empty')
|
|
31
|
+
if (!EVALUATOR_NAME_PATTERN.test(name)) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`Evaluator name '${name}' is invalid. Name must contain only alphanumeric characters, underscores, and hyphens.`
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {(...args: unknown[]) => unknown} fn
|
|
40
|
+
* @param {string} fallback
|
|
41
|
+
* @returns {string}
|
|
42
|
+
*/
|
|
43
|
+
function functionName (fn, fallback) {
|
|
44
|
+
return typeof fn.name === 'string' && fn.name.length > 0 ? fn.name : fallback
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {unknown} evaluators
|
|
49
|
+
* @param {string} kind
|
|
50
|
+
* @returns {Array<[string, (...args: unknown[]) => unknown]>}
|
|
51
|
+
*/
|
|
52
|
+
function normalizeEvaluators (evaluators, kind) {
|
|
53
|
+
if (evaluators == null) return []
|
|
54
|
+
|
|
55
|
+
const normalized = []
|
|
56
|
+
if (Array.isArray(evaluators)) {
|
|
57
|
+
const indexesByName = new Map()
|
|
58
|
+
for (let i = 0; i < evaluators.length; i++) {
|
|
59
|
+
const evaluator = evaluators[i]
|
|
60
|
+
if (typeof evaluator !== 'function') throw new TypeError(`${kind} evaluator must be a function`)
|
|
61
|
+
const name = functionName(evaluator, `${kind}_evaluator_${i}`)
|
|
62
|
+
validateEvaluatorName(name)
|
|
63
|
+
if (indexesByName.has(name)) {
|
|
64
|
+
log.warn('Duplicate %s evaluator name %s; previous evaluator will be overwritten', kind, name)
|
|
65
|
+
normalized[indexesByName.get(name)] = [name, evaluator]
|
|
66
|
+
} else {
|
|
67
|
+
indexesByName.set(name, normalized.length)
|
|
68
|
+
normalized.push([name, evaluator])
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return normalized
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (typeof evaluators !== 'object') {
|
|
75
|
+
throw new TypeError(`${kind} evaluators must be an array of functions or an object keyed by evaluator name`)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
for (const [name, evaluator] of Object.entries(evaluators)) {
|
|
79
|
+
validateEvaluatorName(name)
|
|
80
|
+
if (typeof evaluator !== 'function') throw new TypeError(`${kind} evaluator '${name}' must be a function`)
|
|
81
|
+
normalized.push([name, evaluator])
|
|
82
|
+
}
|
|
83
|
+
return normalized
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @param {unknown} value
|
|
88
|
+
* @returns {string}
|
|
89
|
+
*/
|
|
90
|
+
function inferMetricType (value) {
|
|
91
|
+
if (typeof value === 'boolean') return 'boolean'
|
|
92
|
+
if (typeof value === 'number' && Number.isFinite(value)) return 'score'
|
|
93
|
+
if (typeof value === 'string') return 'categorical'
|
|
94
|
+
return 'json'
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @param {unknown} value
|
|
99
|
+
* @param {WeakSet<object>} seen
|
|
100
|
+
* @returns {NormalizedJsonValue}
|
|
101
|
+
*/
|
|
102
|
+
function normalizeJsonValue (value, seen) {
|
|
103
|
+
if (value === null) return null
|
|
104
|
+
|
|
105
|
+
const valueType = typeof value
|
|
106
|
+
if (valueType === 'string' || valueType === 'boolean') return value
|
|
107
|
+
if (valueType === 'number') return Number.isFinite(value) ? value : String(value)
|
|
108
|
+
if (valueType === 'bigint') return value.toString()
|
|
109
|
+
if (valueType === 'undefined') return null
|
|
110
|
+
if (valueType === 'symbol') return value.toString()
|
|
111
|
+
if (valueType === 'function') return { type: 'function', name: value.name || '<anonymous>' }
|
|
112
|
+
|
|
113
|
+
if (value instanceof Date) return value.toISOString()
|
|
114
|
+
if (value instanceof Error) {
|
|
115
|
+
return { type: value.name, message: value.message, stack: value.stack ?? '' }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (seen.has(value)) return '[Circular]'
|
|
119
|
+
seen.add(value)
|
|
120
|
+
|
|
121
|
+
if (Array.isArray(value)) {
|
|
122
|
+
const normalized = new Array(value.length)
|
|
123
|
+
for (let i = 0; i < value.length; i++) normalized[i] = normalizeJsonValue(value[i], seen)
|
|
124
|
+
seen.delete(value)
|
|
125
|
+
return normalized
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const normalized = {}
|
|
129
|
+
for (const [key, nestedValue] of Object.entries(value)) {
|
|
130
|
+
normalized[key] = normalizeJsonValue(nestedValue, seen)
|
|
131
|
+
}
|
|
132
|
+
seen.delete(value)
|
|
133
|
+
return normalized
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @param {unknown} value
|
|
138
|
+
* @returns {{ [key: string]: NormalizedJsonValue }}
|
|
139
|
+
*/
|
|
140
|
+
function normalizeJsonMetricValue (value) {
|
|
141
|
+
const normalized = normalizeJsonValue(value, new WeakSet())
|
|
142
|
+
if (normalized !== null && typeof normalized === 'object' && !Array.isArray(normalized)) return normalized
|
|
143
|
+
return { value: normalized }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* @param {unknown} value
|
|
148
|
+
* @returns {string}
|
|
149
|
+
*/
|
|
150
|
+
function stringify (value) {
|
|
151
|
+
if (value == null) return ''
|
|
152
|
+
if (typeof value === 'string') return value.toLowerCase()
|
|
153
|
+
if (typeof value === 'object') {
|
|
154
|
+
try {
|
|
155
|
+
const stringified = JSON.stringify(value)
|
|
156
|
+
if (stringified !== undefined) return stringified.toLowerCase()
|
|
157
|
+
} catch {}
|
|
158
|
+
}
|
|
159
|
+
return String(value).toLowerCase()
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* @param {Record<string, unknown> | undefined} userTags
|
|
164
|
+
* @param {Record<string, unknown>} autoTags
|
|
165
|
+
* @returns {string[]}
|
|
166
|
+
*/
|
|
167
|
+
function buildTags (userTags, autoTags) {
|
|
168
|
+
const tags = new Map()
|
|
169
|
+
for (const [key, value] of Object.entries(userTags ?? {})) {
|
|
170
|
+
tags.set(key, `${key}:${value}`)
|
|
171
|
+
}
|
|
172
|
+
for (const [key, value] of Object.entries(autoTags)) {
|
|
173
|
+
if (value !== undefined && value !== null && value !== '') tags.set(key, `${key}:${value}`)
|
|
174
|
+
}
|
|
175
|
+
return [...tags.values()]
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* @param {Record<string, unknown> | undefined} userTags
|
|
180
|
+
* @param {Record<string, unknown>} autoTags
|
|
181
|
+
* @returns {Record<string, unknown>}
|
|
182
|
+
*/
|
|
183
|
+
function buildExperimentTagObject (userTags, autoTags) {
|
|
184
|
+
return userTags ? { ...userTags, ...autoTags } : { ...autoTags }
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @param {number} ms
|
|
189
|
+
* @returns {Promise<void>}
|
|
190
|
+
*/
|
|
191
|
+
function sleep (ms) {
|
|
192
|
+
if (ms <= 0) return Promise.resolve()
|
|
193
|
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* @param {Record<string, unknown> | undefined} recordMetadata
|
|
198
|
+
* @param {Record<string, unknown>} config
|
|
199
|
+
* @returns {Record<string, unknown>}
|
|
200
|
+
*/
|
|
201
|
+
function buildSpanMetadata (recordMetadata, config) {
|
|
202
|
+
return recordMetadata
|
|
203
|
+
? { ...recordMetadata, experiment_config: config }
|
|
204
|
+
: { experiment_config: config }
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
module.exports = {
|
|
208
|
+
buildExperimentTagObject,
|
|
209
|
+
buildSpanMetadata,
|
|
210
|
+
buildTags,
|
|
211
|
+
hasEntries,
|
|
212
|
+
inferMetricType,
|
|
213
|
+
normalizeEvaluators,
|
|
214
|
+
normalizeJsonMetricValue,
|
|
215
|
+
sleep,
|
|
216
|
+
stringify,
|
|
217
|
+
validateEvaluatorName,
|
|
218
|
+
}
|
|
@@ -93,7 +93,7 @@ class AnthropicLLMObsPlugin extends LLMObsPlugin {
|
|
|
93
93
|
|
|
94
94
|
const { usage } = chunk
|
|
95
95
|
if (usage) {
|
|
96
|
-
const responseUsage =
|
|
96
|
+
const responseUsage = (response.usage ??= { input_tokens: 0, output_tokens: 0 })
|
|
97
97
|
responseUsage.output_tokens = usage.output_tokens
|
|
98
98
|
|
|
99
99
|
const cacheCreationTokens = usage.cache_creation_input_tokens
|
|
@@ -8,7 +8,7 @@ const { splitModel } = require('../../../../../datadog-plugin-claude-agent-sdk/s
|
|
|
8
8
|
const subagentToolIds = new Set()
|
|
9
9
|
|
|
10
10
|
function normalizeToolOutputString (raw) {
|
|
11
|
-
const footerIndex = raw.search(
|
|
11
|
+
const footerIndex = raw.search(/agentId: \S+ \(use SendMessage\b/)
|
|
12
12
|
if (footerIndex === -1) return raw
|
|
13
13
|
|
|
14
14
|
return raw.slice(0, footerIndex).trimEnd()
|
|
@@ -24,7 +24,7 @@ class GenAiLLMObsPlugin extends LLMObsPlugin {
|
|
|
24
24
|
// Subscribe to streaming chunk events
|
|
25
25
|
this.addSub('apm:google:genai:request:chunk', ({ ctx, chunk, done }) => {
|
|
26
26
|
ctx.isStreaming = true
|
|
27
|
-
ctx.chunks
|
|
27
|
+
ctx.chunks ||= []
|
|
28
28
|
|
|
29
29
|
if (chunk) ctx.chunks.push(chunk)
|
|
30
30
|
if (!done) return
|
|
@@ -36,7 +36,7 @@ class LangChainLLMObsHandler {
|
|
|
36
36
|
const runIdBase = runId ? runId.split('-').slice(0, -1).join('-') : ''
|
|
37
37
|
|
|
38
38
|
const responseMetadata = message.response_metadata || {}
|
|
39
|
-
usage
|
|
39
|
+
usage ||= responseMetadata.usage || responseMetadata.tokenUsage || {}
|
|
40
40
|
|
|
41
41
|
const inputTokens = usage.promptTokens || usage.inputTokens || usage.prompt_tokens || usage.input_tokens || 0
|
|
42
42
|
const outputTokens =
|
|
@@ -35,12 +35,6 @@ class LLMObs extends NoopLLMObs {
|
|
|
35
35
|
*/
|
|
36
36
|
#hasUserSpanProcessor = false
|
|
37
37
|
|
|
38
|
-
/**
|
|
39
|
-
* Lazily-created experiments facade (see ./experiments).
|
|
40
|
-
* @type {import('./experiments').Experiments | undefined}
|
|
41
|
-
*/
|
|
42
|
-
#experiments
|
|
43
|
-
|
|
44
38
|
/**
|
|
45
39
|
* @param {import('../tracer')} tracer - Tracer instance
|
|
46
40
|
* @param {import('./index')} llmobsModule - LLMObs module instance
|
|
@@ -66,8 +60,7 @@ class LLMObs extends NoopLLMObs {
|
|
|
66
60
|
* a clear message on use.
|
|
67
61
|
*/
|
|
68
62
|
get experiments () {
|
|
69
|
-
|
|
70
|
-
return this.#experiments
|
|
63
|
+
return createExperiments(this._config, this)
|
|
71
64
|
}
|
|
72
65
|
|
|
73
66
|
enable (options = {}) {
|
|
@@ -318,14 +311,14 @@ class LLMObs extends NoopLLMObs {
|
|
|
318
311
|
}
|
|
319
312
|
throw e
|
|
320
313
|
} finally {
|
|
321
|
-
if (autoinstrumented
|
|
314
|
+
if (!autoinstrumented) {
|
|
322
315
|
telemetry.recordLLMObsAnnotate(span, err)
|
|
323
316
|
}
|
|
324
317
|
}
|
|
325
318
|
}
|
|
326
319
|
|
|
327
320
|
exportSpan (span) {
|
|
328
|
-
span
|
|
321
|
+
span ||= this._active()
|
|
329
322
|
let err = ''
|
|
330
323
|
try {
|
|
331
324
|
if (!span) {
|
|
@@ -426,7 +419,7 @@ class LLMObs extends NoopLLMObs {
|
|
|
426
419
|
err = 'invalid_metric_value'
|
|
427
420
|
throw new Error('value must be a boolean for a boolean metric')
|
|
428
421
|
}
|
|
429
|
-
if (metricType === 'json' &&
|
|
422
|
+
if (metricType === 'json' && (typeof value !== 'object' || value == null || Array.isArray(value))) {
|
|
430
423
|
err = 'invalid_metric_value'
|
|
431
424
|
throw new Error('value must be a JSON object for a json metric')
|
|
432
425
|
}
|
|
@@ -239,6 +239,14 @@ class LLMObsSpanProcessor {
|
|
|
239
239
|
|
|
240
240
|
const apmTraceId = span.context().toTraceId(true)
|
|
241
241
|
const llmobsTraceId = mlObsTags[TRACE_ID] ?? apmTraceId
|
|
242
|
+
const dd = {
|
|
243
|
+
span_id: span.context().toSpanId(),
|
|
244
|
+
trace_id: apmTraceId,
|
|
245
|
+
sample_rate: mlObsTags[SAMPLE_RATE],
|
|
246
|
+
sampling_decision: mlObsTags[SAMPLING_DECISION],
|
|
247
|
+
apm_trace_id: apmTraceId,
|
|
248
|
+
}
|
|
249
|
+
if (tags.experiment_id) dd.scope = 'experiments'
|
|
242
250
|
|
|
243
251
|
const llmObsSpanEvent = {
|
|
244
252
|
trace_id: llmobsTraceId,
|
|
@@ -251,13 +259,7 @@ class LLMObsSpanProcessor {
|
|
|
251
259
|
status: error ? 'error' : 'ok',
|
|
252
260
|
meta,
|
|
253
261
|
metrics,
|
|
254
|
-
_dd:
|
|
255
|
-
span_id: span.context().toSpanId(),
|
|
256
|
-
trace_id: apmTraceId,
|
|
257
|
-
sample_rate: mlObsTags[SAMPLE_RATE],
|
|
258
|
-
sampling_decision: mlObsTags[SAMPLING_DECISION],
|
|
259
|
-
apm_trace_id: apmTraceId,
|
|
260
|
-
},
|
|
262
|
+
_dd: dd,
|
|
261
263
|
}
|
|
262
264
|
|
|
263
265
|
if (sessionId) llmObsSpanEvent.session_id = sessionId
|
|
@@ -158,8 +158,7 @@ class LLMObsTagger {
|
|
|
158
158
|
if (modelName) this.tagModelName(span, modelName)
|
|
159
159
|
if (modelProvider) this._setTag(span, MODEL_PROVIDER, modelProvider)
|
|
160
160
|
|
|
161
|
-
sessionId
|
|
162
|
-
registry.get(parent)?.[SESSION_ID] ||
|
|
161
|
+
sessionId ||= registry.get(parent)?.[SESSION_ID] ||
|
|
163
162
|
traceTags[SESSION_ID_TRACE_DEFAULT_KEY] ||
|
|
164
163
|
traceTags[PROPAGATED_SESSION_ID_KEY]
|
|
165
164
|
if (sessionId) this._setTag(span, SESSION_ID, sessionId)
|
|
@@ -49,12 +49,18 @@ class LLMObsSpanWriter extends BaseWriter {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
makePayload (events) {
|
|
52
|
-
return events.map(event =>
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
return events.map(event => {
|
|
53
|
+
const eventData = {
|
|
54
|
+
'_dd.stage': 'raw',
|
|
55
|
+
'_dd.tracer_version': tracerVersion,
|
|
56
|
+
event_type: this._eventType,
|
|
57
|
+
spans: [event],
|
|
58
|
+
}
|
|
59
|
+
if (event?._dd?.scope === 'experiments') {
|
|
60
|
+
eventData['_dd.scope'] = 'experiments'
|
|
61
|
+
}
|
|
62
|
+
return eventData
|
|
63
|
+
})
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
_truncateSpanEvent (event) {
|
|
@@ -55,7 +55,7 @@ class NoopProxy {
|
|
|
55
55
|
|
|
56
56
|
if (typeof fn !== 'function') return
|
|
57
57
|
|
|
58
|
-
options
|
|
58
|
+
options ||= {}
|
|
59
59
|
|
|
60
60
|
return this._tracer.trace(name, options, fn)
|
|
61
61
|
}
|
|
@@ -68,7 +68,7 @@ class NoopProxy {
|
|
|
68
68
|
|
|
69
69
|
if (typeof fn !== 'function') return fn
|
|
70
70
|
|
|
71
|
-
options
|
|
71
|
+
options ||= {}
|
|
72
72
|
|
|
73
73
|
return this._tracer.wrap(name, options, fn)
|
|
74
74
|
}
|
|
@@ -198,7 +198,7 @@ class OtlpTransformer extends OtlpTransformerBase {
|
|
|
198
198
|
if (isJson) {
|
|
199
199
|
dataPoint.startTimeUnixNano = String(dataPoint.startTimeUnixNano)
|
|
200
200
|
dataPoint.timeUnixNano = String(dataPoint.timeUnixNano)
|
|
201
|
-
dataPoint.count
|
|
201
|
+
dataPoint.count ||= 0
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
return dataPoint
|
|
@@ -253,7 +253,8 @@ class PeriodicMetricReader {
|
|
|
253
253
|
#collectAndExport (callback = () => {}) {
|
|
254
254
|
// Atomically drain measurements for export. New measurements can be recorded
|
|
255
255
|
// during export without interfering with this batch.
|
|
256
|
-
const allMeasurements = this.#measurements
|
|
256
|
+
const allMeasurements = this.#measurements
|
|
257
|
+
this.#measurements = []
|
|
257
258
|
|
|
258
259
|
for (const instrument of this.observableInstruments) {
|
|
259
260
|
const observableMeasurements = instrument.collect()
|