dd-trace 5.75.0 → 5.77.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 (53) hide show
  1. package/LICENSE-3rdparty.csv +1 -1
  2. package/ci/init.js +1 -0
  3. package/package.json +15 -15
  4. package/packages/datadog-instrumentations/src/express.js +18 -7
  5. package/packages/datadog-instrumentations/src/jest.js +16 -16
  6. package/packages/datadog-instrumentations/src/openai.js +8 -0
  7. package/packages/datadog-instrumentations/src/playwright.js +47 -0
  8. package/packages/datadog-instrumentations/src/router.js +9 -8
  9. package/packages/datadog-instrumentations/src/vitest.js +94 -8
  10. package/packages/datadog-plugin-cucumber/src/index.js +2 -1
  11. package/packages/datadog-plugin-grpc/src/util.js +5 -1
  12. package/packages/datadog-plugin-http/src/client.js +5 -1
  13. package/packages/datadog-plugin-http2/src/client.js +5 -1
  14. package/packages/datadog-plugin-openai/src/stream-helpers.js +26 -1
  15. package/packages/datadog-plugin-openai/src/tracing.js +46 -1
  16. package/packages/datadog-plugin-vitest/src/index.js +5 -1
  17. package/packages/dd-trace/src/appsec/api_security_sampler.js +1 -1
  18. package/packages/dd-trace/src/appsec/channels.js +1 -0
  19. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +7 -0
  20. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +2 -2
  21. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +4 -3
  22. package/packages/dd-trace/src/appsec/rasp/lfi.js +23 -9
  23. package/packages/dd-trace/src/appsec/stack_trace.js +6 -6
  24. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +6 -0
  25. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +30 -7
  26. package/packages/dd-trace/src/config.js +10 -1
  27. package/packages/dd-trace/src/debugger/devtools_client/index.js +8 -1
  28. package/packages/dd-trace/src/debugger/devtools_client/log.js +15 -4
  29. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +5 -1
  30. package/packages/dd-trace/src/exporters/common/docker.js +1 -1
  31. package/packages/dd-trace/src/exporters/span-stats/writer.js +4 -5
  32. package/packages/dd-trace/src/format.js +4 -5
  33. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +9 -6
  34. package/packages/dd-trace/src/llmobs/plugins/anthropic.js +3 -6
  35. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +3 -2
  36. package/packages/dd-trace/src/llmobs/plugins/openai.js +216 -12
  37. package/packages/dd-trace/src/llmobs/span_processor.js +4 -3
  38. package/packages/dd-trace/src/llmobs/tagger.js +9 -3
  39. package/packages/dd-trace/src/llmobs/util.js +1 -1
  40. package/packages/dd-trace/src/openfeature/writers/exposures.js +2 -2
  41. package/packages/dd-trace/src/opentelemetry/span.js +3 -2
  42. package/packages/dd-trace/src/plugins/ci_plugin.js +3 -1
  43. package/packages/dd-trace/src/plugins/util/serverless.js +1 -2
  44. package/packages/dd-trace/src/plugins/util/test.js +16 -1
  45. package/packages/dd-trace/src/plugins/util/web.js +5 -1
  46. package/packages/dd-trace/src/profiling/config.js +32 -10
  47. package/packages/dd-trace/src/proxy.js +2 -2
  48. package/packages/dd-trace/src/remote_config/capabilities.js +2 -0
  49. package/packages/dd-trace/src/remote_config/index.js +4 -0
  50. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +2 -2
  51. package/packages/dd-trace/src/span_processor.js +4 -1
  52. package/packages/dd-trace/src/startup-log.js +24 -38
  53. package/packages/dd-trace/src/supported-configurations.json +1 -0
@@ -2,6 +2,13 @@
2
2
 
3
3
  const LLMObsPlugin = require('./base')
4
4
 
