dd-trace 6.0.0 → 6.2.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.
Files changed (98) hide show
  1. package/LICENSE-3rdparty.csv +1 -2
  2. package/index.d.ts +28 -1
  3. package/initialize.mjs +4 -2
  4. package/package.json +34 -30
  5. package/packages/datadog-instrumentations/src/ai.js +45 -0
  6. package/packages/datadog-instrumentations/src/apollo-server.js +5 -13
  7. package/packages/datadog-instrumentations/src/child_process.js +3 -3
  8. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +587 -0
  9. package/packages/datadog-instrumentations/src/cucumber.js +102 -43
  10. package/packages/datadog-instrumentations/src/cypress-config.js +11 -3
  11. package/packages/datadog-instrumentations/src/fastify.js +27 -8
  12. package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
  13. package/packages/datadog-instrumentations/src/helpers/instrument.js +7 -0
  14. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +5 -1
  15. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +6 -6
  16. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/claude-agent-sdk.js +17 -0
  17. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  18. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/mercurius.js +31 -0
  19. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +19 -5
  20. package/packages/datadog-instrumentations/src/jest.js +713 -63
  21. package/packages/datadog-instrumentations/src/mercurius.js +11 -0
  22. package/packages/datadog-instrumentations/src/mocha/main.js +24 -3
  23. package/packages/datadog-instrumentations/src/mocha/utils.js +128 -22
  24. package/packages/datadog-instrumentations/src/mocha/worker.js +13 -0
  25. package/packages/datadog-instrumentations/src/mongodb.js +74 -0
  26. package/packages/datadog-instrumentations/src/mongoose.js +4 -5
  27. package/packages/datadog-instrumentations/src/otel-sdk-trace.js +15 -0
  28. package/packages/datadog-instrumentations/src/playwright.js +13 -9
  29. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +4 -0
  30. package/packages/datadog-instrumentations/src/vitest-main.js +26 -1
  31. package/packages/datadog-instrumentations/src/vitest-util.js +2 -0
  32. package/packages/datadog-instrumentations/src/vitest-worker.js +103 -34
  33. package/packages/datadog-plugin-ai/src/index.js +1 -1
  34. package/packages/datadog-plugin-ai/src/tracing.js +66 -3
  35. package/packages/datadog-plugin-ai/src/utils.js +17 -4
  36. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +1 -0
  37. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -0
  38. package/packages/datadog-plugin-child_process/src/index.js +13 -2
  39. package/packages/datadog-plugin-claude-agent-sdk/src/index.js +31 -0
  40. package/packages/datadog-plugin-claude-agent-sdk/src/tracing.js +107 -0
  41. package/packages/datadog-plugin-claude-agent-sdk/src/util.js +15 -0
  42. package/packages/datadog-plugin-cucumber/src/index.js +14 -23
  43. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +351 -11
  44. package/packages/datadog-plugin-cypress/src/index.js +47 -2
  45. package/packages/datadog-plugin-cypress/src/plugin.js +1 -0
  46. package/packages/datadog-plugin-graphql/src/execute.js +25 -25
  47. package/packages/datadog-plugin-graphql/src/index.js +23 -1
  48. package/packages/datadog-plugin-graphql/src/request.js +104 -0
  49. package/packages/datadog-plugin-graphql/src/utils.js +186 -2
  50. package/packages/datadog-plugin-graphql/src/validate.js +25 -2
  51. package/packages/datadog-plugin-jest/src/index.js +30 -14
  52. package/packages/datadog-plugin-mocha/src/index.js +39 -14
  53. package/packages/datadog-plugin-mongodb-core/src/bulk-write.js +43 -0
  54. package/packages/datadog-plugin-mongodb-core/src/index.js +8 -443
  55. package/packages/datadog-plugin-mongodb-core/src/query.js +452 -0
  56. package/packages/datadog-plugin-playwright/src/index.js +3 -2
  57. package/packages/datadog-plugin-vitest/src/index.js +43 -9
  58. package/packages/dd-trace/src/aiguard/index.js +9 -14
  59. package/packages/dd-trace/src/aiguard/integrations/index.js +34 -0
  60. package/packages/dd-trace/src/aiguard/integrations/openai.js +47 -33
  61. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +42 -28
  62. package/packages/dd-trace/src/appsec/graphql.js +9 -6
  63. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +3 -10
  64. package/packages/dd-trace/src/appsec/lambda.js +8 -8
  65. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +26 -1
  66. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +115 -24
  67. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +3 -0
  68. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +3 -0
  69. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +75 -7
  70. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +156 -0
  71. package/packages/dd-trace/src/config/generated-config-types.d.ts +10 -0
  72. package/packages/dd-trace/src/config/supported-configurations.json +37 -0
  73. package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -2
  74. package/packages/dd-trace/src/datastreams/writer.js +14 -2
  75. package/packages/dd-trace/src/encode/0.4.js +23 -2
  76. package/packages/dd-trace/src/encode/0.5.js +12 -1
  77. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +11 -1
  78. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +20 -2
  79. package/packages/dd-trace/src/exporters/common/writer.js +17 -1
  80. package/packages/dd-trace/src/guardrails/index.js +3 -1
  81. package/packages/dd-trace/src/guardrails/telemetry.js +40 -3
  82. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +403 -0
  83. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +8 -395
  84. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +27 -0
  85. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +363 -0
  86. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +270 -0
  87. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/utils.js +10 -0
  88. package/packages/dd-trace/src/msgpack/chunk.js +33 -1
  89. package/packages/dd-trace/src/msgpack/index.js +6 -1
  90. package/packages/dd-trace/src/opentelemetry/span.js +5 -0
  91. package/packages/dd-trace/src/plugin_manager.js +16 -0
  92. package/packages/dd-trace/src/plugins/ci_plugin.js +147 -7
  93. package/packages/dd-trace/src/plugins/index.js +5 -0
  94. package/packages/dd-trace/src/plugins/util/test.js +4 -0
  95. package/packages/dd-trace/src/profiling/exporters/agent.js +28 -3
  96. package/packages/dd-trace/src/service-naming/schemas/v0/graphql.js +6 -0
  97. package/packages/dd-trace/src/service-naming/schemas/v1/graphql.js +8 -0
  98. package/vendor/dist/@apm-js-collab/code-transformer/index.js +5 -6
