dd-trace 6.11.0 → 6.13.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 (139) hide show
  1. package/LICENSE-3rdparty.csv +5 -0
  2. package/ci/test-optimization-validation/framework-adapters/cypress.js +9 -11
  3. package/ci/test-optimization-validation/scenarios/ci-wiring.js +5 -4
  4. package/index.d.ts +143 -25
  5. package/package.json +26 -25
  6. package/packages/datadog-instrumentations/src/bunyan.js +4 -2
  7. package/packages/datadog-instrumentations/src/cucumber.js +4 -4
  8. package/packages/datadog-instrumentations/src/helpers/bunyan.js +12 -3
  9. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +1 -0
  10. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +28 -0
  11. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
  12. package/packages/datadog-instrumentations/src/jest.js +140 -18
  13. package/packages/datadog-instrumentations/src/mariadb-bundle.js +1597 -0
  14. package/packages/datadog-instrumentations/src/mariadb.js +7 -0
  15. package/packages/datadog-instrumentations/src/mocha/utils.js +5 -2
  16. package/packages/datadog-instrumentations/src/pino.js +15 -4
  17. package/packages/datadog-instrumentations/src/playwright-reporter.js +149 -1
  18. package/packages/datadog-instrumentations/src/playwright.js +67 -53
  19. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +13 -12
  20. package/packages/datadog-instrumentations/src/vitest-worker.js +11 -0
  21. package/packages/datadog-instrumentations/src/webdriverio.js +26 -0
  22. package/packages/datadog-instrumentations/src/winston.js +6 -3
  23. package/packages/datadog-plugin-cypress/src/support.js +1 -0
  24. package/packages/datadog-plugin-http/src/client.js +2 -15
  25. package/packages/datadog-plugin-http2/src/client.js +2 -15
  26. package/packages/datadog-plugin-openai-agents/src/integration.js +15 -11
  27. package/packages/datadog-plugin-playwright/src/index.js +13 -1
  28. package/packages/datadog-plugin-undici/src/index.js +2 -15
  29. package/packages/dd-trace/src/aiguard/evaluation.js +94 -20
  30. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -3
  31. package/packages/dd-trace/src/aiguard/messages/anthropic.js +21 -9
  32. package/packages/dd-trace/src/aiguard/noop.js +9 -1
  33. package/packages/dd-trace/src/aiguard/redaction.js +183 -0
  34. package/packages/dd-trace/src/aiguard/sdk.js +7 -2
  35. package/packages/dd-trace/src/aiguard/tags.js +2 -0
  36. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +10 -9
  37. package/packages/dd-trace/src/appsec/reporter.js +2 -0
  38. package/packages/dd-trace/src/appsec/rule_manager.js +2 -0
  39. package/packages/dd-trace/src/appsec/telemetry/index.js +13 -0
  40. package/packages/dd-trace/src/appsec/telemetry/rasp.js +16 -0
  41. package/packages/dd-trace/src/appsec/telemetry/waf.js +24 -0
  42. package/packages/dd-trace/src/carrier.js +107 -48
  43. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +5 -5
  44. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +2 -0
  45. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +5 -3
  46. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +51 -26
  47. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +4 -2
  48. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +5 -11
  49. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +6 -4
  50. package/packages/dd-trace/src/ci-visibility/final-flush.js +20 -0
  51. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +5 -3
  52. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +196 -29
  53. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -2
  54. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +4 -2
  55. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +4 -2
  56. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +5 -5
  57. package/packages/dd-trace/src/config/generated-config-types.d.ts +6 -0
  58. package/packages/dd-trace/src/config/index.js +2 -1
  59. package/packages/dd-trace/src/config/supported-configurations.json +27 -0
  60. package/packages/dd-trace/src/debugger/constants.js +1 -0
  61. package/packages/dd-trace/src/debugger/devtools_client/condition.js +1 -1
  62. package/packages/dd-trace/src/debugger/devtools_client/index.js +3 -10
  63. package/packages/dd-trace/src/debugger/devtools_client/send.js +4 -3
  64. package/packages/dd-trace/src/debugger/index.js +4 -2
  65. package/packages/dd-trace/src/debugger/inspect-segment.js +154 -0
  66. package/packages/dd-trace/src/dogstatsd.js +45 -18
  67. package/packages/dd-trace/src/encode/agentless-json.js +5 -4
  68. package/packages/dd-trace/src/evp_proxy/constants.js +8 -0
  69. package/packages/dd-trace/src/evp_proxy/direct.js +64 -0
  70. package/packages/dd-trace/src/evp_proxy/discovery.js +133 -0
  71. package/packages/dd-trace/src/evp_proxy/path.js +22 -0
  72. package/packages/dd-trace/src/exporters/agent/index.js +22 -2
  73. package/packages/dd-trace/src/exporters/agent/writer.js +4 -4
  74. package/packages/dd-trace/src/exporters/common/request.js +5 -2
  75. package/packages/dd-trace/src/exporters/common/writer.js +24 -2
  76. package/packages/dd-trace/src/exporters/span-stats/index.js +23 -4
  77. package/packages/dd-trace/src/external-logger/src/index.js +4 -3
  78. package/packages/dd-trace/src/flush.js +102 -0
  79. package/packages/dd-trace/src/llmobs/experiments/client.js +10 -2
  80. package/packages/dd-trace/src/llmobs/experiments/dataset.js +301 -14
  81. package/packages/dd-trace/src/llmobs/experiments/experiment.js +440 -112
  82. package/packages/dd-trace/src/llmobs/experiments/index.js +68 -44
  83. package/packages/dd-trace/src/llmobs/experiments/noop.js +103 -12
  84. package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
  85. package/packages/dd-trace/src/llmobs/experiments/util.js +81 -4
  86. package/packages/dd-trace/src/llmobs/index.js +80 -17
  87. package/packages/dd-trace/src/llmobs/noop.js +2 -1
  88. package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +8 -4
  89. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +6 -3
  90. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +13 -6
  91. package/packages/dd-trace/src/llmobs/plugins/openai-agents/utils.js +5 -7
  92. package/packages/dd-trace/src/llmobs/writers/base.js +60 -24
  93. package/packages/dd-trace/src/noop/span.js +5 -0
  94. package/packages/dd-trace/src/openfeature/constants/constants.js +0 -18
  95. package/packages/dd-trace/src/openfeature/index.js +7 -4
  96. package/packages/dd-trace/src/openfeature/writers/base.js +153 -11
  97. package/packages/dd-trace/src/openfeature/writers/exposures.js +70 -15
  98. package/packages/dd-trace/src/openfeature/writers/util.js +97 -15
  99. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +45 -3
  100. package/packages/dd-trace/src/opentelemetry/logs/index.js +4 -1
  101. package/packages/dd-trace/src/opentelemetry/logs/logger_provider.js +7 -4
  102. package/packages/dd-trace/src/opentelemetry/metrics/index.js +3 -1
  103. package/packages/dd-trace/src/opentelemetry/metrics/meter_provider.js +8 -0
  104. package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +4 -2
  105. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +4 -2
  106. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +25 -5
  107. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +61 -25
  108. package/packages/dd-trace/src/opentelemetry/tracer.js +26 -2
  109. package/packages/dd-trace/src/opentracing/propagation/text_map.js +429 -352
  110. package/packages/dd-trace/src/opentracing/span.js +28 -0
  111. package/packages/dd-trace/src/otel-thread-ctx.js +23 -5
  112. package/packages/dd-trace/src/plugin_manager.js +2 -0
  113. package/packages/dd-trace/src/plugins/util/status-validator.js +98 -0
  114. package/packages/dd-trace/src/plugins/util/test.js +74 -21
  115. package/packages/dd-trace/src/plugins/util/web.js +2 -65
  116. package/packages/dd-trace/src/profiling/oom.js +4 -3
  117. package/packages/dd-trace/src/profiling/profiler.js +7 -0
  118. package/packages/dd-trace/src/profiling/profilers/events.js +3 -3
  119. package/packages/dd-trace/src/profiling/profilers/wall.js +14 -14
  120. package/packages/dd-trace/src/proxy.js +36 -8
  121. package/packages/dd-trace/src/runtime_metrics/index.js +5 -0
  122. package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +5 -0
  123. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +17 -6
  124. package/packages/dd-trace/src/serverless/telemetry-delivery-tracker.js +55 -0
  125. package/packages/dd-trace/src/serverless/vercel.js +122 -0
  126. package/packages/dd-trace/src/serverless.js +37 -20
  127. package/packages/dd-trace/src/span_stats.js +40 -3
  128. package/packages/dd-trace/src/telemetry/metrics.js +62 -8
  129. package/packages/dd-trace/src/telemetry/send-data.js +1 -0
  130. package/packages/dd-trace/src/tracer.js +22 -0
  131. package/packages/dd-trace/src/web-tags-cache.js +67 -13
  132. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
  133. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  134. package/vendor/dist/https-proxy-agent/LICENSE +22 -0
  135. package/vendor/dist/https-proxy-agent/index.js +4 -0
  136. package/vendor/dist/protobufjs/index.js +1 -1
  137. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  138. package/vendor/dist/proxy-from-env/LICENSE +20 -0
  139. package/vendor/dist/proxy-from-env/index.js +1 -0