5
+ const allowedParamKeys = new Set([
6
+ 'max_output_tokens',
7
+ 'temperature',
8
+ 'stream',
9
+ 'reasoning'
10
+ ])
11
+
5
12
  function isIterable (obj) {
6
13
  if (obj == null) {
7
14
  return false
@@ -19,7 +26,7 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
19
26
  const methodName = gateResource(normalizeOpenAIResourceName(resource))
20
27
  if (!methodName) return // we will not trace all openai methods for llmobs
21
28
 
22
- const inputs = ctx.args[0] // completion, chat completion, and embeddings take one argument
29
+ const inputs = ctx.args[0] // completion, chat completion, embeddings, and responses take one argument
23
30
  const operation = getOperation(methodName)
24
31
  const kind = operation === 'embedding' ? 'embedding' : 'llm'
25
32
 
@@ -53,6 +60,8 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
53
60
  this._tagChatCompletion(span, inputs, response, error)
54
61
  } else if (operation === 'embedding') {
55
62
  this._tagEmbedding(span, inputs, response, error)
63
+ } else if (operation === 'response') {
64
+ this.#tagResponse(span, inputs, response, error)
56
65
  }
57
66
 
58
67
  if (!error) {
@@ -75,19 +84,30 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
75
84
  const tokenUsage = response.usage
76
85
 
77
86
  if (tokenUsage) {
78
- const inputTokens = tokenUsage.prompt_tokens
79
- if (inputTokens) metrics.inputTokens = inputTokens
87
+ // Responses API uses input_tokens, Chat/Completions use prompt_tokens
88
+ const inputTokens = tokenUsage.input_tokens ?? tokenUsage.prompt_tokens
89
+ if (inputTokens !== undefined) metrics.inputTokens = inputTokens
80
90
 
81
- const outputTokens = tokenUsage.completion_tokens
82
- if (outputTokens) metrics.outputTokens = outputTokens
91
+ // Responses API uses output_tokens, Chat/Completions use completion_tokens
92
+ const outputTokens = tokenUsage.output_tokens ?? tokenUsage.completion_tokens
93
+ if (outputTokens !== undefined) metrics.outputTokens = outputTokens
83
94
 
84
95
  const totalTokens = tokenUsage.total_tokens || (inputTokens + outputTokens)
85
- if (totalTokens) metrics.totalTokens = totalTokens
86
-
87
- const promptTokensDetails = tokenUsage.prompt_tokens_details
88
- if (promptTokensDetails) {
89
- const cacheReadTokens = promptTokensDetails.cached_tokens
90
- if (cacheReadTokens) metrics.cacheReadTokens = cacheReadTokens
96
+ if (totalTokens !== undefined) metrics.totalTokens = totalTokens
97
+
98
+ // Cache tokens - Responses API uses input_tokens_details, Chat/Completions use prompt_tokens_details
99
+ // For Responses API, always include cache tokens (even if 0)
100
+ // For Chat API, only include if > 0
101
+ if (tokenUsage.input_tokens_details) {
102
+ // Responses API - always include
103
+ const cacheReadTokens = tokenUsage.input_tokens_details.cached_tokens
104
+ if (cacheReadTokens !== undefined) metrics.cacheReadTokens = cacheReadTokens
105
+ } else if (tokenUsage.prompt_tokens_details) {
106
+ // Chat/Completions API - only include if > 0
107
+ const cacheReadTokens = tokenUsage.prompt_tokens_details.cached_tokens
108
+ if (cacheReadTokens) {
109
+ metrics.cacheReadTokens = cacheReadTokens
110
+ }
91
111
  }
92
112
  }
93
113
 
@@ -191,6 +211,183 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
191
211
 
192
212
  this._tagger.tagMetadata(span, metadata)
193
213
  }
214
+
215
+ #tagResponse (span, inputs, response, error) {
216
+ // Tag metadata - use allowlist approach for request parameters
217
+
218
+ const { input, model, ...parameters } = inputs
219
+
220
+ // Create input messages
221
+ const inputMessages = []
222
+
223
+ // Add system message if instructions exist
224
+ if (inputs.instructions) {
225
+ inputMessages.push({ role: 'system', content: inputs.instructions })
226
+ }
227
+
228
+ // Handle input - can be string or array of mixed messages
229
+ if (Array.isArray(input)) {
230
+ for (const item of input) {
231
+ if (item.type === 'function_call') {
232
+ // Function call: convert to message with tool_calls
233
+ // Parse arguments if it's a JSON string
234
+ let parsedArgs = item.arguments
235
+ if (typeof parsedArgs === 'string') {
236
+ try {
237
+ parsedArgs = JSON.parse(parsedArgs)
238
+ } catch {
239
+ parsedArgs = {}
240
+ }
241
+ }
242
+ inputMessages.push({
243
+ role: 'assistant',
244
+ toolCalls: [{
245
+ toolId: item.call_id,
246
+ name: item.name,
247
+ arguments: parsedArgs,
248
+ type: item.type
249
+ }]
250
+ })
251
+ } else if (item.type === 'function_call_output') {
252
+ // Function output: convert to user message with tool_results
253
+ inputMessages.push({
254
+ role: 'user',
255
+ toolResults: [{
256
+ toolId: item.call_id,
257
+ result: item.output,
258
+ name: item.name || '',
259
+ type: item.type
260
+ }]
261
+ })
262
+ } else if (item.role && item.content) {
263
+ // Regular message
264
+ inputMessages.push({ role: item.role, content: item.content })
265
+ }
266
+ }
267
+ } else {
268
+ // Simple string input
269
+ inputMessages.push({ role: 'user', content: input })
270
+ }
271
+
272
+ if (error) {
273
+ this._tagger.tagLLMIO(span, inputMessages, [{ content: '' }])
274
+ return
275
+ }
276
+
277
+ // Create output messages
278
+ const outputMessages = []
279
+
280
+ // Handle output - can be string (streaming) or array of message objects (non-streaming)
281
+ if (typeof response.output === 'string') {
282
+ // Simple text output (streaming)
283
+ outputMessages.push({ role: 'assistant', content: response.output })
284
+ } else if (Array.isArray(response.output)) {
285
+ // Array output - process all items to extract reasoning, messages, and tool calls
286
+ // Non-streaming: array of items (messages, function_calls, or reasoning)
287
+ for (const item of response.output) {
288
+ // Handle reasoning type (reasoning responses)
289
+ if (item.type === 'reasoning') {
290
+ // Extract reasoning text from summary
291
+ let reasoningText = ''
292
+ if (Array.isArray(item.summary) && item.summary.length > 0) {
293
+ const summaryItem = item.summary[0]
294
+ if (summaryItem.type === 'summary_text' && summaryItem.text) {
295
+ reasoningText = summaryItem.text
296
+ }
297
+ }
298
+ outputMessages.push({
299
+ role: 'reasoning',
300
+ content: reasoningText
301
+ })
302
+ } else if (item.type === 'function_call') {
303
+ // Handle function_call type (responses API tool calls)
304
+ let args = item.arguments
305
+ // Parse arguments if it's a JSON string
306
+ if (typeof args === 'string') {
307
+ try {
308
+ args = JSON.parse(args)
309
+ } catch {
310
+ args = {}
311
+ }
312
+ }
313
+ outputMessages.push({
314
+ role: 'assistant',
315
+ toolCalls: [{
316
+ toolId: item.call_id,
317
+ name: item.name,
318
+ arguments: args,
319
+ type: item.type
320
+ }]
321
+ })
322
+ } else {
323
+ // Handle regular message objects
324
+ const outputMsg = { role: item.role || 'assistant', content: '' }
325
+
326
+ // Extract content from message
327
+ if (Array.isArray(item.content)) {
328
+ // Content is array of content parts
329
+ // For responses API, text content has type 'output_text', not 'text'
330
+ const textParts = item.content
331
+ .filter(c => c.type === 'output_text')
332
+ .map(c => c.text)
333
+ outputMsg.content = textParts.join('')
334
+ } else if (typeof item.content === 'string') {
335
+ outputMsg.content = item.content
336
+ }
337
+
338
+ // Extract tool calls if present in message.tool_calls
339
+ if (Array.isArray(item.tool_calls)) {
340
+ outputMsg.toolCalls = item.tool_calls.map(tc => {
341
+ let args = tc.function?.arguments || tc.arguments
342
+ // Parse arguments if it's a JSON string
343
+ if (typeof args === 'string') {
344
+ try {
345
+ args = JSON.parse(args)
346
+ } catch {
347
+ args = {}
348
+ }
349
+ }
350
+ return {
351
+ toolId: tc.id,
352
+ name: tc.function?.name || tc.name,
353
+ arguments: args,
354
+ type: tc.type || 'function_call'
355
+ }
356
+ })
357
+ }
358
+
359
+ outputMessages.push(outputMsg)
360
+ }
361
+ }
362
+ } else if (response.output_text) {
363
+ // Fallback: use output_text if available (for simple non-streaming responses without reasoning/tools)
364
+ outputMessages.push({ role: 'assistant', content: response.output_text })
365
+ } else {
366
+ // No output
367
+ outputMessages.push({ role: 'assistant', content: '' })
368
+ }
369
+
370
+ this._tagger.tagLLMIO(span, inputMessages, outputMessages)
371
+
372
+ const metadata = Object.entries(parameters).reduce((obj, [key, value]) => {
373
+ if (allowedParamKeys.has(key)) {
374
+ obj[key] = value
375
+ }
376
+ return obj
377
+ }, {})
378
+
379
+ // Add fields from response object (convert numbers to floats)
380
+ if (response.temperature !== undefined) metadata.temperature = Number(response.temperature)
381
+ if (response.top_p !== undefined) metadata.top_p = Number(response.top_p)
382
+ if (response.tool_choice !== undefined) metadata.tool_choice = response.tool_choice
383
+ if (response.truncation !== undefined) metadata.truncation = response.truncation
384
+ if (response.text !== undefined) metadata.text = response.text
385
+ if (response.usage?.output_tokens_details?.reasoning_tokens !== undefined) {
386
+ metadata.reasoning_tokens = response.usage.output_tokens_details.reasoning_tokens
387
+ }
388
+
389
+ this._tagger.tagMetadata(span, metadata)
390
+ }
194
391
  }