@@ -0,0 +1,156 @@
1
+ 'use strict'
2
+
3
+ const { readFileSync } = require('node:fs')
4
+ const { extname } = require('node:path')
5
+
6
+ const getConfig = require('../../config')
7
+ const request = require('../../exporters/common/request')
8
+ const log = require('../../log')
9
+
10
+ const UPLOAD_TIMEOUT_MS = 30_000
11
+ const TEST_SCREENSHOT_ENDPOINT_PREFIX = '/api/v2/ci/test-runs/'
12
+ const TEST_SCREENSHOT_ENDPOINT_SUFFIX = '/media'
13
+ const UINT64_MAX = 18_446_744_073_709_551_615n
14
+
15
+ function getContentType (filePath) {
16
+ const extension = extname(filePath).toLowerCase()
17
+ if (extension === '.gif') {
18
+ return 'image/gif'
19
+ }
20
+ if (extension === '.jpg' || extension === '.jpeg') {
21
+ return 'image/jpeg'
22
+ }
23
+ if (extension === '.webp') {
24
+ return 'image/webp'
25
+ }
26
+ return 'image/png'
27
+ }
28
+
29
+ function isValidTraceId (traceId) {
30
+ if (!/^[1-9]\d*$/.test(traceId)) {
31
+ return false
32
+ }
33
+ return BigInt(traceId) <= UINT64_MAX
34
+ }
35
+
36
+ /**
37
+ * Renders the idempotency key (`${traceId}:${basename(filePath)}`) into a value safe to carry in
38
+ * the upload's query string. The Agent's evp_proxy validates the forwarded query against a
39
+ * restrictive charset and rejects a raw Cypress filename (spaces, parens, non-ASCII), so the
40
+ * filename part is hex-encoded to [0-9a-f]; the decimal trace id and ':' separator are already in
41
+ * the allowed set and stay readable. Deterministic, so a retried upload reproduces the same key
42
+ * and the backend's UUIDv5 overwrite-on-retry holds.
43
+ *
44
+ * @param {string} idempotencyKey - The raw idempotency key (`<traceId>:<filename>`)
45
+ * @returns {string} A query-safe, deterministic representation of the key
46
+ */
47
+ function toIdempotencyQueryValue (idempotencyKey) {
48
+ const separatorIndex = idempotencyKey.indexOf(':')
49
+ if (separatorIndex === -1) {
50
+ return Buffer.from(idempotencyKey, 'utf8').toString('hex')
51
+ }
52
+ const traceIdPart = idempotencyKey.slice(0, separatorIndex)
53
+ const filenamePart = idempotencyKey.slice(separatorIndex + 1)
54
+ return `${traceIdPart}:${Buffer.from(filenamePart, 'utf8').toString('hex')}`
55
+ }
56
+
57
+ /**
58
+ * Uploads a single test screenshot to the Test Optimization media intake.
59
+ * The trace id is included in the request path and the body is the raw image bytes.
60
+ *
61
+ * The media service requires two values from the tracer, sent as query params so they
62
+ * survive the Agent's evp_proxy (which forwards only an allow-listed header set and would
63
+ * otherwise strip the metadata, leaving the backend with an empty idempotency key):
64
+ * - `idempotencyKey`: stable per artifact and reused on retry, so a retried
65
+ * upload overwrites the same stored object instead of creating a duplicate.
66
+ * - `capturedAtMs`: the capture time in epoch milliseconds, stamped once at
67
+ * capture and resent unchanged on retry (it is part of the stored object key).
68
+ *
69
+ * @param {object} options - Upload options
70
+ * @param {string} options.filePath - Path to the screenshot file
71
+ * @param {string} options.traceId - Test trace id used as the screenshot key
72
+ * @param {string} options.idempotencyKey - Stable per-artifact key, reused on retry
73
+ * @param {number} options.capturedAtMs - Capture time in epoch milliseconds
74
+ * @param {URL} options.url - The base URL for the screenshot upload
75
+ * @param {boolean} [options.isEvpProxy] - Whether to upload through the Agent's evp_proxy
76
+ * @param {string} [options.evpProxyPrefix] - The evp_proxy path prefix (e.g. '/evp_proxy/v4')
77
+ * @param {Function} callback - Callback function (err)
78
+ */
79
+ function uploadTestScreenshot (
80
+ { filePath, traceId, idempotencyKey, capturedAtMs, url, isEvpProxy, evpProxyPrefix },
81
+ callback
82
+ ) {
83
+ const { DD_API_KEY } = getConfig()
84
+
85
+ if (!isValidTraceId(traceId)) {
86
+ return callback(new Error('A non-zero decimal uint64 trace_id is required for test screenshot upload'))
87
+ }
88
+ if (!DD_API_KEY && !isEvpProxy) {
89
+ return callback(new Error('DD_API_KEY is required for test screenshot upload'))
90
+ }
91
+ if (!idempotencyKey) {
92
+ return callback(new Error('An idempotency key is required for test screenshot upload'))
93
+ }
94
+ if (!Number.isInteger(capturedAtMs) || capturedAtMs <= 0) {
95
+ return callback(new Error('A positive captured-at timestamp (epoch ms) is required for test screenshot upload'))
96
+ }
97
+
98
+ let screenshotContent
99
+ try {
100
+ screenshotContent = readFileSync(filePath)
101
+ } catch (err) {
102
+ return callback(new Error(`Failed to read screenshot at ${filePath}: ${err.message}`))
103
+ }
104
+ if (screenshotContent.length === 0) {
105
+ return callback(new Error(`Screenshot at ${filePath} is empty`))
106
+ }
107
+
108
+ // Metadata rides the query string, not X-Dd-* headers: the Agent's evp_proxy strips
109
+ // non-allow-listed headers, so header-borne metadata reached the backend empty. The key is
110
+ // rendered proxy-safe (see toIdempotencyQueryValue) because evp_proxy also validates the
111
+ // forwarded query against a restrictive charset. capturedAtMs is a plain integer and part of the
112
+ // stored object key, so it (and the key) must stay stable across retries.
113
+ const query = new URLSearchParams({
114
+ idempotency_key: toIdempotencyQueryValue(idempotencyKey),
115
+ captured_at_ms: String(capturedAtMs),
116
+ }).toString()
117
+ const basePath = `${TEST_SCREENSHOT_ENDPOINT_PREFIX}${traceId}${TEST_SCREENSHOT_ENDPOINT_SUFFIX}`
118
+
119
+ const contentType = getContentType(filePath)
120
+ const options = {
121
+ method: 'POST',
122
+ headers: {
123
+ 'Content-Type': contentType,
124
+ },
125
+ path: `${basePath}?${query}`,
126
+ timeout: UPLOAD_TIMEOUT_MS,
127
+ url,
128
+ }
129
+
130
+ if (isEvpProxy) {
131
+ // Agent mode: prefix the evp_proxy path, tell the proxy which subdomain to forward to, and
132
+ // drop the API key — the Agent injects it. The query params survive the proxy.
133
+ options.path = `${evpProxyPrefix}${basePath}?${query}`
134
+ options.headers['X-Datadog-EVP-Subdomain'] = 'api'
135
+ } else {
136
+ options.headers['DD-API-KEY'] = DD_API_KEY
137
+ }
138
+
139
+ log.debug('Uploading test screenshot %s to %s', filePath, new URL(options.path, url).href)
140
+
141
+ request(screenshotContent, options, (err, res, statusCode) => {
142
+ if (err) {
143
+ log.error('Error uploading test screenshot: %s', err.message)
144
+ return callback(err)
145
+ }
146
+ if (statusCode === undefined) {
147
+ const uploadError = new Error('Test screenshot upload request was dropped before it was sent')
148
+ log.error('Error uploading test screenshot: %s', uploadError.message)
149
+ return callback(uploadError)
150
+ }
151
+ log.debug('Test screenshot uploaded successfully (status: %d)', statusCode)
152
+ callback(null)
153
+ })
154
+ }
155
+
156
+ module.exports = { TEST_SCREENSHOT_ENDPOINT_PREFIX, TEST_SCREENSHOT_ENDPOINT_SUFFIX, uploadTestScreenshot }
@@ -213,6 +213,7 @@ export interface GeneratedConfig {
213
213
  DD_TRACE_BUNYAN_ENABLED: boolean;
214
214
  DD_TRACE_CASSANDRA_DRIVER_ENABLED: boolean;
215
215
  DD_TRACE_CHILD_PROCESS_ENABLED: boolean;
216
+ DD_TRACE_CLAUDE_AGENT_SDK_ENABLED: boolean;
216
217
  DD_TRACE_COLLECTIONS_ENABLED: boolean;
217
218
  DD_TRACE_COMMONPLUGIN_ENABLED: boolean;
218
219
  DD_TRACE_CONFLUENTINC_KAFKA_JAVASCRIPT_ENABLED: boolean;
@@ -252,11 +253,14 @@ export interface GeneratedConfig {
252
253
  DD_TRACE_GOOGLE_CLOUD_VERTEXAI_ENABLED: boolean;
253
254
  DD_TRACE_GOOGLE_GAX_ENABLED: boolean;
254
255
  DD_TRACE_GOOGLE_GENAI_ENABLED: boolean;
256
+ DD_TRACE_GRAPHQL_COLLAPSE: boolean;
257
+ DD_TRACE_GRAPHQL_DEPTH: number;
255
258
  DD_TRACE_GRAPHQL_ENABLED: boolean;
256
259
  DD_TRACE_GRAPHQL_ERROR_EXTENSIONS: string[];
257
260
  DD_TRACE_GRAPHQL_TAG_ENABLED: boolean;
258
261
  DD_TRACE_GRAPHQL_TOOLS_ENABLED: boolean;
259
262
  DD_TRACE_GRAPHQL_TOOLS_EXECUTOR_ENABLED: boolean;
263
+ DD_TRACE_GRAPHQL_VARIABLES: string[];
260
264
  DD_TRACE_GRAPHQL_YOGA_ENABLED: boolean;
261
265
  DD_TRACE_GRPC_ENABLED: boolean;
262
266
  DD_TRACE_GRPC_GRPC_JS_ENABLED: boolean;
@@ -558,6 +562,7 @@ export interface GeneratedConfig {
558
562
  DD_CIVISIBILITY_TEST_MODULE_ID: string | undefined;
559
563
  DD_CIVISIBILITY_TEST_SESSION_ID: string | undefined;
560
564
  DD_TEST_FAILED_TEST_REPLAY_ENABLED: boolean;
565
+ DD_TEST_FAILURE_SCREENSHOTS_ENABLED: boolean | undefined;
561
566
  DD_TEST_FLEET_CONFIG_PATH: string | undefined;
562
567
  DD_TEST_LOCAL_CONFIG_PATH: string | undefined;
563
568
  DD_TEST_MANAGEMENT_ATTEMPT_TO_FIX_RETRIES: number;
@@ -790,6 +795,7 @@ export interface GeneratedEnvVarConfig {
790
795
  DD_TELEMETRY_LOG_COLLECTION_ENABLED: boolean;
791
796
  DD_TELEMETRY_METRICS_ENABLED: boolean;
792
797
  DD_TEST_FAILED_TEST_REPLAY_ENABLED: boolean;
798
+ DD_TEST_FAILURE_SCREENSHOTS_ENABLED: boolean | undefined;
793
799
  DD_TEST_FLEET_CONFIG_PATH: string | undefined;
794
800
  DD_TEST_LOCAL_CONFIG_PATH: string | undefined;
795
801
  DD_TEST_MANAGEMENT_ATTEMPT_TO_FIX_RETRIES: number;
@@ -868,6 +874,7 @@ export interface GeneratedEnvVarConfig {
868
874
  DD_TRACE_BUNYAN_ENABLED: boolean;
869
875
  DD_TRACE_CASSANDRA_DRIVER_ENABLED: boolean;
870
876
  DD_TRACE_CHILD_PROCESS_ENABLED: boolean;
877
+ DD_TRACE_CLAUDE_AGENT_SDK_ENABLED: boolean;
871
878
  DD_TRACE_CLIENT_IP_ENABLED: boolean;
872
879
  DD_TRACE_CLIENT_IP_HEADER: string | undefined;
873
880
  DD_TRACE_CLOUD_PAYLOAD_TAGGING_MAX_DEPTH: number;
@@ -917,11 +924,14 @@ export interface GeneratedEnvVarConfig {
917
924
  DD_TRACE_GOOGLE_CLOUD_VERTEXAI_ENABLED: boolean;
918
925
  DD_TRACE_GOOGLE_GAX_ENABLED: boolean;
919
926
  DD_TRACE_GOOGLE_GENAI_ENABLED: boolean;
927
+ DD_TRACE_GRAPHQL_COLLAPSE: boolean;
928
+ DD_TRACE_GRAPHQL_DEPTH: number;
920
929
  DD_TRACE_GRAPHQL_ENABLED: boolean;
921
930
  DD_TRACE_GRAPHQL_ERROR_EXTENSIONS: string[];
922
931
  DD_TRACE_GRAPHQL_TAG_ENABLED: boolean;
923
932
  DD_TRACE_GRAPHQL_TOOLS_ENABLED: boolean;
924
933
  DD_TRACE_GRAPHQL_TOOLS_EXECUTOR_ENABLED: boolean;
934
+ DD_TRACE_GRAPHQL_VARIABLES: string[];
925
935
  DD_TRACE_GRAPHQL_YOGA_ENABLED: boolean;
926
936
  DD_TRACE_GRPC_ENABLED: boolean;
927
937
  DD_TRACE_GRPC_GRPC_JS_ENABLED: boolean;
@@ -1819,6 +1819,14 @@
1819
1819
  "namespace": "testOptimization"
1820
1820
  }
1821
1821
  ],
1822
+ "DD_TEST_FAILURE_SCREENSHOTS_ENABLED": [
1823
+ {
1824
+ "implementation": "A",
1825
+ "type": "boolean",
1826
+ "default": null,
1827
+ "namespace": "testOptimization"
1828
+ }
1829
+ ],
1822
1830
  "DD_TEST_FLEET_CONFIG_PATH": [
1823
1831
  {
1824
1832
  "implementation": "A",
@@ -1946,6 +1954,13 @@
1946
1954
  "default": "true"
1947
1955
  }
1948
1956
  ],
1957
+ "DD_TRACE_CLAUDE_AGENT_SDK_ENABLED": [
1958
+ {
1959
+ "implementation": "A",
1960
+ "type": "boolean",
1961
+ "default": "true"
1962
+ }
1963
+ ],
1949
1964
  "DD_TRACE_APOLLO_ENABLED": [
1950
1965
  {
1951
1966
  "implementation": "A",
@@ -2742,6 +2757,21 @@
2742
2757
  "default": "true"
2743
2758
  }
2744
2759
  ],
2760
+ "DD_TRACE_GRAPHQL_COLLAPSE": [
2761
+ {
2762
+ "implementation": "A",
2763
+ "type": "boolean",
2764
+ "default": "true"
2765
+ }
2766
+ ],
2767
+ "DD_TRACE_GRAPHQL_DEPTH": [
2768
+ {
2769
+ "implementation": "A",
2770
+ "type": "int",
2771
+ "default": "-1",
2772
+ "allowed": "-1|\\d+"
2773
+ }
2774
+ ],
2745
2775
  "DD_TRACE_GRAPHQL_ENABLED": [
2746
2776
  {
2747
2777
  "implementation": "A",
@@ -2777,6 +2807,13 @@
2777
2807
  "default": "true"
2778
2808
  }
2779
2809
  ],
2810
+ "DD_TRACE_GRAPHQL_VARIABLES": [
2811
+ {
2812
+ "implementation": "A",
2813
+ "type": "array",
2814
+ "default": ""
2815
+ }
2816
+ ],
2780
2817
  "DD_TRACE_GRAPHQL_YOGA_ENABLED": [
2781
2818
  {
2782
2819
  "implementation": "A",
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const { EOL } = require('node:os')
3
+ const { EOL, platform } = require('node:os')
4
4
 
5
5
  // Load binding first to not import other modules if it throws
6
6
  const libdatadog = require('@datadog/libdatadog')
@@ -71,7 +71,7 @@ class Crashtracker {
71
71
 
72
72
  // Out-of-process symbolication currently works on
73
73
  // Linux only, does not work on Mac.
74
- const resolveMode = require('os').platform === 'linux'
74
+ const resolveMode = platform() === 'linux'
75
75
  ? 'EnabledWithSymbolsInReceiver'
76
76
  : 'EnabledWithInprocessSymbols'
77
77
 
@@ -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
- const encodedPayload = encodeMsgpack(payload)
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
- this._encode(bytes, trace)
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 buffer = Buffer.allocUnsafe(prefixSize + stringSize + traceSize)
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
- this.encodeCodeCoverage(this._coverageBytes, coverage)
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
- const payload = this._encoder.makePayload()
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
- var spawn = require('child_process').spawn
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(JSON.stringify({ metadata: currentMetadata, points: points }))
128
+ proc.stdin.end(payload)
92
129
  }