@@ -272,6 +272,7 @@ export interface GeneratedConfig {
272
272
  DD_TRACE_HAPI_ENABLED: boolean;
273
273
  DD_TRACE_HAPI_HAPI_ENABLED: boolean;
274
274
  DD_TRACE_HONO_ENABLED: boolean;
275
+ DD_TRACE_HTTP_CLIENT_ERROR_STATUSES: string;
275
276
  DD_TRACE_HTTP_ENABLED: boolean;
276
277
  DD_TRACE_HTTP_SERVER_ERROR_STATUSES: string;
277
278
  DD_TRACE_HTTP2_ENABLED: boolean;
@@ -418,6 +419,7 @@ export interface GeneratedConfig {
418
419
  endpoint: string | undefined;
419
420
  maxContentSize: number;
420
421
  maxMessagesLength: number;
422
+ redactionEnabled: boolean;
421
423
  timeout: number;
422
424
  };
423
425
  appsec: {
@@ -473,6 +475,7 @@ export interface GeneratedConfig {
473
475
  agentlessEnabled: boolean | undefined;
474
476
  DD_LLMOBS_ENABLED: boolean;
475
477
  mlApp: string | undefined;
478
+ projectName: string | undefined;
476
479
  sampleRate: number;
477
480
  };
478
481
  logInjection: boolean;
@@ -614,6 +617,7 @@ export interface GeneratedEnvVarConfig {
614
617
  DD_AI_GUARD_ENDPOINT: string | undefined;
615
618
  DD_AI_GUARD_MAX_CONTENT_SIZE: number;
616
619
  DD_AI_GUARD_MAX_MESSAGES_LENGTH: number;
620
+ DD_AI_GUARD_REDACTION_ENABLED: boolean;
617
621
  DD_AI_GUARD_TIMEOUT: number;
618
622
  DD_API_KEY: string | undefined;
619
623
  DD_API_SECURITY_DOWNSTREAM_BODY_ANALYSIS_SAMPLE_RATE: number;
@@ -758,6 +762,7 @@ export interface GeneratedEnvVarConfig {
758
762
  DD_LLMOBS_AGENTLESS_ENABLED: boolean | undefined;
759
763
  DD_LLMOBS_ENABLED: boolean;
760
764
  DD_LLMOBS_ML_APP: string | undefined;
765
+ DD_LLMOBS_PROJECT_NAME: string | undefined;
761
766
  DD_LLMOBS_SAMPLE_RATE: number;
762
767
  DD_LOG_LEVEL: "debug" | "info" | "warn" | "error";
763
768
  DD_LOGS_INJECTION: boolean;
@@ -970,6 +975,7 @@ export interface GeneratedEnvVarConfig {
970
975
  DD_TRACE_HAPI_HAPI_ENABLED: boolean;
971
976
  DD_TRACE_HEADER_TAGS: string[];
972
977
  DD_TRACE_HONO_ENABLED: boolean;
978
+ DD_TRACE_HTTP_CLIENT_ERROR_STATUSES: string;
973
979
  DD_TRACE_HTTP_ENABLED: boolean;
974
980
  DD_TRACE_HTTP_SERVER_ERROR_STATUSES: string;
975
981
  DD_TRACE_HTTP2_ENABLED: boolean;
@@ -510,7 +510,8 @@ class Config extends ConfigBase {
510
510
  if (!this.llmobs.DD_LLMOBS_ENABLED &&
511
511
  !trackedConfigOrigins.has('llmobs.DD_LLMOBS_ENABLED') &&
512
512
  (trackedConfigOrigins.has('llmobs.agentlessEnabled') ||
513
- trackedConfigOrigins.has('llmobs.mlApp'))) {
513
+ trackedConfigOrigins.has('llmobs.mlApp') ||
514
+ trackedConfigOrigins.has('llmobs.projectName'))) {
514
515
  setAndTrack(this, 'llmobs.DD_LLMOBS_ENABLED', true)
515
516
  }
516
517
 
@@ -93,6 +93,16 @@
93
93
  "default": "16"
94
94
  }
95
95
  ],
96
+ "DD_AI_GUARD_REDACTION_ENABLED": [
97
+ {
98
+ "implementation": "A",
99
+ "type": "boolean",
100
+ "configurationNames": [
101
+ "experimental.aiguard.redactionEnabled"
102
+ ],
103
+ "default": "true"
104
+ }
105
+ ],
96
106
  "DD_AI_GUARD_TIMEOUT": [
97
107
  {
98
108
  "implementation": "A",
@@ -1341,6 +1351,16 @@
1341
1351
  "default": null
1342
1352
  }
1343
1353
  ],
1354
+ "DD_LLMOBS_PROJECT_NAME": [
1355
+ {
1356
+ "implementation": "A",
1357
+ "type": "string",
1358
+ "configurationNames": [
1359
+ "llmobs.projectName"
1360
+ ],
1361
+ "default": null
1362
+ }
1363
+ ],
1344
1364
  "DD_LLMOBS_SAMPLE_RATE": [
1345
1365
  {
1346
1366
  "implementation": "A",
@@ -2999,6 +3019,13 @@
2999
3019
  "default": "true"
3000
3020
  }
3001
3021
  ],
3022
+ "DD_TRACE_HTTP_CLIENT_ERROR_STATUSES": [
3023
+ {
3024
+ "implementation": "A",
3025
+ "type": "string",
3026
+ "default": "400-499"
3027
+ }
3028
+ ],
3002
3029
  "DD_TRACE_HTTP_ENABLED": [
3003
3030
  {
3004
3031
  "implementation": "A",
@@ -4,4 +4,5 @@ module.exports = {
4
4
  DEBUGGER_DIAGNOSTICS_V1: '/debugger/v1/diagnostics',
5
5
  DEBUGGER_INPUT_V1: '/debugger/v1/input',
6
6
  DEBUGGER_INPUT_V2: '/debugger/v2/input',
7
+ INSPECT_SEGMENT_GLOBAL_PROPERTY: 'debuggerInspectSegment',
7
8
  }
@@ -55,7 +55,7 @@ function compileSegments (segments) {
55
55
  ? `(() => {
56
56
  try {
57
57
  const result = ${compile(json)}
58
- return typeof result === 'string' ? result : $dd_inspect(result, $dd_segmentInspectOptions)
58
+ return typeof result === 'string' ? result : $dd_inspectSegment(result)
59
59
  } catch (e) {
60
60
  return { expr: ${JSON.stringify(dsl)}, message: \`\${e.name}: \${e.message}\` }
61
61
  }
@@ -4,6 +4,7 @@ const { randomUUID } = require('crypto')
4
4
  const { workerData: { probeSamplerBuffer } } = require('worker_threads')
5
5
  const { version } = require('../../../../../package.json')
6
6
  const processTags = require('../../process-tags')
7
+ const { INSPECT_SEGMENT_GLOBAL_PROPERTY } = require('../constants')
7
8
  const {
8
9
  MAX_SAMPLED_PROBES_PER_PAUSE,
9
10
  SAMPLED_PROBE_COUNT_INDEX,
@@ -23,16 +24,8 @@ require('./remote_config')
23
24
 
24
25
  /** @typedef {import('node:inspector').Debugger.EvaluateOnCallFrameReturnType} EvaluateOnCallFrameResult */
25
26
 
26
- const templateExpressionSetupCode = `
27
- const $dd_inspect = global.require('node:util').inspect;
28
- const $dd_segmentInspectOptions = {
29
- depth: 0,
30
- customInspect: false,
31
- maxArrayLength: 3,
32
- maxStringLength: 8 * 1024,
33
- breakLength: Infinity
34
- };
35
- `
27
+ const templateExpressionSetupCode = 'const $dd_inspectSegment = ' +
28
+ `globalThis[Symbol.for('dd-trace')][${JSON.stringify(INSPECT_SEGMENT_GLOBAL_PROPERTY)}];`
36
29
 
37
30
  // Expression to run on a call frame of the paused thread to get its active trace and span id.
38
31
  const getDDTagsExpression = `(() => {
@@ -141,7 +141,7 @@ function setInputPath (newPath) {
141
141
  * @returns {string} The serialized tags.
142
142
  */
143
143
  function buildTags (tags) {
144
- const serializedTags = []
144
+ let serializedTags = ''
145
145
 
146
146
  for (const [key, rawValue] of tags) {
147
147
  if (rawValue === undefined) continue
@@ -151,8 +151,9 @@ function buildTags (tags) {
151
151
  continue
152
152
  }
153
153
 
154
- serializedTags.push(`${key}:${rawValue}`)
154
+ if (serializedTags) serializedTags += ','
155
+ serializedTags += `${key}:${rawValue}`
155
156
  }
156
157
 
157
- return serializedTags.join(',')
158
+ return serializedTags
158
159
  }
@@ -7,7 +7,7 @@ const { Worker, MessageChannel, threadId: parentThreadId } = require('worker_thr
7
7
  const log = require('../log')
8
8
  const { fetchAgentInfo } = require('../agent/info')
9
9
  const getDebuggerConfig = require('./config')
10
- const { DEBUGGER_DIAGNOSTICS_V1, DEBUGGER_INPUT_V2 } = require('./constants')
10
+ const { DEBUGGER_DIAGNOSTICS_V1, DEBUGGER_INPUT_V2, INSPECT_SEGMENT_GLOBAL_PROPERTY } = require('./constants')
11
11
  const { installProbeSampler, uninstallProbeSampler } = require('./probe_sampler')
12
12
 
13
13
  /**
@@ -64,7 +64,9 @@ function start (config, rcInstance) {
64
64
  const logChannel = new MessageChannel()
65
65
  configChannel = new MessageChannel()
66
66
 
67
- globalThis[Symbol.for('dd-trace')].utilTypes = types
67
+ const debuggerGlobals = globalThis[Symbol.for('dd-trace')]
68
+ debuggerGlobals.utilTypes = types
69
+ debuggerGlobals[INSPECT_SEGMENT_GLOBAL_PROPERTY] = require('./inspect-segment')
68
70
 
69
71
  const probeSamplerBuffer = installProbeSampler()
70
72
 
@@ -0,0 +1,154 @@
1
+ 'use strict'
2
+
3
+ const { inspect, types } = require('node:util')
4
+
5
+ const { NODE_MAJOR } = require('../../../../version')
6
+
7
+ /** @typedef {NonNullable<ReturnType<typeof globalThis.Object.getOwnPropertyDescriptor>>} PropertyDescriptor */
8
+ /** @typedef {Map<unknown, unknown> | Set<unknown>} Collection */
9
+
10
+ const mapEntries = Map.prototype.entries
11
+ const mapSet = Map.prototype.set
12
+ const mapSizeGetter = Object.getOwnPropertyDescriptor(Map.prototype, 'size').get
13
+ const setAdd = Set.prototype.add
14
+ const setSizeGetter = Object.getOwnPropertyDescriptor(Set.prototype, 'size').get
15
+ const setValues = Set.prototype.values
16
+ const mapIteratorNext = Object.getPrototypeOf(mapEntries.call(new Map())).next
17
+ const setIteratorNext = Object.getPrototypeOf(setValues.call(new Set())).next
18
+
19
+ const maxCollectionEntries = 3
20
+ const maxProperties = 5
21
+ const segmentInspectOptions = {
22
+ depth: 0,
23
+ customInspect: false,
24
+ maxArrayLength: maxCollectionEntries,
25
+ maxStringLength: 8 * 1024,
26
+ breakLength: Infinity,
27
+ }
28
+
29
+ module.exports = inspectSegment
30
+
31
+ /**
32
+ * Inspect a dynamic-instrumentation template value without invoking user code.
33
+ * Unlike collections, `util.inspect` has no option for limiting the number of object properties, so this function
34
+ * truncates objects before inspecting them.
35
+ *
36
+ * @param {unknown} value
37
+ * @returns {string}
38
+ */
39
+ function inspectSegment (value) {
40
+ if (value === null || (typeof value !== 'object' && typeof value !== 'function')) {
41
+ return inspect(value, segmentInspectOptions)
42
+ }
43
+ if (types.isProxy(value)) return '[Proxy]'
44
+ if (types.isMap(value)) return inspectCollection(value, true)
45
+ if (types.isSet(value)) return inspectCollection(value, false)
46
+ if (
47
+ Array.isArray(value) ||
48
+ types.isTypedArray(value) ||
49
+ types.isAnyArrayBuffer(value) ||
50
+ types.isDataView(value) ||
51
+ types.isWeakMap(value) ||
52
+ types.isWeakSet(value) ||
53
+ types.isMapIterator(value) ||
54
+ types.isSetIterator(value)
55
+ ) {
56
+ return inspect(value, segmentInspectOptions)
57
+ }
58
+
59
+ /** @type {(string | symbol)[]} */
60
+ const keys = Object.keys(value)
61
+ let propertyCount = keys.length
62
+ const symbols = Object.getOwnPropertySymbols(value)
63
+ for (let i = 0; i < symbols.length; i++) {
64
+ if (Object.getOwnPropertyDescriptor(value, symbols[i])?.enumerable === true) {
65
+ propertyCount++
66
+ if (keys.length < maxProperties) keys.push(symbols[i])
67
+ }
68
+ }
69
+
70
+ if (propertyCount <= maxProperties) {
71
+ // TODO: Decide whether allowing util.inspect to invoke Symbol.toStringTag getters is acceptable. If it is,
72
+ // remove inspectionCanRunUserCode and the related omission paths.
73
+ if (inspectionCanRunUserCode(value)) {
74
+ return '[Value omitted: inspection may execute user code]'
75
+ }
76
+ return inspect(value, segmentInspectOptions)
77
+ }
78
+
79
+ const truncated = {}
80
+ for (let i = 0; i < maxProperties; i++) {
81
+ const descriptor = /** @type {PropertyDescriptor} */ (Object.getOwnPropertyDescriptor(value, keys[i]))
82
+ if (
83
+ (keys[i] === Symbol.toStringTag && descriptor.get !== undefined) ||
84
+ (descriptor.value !== value && inspectionCanRunUserCode(descriptor.value))
85
+ ) {
86
+ return '[Value omitted: inspection may execute user code]'
87
+ }
88
+ if (descriptor.value === value) descriptor.value = truncated
89
+ Object.defineProperty(truncated, keys[i], descriptor)
90
+ }
91
+
92
+ const omitted = propertyCount - maxProperties
93
+ const inspected = inspect(truncated, segmentInspectOptions)
94
+ return `${inspected.slice(0, -2)}, ... ${omitted} more ${omitted === 1 ? 'property' : 'properties'} }`
95
+ }
96
+
97
+ /**
98
+ * Inspect a Map or Set while bounding the number of entries on Node.js 18, where `util.inspect` does not.
99
+ *
100
+ * @param {Collection} value
101
+ * @param {boolean} isMap
102
+ * @returns {string}
103
+ */
104
+ function inspectCollection (value, isMap) {
105
+ if (NODE_MAJOR !== 18) return inspect(value, segmentInspectOptions)
106
+
107
+ const size = (isMap ? mapSizeGetter : setSizeGetter).call(value)
108
+ if (size <= maxCollectionEntries) return inspect(value, segmentInspectOptions)
109
+
110
+ const truncated = isMap ? new Map() : new Set()
111
+ const iterator = (isMap ? mapEntries : setValues).call(value)
112
+ const iteratorNext = isMap ? mapIteratorNext : setIteratorNext
113
+
114
+ for (let i = 0; i < maxCollectionEntries; i++) {
115
+ const result = iteratorNext.call(iterator)
116
+ if (result.done) break
117
+
118
+ if (isMap) {
119
+ const entry = result.value
120
+ const key = entry[0] === value ? truncated : entry[0]
121
+ const entryValue = entry[1] === value ? truncated : entry[1]
122
+ mapSet.call(truncated, key, entryValue)
123
+ } else {
124
+ const entryValue = result.value === value ? truncated : result.value
125
+ setAdd.call(truncated, entryValue)
126
+ }
127
+ }
128
+
129
+ const type = isMap ? 'Map' : 'Set'
130
+ const inspected = inspect(truncated, segmentInspectOptions)
131
+ const normalized = inspected.replace(`${type}(${maxCollectionEntries})`, `${type}(${size})`)
132
+ const remaining = size - maxCollectionEntries
133
+ return `${normalized.slice(0, -2)}, ... ${remaining} more item${remaining === 1 ? '' : 's'} }`
134
+ }
135
+
136
+ /**
137
+ * Determine whether inspecting a value could invoke a proxy trap or toStringTag getter.
138
+ *
139
+ * @param {unknown} value
140
+ * @returns {boolean}
141
+ */
142
+ function inspectionCanRunUserCode (value) {
143
+ const type = typeof value
144
+ if (value === null || (type !== 'object' && type !== 'function')) return false
145
+ if (types.isProxy(value)) return true
146
+
147
+ let current = value
148
+ while (current !== null) {
149
+ if (Object.getOwnPropertyDescriptor(current, Symbol.toStringTag)?.get !== undefined) return true
150
+ current = Object.getPrototypeOf(current)
151
+ if (types.isProxy(current)) return true
152
+ }
153
+ return false
154
+ }
@@ -8,6 +8,8 @@ const request = require('./exporters/common/request')
8
8
  const log = require('./log')
9
9
  const Histogram = require('./histogram')
10
10
  const { entityId } = require('./exporters/common/docker')
11
+ const { registerTelemetryFlusher } = require('./flush')
12
+ const { createServerlessDeliveryTracker } = require('./serverless')
11
13
 
12
14
  const legacyStorage = storage('legacy')
13
15
 
@@ -25,6 +27,8 @@ const TYPE_HISTOGRAM = 'h'
25
27
  class DogStatsDClient {
26
28
  #lookup
27
29
  #tagsPrefix
30
+ #serverlessDeliveryTracker
31
+
28
32
  constructor (options) {
29
33
  this.#lookup = options.lookup
30
34
  if (options.metricsProxyUrl) {
@@ -41,6 +45,7 @@ class DogStatsDClient {
41
45
  this._tags = options.tags
42
46
  this.#tagsPrefix = this._tags?.length ? `|#${this._tags.join(',')}` : ''
43
47
  this._queue = []
48
+ this.#serverlessDeliveryTracker = createServerlessDeliveryTracker()
44
49
  this._buffer = ''
45
50
  this._offset = 0
46
51
  this._udp4 = this._socket('udp4')
@@ -67,23 +72,30 @@ class DogStatsDClient {
67
72
  this._add(stat, value, TYPE_HISTOGRAM, tags)
68
73
  }
69
74
 
70
- flush () {
75
+ flush (done) {
71
76
  const queue = this._enqueue()
72
77
 
73
- if (queue.length === 0) return
78
+ if (queue.length === 0) {
79
+ if (this.#serverlessDeliveryTracker) return this.#serverlessDeliveryTracker.waitForIdle(done)
80
+ return done?.()
81
+ }
74
82
 
75
83
  log.debug('Flushing %s metrics via %s', queue.length, this._httpOptions ? 'HTTP' : 'UDP')
76
84
 
77
85
  this._queue = []
78
86
 
79
- if (this._httpOptions) {
80
- this._sendHttp(queue)
81
- } else {
82
- this._sendUdp(queue)
87
+ const send = complete => {
88
+ if (this._httpOptions) this._sendHttp(queue, complete)
89
+ else this._sendUdp(queue, complete)
90
+ }
91
+ if (this.#serverlessDeliveryTracker) {
92
+ this.#serverlessDeliveryTracker.track(send)
93
+ return this.#serverlessDeliveryTracker.waitForIdle(done)
83
94
  }
95
+ send(done)
84
96
  }
85
97
 
86
- _sendHttp (queue) {
98
+ _sendHttp (queue, done) {
87
99
  const buffer = Buffer.concat(queue)
88
100
  request(buffer, this._httpOptions, (err) => {
89
101
  if (err) {
@@ -95,32 +107,46 @@ class DogStatsDClient {
95
107
  // options. Either way, we can give UDP a try.
96
108
  this._httpOptions = undefined
97
109
  }
98
- this._sendUdp(queue)
110
+ this._sendUdp(queue, done)
111
+ } else {
112
+ done?.()
99
113
  }
100
114
  })
101
115
  }
102
116
 
103
- _sendUdp (queue) {
117
+ _sendUdp (queue, done) {
104
118
  // dgram resolves the local address via the instrumented dns.lookup when it
105
119
  // binds on first send; the noop store keeps that self-traffic off the trace.
106
120
  legacyStorage.run({ noop: true }, () => {
107
121
  if (this._family === 0) {
108
122
  this.#lookup(this._host, (error, address, family) => {
109
- if (error) return log.error('DogStatsDClient: Host not found', error)
110
- this._sendUdpFromQueue(queue, address, family)
123
+ if (error) {
124
+ log.error('DogStatsDClient: Host not found', error)
125
+ return done?.()
126
+ }
127
+ this._sendUdpFromQueue(queue, address, family, done)
111
128
  })
112
129
  } else {
113
- this._sendUdpFromQueue(queue, this._host, this._family)
130
+ this._sendUdpFromQueue(queue, this._host, this._family, done)
114
131
  }
115
132
  })
116
133
  }
117
134
 
118
- _sendUdpFromQueue (queue, address, family) {
135
+ _sendUdpFromQueue (queue, address, family, done) {
119
136
  const socket = family === 6 ? this._udp6 : this._udp4
137
+ let pending = queue.length
138
+ const complete = () => {
139
+ if (--pending === 0) done?.()
140
+ }
120
141
 
121
142
  for (const buffer of queue) {
122
143
  log.debug('Sending to DogStatsD: %s', buffer)
123
- socket.send(buffer, 0, buffer.length, this._port, address)
144
+ try {
145
+ socket.send(buffer, 0, buffer.length, this._port, address, complete)
146
+ } catch (error) {
147
+ log.error('DogStatsDClient: UDP error sending metrics', error)
148
+ complete()
149
+ }
124
150
  }
125
151
  }
126
152
 
@@ -212,12 +238,12 @@ class MetricsAggregationClient {
212
238
  this.reset()
213
239
  }
214
240
 
215
- flush () {
241
+ flush (done) {
216
242
  this._captureCounters()
217
243
  this._captureGauges()
218
244
  this._captureHistograms()
219
245
 
220
- this._client.flush()
246
+ this._client.flush(done)
221
247
  }
222
248
 
223
249
  reset () {
@@ -370,6 +396,7 @@ class CustomMetrics {
370
396
  setInterval(flush, 10 * 1000).unref?.()
371
397
 
372
398
  globalThis[Symbol.for('dd-trace')].beforeExitHandlers.add(flush)
399
+ registerTelemetryFlusher(done => this.flush(done))
373
400
  }
374
401
 
375
402
  increment (stat, value = 1, tags) {
@@ -392,8 +419,8 @@ class CustomMetrics {
392
419
  this.#client.histogram(stat, value, CustomMetrics.tagTranslator(tags))
393
420
  }
394
421
 
395
- flush () {
396
- return this.#client.flush()
422
+ flush (done) {
423
+ return this.#client.flush(done)
397
424
  }
398
425
 
399
426
  /**
@@ -167,17 +167,18 @@ class AgentlessJSONEncoder {
167
167
  const metadataPrefix = metadataJson.slice(0, -1)
168
168
  const hasMetadata = metadataPrefix.length > 1 // more than just '{'
169
169
 
170
- const traceParts = []
170
+ let tracesJson = ''
171
171
  for (const spanStrings of this._traces) {
172
172
  const spansJson = '[' + spanStrings.join(',') + ']'
173
+ if (tracesJson) tracesJson += ','
173
174
  if (hasMetadata) {
174
- traceParts.push(metadataPrefix + ',"spans":' + spansJson + '}')
175
+ tracesJson += metadataPrefix + ',"spans":' + spansJson + '}'
175
176
  } else {
176
- traceParts.push('{"spans":' + spansJson + '}')
177
+ tracesJson += '{"spans":' + spansJson + '}'
177
178
  }
178
179
  }
179
180
 
180
- const payload = '{"traces":[' + traceParts.join(',') + ']}'
181
+ const payload = '{"traces":[' + tracesJson + ']}'
181
182
  this._reset()
182
183
  return Buffer.from(payload, 'utf8')
183
184
  } catch (err) {
@@ -0,0 +1,8 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ EVP_PROXY_PATH_V2: '/evp_proxy/v2',
5
+ EVP_PROXY_PATH_V4: '/evp_proxy/v4',
6
+ EVP_SUBDOMAIN_HEADER_NAME: 'X-Datadog-EVP-Subdomain',
7
+ EVP_EVENT_PLATFORM_SUBDOMAIN: 'event-platform-intake',
8
+ }
@@ -0,0 +1,64 @@
1
+ 'use strict'
2
+
3
+ const { format } = require('node:url')
4
+
5
+ const { HttpsProxyAgent } = require('../../../../vendor/dist/https-proxy-agent')
6
+ const { getProxyForUrl } = require('../../../../vendor/dist/proxy-from-env')
7
+ const log = require('../log')
8
+
9
+ /**
10
+ * @typedef {object} DirectEVPRoute
11
+ * @property {URL} url - Direct intake URL
12
+ * @property {string} basePath - Direct intake base path
13
+ * @property {object} headers - Direct intake authentication headers
14
+ * @property {import('node:https').Agent} [agent] - Optional HTTPS proxy agent
15
+ */
16
+
17
+ /**
18
+ * Creates an authenticated direct EVP intake route.
19
+ *
20
+ * This helper does not perform local receiver discovery.
21
+ *
22
+ * @param {import('../config/config-base')} config - Tracer configuration
23
+ * @param {string} intake - EVP intake subdomain
24
+ * @returns {DirectEVPRoute|undefined} Direct route when credentials and site are available
25
+ */
26
+ function createDirectEVPRoute (config, intake) {
27
+ const apiKey = config.DD_API_KEY
28
+ if (!apiKey || !config.site) return
29
+
30
+ try {
31
+ const hostname = `${intake}.${config.site}`.toLowerCase()
32
+ const url = new URL(format({
33
+ protocol: 'https:',
34
+ hostname,
35
+ }))
36
+ if (
37
+ url.hostname !== hostname ||
38
+ url.username ||
39
+ url.password ||
40
+ url.port ||
41
+ url.pathname !== '/' ||
42
+ url.search ||
43
+ url.hash
44
+ ) {
45
+ throw new Error('Invalid direct EVP intake URL')
46
+ }
47
+
48
+ const proxyUrl = getProxyForUrl(url.href)
49
+ const agent = proxyUrl ? new HttpsProxyAgent(proxyUrl) : undefined
50
+
51
+ return {
52
+ url,
53
+ basePath: '',
54
+ headers: {
55
+ 'DD-API-KEY': apiKey,
56
+ },
57
+ ...(agent && { agent }),
58
+ }
59
+ } catch (error) {
60
+ log.debug('Unable to configure direct EVP intake: %s', error.message)
61
+ }
62
+ }
63
+
64
+ module.exports = { createDirectEVPRoute }