195
392
 
196
393
  // TODO: this will be moved to the APM integration
@@ -207,13 +404,18 @@ function normalizeOpenAIResourceName (resource) {
207
404
  // embeddings
208
405
  case 'embeddings.create':
209
406
  return 'createEmbedding'
407
+
408
+ // responses
409
+ case 'responses.create':
410
+ return 'createResponse'
411
+
210
412
  default:
211
413
  return resource
212
414
  }
213
415
  }
214
416
 
215
417
  function gateResource (resource) {
216
- return ['createCompletion', 'createChatCompletion', 'createEmbedding'].includes(resource)
418
+ return ['createCompletion', 'createChatCompletion', 'createEmbedding', 'createResponse'].includes(resource)
217
419
  ? resource
218
420
  : undefined
219
421
  }
@@ -226,6 +428,8 @@ function getOperation (resource) {
226
428
  return 'chat'
227
429
  case 'createEmbedding':
228
430
  return 'embedding'
431
+ case 'createResponse':
432
+ return 'response'
229
433
  default:
230
434
  // should never happen
231
435
  return 'unknown'
@@ -203,8 +203,8 @@ class LLMObsSpanProcessor {
203
203
  // This function can be reused for other fields if needed
204
204
  // Messages, Documents, and Metrics are safeguarded in `llmobs/tagger.js`
205
205
  #addObject (obj, carrier) {
206
- const seenObjects = new WeakSet()
207
- seenObjects.add(obj) // capture root object
206
+ // Capture root object by default
207
+ const seenObjects = new WeakSet([obj])
208
208
 
209
209
  const isCircular = value => {
210
210
  if (typeof value !== 'object') return false
@@ -224,7 +224,8 @@ class LLMObsSpanProcessor {
224
224
  continue
225
225
  }
226
226
  if (value !== null && typeof value === 'object') {
227
- add(value, carrier[key] = {})
227
+ carrier[key] = Array.isArray(value) ? [] : {}
228
+ add(value, carrier[key])
228
229
  } else {
229
230
  carrier[key] = value
230
231
  }
@@ -292,11 +292,17 @@ class LLMObsTagger {
292
292
  continue
293
293
  }
294
294
 
295
- const { result, toolId, type } = toolResult
295
+ const { result, toolId, name = '', type } = toolResult
296
296
  const toolResultObj = {}
297
297
 
298
298
  const condition1 = this.#tagConditionalString(result, 'Tool result', toolResultObj, 'result')
299
299
  const condition2 = this.#tagConditionalString(toolId, 'Tool ID', toolResultObj, 'tool_id')
300
+ // name can be empty string, so always include it
301
+ if (typeof name === 'string') {
302
+ toolResultObj.name = name
303
+ } else {
304
+ this.#handleFailure(`[LLMObs] Expected tool result name to be a string, instead got "${typeof name}"`)
305
+ }
300
306
  const condition3 = this.#tagConditionalString(type, 'Tool type', toolResultObj, 'type')
301
307
 
302
308
  if (condition1 && condition2 && condition3) {
@@ -332,13 +338,13 @@ class LLMObsTagger {
332
338
  const toolId = message.toolId
333
339
  const messageObj = { content }
334
340
 
341
+ let condition = this.#tagConditionalString(role, 'Message role', messageObj, 'role')
342
+
335
343
  const valid = typeof content === 'string'
336
344
  if (!valid) {
337
345
  this.#handleFailure('Message content must be a string.', 'invalid_io_messages')
338
346
  }
339
347
 
340
- let condition = this.#tagConditionalString(role, 'Message role', messageObj, 'role')
341
-
342
348
  if (toolCalls) {
343
349
  const filteredToolCalls = this.#filterToolCalls(toolCalls)
344
350
 
@@ -6,7 +6,7 @@ function encodeUnicode (str = '') {
6
6
  let result = ''
7
7
  for (let i = 0; i < str.length; i++) {
8
8
  const code = str.charCodeAt(i)
9
- result += code > 127 ? `\\u${code.toString(16).padStart(4, '0')}` : str[i]
9
+ result += code > 127 ? String.raw`\u${code.toString(16).padStart(4, '0')}` : str[i]
10
10
  }
11
11
  return result
12
12
  }
@@ -27,7 +27,7 @@ const {
27
27
 
28
28
  /**
29
29
  * @typedef {Object} ExposureContext
30
- * @property {string} service_name - Service name
30
+ * @property {string} service - Service name
31
31
  * @property {string} [version] - Service version
32
32
  * @property {string} [env] - Service environment
33
33
  */
@@ -127,7 +127,7 @@ class ExposuresWriter extends BaseFFEWriter {
127
127
  */
128
128
  _buildContext () {
129
129
  const context = {
130
- service_name: this._config.service || 'unknown'
130
+ service: this._config.service || 'unknown'
131
131
  }
132
132
 
133
133
  // Only include version and env if they are defined
@@ -10,7 +10,7 @@ const { timeInputToHrTime } = require('@opentelemetry/core')
10
10
  const tracer = require('../../')
11
11
  const DatadogSpan = require('../opentracing/span')
12
12
  const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK, IGNORE_OTEL_ERROR } = require('../constants')
13
- const { SERVICE_NAME, RESOURCE_NAME } = require('../../../../ext/tags')
13
+ const { SERVICE_NAME, RESOURCE_NAME, SPAN_KIND } = require('../../../../ext/tags')
14
14
  const kinds = require('../../../../ext/kinds')
15
15
 
16
16
  const SpanContext = require('./span_context')
@@ -146,7 +146,8 @@ class Span {
146
146
  integrationName: parentTracer?._isOtelLibrary ? 'otel.library' : 'otel',
147
147
  tags: {
148
148
  [SERVICE_NAME]: _tracer._service,
149
- [RESOURCE_NAME]: spanName
149
+ [RESOURCE_NAME]: spanName,
150
+ [SPAN_KIND]: spanKindNames[kind]
150
151
  },
151
152
  links
152
153
  }, _tracer._debug)
@@ -34,7 +34,8 @@ const {
34
34
  getLibraryCapabilitiesTags,
35
35
  getPullRequestDiff,
36
36
  getModifiedFilesFromDiff,
37
- getPullRequestBaseBranch
37
+ getPullRequestBaseBranch,
38
+ TEST_IS_TEST_FRAMEWORK_WORKER
38
39
  } = require('./util/test')
39
40
  const { getRepositoryRoot } = require('./util/git')
40
41
  const Plugin = require('./plugin')
@@ -311,6 +312,7 @@ module.exports = class CiPlugin extends Plugin {
311
312
  span.parent_id = id(span.parent_id)
312
313
 
313
314
  if (span.name?.startsWith(`${this.constructor.id}.`)) {
315
+ span.meta[TEST_IS_TEST_FRAMEWORK_WORKER] = 'true'
314
316
  // augment with git information (since it will not be available in the worker)
315
317
  for (const key in this.testEnvironmentMetadata) {
316
318
  // CAREFUL: this bypasses the metadata/metrics distinction
@@ -3,7 +3,6 @@
3
3
  const types = require('../../../../../ext/types')
4
4
  const web = require('./web')
5
5
 
6
- const serverless = { ...web }
7
- serverless.TYPE = types.SERVERLESS
6
+ const serverless = { ...web, TYPE: types.SERVERLESS }
8
7
 
9
8
  module.exports = serverless
@@ -92,6 +92,8 @@ const CI_APP_ORIGIN = 'ciapp-test'
92
92
  const JEST_TEST_RUNNER = 'test.jest.test_runner'
93
93
  const JEST_DISPLAY_NAME = 'test.jest.display_name'
94
94
 
95
+ const VITEST_POOL = 'test.vitest.pool'
96
+
95
97
  const CUCUMBER_IS_PARALLEL = 'test.cucumber.is_parallel'
96
98
  const MOCHA_IS_PARALLEL = 'test.mocha.is_parallel'
97
99
 
@@ -130,6 +132,8 @@ const PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE = 90
130
132
  const VITEST_WORKER_TRACE_PAYLOAD_CODE = 100
131
133
  const VITEST_WORKER_LOGS_PAYLOAD_CODE = 102
132
134
 
135
+ const TEST_IS_TEST_FRAMEWORK_WORKER = 'test.is_test_framework_worker'
136
+
133
137
  // Library Capabilities Tagging
134
138
  const DD_CAPABILITIES_TEST_IMPACT_ANALYSIS = '_dd.library_capabilities.test_impact_analysis'
135
139
  const DD_CAPABILITIES_EARLY_FLAKE_DETECTION = '_dd.library_capabilities.early_flake_detection'
@@ -203,6 +207,7 @@ module.exports = {
203
207
  TEST_FRAMEWORK_VERSION,
204
208
  JEST_TEST_RUNNER,
205
209
  JEST_DISPLAY_NAME,
210
+ VITEST_POOL,
206
211
  CUCUMBER_IS_PARALLEL,
207
212
  MOCHA_IS_PARALLEL,
208
213
  TEST_TYPE,
@@ -223,6 +228,7 @@ module.exports = {
223
228
  PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE,
224
229
  VITEST_WORKER_TRACE_PAYLOAD_CODE,
225
230
  VITEST_WORKER_LOGS_PAYLOAD_CODE,
231
+ TEST_IS_TEST_FRAMEWORK_WORKER,
226
232
  TEST_SOURCE_START,
227
233
  TEST_SKIPPED_BY_ITR,
228
234
  TEST_IS_NEW,
@@ -621,20 +627,29 @@ function getCodeOwnersFileEntries (rootDir) {
621
627
  return entries.reverse()
622
628
  }
623
629
 
630
+ const codeOwnersPerFileName = new Map()
631
+
624
632
  function getCodeOwnersForFilename (filename, entries) {
625
633
  if (!entries) {
626
634
  return null
627
635
  }
636
+ if (codeOwnersPerFileName.has(filename)) {
637
+ return codeOwnersPerFileName.get(filename)
638
+ }
628
639
  for (const entry of entries) {
629
640
  try {
630
641
  const isResponsible = ignore().add(entry.pattern).ignores(filename)
631
642
  if (isResponsible) {
632
- return JSON.stringify(entry.owners)
643
+ const codeOwners = JSON.stringify(entry.owners)
644
+ codeOwnersPerFileName.set(filename, codeOwners)
645
+ return codeOwners
633
646
  }
634
647
  } catch {
648
+ codeOwnersPerFileName.set(filename, null)
635
649
  return null
636
650
  }
637
651
  }
652
+ codeOwnersPerFileName.set(filename, null)
638
653
  return null
639
654
  }
640
655
 
@@ -577,13 +577,17 @@ function getHeadersToRecord (config) {
577
577
  return []
578
578
  }
579
579
 
580
+ function isNot500ErrorCode (code) {
581
+ return code < 500
582
+ }
583
+
580
584
  function getStatusValidator (config) {
581
585
  if (typeof config.validateStatus === 'function') {
582
586
  return config.validateStatus
583
587
  } else if (config.hasOwnProperty('validateStatus')) {
584
588
  log.error('Expected `validateStatus` to be a function.')
585
589
  }
586
- return code => code < 500
590
+ return isNot500ErrorCode
587
591
  }
588
592
 
589
593
  const noop = () => {}
@@ -269,9 +269,27 @@ module.exports = { Config }
269
269
  function getProfilers ({
270
270
  DD_PROFILING_HEAP_ENABLED, DD_PROFILING_WALLTIME_ENABLED, DD_PROFILING_PROFILERS
271
271
  }) {
272
- // First consider "legacy" DD_PROFILING_PROFILERS env variable, defaulting to wall + space
272
+ // First consider "legacy" DD_PROFILING_PROFILERS env variable, defaulting to space + wall
273
273
  // Use a Set to avoid duplicates
274
- const profilers = new Set((DD_PROFILING_PROFILERS ?? 'wall,space').split(','))
274
+ // NOTE: space profiler is very deliberately in the first position. This way
275
+ // when profilers are stopped sequentially one after the other to create
276
+ // snapshots the space profile won't include memory taken by profiles created
277
+ // before it in the sequence. That memory is ultimately transient and will be
278
+ // released when all profiles are subsequently encoded.
279
+ const profilers = new Set((DD_PROFILING_PROFILERS ?? 'space,wall').split(','))
280
+
281
+ let spaceExplicitlyEnabled = false
282
+ // Add/remove space depending on the value of DD_PROFILING_HEAP_ENABLED
283
+ if (DD_PROFILING_HEAP_ENABLED != null) {
284
+ if (isTrue(DD_PROFILING_HEAP_ENABLED)) {
285
+ if (!profilers.has('space')) {
286
+ profilers.add('space')
287
+ spaceExplicitlyEnabled = true
288
+ }
289
+ } else if (isFalse(DD_PROFILING_HEAP_ENABLED)) {
290
+ profilers.delete('space')
291
+ }
292
+ }
275
293
 
276
294
  // Add/remove wall depending on the value of DD_PROFILING_WALLTIME_ENABLED
277
295
  if (DD_PROFILING_WALLTIME_ENABLED != null) {
@@ -279,19 +297,23 @@ function getProfilers ({
279
297
  profilers.add('wall')
280
298
  } else if (isFalse(DD_PROFILING_WALLTIME_ENABLED)) {
281
299
  profilers.delete('wall')
300
+ profilers.delete('cpu') // remove alias too
282
301
  }
283
302
  }
284
303
 
285
- // Add/remove wall depending on the value of DD_PROFILING_HEAP_ENABLED
286
- if (DD_PROFILING_HEAP_ENABLED != null) {
287
- if (isTrue(DD_PROFILING_HEAP_ENABLED)) {
288
- profilers.add('space')
289
- } else if (isFalse(DD_PROFILING_HEAP_ENABLED)) {
290
- profilers.delete('space')
304
+ const profilersArray = [...profilers]
305
+ // If space was added through DD_PROFILING_HEAP_ENABLED, ensure it is in the
306
+ // first position. Basically, the only way for it not to be in the first
307
+ // position is if it was explicitly specified in a different position in
308
+ // DD_PROFILING_PROFILERS.
309
+ if (spaceExplicitlyEnabled) {
310
+ const spaceIdx = profilersArray.indexOf('space')
311
+ if (spaceIdx > 0) {
312
+ profilersArray.splice(spaceIdx, 1)
313
+ profilersArray.unshift('space')
291
314
  }
292
315
  }
293
-
294
- return [...profilers]
316
+ return profilersArray
295
317
  }
296
318
 
297
319
  function getExportStrategy (name, options) {
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
  const NoopProxy = require('./noop/proxy')
3
3
  const DatadogTracer = require('./tracer')
4
- const Config = require('./config')
4
+ const getConfig = require('./config')
5
5
  const runtimeMetrics = require('./runtime_metrics')
6
6
  const log = require('./log')
7
7
  const { setStartupLogPluginManager } = require('./startup-log')
@@ -98,7 +98,7 @@ class Tracer extends NoopProxy {
98
98
  this._initialized = true
99
99
 
100
100
  try {
101
- const config = new Config(options) // TODO: support dynamic code config
101
+ const config = getConfig(options) // TODO: support dynamic code config
102
102
 
103
103
  if (config.crashtracking.enabled) {
104
104
  require('./crashtracking').start(config)
@@ -16,6 +16,8 @@ module.exports = {
16
16
  APM_TRACING_LOGS_INJECTION: 1n << 13n,
17
17
  APM_TRACING_HTTP_HEADER_TAGS: 1n << 14n,
18
18
  APM_TRACING_CUSTOM_TAGS: 1n << 15n,
19
+ ASM_PROCESSOR_OVERRIDES: 1n << 16n,
20
+ ASM_CUSTOM_DATA_SCANNERS: 1n << 17n,
19
21
  ASM_EXCLUSION_DATA: 1n << 18n,
20
22
  APM_TRACING_ENABLED: 1n << 19n,
21
23
  ASM_RASP_SQLI: 1n << 21n,
@@ -90,6 +90,8 @@ function enableWafUpdate (appsecConfig) {
90
90
  rc.updateCapabilities(RemoteConfigCapabilities.ASM_CUSTOM_RULES, true)
91
91
  rc.updateCapabilities(RemoteConfigCapabilities.ASM_CUSTOM_BLOCKING_RESPONSE, true)
92
92
  rc.updateCapabilities(RemoteConfigCapabilities.ASM_TRUSTED_IPS, true)
93
+ rc.updateCapabilities(RemoteConfigCapabilities.ASM_PROCESSOR_OVERRIDES, true)
94
+ rc.updateCapabilities(RemoteConfigCapabilities.ASM_CUSTOM_DATA_SCANNERS, true)
93
95
  rc.updateCapabilities(RemoteConfigCapabilities.ASM_EXCLUSION_DATA, true)
94
96
  rc.updateCapabilities(RemoteConfigCapabilities.ASM_ENDPOINT_FINGERPRINT, true)
95
97
  rc.updateCapabilities(RemoteConfigCapabilities.ASM_SESSION_FINGERPRINT, true)
@@ -129,6 +131,8 @@ function disableWafUpdate () {
129
131
  rc.updateCapabilities(RemoteConfigCapabilities.ASM_CUSTOM_RULES, false)
130
132
  rc.updateCapabilities(RemoteConfigCapabilities.ASM_CUSTOM_BLOCKING_RESPONSE, false)
131
133
  rc.updateCapabilities(RemoteConfigCapabilities.ASM_TRUSTED_IPS, false)
134
+ rc.updateCapabilities(RemoteConfigCapabilities.ASM_PROCESSOR_OVERRIDES, false)
135
+ rc.updateCapabilities(RemoteConfigCapabilities.ASM_CUSTOM_DATA_SCANNERS, false)
132
136
  rc.updateCapabilities(RemoteConfigCapabilities.ASM_EXCLUSION_DATA, false)
133
137
  rc.updateCapabilities(RemoteConfigCapabilities.ASM_ENDPOINT_FINGERPRINT, false)
134
138
  rc.updateCapabilities(RemoteConfigCapabilities.ASM_SESSION_FINGERPRINT, false)
@@ -22,7 +22,7 @@ let nativeMetrics = null
22
22
  let gcObserver = null
23
23
  let interval = null
24
24
  let client = null
25
- let lastTime = 0n
25
+ let lastTime = 0
26
26
  let lastCpuUsage = null
27
27
  let eventLoopDelayObserver = null
28
28
 
@@ -103,7 +103,6 @@ module.exports = {
103
103
  interval = null
104
104
 
105
105
  client = null
106
- lastTime = 0n
107
106
  lastCpuUsage = null
108
107
 
109
108
  gcObserver?.disconnect()
@@ -339,6 +338,7 @@ function startGCObserver () {
339
338
 
340
339
  gcObserver = new PerformanceObserver(list => {
341
340
  for (const entry of list.getEntries()) {
341
+ // @ts-expect-error - entry.detail?.kind and entry.kind are not typed
342
342
  const type = gcType(entry.detail?.kind || entry.kind)
343
343
  const duration = entry.duration * 1_000_000
344
344
 
@@ -47,11 +47,14 @@ class SpanProcessor {
47
47
  this._spanSampler.sample(spanContext)
48
48
  this._gitMetadataTagger.tagGitMetadata(spanContext)
49
49
 
50
+ let isChunkRoot = true
51
+
50
52
  for (const span of started) {
51
53
  if (span._duration === undefined) {
52
54
  active.push(span)
53
55
  } else {
54
- const formattedSpan = format(span)
56
+ const formattedSpan = format(span, isChunkRoot)
57
+ isChunkRoot = false
55
58
  this._stats?.onSpanFinished(formattedSpan)
56
59
  formatted.push(formattedSpan)
57
60