dd-trace 5.49.0 → 5.50.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/LICENSE-3rdparty.csv +1 -4
- package/README.md +5 -15
- package/index.d.ts +1 -0
- package/package.json +4 -8
- package/packages/datadog-core/src/storage.js +4 -3
- package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +0 -1
- package/packages/datadog-shimmer/src/shimmer.js +76 -68
- package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +1 -17
- package/packages/dd-trace/src/appsec/iast/iast-plugin.js +1 -1
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +1 -1
- package/packages/dd-trace/src/config.js +24 -32
- package/packages/dd-trace/src/datastreams/processor.js +3 -5
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +34 -16
- package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +1 -1
- package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -1
- package/packages/dd-trace/src/dogstatsd.js +11 -4
- package/packages/dd-trace/src/llmobs/index.js +4 -1
- package/packages/dd-trace/src/llmobs/sdk.js +146 -112
- package/packages/dd-trace/src/llmobs/tagger.js +13 -9
- package/packages/dd-trace/src/llmobs/telemetry.js +50 -1
- package/packages/dd-trace/src/payload-tagging/jsonpath-plus.js +1 -1
- package/packages/dd-trace/src/profiling/config.js +0 -6
- package/packages/dd-trace/src/profiling/profilers/wall.js +8 -12
- package/packages/dd-trace/src/span_stats.js +2 -2
- package/packages/dd-trace/src/debugger/devtools_client/lock.js +0 -8
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const lock = require('mutexify/promise')()
|
|
3
4
|
const { getGeneratedPosition } = require('./source-maps')
|
|
4
|
-
const lock = require('./lock')()
|
|
5
5
|
const session = require('./session')
|
|
6
6
|
const { compile: compileCondition, compileSegments, templateRequiresEvaluation } = require('./condition')
|
|
7
7
|
const { MAX_SNAPSHOTS_PER_SECOND_PER_PROBE, MAX_NON_SNAPSHOTS_PER_SECOND_PER_PROBE } = require('./defaults')
|
|
@@ -64,14 +64,14 @@ async function addBreakpoint (probe) {
|
|
|
64
64
|
const release = await lock()
|
|
65
65
|
|
|
66
66
|
try {
|
|
67
|
-
log.debug(
|
|
68
|
-
'[debugger:devtools_client] Adding breakpoint at %s:%d:%d (probe: %s, version: %d)',
|
|
69
|
-
url, lineNumber, columnNumber, probe.id, probe.version
|
|
70
|
-
)
|
|
71
|
-
|
|
72
67
|
const locationKey = generateLocationKey(scriptId, lineNumber, columnNumber)
|
|
73
68
|
const breakpoint = locationToBreakpoint.get(locationKey)
|
|
74
69
|
|
|
70
|
+
log.debug(
|
|
71
|
+
'[debugger:devtools_client] %s breakpoint at %s:%d:%d (probe: %s, version: %d)',
|
|
72
|
+
breakpoint ? 'Updating' : 'Adding', url, lineNumber, columnNumber, probe.id, probe.version
|
|
73
|
+
)
|
|
74
|
+
|
|
75
75
|
if (breakpoint) {
|
|
76
76
|
// A breakpoint already exists at this location, so we need to add the probe to the existing breakpoint
|
|
77
77
|
await updateBreakpoint(breakpoint, probe)
|
|
@@ -82,10 +82,15 @@ async function addBreakpoint (probe) {
|
|
|
82
82
|
lineNumber: lineNumber - 1, // Beware! lineNumber is zero-indexed
|
|
83
83
|
columnNumber
|
|
84
84
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
let result
|
|
86
|
+
try {
|
|
87
|
+
result = await session.post('Debugger.setBreakpoint', {
|
|
88
|
+
location,
|
|
89
|
+
condition: probe.condition
|
|
90
|
+
})
|
|
91
|
+
} catch (err) {
|
|
92
|
+
throw new Error(`Error setting breakpoint for probe ${probe.id}`, { cause: err })
|
|
93
|
+
}
|
|
89
94
|
probeToLocation.set(probe.id, locationKey)
|
|
90
95
|
locationToBreakpoint.set(locationKey, { id: result.breakpointId, location, locationKey })
|
|
91
96
|
breakpointToProbes.set(result.breakpointId, new Map([[probe.id, probe]]))
|
|
@@ -120,7 +125,11 @@ async function removeBreakpoint ({ id }) {
|
|
|
120
125
|
if (breakpointToProbes.size === 0) {
|
|
121
126
|
await stop() // TODO: Will this actually delete the breakpoint?
|
|
122
127
|
} else {
|
|
123
|
-
|
|
128
|
+
try {
|
|
129
|
+
await session.post('Debugger.removeBreakpoint', { breakpointId: breakpoint.id })
|
|
130
|
+
} catch (err) {
|
|
131
|
+
throw new Error(`Error removing breakpoint for probe ${id}`, { cause: err })
|
|
132
|
+
}
|
|
124
133
|
}
|
|
125
134
|
} else {
|
|
126
135
|
await updateBreakpoint(breakpoint)
|
|
@@ -144,12 +153,21 @@ async function updateBreakpoint (breakpoint, probe) {
|
|
|
144
153
|
const condition = compileCompoundCondition(Array.from(probesAtLocation.values()))
|
|
145
154
|
|
|
146
155
|
if (condition || conditionBeforeNewProbe !== condition) {
|
|
147
|
-
|
|
156
|
+
try {
|
|
157
|
+
await session.post('Debugger.removeBreakpoint', { breakpointId: breakpoint.id })
|
|
158
|
+
} catch (err) {
|
|
159
|
+
throw new Error(`Error removing breakpoint for probe ${probe.id}`, { cause: err })
|
|
160
|
+
}
|
|
148
161
|
breakpointToProbes.delete(breakpoint.id)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
162
|
+
let result
|
|
163
|
+
try {
|
|
164
|
+
result = await session.post('Debugger.setBreakpoint', {
|
|
165
|
+
location: breakpoint.location,
|
|
166
|
+
condition
|
|
167
|
+
})
|
|
168
|
+
} catch (err) {
|
|
169
|
+
throw new Error(`Error setting breakpoint for probe ${probe.id}`, { cause: err })
|
|
170
|
+
}
|
|
153
171
|
breakpoint.id = result.breakpointId
|
|
154
172
|
breakpointToProbes.set(result.breakpointId, probesAtLocation)
|
|
155
173
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { workerData: { rcPort } } = require('node:worker_threads')
|
|
4
|
-
const lock = require('
|
|
4
|
+
const lock = require('mutexify/promise')()
|
|
5
5
|
const { addBreakpoint, removeBreakpoint } = require('./breakpoints')
|
|
6
6
|
const { ackReceived, ackInstalled, ackError } = require('./status')
|
|
7
7
|
const log = require('../../log')
|
|
@@ -26,7 +26,7 @@ const ddtags = [
|
|
|
26
26
|
['host_name', hostname],
|
|
27
27
|
[GIT_COMMIT_SHA, config.commitSHA],
|
|
28
28
|
[GIT_REPOSITORY_URL, config.repositoryUrl]
|
|
29
|
-
].map((pair) => pair.join(':')).join(',')
|
|
29
|
+
].filter(([, value]) => value !== undefined).map((pair) => pair.join(':')).join(',')
|
|
30
30
|
|
|
31
31
|
const path = `/debugger/v1/input?${stringify({ ddtags })}`
|
|
32
32
|
|
|
@@ -308,12 +308,16 @@ class MetricsAggregationClient {
|
|
|
308
308
|
}
|
|
309
309
|
|
|
310
310
|
for (const [tag, next] of node.nodes) {
|
|
311
|
-
|
|
311
|
+
tags.push(tag)
|
|
312
|
+
this._captureNode(next, name, tags, fn)
|
|
313
|
+
tags.pop()
|
|
312
314
|
}
|
|
313
315
|
}
|
|
314
316
|
|
|
315
|
-
_ensureTree (tree, name, tags, value) {
|
|
316
|
-
|
|
317
|
+
_ensureTree (tree, name, tags = [], value) {
|
|
318
|
+
if (!Array.isArray(tags)) {
|
|
319
|
+
tags = [tags]
|
|
320
|
+
}
|
|
317
321
|
|
|
318
322
|
let node = this._ensureNode(tree, name, value)
|
|
319
323
|
|
|
@@ -331,7 +335,10 @@ class MetricsAggregationClient {
|
|
|
331
335
|
|
|
332
336
|
if (!node) {
|
|
333
337
|
node = { nodes: new Map(), touched: false, value }
|
|
334
|
-
|
|
338
|
+
|
|
339
|
+
if (typeof key === 'string') {
|
|
340
|
+
container.set(key, node)
|
|
341
|
+
}
|
|
335
342
|
}
|
|
336
343
|
|
|
337
344
|
return node
|
|
@@ -4,9 +4,9 @@ const log = require('../log')
|
|
|
4
4
|
const { PROPAGATED_PARENT_ID_KEY } = require('./constants/tags')
|
|
5
5
|
const { storage } = require('./storage')
|
|
6
6
|
|
|
7
|
+
const telemetry = require('./telemetry')
|
|
7
8
|
const LLMObsSpanProcessor = require('./span_processor')
|
|
8
9
|
|
|
9
|
-
const telemetry = require('./telemetry')
|
|
10
10
|
const { channel } = require('dc-polyfill')
|
|
11
11
|
const spanProcessCh = channel('dd-trace:span:process')
|
|
12
12
|
const evalMetricAppendCh = channel('llmobs:eval-metric:append')
|
|
@@ -94,12 +94,15 @@ function handleLLMObsParentIdInjection ({ carrier }) {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
function handleFlush () {
|
|
97
|
+
let err = ''
|
|
97
98
|
try {
|
|
98
99
|
spanWriter.flush()
|
|
99
100
|
evalWriter.flush()
|
|
100
101
|
} catch (e) {
|
|
102
|
+
err = 'writer_flush_error'
|
|
101
103
|
log.warn(`Failed to flush LLMObs spans and evaluation metrics: ${e.message}`)
|
|
102
104
|
}
|
|
105
|
+
telemetry.recordUserFlush(err)
|
|
103
106
|
}
|
|
104
107
|
|
|
105
108
|
function handleSpanProcess (data) {
|
|
@@ -201,7 +201,7 @@ class LLMObs extends NoopLLMObs {
|
|
|
201
201
|
return this._tracer.wrap(name, spanOptions, wrapped)
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
-
annotate (span, options) {
|
|
204
|
+
annotate (span, options, autoinstrumented = false) {
|
|
205
205
|
if (!this.enabled) return
|
|
206
206
|
|
|
207
207
|
if (!span) {
|
|
@@ -213,150 +213,184 @@ class LLMObs extends NoopLLMObs {
|
|
|
213
213
|
span = this._active()
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
-
|
|
217
|
-
throw new Error('No span provided and no active LLMObs-generated span found')
|
|
218
|
-
}
|
|
219
|
-
if (!options) {
|
|
220
|
-
throw new Error('No options provided for annotation.')
|
|
221
|
-
}
|
|
216
|
+
let err = ''
|
|
222
217
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
218
|
+
try {
|
|
219
|
+
if (!span) {
|
|
220
|
+
err = 'invalid_span_no_active_spans'
|
|
221
|
+
throw new Error('No span provided and no active LLMObs-generated span found')
|
|
222
|
+
}
|
|
223
|
+
if (!options) {
|
|
224
|
+
err = 'invalid_options'
|
|
225
|
+
throw new Error('No options provided for annotation.')
|
|
226
|
+
}
|
|
229
227
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
228
|
+
if (!LLMObsTagger.tagMap.has(span)) {
|
|
229
|
+
err = 'invalid_span_type'
|
|
230
|
+
throw new Error('Span must be an LLMObs-generated span')
|
|
231
|
+
}
|
|
232
|
+
if (span._duration !== undefined) {
|
|
233
|
+
err = 'invalid_finished_span'
|
|
234
|
+
throw new Error('Cannot annotate a finished span')
|
|
235
|
+
}
|
|
234
236
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
this._tagger.tagLLMIO(span, inputData, outputData)
|
|
240
|
-
} else if (spanKind === 'embedding') {
|
|
241
|
-
this._tagger.tagEmbeddingIO(span, inputData, outputData)
|
|
242
|
-
} else if (spanKind === 'retrieval') {
|
|
243
|
-
this._tagger.tagRetrievalIO(span, inputData, outputData)
|
|
244
|
-
} else {
|
|
245
|
-
this._tagger.tagTextIO(span, inputData, outputData)
|
|
237
|
+
const spanKind = LLMObsTagger.tagMap.get(span)[SPAN_KIND]
|
|
238
|
+
if (!spanKind) {
|
|
239
|
+
err = 'invalid_no_span_kind'
|
|
240
|
+
throw new Error('LLMObs span must have a span kind specified')
|
|
246
241
|
}
|
|
247
|
-
}
|
|
248
242
|
|
|
249
|
-
|
|
250
|
-
this._tagger.tagMetadata(span, metadata)
|
|
251
|
-
}
|
|
243
|
+
const { inputData, outputData, metadata, metrics, tags } = options
|
|
252
244
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
245
|
+
if (inputData || outputData) {
|
|
246
|
+
if (spanKind === 'llm') {
|
|
247
|
+
this._tagger.tagLLMIO(span, inputData, outputData)
|
|
248
|
+
} else if (spanKind === 'embedding') {
|
|
249
|
+
this._tagger.tagEmbeddingIO(span, inputData, outputData)
|
|
250
|
+
} else if (spanKind === 'retrieval') {
|
|
251
|
+
this._tagger.tagRetrievalIO(span, inputData, outputData)
|
|
252
|
+
} else {
|
|
253
|
+
this._tagger.tagTextIO(span, inputData, outputData)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
256
|
|
|
257
|
-
|
|
258
|
-
|
|
257
|
+
if (metadata) {
|
|
258
|
+
this._tagger.tagMetadata(span, metadata)
|
|
259
|
+
}
|
|
260
|
+
if (metrics) {
|
|
261
|
+
this._tagger.tagMetrics(span, metrics)
|
|
262
|
+
}
|
|
263
|
+
if (tags) {
|
|
264
|
+
this._tagger.tagSpanTags(span, tags)
|
|
265
|
+
}
|
|
266
|
+
} catch (e) {
|
|
267
|
+
if (e.ddErrorTag) {
|
|
268
|
+
err = e.ddErrorTag
|
|
269
|
+
}
|
|
270
|
+
throw e
|
|
271
|
+
} finally {
|
|
272
|
+
if (autoinstrumented === false) {
|
|
273
|
+
telemetry.recordLLMObsAnnotate(span, err)
|
|
274
|
+
}
|
|
259
275
|
}
|
|
260
276
|
}
|
|
261
277
|
|
|
262
278
|
exportSpan (span) {
|
|
263
279
|
span = span || this._active()
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
280
|
+
let err = ''
|
|
281
|
+
try {
|
|
282
|
+
if (!span) {
|
|
283
|
+
err = 'no_active_span'
|
|
284
|
+
throw new Error('No span provided and no active LLMObs-generated span found')
|
|
285
|
+
}
|
|
286
|
+
if (!(span instanceof Span)) {
|
|
287
|
+
err = 'invalid_span'
|
|
288
|
+
throw new Error('Span must be a valid Span object.')
|
|
289
|
+
}
|
|
290
|
+
if (!LLMObsTagger.tagMap.has(span)) {
|
|
291
|
+
err = 'invalid_span'
|
|
292
|
+
throw new Error('Span must be an LLMObs-generated span')
|
|
293
|
+
}
|
|
294
|
+
} catch (e) {
|
|
295
|
+
telemetry.recordExportSpan(span, err)
|
|
296
|
+
throw e
|
|
275
297
|
}
|
|
276
|
-
|
|
277
298
|
try {
|
|
278
299
|
return {
|
|
279
300
|
traceId: span.context().toTraceId(true),
|
|
280
301
|
spanId: span.context().toSpanId()
|
|
281
302
|
}
|
|
282
303
|
} catch {
|
|
283
|
-
|
|
304
|
+
err = 'invalid_span'
|
|
305
|
+
logger.warn('Failed to export span. Span must be a valid Span object.')
|
|
306
|
+
} finally {
|
|
307
|
+
telemetry.recordExportSpan(span, err)
|
|
284
308
|
}
|
|
285
309
|
}
|
|
286
310
|
|
|
287
311
|
submitEvaluation (llmobsSpanContext, options = {}) {
|
|
288
312
|
if (!this.enabled) return
|
|
289
313
|
|
|
314
|
+
let err = ''
|
|
290
315
|
const { traceId, spanId } = llmobsSpanContext
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
if (typeof timestampMs !== 'number' || timestampMs < 0) {
|
|
306
|
-
throw new Error('timestampMs must be a non-negative integer. Evaluation metric data will not be sent')
|
|
307
|
-
}
|
|
316
|
+
try {
|
|
317
|
+
if (!traceId || !spanId) {
|
|
318
|
+
err = 'invalid_span'
|
|
319
|
+
throw new Error(
|
|
320
|
+
'spanId and traceId must both be specified for the given evaluation metric to be submitted.'
|
|
321
|
+
)
|
|
322
|
+
}
|
|
323
|
+
const mlApp = options.mlApp || this._config.llmobs.mlApp
|
|
324
|
+
if (!mlApp) {
|
|
325
|
+
err = 'missing_ml_app'
|
|
326
|
+
throw new Error(
|
|
327
|
+
'ML App name is required for sending evaluation metrics. Evaluation metric data will not be sent.'
|
|
328
|
+
)
|
|
329
|
+
}
|
|
308
330
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
if (!metricType || !['categorical', 'score'].includes(metricType)) {
|
|
315
|
-
throw new Error('metricType must be one of "categorical" or "score"')
|
|
316
|
-
}
|
|
331
|
+
const timestampMs = options.timestampMs || Date.now()
|
|
332
|
+
if (typeof timestampMs !== 'number' || timestampMs < 0) {
|
|
333
|
+
err = 'invalid_timestamp'
|
|
334
|
+
throw new Error('timestampMs must be a non-negative integer. Evaluation metric data will not be sent')
|
|
335
|
+
}
|
|
317
336
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
337
|
+
const { label, value, tags } = options
|
|
338
|
+
const metricType = options.metricType?.toLowerCase()
|
|
339
|
+
if (!label) {
|
|
340
|
+
err = 'invalid_metric_label'
|
|
341
|
+
throw new Error('label must be the specified name of the evaluation metric')
|
|
342
|
+
}
|
|
343
|
+
if (!metricType || !['categorical', 'score'].includes(metricType)) {
|
|
344
|
+
err = 'invalid_metric_type'
|
|
345
|
+
throw new Error('metricType must be one of "categorical" or "score"')
|
|
346
|
+
}
|
|
347
|
+
if (metricType === 'categorical' && typeof value !== 'string') {
|
|
348
|
+
err = 'invalid_metric_value'
|
|
349
|
+
throw new Error('value must be a string for a categorical metric.')
|
|
350
|
+
}
|
|
351
|
+
if (metricType === 'score' && typeof value !== 'number') {
|
|
352
|
+
err = 'invalid_metric_value'
|
|
353
|
+
throw new Error('value must be a number for a score metric.')
|
|
354
|
+
}
|
|
324
355
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
356
|
+
const evaluationTags = {
|
|
357
|
+
'ddtrace.version': tracerVersion,
|
|
358
|
+
ml_app: mlApp
|
|
359
|
+
}
|
|
329
360
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
361
|
+
if (tags) {
|
|
362
|
+
for (const key in tags) {
|
|
363
|
+
const tag = tags[key]
|
|
364
|
+
if (typeof tag === 'string') {
|
|
365
|
+
evaluationTags[key] = tag
|
|
366
|
+
} else if (typeof tag.toString === 'function') {
|
|
367
|
+
evaluationTags[key] = tag.toString()
|
|
368
|
+
} else if (tag == null) {
|
|
369
|
+
evaluationTags[key] = Object.prototype.toString.call(tag)
|
|
370
|
+
} else {
|
|
371
|
+
// should be a rare case
|
|
372
|
+
// every object in JS has a toString, otherwise every primitive has its own toString
|
|
373
|
+
// null and undefined are handled above
|
|
374
|
+
err = 'invalid_tags'
|
|
375
|
+
throw new Error('Failed to parse tags. Tags for evaluation metrics must be strings')
|
|
376
|
+
}
|
|
344
377
|
}
|
|
345
378
|
}
|
|
346
|
-
}
|
|
347
379
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
380
|
+
const payload = {
|
|
381
|
+
span_id: spanId,
|
|
382
|
+
trace_id: traceId,
|
|
383
|
+
label,
|
|
384
|
+
metric_type: metricType,
|
|
385
|
+
ml_app: mlApp,
|
|
386
|
+
[`${metricType}_value`]: value,
|
|
387
|
+
timestamp_ms: timestampMs,
|
|
388
|
+
tags: Object.entries(evaluationTags).map(([key, value]) => `${key}:${value}`)
|
|
389
|
+
}
|
|
390
|
+
evalMetricAppendCh.publish(payload)
|
|
391
|
+
} finally {
|
|
392
|
+
telemetry.recordSubmitEvaluation(options, err)
|
|
357
393
|
}
|
|
358
|
-
|
|
359
|
-
evalMetricAppendCh.publish(payload)
|
|
360
394
|
}
|
|
361
395
|
|
|
362
396
|
flush () {
|
|
@@ -375,7 +409,7 @@ class LLMObs extends NoopLLMObs {
|
|
|
375
409
|
annotations.outputData = output
|
|
376
410
|
}
|
|
377
411
|
|
|
378
|
-
this.annotate(span, annotations)
|
|
412
|
+
this.annotate(span, annotations, true)
|
|
379
413
|
}
|
|
380
414
|
|
|
381
415
|
_active () {
|
|
@@ -135,7 +135,7 @@ class LLMObsTagger {
|
|
|
135
135
|
if (typeof value === 'number') {
|
|
136
136
|
filterdMetrics[processedKey] = value
|
|
137
137
|
} else {
|
|
138
|
-
this._handleFailure(`Value for metric '${key}' must be a number, instead got ${value}
|
|
138
|
+
this._handleFailure(`Value for metric '${key}' must be a number, instead got ${value}`, 'invalid_metrics')
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
|
|
@@ -169,7 +169,7 @@ class LLMObsTagger {
|
|
|
169
169
|
this._setTag(span, key, JSON.stringify(data))
|
|
170
170
|
} catch {
|
|
171
171
|
const type = key === INPUT_VALUE ? 'input' : 'output'
|
|
172
|
-
this._handleFailure(`Failed to parse ${type} value, must be JSON serializable
|
|
172
|
+
this._handleFailure(`Failed to parse ${type} value, must be JSON serializable.`, 'invalid_io_text')
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
175
|
}
|
|
@@ -187,7 +187,7 @@ class LLMObsTagger {
|
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
if (document == null || typeof document !== 'object') {
|
|
190
|
-
this._handleFailure('Documents must be a string, object, or list of objects.')
|
|
190
|
+
this._handleFailure('Documents must be a string, object, or list of objects.', 'invalid_embedding_io')
|
|
191
191
|
return undefined
|
|
192
192
|
}
|
|
193
193
|
|
|
@@ -195,7 +195,7 @@ class LLMObsTagger {
|
|
|
195
195
|
let validDocument = true
|
|
196
196
|
|
|
197
197
|
if (typeof text !== 'string') {
|
|
198
|
-
this._handleFailure('Document text must be a string.')
|
|
198
|
+
this._handleFailure('Document text must be a string.', 'invalid_embedding_io')
|
|
199
199
|
validDocument = false
|
|
200
200
|
}
|
|
201
201
|
|
|
@@ -226,7 +226,7 @@ class LLMObsTagger {
|
|
|
226
226
|
}
|
|
227
227
|
|
|
228
228
|
if (message == null || typeof message !== 'object') {
|
|
229
|
-
this._handleFailure('Messages must be a string, object, or list of objects')
|
|
229
|
+
this._handleFailure('Messages must be a string, object, or list of objects', 'invalid_io_messages')
|
|
230
230
|
return undefined
|
|
231
231
|
}
|
|
232
232
|
|
|
@@ -237,7 +237,7 @@ class LLMObsTagger {
|
|
|
237
237
|
const messageObj = { content }
|
|
238
238
|
|
|
239
239
|
if (typeof content !== 'string') {
|
|
240
|
-
this._handleFailure('Message content must be a string.')
|
|
240
|
+
this._handleFailure('Message content must be a string.', 'invalid_io_messages')
|
|
241
241
|
validMessage = false
|
|
242
242
|
}
|
|
243
243
|
|
|
@@ -250,7 +250,7 @@ class LLMObsTagger {
|
|
|
250
250
|
|
|
251
251
|
const filteredToolCalls = toolCalls.map(toolCall => {
|
|
252
252
|
if (typeof toolCall !== 'object') {
|
|
253
|
-
this._handleFailure('Tool call must be an object.')
|
|
253
|
+
this._handleFailure('Tool call must be an object.', 'invalid_io_messages')
|
|
254
254
|
return undefined
|
|
255
255
|
}
|
|
256
256
|
|
|
@@ -313,11 +313,15 @@ class LLMObsTagger {
|
|
|
313
313
|
|
|
314
314
|
// any public-facing LLMObs APIs using this tagger should not soft fail
|
|
315
315
|
// auto-instrumentation should soft fail
|
|
316
|
-
_handleFailure (msg) {
|
|
316
|
+
_handleFailure (msg, errorTag) {
|
|
317
317
|
if (this.softFail) {
|
|
318
318
|
log.warn(msg)
|
|
319
319
|
} else {
|
|
320
|
-
|
|
320
|
+
const err = new Error(msg)
|
|
321
|
+
if (errorTag) {
|
|
322
|
+
Object.defineProperty(err, 'ddErrorTag', { get () { return errorTag } })
|
|
323
|
+
}
|
|
324
|
+
throw err
|
|
321
325
|
}
|
|
322
326
|
}
|
|
323
327
|
|
|
@@ -109,11 +109,60 @@ function recordDroppedPayload (numEvents, eventType, error) {
|
|
|
109
109
|
llmobsMetrics.count(metricName, tags).inc(numEvents)
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
function recordLLMObsAnnotate (span, err, value = 1) {
|
|
113
|
+
const mlObsTags = LLMObsTagger.tagMap.get(span) || {}
|
|
114
|
+
const spanKind = mlObsTags[SPAN_KIND] || 'N/A'
|
|
115
|
+
const isRootSpan = mlObsTags[PARENT_ID_KEY] === ROOT_PARENT_ID
|
|
116
|
+
|
|
117
|
+
const tags = {
|
|
118
|
+
error: Number(!!err),
|
|
119
|
+
span_kind: spanKind,
|
|
120
|
+
is_root_span: Number(isRootSpan)
|
|
121
|
+
}
|
|
122
|
+
if (err) tags.error_type = err
|
|
123
|
+
llmobsMetrics.count('annotations', tags).inc(value)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function recordUserFlush (err, value = 1) {
|
|
127
|
+
const tags = { error: Number(!!err) }
|
|
128
|
+
if (err) tags.error_type = err
|
|
129
|
+
llmobsMetrics.count('user_flush', tags).inc(value)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function recordExportSpan (span, err, value = 1) {
|
|
133
|
+
const mlObsTags = LLMObsTagger.tagMap.get(span) || {}
|
|
134
|
+
const spanKind = mlObsTags[SPAN_KIND] || 'N/A'
|
|
135
|
+
const isRootSpan = mlObsTags[PARENT_ID_KEY] === ROOT_PARENT_ID
|
|
136
|
+
|
|
137
|
+
const tags = {
|
|
138
|
+
error: Number(!!err),
|
|
139
|
+
span_kind: spanKind,
|
|
140
|
+
is_root_span: Number(isRootSpan)
|
|
141
|
+
}
|
|
142
|
+
if (err) tags.error_type = err
|
|
143
|
+
llmobsMetrics.count('spans_exported', tags).inc(value)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function recordSubmitEvaluation (options, err, value = 1) {
|
|
147
|
+
const tags = {
|
|
148
|
+
error: Number(!!err),
|
|
149
|
+
custom_joining_key: 0
|
|
150
|
+
}
|
|
151
|
+
const metricType = options?.metricType?.toLowerCase()
|
|
152
|
+
if (metricType !== 'categorical' && metricType !== 'score') tags.metric_type = 'other'
|
|
153
|
+
if (err) tags.error_type = err
|
|
154
|
+
llmobsMetrics.count('evals_submitted', tags).inc(value)
|
|
155
|
+
}
|
|
156
|
+
|
|
112
157
|
module.exports = {
|
|
113
158
|
recordLLMObsEnabled,
|
|
114
159
|
incrementLLMObsSpanStartCount,
|
|
115
160
|
incrementLLMObsSpanFinishedCount,
|
|
116
161
|
recordLLMObsRawSpanSize,
|
|
117
162
|
recordLLMObsSpanSize,
|
|
118
|
-
recordDroppedPayload
|
|
163
|
+
recordDroppedPayload,
|
|
164
|
+
recordLLMObsAnnotate,
|
|
165
|
+
recordUserFlush,
|
|
166
|
+
recordExportSpan,
|
|
167
|
+
recordSubmitEvaluation
|
|
119
168
|
}
|
|
@@ -1694,7 +1694,7 @@ JSONPath.prototype._handleCallback = function (fullRetObj, callback, type) {
|
|
|
1694
1694
|
* @param {string} parentPropName
|
|
1695
1695
|
* @param {JSONPathCallback} callback
|
|
1696
1696
|
* @param {boolean} hasArrExpr
|
|
1697
|
-
* @param {boolean} literalPriority
|
|
1697
|
+
* @param {boolean} [literalPriority]
|
|
1698
1698
|
* @returns {ReturnObject|ReturnObject[]}
|
|
1699
1699
|
*/
|
|
1700
1700
|
JSONPath.prototype._trace = function (expr, val, path, parent, parentPropName, callback, hasArrExpr, literalPriority) {
|
|
@@ -15,7 +15,6 @@ const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../plugins/util/tags')
|
|
|
15
15
|
const { tagger } = require('./tagger')
|
|
16
16
|
const { isFalse, isTrue } = require('../util')
|
|
17
17
|
const { getAzureTagsFromMetadata, getAzureAppMetadata } = require('../azure_metadata')
|
|
18
|
-
const satisfies = require('semifies')
|
|
19
18
|
|
|
20
19
|
class Config {
|
|
21
20
|
constructor (options = {}) {
|
|
@@ -23,7 +22,6 @@ class Config {
|
|
|
23
22
|
DD_AGENT_HOST,
|
|
24
23
|
DD_ENV,
|
|
25
24
|
DD_INTERNAL_PROFILING_TIMELINE_SAMPLING_ENABLED, // used for testing
|
|
26
|
-
DD_PROFILING_ASYNC_ID_ENABLED,
|
|
27
25
|
DD_PROFILING_CODEHOTSPOTS_ENABLED,
|
|
28
26
|
DD_PROFILING_CPU_ENABLED,
|
|
29
27
|
DD_PROFILING_DEBUG_SOURCE_MAPS,
|
|
@@ -181,10 +179,6 @@ class Config {
|
|
|
181
179
|
this.timelineSamplingEnabled = isTrue(coalesce(options.timelineSamplingEnabled,
|
|
182
180
|
DD_INTERNAL_PROFILING_TIMELINE_SAMPLING_ENABLED, true))
|
|
183
181
|
|
|
184
|
-
// Async ID gathering only works reliably on Node >= 22.10.0
|
|
185
|
-
this.asyncIdEnabled = isTrue(coalesce(options.asyncIdEnabled,
|
|
186
|
-
DD_PROFILING_ASYNC_ID_ENABLED, this.timelineEnabled && satisfies(process.versions.node, '>=22.10.0')))
|
|
187
|
-
|
|
188
182
|
this.codeHotspotsEnabled = isTrue(coalesce(options.codeHotspotsEnabled,
|
|
189
183
|
DD_PROFILING_CODEHOTSPOTS_ENABLED,
|
|
190
184
|
DD_PROFILING_EXPERIMENTAL_CODEHOTSPOTS_ENABLED, samplingContextsAvailable))
|