dd-trace 5.90.0 → 5.92.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/index.d.ts +7 -0
- package/package.json +8 -7
- package/packages/datadog-instrumentations/src/child_process.js +14 -8
- package/packages/datadog-instrumentations/src/cucumber.js +18 -3
- package/packages/datadog-instrumentations/src/graphql.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langgraph.js +30 -0
- package/packages/datadog-instrumentations/src/jest.js +9 -2
- package/packages/datadog-instrumentations/src/langgraph.js +7 -0
- package/packages/datadog-instrumentations/src/mocha/main.js +32 -9
- package/packages/datadog-instrumentations/src/mocha/utils.js +0 -1
- package/packages/datadog-instrumentations/src/mocha/worker.js +2 -2
- package/packages/datadog-instrumentations/src/vitest.js +53 -24
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +1 -1
- package/packages/datadog-plugin-cypress/src/support.js +5 -7
- package/packages/datadog-plugin-graphql/src/execute.js +2 -2
- package/packages/datadog-plugin-graphql/src/resolve.js +22 -35
- package/packages/datadog-plugin-http/src/client.js +1 -1
- package/packages/datadog-plugin-langgraph/src/index.js +24 -0
- package/packages/datadog-plugin-langgraph/src/stream.js +41 -0
- package/packages/dd-trace/src/config/defaults.js +3 -0
- package/packages/dd-trace/src/config/index.js +14 -1
- package/packages/dd-trace/src/config/supported-configurations.json +7 -0
- package/packages/dd-trace/src/constants.js +1 -0
- package/packages/dd-trace/src/crashtracking/crashtracker.js +1 -1
- package/packages/dd-trace/src/debugger/devtools_client/config.js +3 -0
- package/packages/dd-trace/src/dogstatsd.js +1 -0
- package/packages/dd-trace/src/encode/agentless-json.js +4 -1
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chain.js +11 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +2 -0
- package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +114 -0
- package/packages/dd-trace/src/plugins/ci_plugin.js +2 -2
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/util/test.js +7 -10
- package/packages/dd-trace/src/priority_sampler.js +20 -2
- package/packages/dd-trace/src/process-tags/index.js +41 -34
- package/packages/dd-trace/src/profiling/profilers/wall.js +9 -1
- package/packages/dd-trace/src/proxy.js +4 -0
- package/packages/dd-trace/src/telemetry/send-data.js +5 -0
- package/packages/dd-trace/src/telemetry/session-propagation.js +78 -0
- package/packages/dd-trace/src/telemetry/telemetry.js +3 -0
|
@@ -10,13 +10,13 @@ class GraphQLResolvePlugin extends TracingPlugin {
|
|
|
10
10
|
static operation = 'resolve'
|
|
11
11
|
|
|
12
12
|
start (fieldCtx) {
|
|
13
|
-
const { info, rootCtx, args } = fieldCtx
|
|
13
|
+
const { info, rootCtx, args, path: pathAsArray, pathString } = fieldCtx
|
|
14
14
|
|
|
15
|
-
const path = getPath(
|
|
15
|
+
const path = getPath(this.config, pathAsArray)
|
|
16
16
|
|
|
17
17
|
// we need to get the parent span to the field if it exists for correct span parenting
|
|
18
18
|
// of nested fields
|
|
19
|
-
const parentField = getParentField(rootCtx,
|
|
19
|
+
const parentField = getParentField(rootCtx, pathString)
|
|
20
20
|
const childOf = parentField?.ctx?.currentStore?.span
|
|
21
21
|
|
|
22
22
|
fieldCtx.parent = parentField
|
|
@@ -76,9 +76,9 @@ class GraphQLResolvePlugin extends TracingPlugin {
|
|
|
76
76
|
super(...args)
|
|
77
77
|
|
|
78
78
|
this.addTraceSub('updateField', (ctx) => {
|
|
79
|
-
const { field,
|
|
79
|
+
const { field, error, path: pathAsArray } = ctx
|
|
80
80
|
|
|
81
|
-
const path = getPath(
|
|
81
|
+
const path = getPath(this.config, pathAsArray)
|
|
82
82
|
|
|
83
83
|
if (!shouldInstrument(this.config, path)) return
|
|
84
84
|
|
|
@@ -118,28 +118,14 @@ function shouldInstrument (config, path) {
|
|
|
118
118
|
return config.depth < 0 || config.depth >= depth
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
function getPath (
|
|
122
|
-
|
|
123
|
-
? withCollapse(
|
|
124
|
-
:
|
|
125
|
-
return responsePathAsArray(info && info.path)
|
|
121
|
+
function getPath (config, pathAsArray) {
|
|
122
|
+
return config.collapse
|
|
123
|
+
? withCollapse(pathAsArray)
|
|
124
|
+
: pathAsArray
|
|
126
125
|
}
|
|
127
126
|
|
|
128
|
-
function
|
|
129
|
-
|
|
130
|
-
let curr = path
|
|
131
|
-
while (curr) {
|
|
132
|
-
flattened.push(curr.key)
|
|
133
|
-
curr = curr.prev
|
|
134
|
-
}
|
|
135
|
-
return flattened.reverse()
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function withCollapse (responsePathAsArray) {
|
|
139
|
-
return function () {
|
|
140
|
-
return responsePathAsArray.apply(this, arguments)
|
|
141
|
-
.map(segment => typeof segment === 'number' ? '*' : segment)
|
|
142
|
-
}
|
|
127
|
+
function withCollapse (pathAsArray) {
|
|
128
|
+
return pathAsArray.map(segment => typeof segment === 'number' ? '*' : segment)
|
|
143
129
|
}
|
|
144
130
|
|
|
145
131
|
function getResolverInfo (info, args) {
|
|
@@ -173,19 +159,20 @@ function getResolverInfo (info, args) {
|
|
|
173
159
|
return resolverInfo
|
|
174
160
|
}
|
|
175
161
|
|
|
176
|
-
function getParentField (parentCtx,
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
162
|
+
function getParentField (parentCtx, pathToString) {
|
|
163
|
+
let current = pathToString
|
|
164
|
+
|
|
165
|
+
while (current) {
|
|
166
|
+
const lastJoin = current.lastIndexOf('.')
|
|
167
|
+
if (lastJoin === -1) break
|
|
168
|
+
|
|
169
|
+
current = current.slice(0, lastJoin)
|
|
170
|
+
const field = parentCtx.fields[current]
|
|
171
|
+
|
|
172
|
+
if (field) return field
|
|
182
173
|
}
|
|
183
174
|
|
|
184
175
|
return null
|
|
185
176
|
}
|
|
186
177
|
|
|
187
|
-
function getField (parentCtx, path) {
|
|
188
|
-
return parentCtx.fields[path.join('.')]
|
|
189
|
-
}
|
|
190
|
-
|
|
191
178
|
module.exports = GraphQLResolvePlugin
|
|
@@ -211,7 +211,7 @@ function getHeaders (config) {
|
|
|
211
211
|
if (typeof header === 'string') {
|
|
212
212
|
const separatorIndex = header.indexOf(':')
|
|
213
213
|
result.push(separatorIndex === -1
|
|
214
|
-
? [header, undefined]
|
|
214
|
+
? [header.toLowerCase(), undefined]
|
|
215
215
|
: [
|
|
216
216
|
header.slice(0, separatorIndex).toLowerCase(),
|
|
217
217
|
header.slice(separatorIndex + 1),
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const CompositePlugin = require('../../dd-trace/src/plugins/composite')
|
|
4
|
+
const langgraphLLMObsPlugins = require('../../dd-trace/src/llmobs/plugins/langgraph')
|
|
5
|
+
const streamPlugin = require('./stream')
|
|
6
|
+
|
|
7
|
+
const plugins = {}
|
|
8
|
+
|
|
9
|
+
// CRITICAL: LLMObs plugins MUST come first
|
|
10
|
+
for (const Plugin of langgraphLLMObsPlugins) {
|
|
11
|
+
plugins[Plugin.id] = Plugin
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Tracing plugins second
|
|
15
|
+
for (const Plugin of streamPlugin) {
|
|
16
|
+
plugins[Plugin.id] = Plugin
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
class LanggraphPlugin extends CompositePlugin {
|
|
20
|
+
static id = 'langgraph'
|
|
21
|
+
static plugins = plugins
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = LanggraphPlugin
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
|
|
4
|
+
const { spanHasError } = require('../../dd-trace/src/llmobs/util')
|
|
5
|
+
|
|
6
|
+
// We are only tracing Pregel.stream because Pregel.invoke calls stream internally resulting in
|
|
7
|
+
// a graph with spans that look redundant.
|
|
8
|
+
class PregelStreamPlugin extends TracingPlugin {
|
|
9
|
+
static id = 'langgraph_pregel_stream'
|
|
10
|
+
static prefix = 'tracing:orchestrion:@langchain/langgraph:Pregel_stream'
|
|
11
|
+
|
|
12
|
+
bindStart (ctx) {
|
|
13
|
+
this.startSpan('LangGraph', {
|
|
14
|
+
service: this.config.service,
|
|
15
|
+
kind: 'internal',
|
|
16
|
+
component: 'langgraph',
|
|
17
|
+
}, ctx)
|
|
18
|
+
return ctx.currentStore
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
class NextStreamPlugin extends TracingPlugin {
|
|
22
|
+
static id = 'langgraph_stream_next'
|
|
23
|
+
static prefix = 'tracing:orchestrion:@langchain/langgraph:Pregel_stream_next'
|
|
24
|
+
|
|
25
|
+
bindStart (ctx) {
|
|
26
|
+
return ctx.currentStore
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
asyncEnd (ctx) {
|
|
30
|
+
const span = ctx.currentStore?.span
|
|
31
|
+
if (!span) return
|
|
32
|
+
if (ctx.result.done === true || spanHasError(span)) {
|
|
33
|
+
span.finish()
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = [
|
|
39
|
+
PregelStreamPlugin,
|
|
40
|
+
NextStreamPlugin,
|
|
41
|
+
]
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const pkg = require('../pkg')
|
|
4
4
|
const { isFalse, isTrue } = require('../util')
|
|
5
|
+
const { DD_MAJOR } = require('../../../../version')
|
|
5
6
|
const { getEnvironmentVariable: getEnv } = require('./helper')
|
|
6
7
|
|
|
7
8
|
const {
|
|
@@ -106,6 +107,7 @@ const defaultsWithoutSupportedConfigurationEntry = {
|
|
|
106
107
|
isGCPFunction: false,
|
|
107
108
|
instrumentationSource: 'manual',
|
|
108
109
|
isServiceUserProvided: false,
|
|
110
|
+
isServiceNameInferred: true,
|
|
109
111
|
lookup: undefined,
|
|
110
112
|
plugins: true,
|
|
111
113
|
}
|
|
@@ -116,6 +118,7 @@ const defaultsWithoutSupportedConfigurationEntry = {
|
|
|
116
118
|
// TODO: These entries should be removed. They are off by default
|
|
117
119
|
// because they rely on other configs.
|
|
118
120
|
const defaultsWithConditionalRuntimeBehavior = {
|
|
121
|
+
startupLogs: DD_MAJOR >= 6,
|
|
119
122
|
isGitUploadEnabled: false,
|
|
120
123
|
isImpactedTestsEnabled: false,
|
|
121
124
|
isIntelligentTestRunnerEnabled: false,
|
|
@@ -49,6 +49,8 @@ const VALID_PROPAGATION_BEHAVIOR_EXTRACT = new Set(['continue', 'restart', 'igno
|
|
|
49
49
|
const VALID_LOG_LEVELS = new Set(['debug', 'info', 'warn', 'error'])
|
|
50
50
|
const DEFAULT_OTLP_PORT = 4318
|
|
51
51
|
const RUNTIME_ID = uuid()
|
|
52
|
+
// eslint-disable-next-line eslint-rules/eslint-process-env -- internal propagation, not user config
|
|
53
|
+
const ROOT_SESSION_ID = process.env.DD_ROOT_JS_SESSION_ID || RUNTIME_ID
|
|
52
54
|
const NAMING_VERSIONS = new Set(['v0', 'v1'])
|
|
53
55
|
const DEFAULT_NAMING_VERSION = 'v0'
|
|
54
56
|
|
|
@@ -145,6 +147,8 @@ class Config {
|
|
|
145
147
|
'runtime-id': RUNTIME_ID,
|
|
146
148
|
})
|
|
147
149
|
|
|
150
|
+
this.rootSessionId = ROOT_SESSION_ID
|
|
151
|
+
|
|
148
152
|
if (this.isCiVisibility) {
|
|
149
153
|
tagger.add(this.tags, {
|
|
150
154
|
[ORIGIN_KEY]: 'ciapp-test',
|
|
@@ -722,8 +726,10 @@ class Config {
|
|
|
722
726
|
// Priority:
|
|
723
727
|
// DD_SERVICE > tags.service > OTEL_SERVICE_NAME > NX_TASK_TARGET_PROJECT (if DD_ENABLE_NX_SERVICE_NAME) > default
|
|
724
728
|
let serviceName = DD_SERVICE || tags.service || OTEL_SERVICE_NAME
|
|
729
|
+
let isServiceNameInferred
|
|
725
730
|
if (!serviceName && NX_TASK_TARGET_PROJECT) {
|
|
726
731
|
if (isTrue(DD_ENABLE_NX_SERVICE_NAME)) {
|
|
732
|
+
isServiceNameInferred = true
|
|
727
733
|
serviceName = NX_TASK_TARGET_PROJECT
|
|
728
734
|
} else if (DD_MAJOR < 6) {
|
|
729
735
|
// Warn about v6 behavior change for Nx projects
|
|
@@ -734,6 +740,7 @@ class Config {
|
|
|
734
740
|
}
|
|
735
741
|
}
|
|
736
742
|
setString(target, 'service', serviceName)
|
|
743
|
+
if (serviceName) setBoolean(target, 'isServiceNameInferred', isServiceNameInferred ?? false)
|
|
737
744
|
if (DD_SERVICE_MAPPING) {
|
|
738
745
|
target.serviceMapping = Object.fromEntries(
|
|
739
746
|
DD_SERVICE_MAPPING.split(',').map(x => x.trim().split(':'))
|
|
@@ -1004,7 +1011,11 @@ class Config {
|
|
|
1004
1011
|
setUnit(opts, 'sampleRate', options.sampleRate ?? options.ingestion.sampleRate)
|
|
1005
1012
|
opts['sampler.rateLimit'] = maybeInt(options.rateLimit ?? options.ingestion.rateLimit)
|
|
1006
1013
|
setSamplingRule(opts, 'sampler.rules', options.samplingRules)
|
|
1007
|
-
|
|
1014
|
+
const optService = options.service || tags.service
|
|
1015
|
+
setString(opts, 'service', optService)
|
|
1016
|
+
if (optService) {
|
|
1017
|
+
setBoolean(opts, 'isServiceNameInferred', false)
|
|
1018
|
+
}
|
|
1008
1019
|
opts.serviceMapping = options.serviceMapping
|
|
1009
1020
|
setString(opts, 'site', options.site)
|
|
1010
1021
|
if (options.spanAttributeSchema) {
|
|
@@ -1118,6 +1129,8 @@ class Config {
|
|
|
1118
1129
|
setBoolean(calc, 'reportHostname', true)
|
|
1119
1130
|
// Clear sampling rules - server-side sampling handles this
|
|
1120
1131
|
calc['sampler.rules'] = []
|
|
1132
|
+
// Agentless intake only accepts 64-bit trace IDs; disable 128-bit generation
|
|
1133
|
+
setBoolean(calc, 'traceId128BitGenerationEnabled', false)
|
|
1121
1134
|
}
|
|
1122
1135
|
|
|
1123
1136
|
if (this.#isCiVisibility()) {
|
|
@@ -2995,6 +2995,13 @@
|
|
|
2995
2995
|
"default": "true"
|
|
2996
2996
|
}
|
|
2997
2997
|
],
|
|
2998
|
+
"DD_TRACE_LANGGRAPH_ENABLED": [
|
|
2999
|
+
{
|
|
3000
|
+
"implementation": "C",
|
|
3001
|
+
"type": "boolean",
|
|
3002
|
+
"default": "true"
|
|
3003
|
+
}
|
|
3004
|
+
],
|
|
2998
3005
|
"DD_TRACE_LDAPJS_ENABLED": [
|
|
2999
3006
|
{
|
|
3000
3007
|
"implementation": "A",
|
|
@@ -23,6 +23,7 @@ module.exports = {
|
|
|
23
23
|
SPAN_SAMPLING_MAX_PER_SECOND: '_dd.span_sampling.max_per_second',
|
|
24
24
|
DATADOG_LAMBDA_EXTENSION_PATH: '/opt/extensions/datadog-agent',
|
|
25
25
|
DECISION_MAKER_KEY: '_dd.p.dm',
|
|
26
|
+
SAMPLING_KNUTH_RATE: '_dd.p.ksr',
|
|
26
27
|
PROCESS_ID: 'process_id',
|
|
27
28
|
ERROR_TYPE: 'error.type',
|
|
28
29
|
ERROR_MESSAGE: 'error.message',
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
const { workerData: { config: parentConfig, parentThreadId, configPort } } = require('node:worker_threads')
|
|
4
4
|
const { getAgentUrl } = require('../../agent/url')
|
|
5
|
+
const processTags = require('../../process-tags')
|
|
5
6
|
const log = require('./log')
|
|
6
7
|
|
|
8
|
+
processTags.initialize()
|
|
9
|
+
|
|
7
10
|
const config = module.exports = {
|
|
8
11
|
...parentConfig,
|
|
9
12
|
parentThreadId,
|
|
@@ -16,6 +16,9 @@ const SOFT_LIMIT = 8 * 1024 * 1024 // 8MB
|
|
|
16
16
|
function formatSpan (span, isFirstSpan) {
|
|
17
17
|
span = normalizeSpan(truncateSpan(span, false))
|
|
18
18
|
|
|
19
|
+
// Remove _dd.p.tid (the upper 64 bits of a 128-bit trace ID) since trace_id is truncated to lower 64 bits
|
|
20
|
+
delete span.meta['_dd.p.tid']
|
|
21
|
+
|
|
19
22
|
if (span.span_events) {
|
|
20
23
|
span.meta.events = JSON.stringify(span.span_events)
|
|
21
24
|
delete span.span_events
|
|
@@ -45,7 +48,7 @@ function formatSpan (span, isFirstSpan) {
|
|
|
45
48
|
*/
|
|
46
49
|
function spanToJSON (span) {
|
|
47
50
|
const result = {
|
|
48
|
-
trace_id: span.trace_id.toString(16).toLowerCase(),
|
|
51
|
+
trace_id: span.trace_id.toString(16).toLowerCase().slice(-16),
|
|
49
52
|
span_id: span.span_id.toString(16).toLowerCase(),
|
|
50
53
|
parent_id: span.parent_id.toString(16).toLowerCase(),
|
|
51
54
|
name: span.name,
|
|
@@ -15,6 +15,17 @@ class LangChainLLMObsChainHandler extends LangChainLLMObsHandler {
|
|
|
15
15
|
// chain spans will always be workflows
|
|
16
16
|
this._tagger.tagTextIO(span, input, output)
|
|
17
17
|
}
|
|
18
|
+
|
|
19
|
+
getName ({ span, instance }) {
|
|
20
|
+
const firstCallable = instance?.first
|
|
21
|
+
|
|
22
|
+
if (firstCallable?.constructor?.name === 'ChannelWrite') return
|
|
23
|
+
|
|
24
|
+
const firstCallableIsLangGraph = firstCallable?.lc_namespace?.includes('langgraph')
|
|
25
|
+
const firstCallableName = firstCallable?.name
|
|
26
|
+
|
|
27
|
+
return firstCallableIsLangGraph ? firstCallableName : super.getName({ span })
|
|
28
|
+
}
|
|
18
29
|
}
|
|
19
30
|
|
|
20
31
|
module.exports = LangChainLLMObsChainHandler
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const LLMObsPlugin = require('../base')
|
|
4
|
+
const { spanHasError } = require('../../util')
|
|
5
|
+
|
|
6
|
+
const streamDataMap = new WeakMap()
|
|
7
|
+
|
|
8
|
+
function formatIO (data) {
|
|
9
|
+
if (data == null) return ''
|
|
10
|
+
|
|
11
|
+
if (typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean') {
|
|
12
|
+
return data
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (data.constructor?.name === 'Object') {
|
|
16
|
+
const formatted = {}
|
|
17
|
+
for (const [key, value] of Object.entries(data)) {
|
|
18
|
+
formatted[key] = formatIO(value)
|
|
19
|
+
}
|
|
20
|
+
return formatted
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (Array.isArray(data)) {
|
|
24
|
+
return data.map(item => formatIO(item))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
return JSON.stringify(data)
|
|
29
|
+
} catch {
|
|
30
|
+
return String(data)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
class PregelStreamLLMObsPlugin extends LLMObsPlugin {
|
|
35
|
+
static id = 'llmobs_langgraph_pregel_stream'
|
|
36
|
+
static integration = 'langgraph'
|
|
37
|
+
static prefix = 'tracing:orchestrion:@langchain/langgraph:Pregel_stream'
|
|
38
|
+
|
|
39
|
+
getLLMObsSpanRegisterOptions (ctx) {
|
|
40
|
+
const name = ctx.self.name || 'LangGraph'
|
|
41
|
+
|
|
42
|
+
const enabled = this._tracerConfig.llmobs.enabled
|
|
43
|
+
if (!enabled) return
|
|
44
|
+
|
|
45
|
+
const span = ctx.currentStore?.span
|
|
46
|
+
if (!span) return
|
|
47
|
+
streamDataMap.set(span, {
|
|
48
|
+
streamInputs: ctx.arguments?.[0],
|
|
49
|
+
chunks: [],
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
kind: 'workflow',
|
|
54
|
+
name,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
asyncEnd () {}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
class NextStreamLLMObsPlugin extends LLMObsPlugin {
|
|
62
|
+
static id = 'llmobs_langgraph_next_stream'
|
|
63
|
+
static prefix = 'tracing:orchestrion:@langchain/langgraph:Pregel_stream_next'
|
|
64
|
+
|
|
65
|
+
start () {} // no-op: span was already registered by PregelStreamLLMObsPlugin
|
|
66
|
+
|
|
67
|
+
end () {} // no-op: context restore is handled by PregelStreamLLMObsPlugin
|
|
68
|
+
|
|
69
|
+
error (ctx) {
|
|
70
|
+
const span = ctx.currentStore?.span
|
|
71
|
+
if (!span) return
|
|
72
|
+
|
|
73
|
+
this.#tagAndCleanup(span, true)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
setLLMObsTags (ctx) {
|
|
77
|
+
const span = ctx.currentStore?.span
|
|
78
|
+
if (!span) return
|
|
79
|
+
|
|
80
|
+
// Accumulate chunks until done
|
|
81
|
+
if (ctx.result?.value && !ctx.result.done) {
|
|
82
|
+
const streamData = streamDataMap.get(span)
|
|
83
|
+
if (streamData) {
|
|
84
|
+
streamData.chunks.push(ctx.result.value)
|
|
85
|
+
}
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Tag on last chunk
|
|
90
|
+
if (ctx.result?.done) {
|
|
91
|
+
const hasError = ctx.error || spanHasError(span)
|
|
92
|
+
this.#tagAndCleanup(span, hasError)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
#tagAndCleanup (span, hasError) {
|
|
97
|
+
const streamData = streamDataMap.get(span)
|
|
98
|
+
if (!streamData) return
|
|
99
|
+
|
|
100
|
+
const { streamInputs: inputs, chunks } = streamData
|
|
101
|
+
const input = inputs == null ? undefined : formatIO(inputs)
|
|
102
|
+
const lastChunk = chunks.length > 0 ? chunks[chunks.length - 1] : undefined
|
|
103
|
+
const output = !hasError && lastChunk != null ? formatIO(lastChunk) : undefined
|
|
104
|
+
|
|
105
|
+
this._tagger.tagTextIO(span, input, output)
|
|
106
|
+
|
|
107
|
+
streamDataMap.delete(span)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = [
|
|
112
|
+
PregelStreamLLMObsPlugin,
|
|
113
|
+
NextStreamLLMObsPlugin,
|
|
114
|
+
]
|
|
@@ -125,7 +125,7 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
125
125
|
this._pendingRequestErrorTags = []
|
|
126
126
|
|
|
127
127
|
this.addSub(`ci:${this.constructor.id}:library-configuration`, (ctx) => {
|
|
128
|
-
const { onDone,
|
|
128
|
+
const { onDone, frameworkVersion } = ctx
|
|
129
129
|
ctx.currentStore = storage('legacy').getStore()
|
|
130
130
|
|
|
131
131
|
if (!this.tracer._exporter || !this.tracer._exporter.getLibraryConfiguration) {
|
|
@@ -143,7 +143,7 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
143
143
|
? getSessionRequestErrorTags(this.testSessionSpan)
|
|
144
144
|
: Object.fromEntries(this._pendingRequestErrorTags.map(({ tag, value }) => [tag, value]))
|
|
145
145
|
|
|
146
|
-
const libraryCapabilitiesTags = getLibraryCapabilitiesTags(this.constructor.id,
|
|
146
|
+
const libraryCapabilitiesTags = getLibraryCapabilitiesTags(this.constructor.id, frameworkVersion)
|
|
147
147
|
const metadataTags = {
|
|
148
148
|
test: {
|
|
149
149
|
...libraryCapabilitiesTags,
|
|
@@ -31,6 +31,7 @@ const plugins = {
|
|
|
31
31
|
get '@redis/client' () { return require('../../../datadog-plugin-redis/src') },
|
|
32
32
|
get '@smithy/smithy-client' () { return require('../../../datadog-plugin-aws-sdk/src') },
|
|
33
33
|
get '@vitest/runner' () { return require('../../../datadog-plugin-vitest/src') },
|
|
34
|
+
get '@langchain/langgraph' () { return require('../../../datadog-plugin-langgraph/src') },
|
|
34
35
|
get aerospike () { return require('../../../datadog-plugin-aerospike/src') },
|
|
35
36
|
get ai () { return require('../../../datadog-plugin-ai/src') },
|
|
36
37
|
get amqp10 () { return require('../../../datadog-plugin-amqp10/src') },
|
|
@@ -150,7 +150,6 @@ const DD_CAPABILITIES_FAILED_TEST_REPLAY = '_dd.library_capabilities.failed_test
|
|
|
150
150
|
const DD_CI_LIBRARY_CONFIGURATION_ERROR = '_dd.ci.library_configuration_error'
|
|
151
151
|
|
|
152
152
|
const UNSUPPORTED_TIA_FRAMEWORKS = new Set(['playwright', 'vitest'])
|
|
153
|
-
const UNSUPPORTED_TIA_FRAMEWORKS_PARALLEL_MODE = new Set(['cucumber', 'mocha'])
|
|
154
153
|
const MINIMUM_FRAMEWORK_VERSION_FOR_EFD = {
|
|
155
154
|
playwright: '>=1.38.0',
|
|
156
155
|
}
|
|
@@ -170,7 +169,6 @@ const MINIMUM_FRAMEWORK_VERSION_FOR_FAILED_TEST_REPLAY = {
|
|
|
170
169
|
playwright: '>=1.38.0',
|
|
171
170
|
}
|
|
172
171
|
|
|
173
|
-
const UNSUPPORTED_ATTEMPT_TO_FIX_FRAMEWORKS_PARALLEL_MODE = new Set(['mocha'])
|
|
174
172
|
const NOT_SUPPORTED_GRANULARITY_IMPACTED_TESTS_FRAMEWORKS = new Set(['mocha', 'playwright', 'vitest'])
|
|
175
173
|
|
|
176
174
|
const TEST_LEVEL_EVENT_TYPES = [
|
|
@@ -987,9 +985,8 @@ function getFormattedError (error, repositoryRoot) {
|
|
|
987
985
|
return newError
|
|
988
986
|
}
|
|
989
987
|
|
|
990
|
-
function isTiaSupported (testFramework
|
|
991
|
-
return !
|
|
992
|
-
(isParallel && UNSUPPORTED_TIA_FRAMEWORKS_PARALLEL_MODE.has(testFramework)))
|
|
988
|
+
function isTiaSupported (testFramework) {
|
|
989
|
+
return !UNSUPPORTED_TIA_FRAMEWORKS.has(testFramework)
|
|
993
990
|
}
|
|
994
991
|
|
|
995
992
|
function isEarlyFlakeDetectionSupported (testFramework, frameworkVersion) {
|
|
@@ -1016,12 +1013,12 @@ function isDisableSupported (testFramework, frameworkVersion) {
|
|
|
1016
1013
|
: true
|
|
1017
1014
|
}
|
|
1018
1015
|
|
|
1019
|
-
function isAttemptToFixSupported (testFramework,
|
|
1016
|
+
function isAttemptToFixSupported (testFramework, frameworkVersion) {
|
|
1020
1017
|
if (testFramework === 'playwright') {
|
|
1021
1018
|
return satisfies(frameworkVersion, MINIMUM_FRAMEWORK_VERSION_FOR_ATTEMPT_TO_FIX[testFramework])
|
|
1022
1019
|
}
|
|
1023
1020
|
|
|
1024
|
-
return
|
|
1021
|
+
return true
|
|
1025
1022
|
}
|
|
1026
1023
|
|
|
1027
1024
|
function isFailedTestReplaySupported (testFramework, frameworkVersion) {
|
|
@@ -1030,9 +1027,9 @@ function isFailedTestReplaySupported (testFramework, frameworkVersion) {
|
|
|
1030
1027
|
: true
|
|
1031
1028
|
}
|
|
1032
1029
|
|
|
1033
|
-
function getLibraryCapabilitiesTags (testFramework,
|
|
1030
|
+
function getLibraryCapabilitiesTags (testFramework, frameworkVersion) {
|
|
1034
1031
|
return {
|
|
1035
|
-
[DD_CAPABILITIES_TEST_IMPACT_ANALYSIS]: isTiaSupported(testFramework
|
|
1032
|
+
[DD_CAPABILITIES_TEST_IMPACT_ANALYSIS]: isTiaSupported(testFramework)
|
|
1036
1033
|
? '1'
|
|
1037
1034
|
: undefined,
|
|
1038
1035
|
[DD_CAPABILITIES_EARLY_FLAKE_DETECTION]: isEarlyFlakeDetectionSupported(testFramework, frameworkVersion)
|
|
@@ -1049,7 +1046,7 @@ function getLibraryCapabilitiesTags (testFramework, isParallel, frameworkVersion
|
|
|
1049
1046
|
? '1'
|
|
1050
1047
|
: undefined,
|
|
1051
1048
|
[DD_CAPABILITIES_TEST_MANAGEMENT_ATTEMPT_TO_FIX]:
|
|
1052
|
-
isAttemptToFixSupported(testFramework,
|
|
1049
|
+
isAttemptToFixSupported(testFramework, frameworkVersion)
|
|
1053
1050
|
? '5'
|
|
1054
1051
|
: undefined,
|
|
1055
1052
|
[DD_CAPABILITIES_FAILED_TEST_REPLAY]: isFailedTestReplaySupported(testFramework, frameworkVersion)
|
|
@@ -31,10 +31,21 @@ const {
|
|
|
31
31
|
SAMPLING_LIMIT_DECISION,
|
|
32
32
|
SAMPLING_AGENT_DECISION,
|
|
33
33
|
DECISION_MAKER_KEY,
|
|
34
|
+
SAMPLING_KNUTH_RATE,
|
|
34
35
|
} = require('./constants')
|
|
35
36
|
|
|
36
37
|
const DEFAULT_KEY = 'service:,env:'
|
|
37
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Formats a sampling rate as a string with up to 6 significant digits and no trailing zeros.
|
|
41
|
+
*
|
|
42
|
+
* @param {number} rate
|
|
43
|
+
* @returns {string}
|
|
44
|
+
*/
|
|
45
|
+
function formatKnuthRate (rate) {
|
|
46
|
+
return Number(rate.toPrecision(6)).toString()
|
|
47
|
+
}
|
|
48
|
+
|
|
38
49
|
const defaultSampler = new Sampler(AUTO_KEEP)
|
|
39
50
|
|
|
40
51
|
/**
|
|
@@ -254,6 +265,7 @@ class PrioritySampler {
|
|
|
254
265
|
*/
|
|
255
266
|
#getPriorityByRule (context, rule) {
|
|
256
267
|
context._trace[SAMPLING_RULE_DECISION] = rule.sampleRate
|
|
268
|
+
context._trace.tags[SAMPLING_KNUTH_RATE] = formatKnuthRate(rule.sampleRate)
|
|
257
269
|
context._sampling.mechanism = SAMPLING_MECHANISM_RULE
|
|
258
270
|
if (rule.provenance === 'customer') context._sampling.mechanism = SAMPLING_MECHANISM_REMOTE_USER
|
|
259
271
|
if (rule.provenance === 'dynamic') context._sampling.mechanism = SAMPLING_MECHANISM_REMOTE_DYNAMIC
|
|
@@ -290,9 +302,15 @@ class PrioritySampler {
|
|
|
290
302
|
// TODO: Change underscored properties to private ones.
|
|
291
303
|
const sampler = this._samplers[key] || this._samplers[DEFAULT_KEY]
|
|
292
304
|
|
|
293
|
-
|
|
305
|
+
const rate = sampler.rate()
|
|
306
|
+
context._trace[SAMPLING_AGENT_DECISION] = rate
|
|
294
307
|
|
|
295
|
-
|
|
308
|
+
if (sampler === defaultSampler) {
|
|
309
|
+
context._sampling.mechanism = SAMPLING_MECHANISM_DEFAULT
|
|
310
|
+
} else {
|
|
311
|
+
context._trace.tags[SAMPLING_KNUTH_RATE] = formatKnuthRate(rate)
|
|
312
|
+
context._sampling.mechanism = SAMPLING_MECHANISM_AGENT
|
|
313
|
+
}
|
|
296
314
|
|
|
297
315
|
return sampler.isSampled(context) ? AUTO_KEEP : AUTO_REJECT
|
|
298
316
|
}
|