dd-trace 6.1.0 → 6.3.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 +1 -1
- package/index.d.ts +7 -0
- package/initialize.mjs +4 -2
- package/package.json +5 -5
- package/packages/datadog-instrumentations/src/cookie.js +7 -1
- package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +5 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/mercurius.js +31 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/modelcontextprotocol-sdk.js +30 -54
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +19 -5
- package/packages/datadog-instrumentations/src/http/server.js +27 -0
- package/packages/datadog-instrumentations/src/mercurius.js +11 -0
- package/packages/datadog-instrumentations/src/modelcontextprotocol-sdk.js +248 -1
- package/packages/datadog-instrumentations/src/otel-sdk-trace.js +15 -0
- package/packages/datadog-instrumentations/src/playwright.js +40 -3
- package/packages/datadog-instrumentations/src/vitest-main.js +0 -2
- package/packages/datadog-instrumentations/src/vitest-util.js +0 -3
- package/packages/datadog-instrumentations/src/vitest-worker.js +0 -1
- package/packages/datadog-plugin-azure-functions/src/index.js +2 -1
- package/packages/datadog-plugin-graphql/src/execute.js +4 -15
- package/packages/datadog-plugin-graphql/src/index.js +6 -0
- package/packages/datadog-plugin-graphql/src/request.js +104 -0
- package/packages/datadog-plugin-graphql/src/utils.js +177 -0
- package/packages/datadog-plugin-graphql/src/validate.js +24 -1
- package/packages/datadog-plugin-modelcontextprotocol-sdk/src/tracing.js +144 -26
- package/packages/datadog-plugin-modelcontextprotocol-sdk/src/utils.js +30 -0
- package/packages/datadog-plugin-playwright/src/index.js +1 -1
- package/packages/datadog-plugin-vitest/src/index.js +0 -8
- package/packages/datadog-shimmer/src/shimmer.js +9 -2
- package/packages/dd-trace/src/appsec/channels.js +1 -0
- package/packages/dd-trace/src/appsec/index.js +6 -3
- package/packages/dd-trace/src/appsec/lambda.js +8 -8
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +1 -1
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +23 -16
- package/packages/dd-trace/src/config/config-types.d.ts +8 -0
- package/packages/dd-trace/src/config/defaults.js +20 -15
- package/packages/dd-trace/src/config/index.js +13 -3
- package/packages/dd-trace/src/config/parsers.js +79 -0
- package/packages/dd-trace/src/datastreams/writer.js +14 -2
- package/packages/dd-trace/src/encode/0.4.js +23 -2
- package/packages/dd-trace/src/encode/0.5.js +12 -1
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +11 -1
- package/packages/dd-trace/src/encode/coverage-ci-visibility.js +20 -2
- package/packages/dd-trace/src/exporters/common/writer.js +17 -1
- package/packages/dd-trace/src/guardrails/index.js +3 -1
- package/packages/dd-trace/src/guardrails/telemetry.js +40 -3
- package/packages/dd-trace/src/llmobs/constants/tags.js +1 -0
- package/packages/dd-trace/src/llmobs/tagger.js +15 -1
- package/packages/dd-trace/src/msgpack/chunk.js +33 -1
- package/packages/dd-trace/src/msgpack/index.js +6 -1
- package/packages/dd-trace/src/opentelemetry/span_processor.js +7 -5
- package/packages/dd-trace/src/opentelemetry/tracer_provider.js +32 -16
- package/packages/dd-trace/src/plugins/index.js +3 -0
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +20 -11
- package/packages/dd-trace/src/service-naming/schemas/v0/graphql.js +6 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/graphql.js +8 -0
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +5 -6
|
@@ -297,8 +297,87 @@ const parsers = {
|
|
|
297
297
|
},
|
|
298
298
|
}
|
|
299
299
|
|
|
300
|
+
const programmaticTypeCoercions = {
|
|
301
|
+
/**
|
|
302
|
+
* @param {unknown} value
|
|
303
|
+
*/
|
|
304
|
+
BOOLEAN (value) {
|
|
305
|
+
if (typeof value === 'boolean') {
|
|
306
|
+
return value
|
|
307
|
+
}
|
|
308
|
+
if (typeof value === 'string') {
|
|
309
|
+
return parsers.BOOLEAN(value)
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
/**
|
|
313
|
+
* @param {unknown} value
|
|
314
|
+
*/
|
|
315
|
+
INT (value) {
|
|
316
|
+
if (typeof value === 'number' || typeof value === 'string' && value.trim() !== '') {
|
|
317
|
+
return parsers.INT(value)
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
/**
|
|
321
|
+
* @param {unknown} value
|
|
322
|
+
*/
|
|
323
|
+
DECIMAL (value) {
|
|
324
|
+
if (typeof value === 'number' || typeof value === 'string' && value.trim() !== '') {
|
|
325
|
+
return parsers.DECIMAL(value)
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
/**
|
|
329
|
+
* @param {unknown} value
|
|
330
|
+
*/
|
|
331
|
+
STRING (value) {
|
|
332
|
+
if (typeof value === 'string') {
|
|
333
|
+
return value
|
|
334
|
+
}
|
|
335
|
+
if (typeof value === 'boolean' || typeof value === 'number' && Number.isFinite(value)) {
|
|
336
|
+
return String(value)
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
/**
|
|
340
|
+
* @param {unknown} value
|
|
341
|
+
*/
|
|
342
|
+
FUNCTION (value) {
|
|
343
|
+
if (typeof value === 'function') {
|
|
344
|
+
return value
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
/**
|
|
348
|
+
* @param {unknown} value
|
|
349
|
+
*/
|
|
350
|
+
ARRAY (value) {
|
|
351
|
+
if (Array.isArray(value)) {
|
|
352
|
+
for (const item of value) {
|
|
353
|
+
if (typeof item !== 'string' && typeof item !== 'number' && typeof item !== 'boolean') {
|
|
354
|
+
return
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return value
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
/**
|
|
361
|
+
* @param {unknown} value
|
|
362
|
+
*/
|
|
363
|
+
MAP (value) {
|
|
364
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
365
|
+
return value
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
/**
|
|
369
|
+
* @param {unknown} value
|
|
370
|
+
*/
|
|
371
|
+
JSON (value) {
|
|
372
|
+
if (typeof value === 'object' && value !== null) {
|
|
373
|
+
return value
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
}
|
|
377
|
+
|
|
300
378
|
module.exports = {
|
|
301
379
|
parsers,
|
|
380
|
+
programmaticTypeCoercions,
|
|
302
381
|
transformers,
|
|
303
382
|
telemetryTransformers,
|
|
304
383
|
setWarnInvalidValue,
|
|
@@ -4,7 +4,7 @@ const zlib = require('zlib')
|
|
|
4
4
|
const pkg = require('../../../../package.json')
|
|
5
5
|
const log = require('../log')
|
|
6
6
|
const request = require('../exporters/common/request')
|
|
7
|
-
const { encode: encodeMsgpack } = require('../msgpack')
|
|
7
|
+
const { encode: encodeMsgpack, MAX_SIZE: MAX_CHUNK_SIZE } = require('../msgpack')
|
|
8
8
|
|
|
9
9
|
function makeRequest (data, url, cb) {
|
|
10
10
|
const options = {
|
|
@@ -36,7 +36,19 @@ class DataStreamsWriter {
|
|
|
36
36
|
log.debug('Maximum number of active requests reached. Payload discarded: %j', payload)
|
|
37
37
|
return
|
|
38
38
|
}
|
|
39
|
-
|
|
39
|
+
|
|
40
|
+
let encodedPayload
|
|
41
|
+
try {
|
|
42
|
+
encodedPayload = encodeMsgpack(payload)
|
|
43
|
+
} catch (error) {
|
|
44
|
+
if (error.code !== 'ERR_MSGPACK_CHUNK_OVERFLOW') throw error
|
|
45
|
+
// The msgpack-encoded pipeline-stats payload exceeded the agent
|
|
46
|
+
// intake cap. Dropping it locally is safer than letting the
|
|
47
|
+
// RangeError crash the host process; the agent would reject the
|
|
48
|
+
// oversized payload at the network boundary anyway.
|
|
49
|
+
log.error('DataStreamsWriter dropped a payload that exceeded the %d byte chunk cap', MAX_CHUNK_SIZE)
|
|
50
|
+
return
|
|
51
|
+
}
|
|
40
52
|
|
|
41
53
|
zlib.gzip(encodedPayload, { level: 1 }, (err, compressedData) => {
|
|
42
54
|
if (err) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const getConfig = require('../config')
|
|
4
|
-
const { MsgpackChunk } = require('../msgpack')
|
|
4
|
+
const { MsgpackChunk, MAX_SIZE: MAX_CHUNK_SIZE } = require('../msgpack')
|
|
5
5
|
const log = require('../log')
|
|
6
6
|
const { normalizeSpan, eventTimeNano } = require('./tags-processors')
|
|
7
7
|
|
|
@@ -265,7 +265,28 @@ class AgentEncoder {
|
|
|
265
265
|
|
|
266
266
|
this._traceCount++
|
|
267
267
|
|
|
268
|
-
|
|
268
|
+
try {
|
|
269
|
+
this._encode(bytes, trace)
|
|
270
|
+
} catch (error) {
|
|
271
|
+
if (error.code !== 'ERR_MSGPACK_CHUNK_OVERFLOW') throw error
|
|
272
|
+
// The trace, or the queued payload it joined, hit the chunk cap.
|
|
273
|
+
// Rolling back just the in-flight trace is unsafe: the string cache
|
|
274
|
+
// may already hold subarrays / indices pointing at bytes we'd
|
|
275
|
+
// discard, and the next encode would emit stale bytes against a
|
|
276
|
+
// smaller string table. Drop the whole queued payload so the next
|
|
277
|
+
// encode starts from a clean state. Emitting a partial buffer would
|
|
278
|
+
// corrupt the msgpack wire. The virtual `reset()` is called (not
|
|
279
|
+
// `_reset()`) so subclasses can clear their own per-payload state
|
|
280
|
+
// (e.g. `AgentlessCiVisibilityEncoder._eventCount`).
|
|
281
|
+
const dropped = this._traceCount
|
|
282
|
+
this.reset()
|
|
283
|
+
log.error(
|
|
284
|
+
'Trace encoder reset after exceeding the %d byte chunk cap; dropped %d trace(s)',
|
|
285
|
+
MAX_CHUNK_SIZE,
|
|
286
|
+
dropped
|
|
287
|
+
)
|
|
288
|
+
return
|
|
289
|
+
}
|
|
269
290
|
|
|
270
291
|
if (this.#debugEncoding) {
|
|
271
292
|
const end = bytes.length
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { MAX_SIZE, OverflowError } = require('../msgpack')
|
|
3
4
|
const { normalizeSpan } = require('./tags-processors')
|
|
4
5
|
const { AgentEncoder: BaseEncoder, stringifySpanEvents } = require('./0.4')
|
|
5
6
|
|
|
@@ -30,7 +31,17 @@ class AgentEncoder extends BaseEncoder {
|
|
|
30
31
|
const prefixSize = 1
|
|
31
32
|
const stringSize = this._stringBytes.length + 5
|
|
32
33
|
const traceSize = this._traceBytes.length + 5
|
|
33
|
-
const
|
|
34
|
+
const payloadSize = prefixSize + stringSize + traceSize
|
|
35
|
+
|
|
36
|
+
// The string table and the trace bytes are capped independently, so both
|
|
37
|
+
// can sit just under the cap while their concatenation crosses it. `encode`
|
|
38
|
+
// never sees this overflow — it only exists once the two are summed here —
|
|
39
|
+
// so the writer's flush-time catch drops the payload.
|
|
40
|
+
if (payloadSize > MAX_SIZE) {
|
|
41
|
+
throw new OverflowError(payloadSize)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const buffer = Buffer.allocUnsafe(payloadSize)
|
|
34
45
|
|
|
35
46
|
buffer[0] = ARRAY_OF_TWO
|
|
36
47
|
|
|
@@ -7,7 +7,7 @@ const {
|
|
|
7
7
|
TELEMETRY_ENDPOINT_PAYLOAD_SERIALIZATION_MS,
|
|
8
8
|
TELEMETRY_ENDPOINT_PAYLOAD_EVENTS_COUNT,
|
|
9
9
|
} = require('../ci-visibility/telemetry')
|
|
10
|
-
const { MsgpackChunk } = require('../msgpack')
|
|
10
|
+
const { MsgpackChunk, MAX_SIZE, OverflowError } = require('../msgpack')
|
|
11
11
|
const { AgentEncoder } = require('./0.4')
|
|
12
12
|
const {
|
|
13
13
|
truncateSpanTestOpt,
|
|
@@ -371,6 +371,16 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
|
|
|
371
371
|
|
|
372
372
|
const eventsBytes = this._traceBytes
|
|
373
373
|
const totalSize = prefixBytes.length + eventsBytes.length
|
|
374
|
+
|
|
375
|
+
// The metadata prefix (built here, not during `encode`) and the events are
|
|
376
|
+
// capped independently, so both can stay under the cap while the assembled
|
|
377
|
+
// payload crosses it. An oversized metadata tag also overflows the prefix
|
|
378
|
+
// chunk itself inside `_encodePayloadStart` above; either way the tagged
|
|
379
|
+
// error propagates to the writer's flush-time catch to drop the payload.
|
|
380
|
+
if (totalSize > MAX_SIZE) {
|
|
381
|
+
throw new OverflowError(totalSize)
|
|
382
|
+
}
|
|
383
|
+
|
|
374
384
|
const buffer = Buffer.allocUnsafe(totalSize)
|
|
375
385
|
prefixBytes.buffer.copy(buffer, 0, 0, prefixBytes.length)
|
|
376
386
|
eventsBytes.buffer.copy(buffer, prefixBytes.length, 0, eventsBytes.length)
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
'use strict'
|
|
2
|
-
const { MsgpackChunk } = require('../msgpack')
|
|
2
|
+
const { MsgpackChunk, MAX_SIZE: MAX_CHUNK_SIZE } = require('../msgpack')
|
|
3
3
|
|
|
4
4
|
const {
|
|
5
5
|
distributionMetric,
|
|
6
6
|
TELEMETRY_ENDPOINT_PAYLOAD_SERIALIZATION_MS,
|
|
7
7
|
TELEMETRY_ENDPOINT_PAYLOAD_EVENTS_COUNT,
|
|
8
8
|
} = require('../ci-visibility/telemetry')
|
|
9
|
+
const log = require('../log')
|
|
9
10
|
const FormData = require('../exporters/common/form-data')
|
|
10
11
|
const { AgentEncoder } = require('./0.4')
|
|
11
12
|
|
|
@@ -40,7 +41,24 @@ class CoverageCIVisibilityEncoder extends AgentEncoder {
|
|
|
40
41
|
const startTime = Date.now()
|
|
41
42
|
|
|
42
43
|
this._coveragesCount++
|
|
43
|
-
|
|
44
|
+
try {
|
|
45
|
+
this.encodeCodeCoverage(this._coverageBytes, coverage)
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (error.code !== 'ERR_MSGPACK_CHUNK_OVERFLOW') throw error
|
|
48
|
+
// The coverage payload hit the chunk cap. `_coverageBytes` may
|
|
49
|
+
// hold a partial entry and a stale `coverages` array prefix. Drop
|
|
50
|
+
// the whole queued payload so the next encode starts from a clean
|
|
51
|
+
// state; the parent `AgentEncoder` does the equivalent for its own
|
|
52
|
+
// bytes / string cache via the inherited `_reset()`.
|
|
53
|
+
const dropped = this._coveragesCount
|
|
54
|
+
this.reset()
|
|
55
|
+
log.error(
|
|
56
|
+
'Coverage encoder reset after exceeding the %d byte chunk cap; dropped %d coverage(s)',
|
|
57
|
+
MAX_CHUNK_SIZE,
|
|
58
|
+
dropped
|
|
59
|
+
)
|
|
60
|
+
return
|
|
61
|
+
}
|
|
44
62
|
|
|
45
63
|
distributionMetric(
|
|
46
64
|
TELEMETRY_ENDPOINT_PAYLOAD_SERIALIZATION_MS,
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const { channel } = require('dc-polyfill')
|
|
4
4
|
|
|
5
5
|
const log = require('../../log')
|
|
6
|
+
const { MAX_SIZE: MAX_CHUNK_SIZE } = require('../../msgpack')
|
|
6
7
|
const request = require('./request')
|
|
7
8
|
const { safeJSONStringify } = require('./util')
|
|
8
9
|
|
|
@@ -27,7 +28,22 @@ class Writer {
|
|
|
27
28
|
this.#isFirstFlush = false
|
|
28
29
|
this._beforeFirstFlush()
|
|
29
30
|
}
|
|
30
|
-
|
|
31
|
+
let payload
|
|
32
|
+
try {
|
|
33
|
+
payload = this._encoder.makePayload()
|
|
34
|
+
} catch (error) {
|
|
35
|
+
if (error.code !== 'ERR_MSGPACK_CHUNK_OVERFLOW') throw error
|
|
36
|
+
// Multi-chunk encoders (v0.5, CI Visibility) only learn the assembled
|
|
37
|
+
// payload exceeds the cap when `makePayload` stitches the chunks
|
|
38
|
+
// together, after `encode` already returned — so the encode-time catch
|
|
39
|
+
// never sees it. Drop the queued payload here instead of letting the
|
|
40
|
+
// RangeError escape into the host application; the agent would reject
|
|
41
|
+
// the oversized payload at the network boundary anyway.
|
|
42
|
+
this._encoder.reset()
|
|
43
|
+
log.error('Writer dropped %d trace(s) that exceeded the %d byte chunk cap', count, MAX_CHUNK_SIZE)
|
|
44
|
+
done()
|
|
45
|
+
return
|
|
46
|
+
}
|
|
31
47
|
this._sendPayload(payload, count, done)
|
|
32
48
|
} else {
|
|
33
49
|
done()
|
|
@@ -48,6 +48,8 @@ function guard (fn) {
|
|
|
48
48
|
if (!clobberBailout && (NODE_MAJOR < minMajor || NODE_MAJOR >= nextMajor)) {
|
|
49
49
|
initBailout = true
|
|
50
50
|
var runtimeInfo = 'Incompatible runtime Node.js ' + version + ', supported runtimes: Node.js ' + supportedRange
|
|
51
|
+
// When not forced, the process bails out here and may call process.exit() right away;
|
|
52
|
+
// forward synchronously so the telemetry child can't outlive us and wedge the exit.
|
|
51
53
|
telemetry([
|
|
52
54
|
{ name: 'abort', tags: ['reason:incompatible_runtime'] },
|
|
53
55
|
{ name: 'abort.runtime', tags: [] }
|
|
@@ -55,7 +57,7 @@ function guard (fn) {
|
|
|
55
57
|
result: 'abort',
|
|
56
58
|
result_class: 'incompatible_runtime',
|
|
57
59
|
result_reason: runtimeInfo
|
|
58
|
-
})
|
|
60
|
+
}, !forced)
|
|
59
61
|
log.info('Aborting application instrumentation due to incompatible_runtime.')
|
|
60
62
|
log.info('Found incompatible runtime Node.js %s, Supported runtimes: Node.js %s.', version, supportedRange)
|
|
61
63
|
if (forced) {
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
var fs = require('fs')
|
|
4
|
-
|
|
4
|
+
// Capture the child_process functions at load time, before the tracer wraps the module.
|
|
5
|
+
// Reaching through require('child_process') at send time would route the forwarder through
|
|
6
|
+
// the tracer's own child_process instrumentation once the tracer is initialized.
|
|
7
|
+
var childProcess = require('child_process')
|
|
8
|
+
var spawn = childProcess.spawn
|
|
9
|
+
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
10
|
+
var spawnSync = childProcess.spawnSync
|
|
5
11
|
var tracerVersion = require('../../../../package.json').version
|
|
6
12
|
var log = require('./log')
|
|
7
13
|
|
|
@@ -47,7 +53,7 @@ function shouldSend (point) {
|
|
|
47
53
|
return true
|
|
48
54
|
}
|
|
49
55
|
|
|
50
|
-
function sendTelemetry (name, tags, resultMetadata) {
|
|
56
|
+
function sendTelemetry (name, tags, resultMetadata, synchronous) {
|
|
51
57
|
var points = name
|
|
52
58
|
if (typeof name === 'string') {
|
|
53
59
|
points = [{ name: name, tags: tags || [] }]
|
|
@@ -74,6 +80,37 @@ function sendTelemetry (name, tags, resultMetadata) {
|
|
|
74
80
|
}
|
|
75
81
|
}
|
|
76
82
|
|
|
83
|
+
var payload = JSON.stringify({ metadata: currentMetadata, points: points })
|
|
84
|
+
|
|
85
|
+
// A forwarder spawned asynchronously can still be tearing down its stdio pipes when the
|
|
86
|
+
// injected app calls process.exit(); on Node 24.0.0/24.1.x that deadlocks the exit
|
|
87
|
+
// (fixed upstream in 24.2), hanging short-lived single-step-install processes. On the
|
|
88
|
+
// bailout path the caller passes synchronous=true, so the child is fully reaped before we
|
|
89
|
+
// return and nothing survives to race the exit. spawnSync is only reached on that path,
|
|
90
|
+
// before any instrumentation is active, so it never traces the forwarder. It exists since
|
|
91
|
+
// Node 0.11.12; the guardrails still target >=0.8, which predates the exit bug anyway.
|
|
92
|
+
if (synchronous && spawnSync) {
|
|
93
|
+
// Bound the blocking send: this telemetry is best-effort and the whole point of the
|
|
94
|
+
// synchronous path is to avoid a hung exit, so a forwarder that wedges must not become a
|
|
95
|
+
// new hard hang. On timeout spawnSync kills the child and returns error.code ETIMEDOUT.
|
|
96
|
+
var result = spawnSync(telemetryForwarderPath, ['library_entrypoint'], {
|
|
97
|
+
input: payload,
|
|
98
|
+
stdio: ['pipe', 'ignore', 'ignore'],
|
|
99
|
+
timeout: 1000,
|
|
100
|
+
killSignal: 'SIGKILL'
|
|
101
|
+
})
|
|
102
|
+
if (result.error) {
|
|
103
|
+
if (result.error.code === 'ETIMEDOUT') {
|
|
104
|
+
log.error('Telemetry forwarder timed out')
|
|
105
|
+
} else {
|
|
106
|
+
log.error('Failed to spawn telemetry forwarder')
|
|
107
|
+
}
|
|
108
|
+
} else if (result.status) {
|
|
109
|
+
log.error('Telemetry forwarder exited with code', result.status)
|
|
110
|
+
}
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
|
|
77
114
|
var proc = spawn(telemetryForwarderPath, ['library_entrypoint'], {
|
|
78
115
|
stdio: 'pipe'
|
|
79
116
|
})
|
|
@@ -88,5 +125,5 @@ function sendTelemetry (name, tags, resultMetadata) {
|
|
|
88
125
|
proc.stdin.on('error', function () {
|
|
89
126
|
log.error('Failed to write telemetry data to telemetry forwarder')
|
|
90
127
|
})
|
|
91
|
-
proc.stdin.end(
|
|
128
|
+
proc.stdin.end(payload)
|
|
92
129
|
}
|
|
@@ -13,6 +13,7 @@ module.exports = {
|
|
|
13
13
|
ML_APP: '_ml_obs.meta.ml_app',
|
|
14
14
|
PROPAGATED_PARENT_ID_KEY: '_dd.p.llmobs_parent_id',
|
|
15
15
|
PROPAGATED_ML_APP_KEY: '_dd.p.llmobs_ml_app',
|
|
16
|
+
PROPAGATED_SESSION_ID_KEY: '_dd.p.llmobs_sid',
|
|
16
17
|
PARENT_ID_KEY: '_ml_obs.llmobs_parent_id',
|
|
17
18
|
PROPAGATED_SAMPLE_RATE_KEY: '_dd.p.llmobs_sr',
|
|
18
19
|
PROPAGATED_SAMPLING_DECISION_KEY: '_dd.p.llmobs_sd',
|
|
@@ -35,6 +35,7 @@ const {
|
|
|
35
35
|
INTEGRATION,
|
|
36
36
|
DECORATOR,
|
|
37
37
|
PROPAGATED_ML_APP_KEY,
|
|
38
|
+
PROPAGATED_SESSION_ID_KEY,
|
|
38
39
|
DEFAULT_PROMPT_NAME,
|
|
39
40
|
INTERNAL_CONTEXT_VARIABLE_KEYS,
|
|
40
41
|
INTERNAL_QUERY_VARIABLE_KEYS,
|
|
@@ -139,7 +140,8 @@ class LLMObsTagger {
|
|
|
139
140
|
if (modelName) this.tagModelName(span, modelName)
|
|
140
141
|
if (modelProvider) this._setTag(span, MODEL_PROVIDER, modelProvider)
|
|
141
142
|
|
|
142
|
-
|
|
143
|
+
const traceTags = span.context()._trace.tags
|
|
144
|
+
sessionId = sessionId || registry.get(parent)?.[SESSION_ID] || traceTags[PROPAGATED_SESSION_ID_KEY]
|
|
143
145
|
if (sessionId) this._setTag(span, SESSION_ID, sessionId)
|
|
144
146
|
if (integration) this._setTag(span, INTEGRATION, integration)
|
|
145
147
|
if (_decorator) this._setTag(span, DECORATOR, _decorator)
|
|
@@ -752,6 +754,18 @@ class LLMObsTagger {
|
|
|
752
754
|
|
|
753
755
|
const tagsCarrier = registry.get(span)
|
|
754
756
|
tagsCarrier[key] = value
|
|
757
|
+
|
|
758
|
+
// The first session set in a trace becomes the trace-level default, stored on the trace-shared
|
|
759
|
+
// propagating tags so later spans (incl. those under a session-less parent) inherit it and it
|
|
760
|
+
// rides `x-datadog-tags` across service boundaries. Established here, the single choke point for
|
|
761
|
+
// session writes, so sessions post-populated by integrations after span start also seed it.
|
|
762
|
+
// First-writer wins, so an explicit session still overrides locally.
|
|
763
|
+
if (key === SESSION_ID && value) {
|
|
764
|
+
const traceTags = span.context()._trace.tags
|
|
765
|
+
if (traceTags[PROPAGATED_SESSION_ID_KEY] === undefined) {
|
|
766
|
+
traceTags[PROPAGATED_SESSION_ID_KEY] = value
|
|
767
|
+
}
|
|
768
|
+
}
|
|
755
769
|
}
|
|
756
770
|
}
|
|
757
771
|
|
|
@@ -10,6 +10,33 @@ const SHRINK_AFTER_FLUSHES = 32
|
|
|
10
10
|
// shape: after a halving step the post-shrink fill is the prior peak doubled,
|
|
11
11
|
// still under 50 %.
|
|
12
12
|
const SHRINK_USAGE_RATIO = 4
|
|
13
|
+
// Hard cap on chunk growth. The agent's trace intake rejects payloads over
|
|
14
|
+
// 50 MiB, so anything past that is dead on arrival anyway. A pathological
|
|
15
|
+
// single trace (unsanitized meta tag the size of a media file, multi-MB
|
|
16
|
+
// stack trace) used to grow the buffer without limit until either the
|
|
17
|
+
// allocation failed or the process tripped its memory ceiling. `reserve`
|
|
18
|
+
// now refuses the growth with a tagged `RangeError`; `AgentEncoder` catches
|
|
19
|
+
// it, resets the in-flight payload, and logs.
|
|
20
|
+
const MAX_SIZE = 50 * 1024 * 1024 // 50 MiB
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Thrown when a chunk — or an assembled payload stitched from several chunks —
|
|
24
|
+
* would cross `MAX_SIZE`. Shared so the `reserve` cap and the per-encoder
|
|
25
|
+
* assembled-size guards (`0.5`, agentless CI Visibility) raise the same `code`,
|
|
26
|
+
* which every writer's `flush` recognises to drop the payload instead of
|
|
27
|
+
* crashing the host.
|
|
28
|
+
*/
|
|
29
|
+
class OverflowError extends RangeError {
|
|
30
|
+
code = 'ERR_MSGPACK_CHUNK_OVERFLOW'
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {number} needed Requested total byte size.
|
|
34
|
+
*/
|
|
35
|
+
constructor (needed) {
|
|
36
|
+
super(`MsgpackChunk capped at ${MAX_SIZE} bytes; requested ${needed}`)
|
|
37
|
+
this.name = 'OverflowError'
|
|
38
|
+
}
|
|
39
|
+
}
|
|
13
40
|
|
|
14
41
|
/**
|
|
15
42
|
* Resizable msgpack write buffer. Owns the byte-layout primitives the encoder
|
|
@@ -100,11 +127,14 @@ class MsgpackChunk {
|
|
|
100
127
|
const needed = this.length + size
|
|
101
128
|
|
|
102
129
|
if (needed > this.buffer.length) {
|
|
130
|
+
if (needed > MAX_SIZE) {
|
|
131
|
+
throw new OverflowError(needed)
|
|
132
|
+
}
|
|
103
133
|
let newSize = this.buffer.length
|
|
104
134
|
// `*= 2` instead of `<<= 1`: `1073741824 << 1` is negative as int32,
|
|
105
135
|
// and msgpack values can legitimately reach the multi-GiB range.
|
|
106
136
|
while (newSize < needed) newSize *= 2
|
|
107
|
-
this.#resize(newSize)
|
|
137
|
+
this.#resize(Math.min(newSize, MAX_SIZE))
|
|
108
138
|
}
|
|
109
139
|
|
|
110
140
|
this.length += size
|
|
@@ -454,3 +484,5 @@ class MsgpackChunk {
|
|
|
454
484
|
}
|
|
455
485
|
|
|
456
486
|
module.exports = MsgpackChunk
|
|
487
|
+
module.exports.MAX_SIZE = MAX_SIZE
|
|
488
|
+
module.exports.OverflowError = OverflowError
|
|
@@ -14,32 +14,34 @@ class NoopSpanProcessor {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
class MultiSpanProcessor extends NoopSpanProcessor {
|
|
17
|
+
#processors
|
|
18
|
+
|
|
17
19
|
constructor (spanProcessors) {
|
|
18
20
|
super()
|
|
19
|
-
this
|
|
21
|
+
this.#processors = spanProcessors
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
forceFlush () {
|
|
23
25
|
return Promise.all(
|
|
24
|
-
this.
|
|
26
|
+
this.#processors.map(p => p.forceFlush())
|
|
25
27
|
)
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
onStart (span, context) {
|
|
29
|
-
for (const processor of this
|
|
31
|
+
for (const processor of this.#processors) {
|
|
30
32
|
processor.onStart(span, context)
|
|
31
33
|
}
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
onEnd (span) {
|
|
35
|
-
for (const processor of this
|
|
37
|
+
for (const processor of this.#processors) {
|
|
36
38
|
processor.onEnd(span)
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
shutdown () {
|
|
41
43
|
return Promise.all(
|
|
42
|
-
this.
|
|
44
|
+
this.#processors.map(p => p.shutdown())
|
|
43
45
|
)
|
|
44
46
|
}
|
|
45
47
|
}
|
|
@@ -10,46 +10,62 @@ const { MultiSpanProcessor, NoopSpanProcessor } = require('./span_processor')
|
|
|
10
10
|
const Tracer = require('./tracer')
|
|
11
11
|
|
|
12
12
|
class TracerProvider {
|
|
13
|
+
#activeProcessor = new NoopSpanProcessor()
|
|
14
|
+
#contextManager = new ContextManager()
|
|
15
|
+
#processors = []
|
|
16
|
+
#tracers = new Map()
|
|
17
|
+
|
|
13
18
|
constructor (config = {}) {
|
|
14
19
|
this.config = config
|
|
15
20
|
this.resource = config.resource
|
|
16
21
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
// @opentelemetry/sdk-trace 2.x (used by @opentelemetry/sdk-node 0.220+)
|
|
23
|
+
// dropped `addSpanProcessor` and hands the processors to the provider
|
|
24
|
+
// constructor instead. Wire them the same way the 1.x `addSpanProcessor`
|
|
25
|
+
// path does, so a NodeSDK configured with a trace exporter or custom
|
|
26
|
+
// processors still delivers onStart/onEnd to them.
|
|
27
|
+
if (Array.isArray(config.spanProcessors)) {
|
|
28
|
+
for (const spanProcessor of config.spanProcessors) {
|
|
29
|
+
this.addSpanProcessor(spanProcessor)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
21
32
|
}
|
|
22
33
|
|
|
23
34
|
getTracer (name = 'opentelemetry', version = '0.0.0', options) {
|
|
24
35
|
const key = `${name}@${version}`
|
|
25
|
-
if (!this.
|
|
26
|
-
this.
|
|
36
|
+
if (!this.#tracers.has(key)) {
|
|
37
|
+
this.#tracers.set(key, new Tracer(
|
|
27
38
|
{ ...options, name, version },
|
|
28
39
|
this.config,
|
|
29
40
|
this
|
|
30
41
|
))
|
|
31
42
|
}
|
|
32
|
-
return this.
|
|
43
|
+
return this.#tracers.get(key)
|
|
33
44
|
}
|
|
34
45
|
|
|
46
|
+
/**
|
|
47
|
+
* @param {NoopSpanProcessor} spanProcessor
|
|
48
|
+
*/
|
|
35
49
|
addSpanProcessor (spanProcessor) {
|
|
36
|
-
if (
|
|
37
|
-
|
|
50
|
+
if (this.#processors.includes(spanProcessor)) return
|
|
51
|
+
|
|
52
|
+
if (!this.#processors.length) {
|
|
53
|
+
this.#activeProcessor.shutdown()
|
|
38
54
|
}
|
|
39
|
-
this.
|
|
40
|
-
this
|
|
41
|
-
this
|
|
55
|
+
this.#processors.push(spanProcessor)
|
|
56
|
+
this.#activeProcessor = new MultiSpanProcessor(
|
|
57
|
+
this.#processors
|
|
42
58
|
)
|
|
43
59
|
}
|
|
44
60
|
|
|
45
61
|
getActiveSpanProcessor () {
|
|
46
|
-
return this
|
|
62
|
+
return this.#activeProcessor
|
|
47
63
|
}
|
|
48
64
|
|
|
49
65
|
// Not actually required by the SDK spec, but the official Node.js SDK does
|
|
50
66
|
// this and the docs reflect that so we should do this too for familiarity.
|
|
51
67
|
register (config = {}) {
|
|
52
|
-
context.setGlobalContextManager(this
|
|
68
|
+
context.setGlobalContextManager(this.#contextManager)
|
|
53
69
|
if (!trace.setGlobalTracerProvider(this)) {
|
|
54
70
|
trace.getTracerProvider().setDelegate(this)
|
|
55
71
|
}
|
|
@@ -69,11 +85,11 @@ class TracerProvider {
|
|
|
69
85
|
}
|
|
70
86
|
|
|
71
87
|
exporter._writer?.flush()
|
|
72
|
-
return this.
|
|
88
|
+
return this.#activeProcessor.forceFlush()
|
|
73
89
|
}
|
|
74
90
|
|
|
75
91
|
shutdown () {
|
|
76
|
-
return this.
|
|
92
|
+
return this.#activeProcessor.shutdown()
|
|
77
93
|
}
|
|
78
94
|
}
|
|
79
95
|
|
|
@@ -84,6 +84,9 @@ const plugins = {
|
|
|
84
84
|
get langchain () { return require('../../../datadog-plugin-langchain/src') },
|
|
85
85
|
get mariadb () { return require('../../../datadog-plugin-mariadb/src') },
|
|
86
86
|
get memcached () { return require('../../../datadog-plugin-memcached/src') },
|
|
87
|
+
// mercurius is traced under the graphql plugin: its instrumentation opens the
|
|
88
|
+
// top-level graphql.request span handled by the graphql CompositePlugin.
|
|
89
|
+
get mercurius () { return require('../../../datadog-plugin-graphql/src') },
|
|
87
90
|
get 'microgateway-core' () { return require('../../../datadog-plugin-microgateway-core/src') },
|
|
88
91
|
get mocha () { return require('../../../datadog-plugin-mocha/src') },
|
|
89
92
|
get 'mocha-each' () { return require('../../../datadog-plugin-mocha/src') },
|