dd-trace 5.96.0 → 5.97.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 (114) hide show
  1. package/index.d.ts +34 -0
  2. package/package.json +9 -7
  3. package/packages/datadog-esbuild/index.js +20 -9
  4. package/packages/datadog-instrumentations/src/child_process.js +7 -17
  5. package/packages/datadog-instrumentations/src/crypto.js +1 -2
  6. package/packages/datadog-instrumentations/src/cucumber.js +4 -1
  7. package/packages/datadog-instrumentations/src/cypress-config.js +324 -0
  8. package/packages/datadog-instrumentations/src/cypress.js +86 -4
  9. package/packages/datadog-instrumentations/src/dns.js +1 -2
  10. package/packages/datadog-instrumentations/src/express.js +4 -4
  11. package/packages/datadog-instrumentations/src/fs.js +27 -29
  12. package/packages/datadog-instrumentations/src/graphql.js +1 -1
  13. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +41 -13
  14. package/packages/datadog-instrumentations/src/helpers/hook.js +31 -6
  15. package/packages/datadog-instrumentations/src/helpers/hooks.js +12 -19
  16. package/packages/datadog-instrumentations/src/helpers/instrument.js +27 -13
  17. package/packages/datadog-instrumentations/src/helpers/register.js +103 -142
  18. package/packages/datadog-instrumentations/src/http/client.js +2 -3
  19. package/packages/datadog-instrumentations/src/http/server.js +2 -5
  20. package/packages/datadog-instrumentations/src/http2/client.js +1 -3
  21. package/packages/datadog-instrumentations/src/http2/server.js +1 -3
  22. package/packages/datadog-instrumentations/src/jest.js +13 -4
  23. package/packages/datadog-instrumentations/src/limitd-client.js +1 -1
  24. package/packages/datadog-instrumentations/src/mocha/utils.js +4 -1
  25. package/packages/datadog-instrumentations/src/net.js +2 -8
  26. package/packages/datadog-instrumentations/src/pino.js +1 -1
  27. package/packages/datadog-instrumentations/src/playwright.js +4 -1
  28. package/packages/datadog-instrumentations/src/prisma.js +1 -2
  29. package/packages/datadog-instrumentations/src/selenium.js +4 -1
  30. package/packages/datadog-instrumentations/src/sequelize.js +1 -1
  31. package/packages/datadog-instrumentations/src/url.js +1 -3
  32. package/packages/datadog-instrumentations/src/vitest.js +5 -1
  33. package/packages/datadog-instrumentations/src/vm.js +1 -3
  34. package/packages/datadog-plugin-aws-sdk/src/base.js +4 -3
  35. package/packages/datadog-plugin-cucumber/src/index.js +7 -3
  36. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +57 -5
  37. package/packages/datadog-plugin-graphql/src/resolve.js +1 -1
  38. package/packages/datadog-plugin-jest/src/index.js +4 -2
  39. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +31 -4
  40. package/packages/datadog-plugin-mocha/src/index.js +5 -2
  41. package/packages/datadog-plugin-next/src/index.js +2 -14
  42. package/packages/datadog-plugin-openai/src/services.js +1 -0
  43. package/packages/datadog-webpack/index.js +3 -3
  44. package/packages/dd-trace/index.js +12 -10
  45. package/packages/dd-trace/src/agent/url.js +2 -2
  46. package/packages/dd-trace/src/aiguard/sdk.js +4 -0
  47. package/packages/dd-trace/src/appsec/blocking.js +3 -0
  48. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +1 -1
  49. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +1 -1
  50. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  51. package/packages/dd-trace/src/appsec/remote_config.js +1 -0
  52. package/packages/dd-trace/src/appsec/sdk/index.js +4 -0
  53. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +6 -1
  54. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +4 -0
  55. package/packages/dd-trace/src/config/defaults.js +316 -146
  56. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -1
  57. package/packages/dd-trace/src/config/helper.js +59 -10
  58. package/packages/dd-trace/src/config/index.js +569 -1505
  59. package/packages/dd-trace/src/config/parsers.js +256 -0
  60. package/packages/dd-trace/src/config/remote_config.js +59 -2
  61. package/packages/dd-trace/src/config/supported-configurations.json +350 -433
  62. package/packages/dd-trace/src/crashtracking/crashtracker.js +7 -1
  63. package/packages/dd-trace/src/crashtracking/index.js +1 -7
  64. package/packages/dd-trace/src/debugger/index.js +1 -1
  65. package/packages/dd-trace/src/dogstatsd.js +12 -9
  66. package/packages/dd-trace/src/encode/0.4.js +1 -1
  67. package/packages/dd-trace/src/exporters/agent/writer.js +7 -1
  68. package/packages/dd-trace/src/exporters/common/request.js +9 -0
  69. package/packages/dd-trace/src/exporters/common/writer.js +12 -2
  70. package/packages/dd-trace/src/heap_snapshots.js +3 -0
  71. package/packages/dd-trace/src/index.js +5 -2
  72. package/packages/dd-trace/src/lambda/runtime/ritm.js +6 -6
  73. package/packages/dd-trace/src/llmobs/index.js +4 -1
  74. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +5 -1
  75. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +60 -12
  76. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +4 -2
  77. package/packages/dd-trace/src/llmobs/sdk.js +12 -8
  78. package/packages/dd-trace/src/llmobs/span_processor.js +1 -1
  79. package/packages/dd-trace/src/llmobs/tagger.js +9 -6
  80. package/packages/dd-trace/src/llmobs/writers/base.js +2 -0
  81. package/packages/dd-trace/src/llmobs/writers/util.js +3 -0
  82. package/packages/dd-trace/src/log/index.js +26 -55
  83. package/packages/dd-trace/src/log/writer.js +7 -19
  84. package/packages/dd-trace/src/noop/proxy.js +8 -0
  85. package/packages/dd-trace/src/opentelemetry/logs/index.js +1 -1
  86. package/packages/dd-trace/src/opentelemetry/metrics/index.js +1 -1
  87. package/packages/dd-trace/src/opentracing/propagation/text_map.js +9 -4
  88. package/packages/dd-trace/src/payload-tagging/config/index.js +6 -5
  89. package/packages/dd-trace/src/plugin_manager.js +8 -6
  90. package/packages/dd-trace/src/plugins/ci_plugin.js +4 -0
  91. package/packages/dd-trace/src/plugins/plugin.js +7 -4
  92. package/packages/dd-trace/src/process-tags/index.js +3 -0
  93. package/packages/dd-trace/src/profiler.js +27 -2
  94. package/packages/dd-trace/src/profiling/config.js +73 -241
  95. package/packages/dd-trace/src/profiling/exporter_cli.js +1 -4
  96. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +6 -2
  97. package/packages/dd-trace/src/profiling/profiler.js +56 -44
  98. package/packages/dd-trace/src/profiling/profilers/events.js +2 -3
  99. package/packages/dd-trace/src/profiling/profilers/wall.js +89 -6
  100. package/packages/dd-trace/src/profiling/ssi-heuristics.js +4 -1
  101. package/packages/dd-trace/src/propagation-hash/index.js +2 -1
  102. package/packages/dd-trace/src/proxy.js +32 -3
  103. package/packages/dd-trace/src/remote_config/index.js +3 -0
  104. package/packages/dd-trace/src/require-package-json.js +8 -4
  105. package/packages/dd-trace/src/ritm.js +58 -26
  106. package/packages/dd-trace/src/runtime_metrics/index.js +3 -0
  107. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +3 -0
  108. package/packages/dd-trace/src/sampler.js +1 -1
  109. package/packages/dd-trace/src/standalone/index.js +3 -0
  110. package/packages/dd-trace/src/telemetry/index.js +2 -3
  111. package/packages/dd-trace/src/telemetry/send-data.js +5 -19
  112. package/packages/dd-trace/src/telemetry/session-propagation.js +19 -44
  113. package/packages/dd-trace/src/telemetry/telemetry.js +28 -171
  114. package/packages/dd-trace/src/util.js +0 -9
@@ -6,9 +6,8 @@ const { pathToFileURL } = require('url')
6
6
  const satisfies = require('../../../../vendor/dist/semifies')
7
7
  const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../plugins/util/tags')
8
8
  const { getIsAzureFunction } = require('../serverless')
9
- const { isFalse, isTrue } = require('../util')
10
9
  const { getAzureTagsFromMetadata, getAzureAppMetadata, getAzureFunctionMetadata } = require('../azure_metadata')
11
- const { getEnvironmentVariable, getValueFromEnvSources } = require('../config/helper')
10
+ const { getEnvironmentVariable } = require('../config/helper')
12
11
  const { getAgentUrl } = require('../agent/url')
13
12
  const { isACFActive } = require('../../../datadog-core/src/storage')
14
13
 
@@ -22,59 +21,22 @@ const { oomExportStrategies, snapshotKinds } = require('./constants')
22
21
  const { tagger } = require('./tagger')
23
22
 
24
23
  class Config {
25
- constructor (options = {}) {
26
- // TODO: Remove entries that were already resolved in config.
27
- // For the others, move them over to config.
24
+ constructor (options) {
28
25
  const AWS_LAMBDA_FUNCTION_NAME = getEnvironmentVariable('AWS_LAMBDA_FUNCTION_NAME')
29
26
 
30
- // TODO: Move initialization of these values to packages/dd-trace/src/config/index.js, and just read from config
31
- const {
32
- DD_INTERNAL_PROFILING_TIMELINE_SAMPLING_ENABLED,
33
- DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED,
34
- DD_PROFILING_CODEHOTSPOTS_ENABLED,
35
- DD_PROFILING_CPU_ENABLED,
36
- DD_PROFILING_DEBUG_SOURCE_MAPS,
37
- DD_PROFILING_DEBUG_UPLOAD_COMPRESSION,
38
- DD_PROFILING_ENDPOINT_COLLECTION_ENABLED,
39
- DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES,
40
- DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE,
41
- DD_PROFILING_EXPERIMENTAL_OOM_MAX_HEAP_EXTENSION_COUNT,
42
- DD_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED,
43
- DD_PROFILING_HEAP_ENABLED,
44
- DD_PROFILING_HEAP_SAMPLING_INTERVAL,
45
- DD_PROFILING_PPROF_PREFIX,
46
- DD_PROFILING_PROFILERS,
47
- DD_PROFILING_TIMELINE_ENABLED,
48
- DD_PROFILING_UPLOAD_PERIOD,
49
- DD_PROFILING_UPLOAD_TIMEOUT,
50
- DD_PROFILING_V8_PROFILER_BUG_WORKAROUND,
51
- DD_PROFILING_WALLTIME_ENABLED,
52
- DD_TAGS,
53
- } = getProfilingEnvValues()
54
-
55
- // Must be longer than one minute so pad with five seconds
56
- const flushInterval = options.interval ?? (Number(DD_PROFILING_UPLOAD_PERIOD) * 1000 || 65 * 1000)
57
- const uploadTimeout = options.uploadTimeout ?? (Number(DD_PROFILING_UPLOAD_TIMEOUT) || 60 * 1000)
58
- const pprofPrefix = options.pprofPrefix ?? DD_PROFILING_PPROF_PREFIX ?? ''
59
-
60
- // TODO: Remove the fallback. Just use the value from the config.
61
- this.service = options.service || 'node'
27
+ this.version = options.version
28
+ this.service = options.service
62
29
  this.env = options.env
63
30
  this.functionname = AWS_LAMBDA_FUNCTION_NAME
64
31
 
65
- this.version = options.version
66
- this.tags = Object.assign(
67
- tagger.parse(DD_TAGS),
68
- tagger.parse(options.tags),
69
- tagger.parse({
70
- env: options.env,
32
+ this.tags = {
33
+ ...options.tags,
34
+ ...tagger.parse({
71
35
  host: options.reportHostname ? require('os').hostname() : undefined,
72
- service: this.service,
73
- version: this.version,
74
36
  functionname: AWS_LAMBDA_FUNCTION_NAME,
75
37
  }),
76
- getAzureTagsFromMetadata(getIsAzureFunction() ? getAzureFunctionMetadata() : getAzureAppMetadata())
77
- )
38
+ ...getAzureTagsFromMetadata(getIsAzureFunction() ? getAzureFunctionMetadata() : getAzureAppMetadata()),
39
+ }
78
40
 
79
41
  // Add source code integration tags if available
80
42
  if (options.repositoryUrl && options.commitSHA) {
@@ -82,58 +44,35 @@ class Config {
82
44
  this.tags[GIT_COMMIT_SHA] = options.commitSHA
83
45
  }
84
46
 
85
- this.logger = ensureLogger(options.logger)
86
- // Profiler sampling contexts are not available on Windows, so features
87
- // depending on those (code hotspots and endpoint collection) need to default
88
- // to false on Windows.
89
- const samplingContextsAvailable = process.platform !== 'win32'
90
- function checkOptionAllowed (option, description, condition) {
91
- if (option && !condition) {
92
- // injection hardening: all of these can only happen if user explicitly
93
- // sets an environment variable to its non-default value on the platform.
94
- // In practical terms, it'd require someone explicitly turning on OOM
95
- // monitoring, code hotspots, endpoint profiling, or CPU profiling on
96
- // Windows, where it is not supported.
97
- throw new Error(`${description} not supported on ${process.platform}.`)
98
- }
99
- }
100
- function checkOptionWithSamplingContextAllowed (option, description) {
101
- checkOptionAllowed(option, description, samplingContextsAvailable)
102
- }
47
+ // Normalize from seconds to milliseconds. Default must be longer than a minute.
48
+ this.flushInterval = options.DD_PROFILING_UPLOAD_PERIOD * 1000
49
+ this.uploadTimeout = options.DD_PROFILING_UPLOAD_TIMEOUT
50
+ this.sourceMap = options.DD_PROFILING_SOURCE_MAP
51
+ this.debugSourceMaps = options.DD_PROFILING_DEBUG_SOURCE_MAPS
52
+ this.endpointCollectionEnabled = options.DD_PROFILING_ENDPOINT_COLLECTION_ENABLED
53
+ this.pprofPrefix = options.DD_PROFILING_PPROF_PREFIX
54
+ this.v8ProfilerBugWorkaroundEnabled = options.DD_PROFILING_V8_PROFILER_BUG_WORKAROUND
103
55
 
104
- this.flushInterval = flushInterval
105
- this.uploadTimeout = uploadTimeout
106
- this.sourceMap = options.sourceMap
107
- this.debugSourceMaps = isTrue(options.debugSourceMaps ?? DD_PROFILING_DEBUG_SOURCE_MAPS)
108
- this.endpointCollectionEnabled = isTrue(options.endpointCollection ??
109
- DD_PROFILING_ENDPOINT_COLLECTION_ENABLED ?? samplingContextsAvailable)
110
- checkOptionWithSamplingContextAllowed(this.endpointCollectionEnabled, 'Endpoint collection')
111
-
112
- this.pprofPrefix = pprofPrefix
113
- this.v8ProfilerBugWorkaroundEnabled = isTrue(options.v8ProfilerBugWorkaround ??
114
- DD_PROFILING_V8_PROFILER_BUG_WORKAROUND ?? true)
56
+ this.logger = ensureLogger(options.logger)
115
57
  this.url = getAgentUrl(options)
116
58
 
117
- this.libraryInjected = options.libraryInjected
118
- this.activation = options.activation
119
- this.exporters = ensureExporters(options.exporters || [
120
- new AgentExporter(this),
121
- ], this)
59
+ this.libraryInjected = !!options.DD_INJECTION_ENABLED
122
60
 
123
- // OOM monitoring does not work well on Windows, so it is disabled by default.
124
- const oomMonitoringSupported = process.platform !== 'win32'
61
+ let activation
62
+ if (options.profiling.enabled === 'auto') {
63
+ activation = 'auto'
64
+ } else if (options.profiling.enabled === 'true') {
65
+ activation = 'manual'
66
+ } // else activation = undefined
125
67
 
126
- const oomMonitoringEnabled = isTrue(options.oomMonitoring ??
127
- DD_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED ?? oomMonitoringSupported)
128
- checkOptionAllowed(oomMonitoringEnabled, 'OOM monitoring', oomMonitoringSupported)
68
+ this.activation = activation
69
+ this.exporters = ensureExporters(options.DD_PROFILING_EXPORTERS, this)
129
70
 
130
- const heapLimitExtensionSize = options.oomHeapLimitExtensionSize ??
131
- (Number(DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE) || 0)
132
- const maxHeapExtensionCount = options.oomMaxHeapExtensionCount ??
133
- (Number(DD_PROFILING_EXPERIMENTAL_OOM_MAX_HEAP_EXTENSION_COUNT) || 0)
71
+ const oomMonitoringEnabled = options.DD_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED
72
+ const heapLimitExtensionSize = options.DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE
73
+ const maxHeapExtensionCount = options.DD_PROFILING_EXPERIMENTAL_OOM_MAX_HEAP_EXTENSION_COUNT
134
74
  const exportStrategies = oomMonitoringEnabled
135
- ? ensureOOMExportStrategies(options.oomExportStrategies ?? DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES ??
136
- [oomExportStrategies.PROCESS], this)
75
+ ? ensureOOMExportStrategies(options.DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES, this)
137
76
  : []
138
77
  const exportCommand = oomMonitoringEnabled ? buildExportCommand(this) : undefined
139
78
  this.oomMonitoring = {
@@ -144,61 +83,26 @@ class Config {
144
83
  exportCommand,
145
84
  }
146
85
 
147
- const profilers = options.profilers || getProfilers({
148
- DD_PROFILING_HEAP_ENABLED,
149
- DD_PROFILING_WALLTIME_ENABLED,
150
- DD_PROFILING_PROFILERS,
151
- })
86
+ const profilers = getProfilers(options)
152
87
 
153
- this.timelineEnabled = isTrue(
154
- options.timelineEnabled ?? DD_PROFILING_TIMELINE_ENABLED ?? samplingContextsAvailable
155
- )
156
- checkOptionWithSamplingContextAllowed(this.timelineEnabled, 'Timeline view')
157
- this.timelineSamplingEnabled = isTrue(
158
- options.timelineSamplingEnabled ?? DD_INTERNAL_PROFILING_TIMELINE_SAMPLING_ENABLED ?? true
159
- )
88
+ this.timelineEnabled = options.DD_PROFILING_TIMELINE_ENABLED
89
+ this.timelineSamplingEnabled = options.DD_INTERNAL_PROFILING_TIMELINE_SAMPLING_ENABLED
90
+ this.codeHotspotsEnabled = options.DD_PROFILING_CODEHOTSPOTS_ENABLED
91
+ this.cpuProfilingEnabled = options.DD_PROFILING_CPU_ENABLED
92
+ this.heapSamplingInterval = options.DD_PROFILING_HEAP_SAMPLING_INTERVAL
160
93
 
161
- this.codeHotspotsEnabled = isTrue(
162
- options.codeHotspotsEnabled ?? DD_PROFILING_CODEHOTSPOTS_ENABLED ?? samplingContextsAvailable
163
- )
164
- checkOptionWithSamplingContextAllowed(this.codeHotspotsEnabled, 'Code hotspots')
165
-
166
- this.cpuProfilingEnabled = isTrue(
167
- options.cpuProfilingEnabled ?? DD_PROFILING_CPU_ENABLED ?? samplingContextsAvailable
168
- )
169
- checkOptionWithSamplingContextAllowed(this.cpuProfilingEnabled, 'CPU profiling')
170
-
171
- this.samplingInterval = options.samplingInterval || 1e3 / 99 // 99hz in millis
172
-
173
- this.heapSamplingInterval = options.heapSamplingInterval ??
174
- (Number(DD_PROFILING_HEAP_SAMPLING_INTERVAL) || 512 * 1024)
94
+ this.samplingInterval = 1e3 / 99 // 99hz in milliseconds
175
95
 
176
96
  const isAtLeast24 = satisfies(process.versions.node, '>=24.0.0')
177
97
 
178
- const uploadCompression0 = options.uploadCompression ?? DD_PROFILING_DEBUG_UPLOAD_COMPRESSION ?? 'on'
98
+ const uploadCompression0 = options.DD_PROFILING_DEBUG_UPLOAD_COMPRESSION
179
99
  let [uploadCompression, level0] = uploadCompression0.split('-')
180
- if (!['on', 'off', 'gzip', 'zstd'].includes(uploadCompression)) {
181
- this.logger.warn(`Invalid profile upload compression method "${uploadCompression0}". Will use "on".`)
182
- uploadCompression = 'on'
183
- }
184
100
  let level = level0 ? Number.parseInt(level0, 10) : undefined
185
101
  if (level !== undefined) {
186
- if (['on', 'off'].includes(uploadCompression)) {
187
- this.logger.warn(`Compression levels are not supported for "${uploadCompression}".`)
188
- level = undefined
189
- } else if (Number.isNaN(level)) {
190
- this.logger.warn(
191
- `Invalid compression level "${level0}". Will use default level.`)
192
- level = undefined
193
- } else if (level < 1) {
194
- this.logger.warn(`Invalid compression level ${level}. Will use 1.`)
195
- level = 1
196
- } else {
197
- const maxLevel = { gzip: 9, zstd: 22 }[uploadCompression]
198
- if (level > maxLevel) {
199
- this.logger.warn(`Invalid compression level ${level}. Will use ${maxLevel}.`)
200
- level = maxLevel
201
- }
102
+ const maxLevel = { gzip: 9, zstd: 22 }[uploadCompression]
103
+ if (level > maxLevel) {
104
+ this.logger.warn(`Invalid compression level ${level}. Will use ${maxLevel}.`)
105
+ level = maxLevel
202
106
  }
203
107
  }
204
108
 
@@ -219,13 +123,9 @@ class Config {
219
123
  that.asyncContextFrameEnabled = false
220
124
  }
221
125
 
222
- const canUseAsyncContextFrame = samplingContextsAvailable && isACFActive
223
-
224
- this.asyncContextFrameEnabled = isTrue(DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED ?? canUseAsyncContextFrame)
225
- if (this.asyncContextFrameEnabled && !canUseAsyncContextFrame) {
226
- if (!samplingContextsAvailable) {
227
- turnOffAsyncContextFrame(`on ${process.platform}`)
228
- } else if (isAtLeast24) {
126
+ this.asyncContextFrameEnabled = options.DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED ?? isACFActive
127
+ if (this.asyncContextFrameEnabled && !isACFActive) {
128
+ if (isAtLeast24) {
229
129
  turnOffAsyncContextFrame('with --no-async-context-frame')
230
130
  } else if (satisfies(process.versions.node, '>=22.9.0')) {
231
131
  turnOffAsyncContextFrame('without --experimental-async-context-frame')
@@ -234,7 +134,7 @@ class Config {
234
134
  }
235
135
  }
236
136
 
237
- this.heartbeatInterval = options.heartbeatInterval || 60 * 1000 // 1 minute
137
+ this.heartbeatInterval = options.telemetry.heartbeatInterval
238
138
 
239
139
  this.profilers = ensureProfilers(profilers, this)
240
140
  }
@@ -248,7 +148,7 @@ class Config {
248
148
  endpointCollectionEnabled: this.endpointCollectionEnabled,
249
149
  heapSamplingInterval: this.heapSamplingInterval,
250
150
  oomMonitoring: { ...this.oomMonitoring },
251
- profilerTypes: this.profilers.map(p => p.type),
151
+ profilerTypes: this.profilers.map(profiler => profiler.type),
252
152
  sourceMap: this.sourceMap,
253
153
  timelineEnabled: this.timelineEnabled,
254
154
  timelineSamplingEnabled: this.timelineSamplingEnabled,
@@ -263,7 +163,9 @@ class Config {
263
163
  module.exports = { Config }
264
164
 
265
165
  function getProfilers ({
266
- DD_PROFILING_HEAP_ENABLED, DD_PROFILING_WALLTIME_ENABLED, DD_PROFILING_PROFILERS,
166
+ DD_PROFILING_HEAP_ENABLED,
167
+ DD_PROFILING_WALLTIME_ENABLED,
168
+ DD_PROFILING_PROFILERS,
267
169
  }) {
268
170
  // First consider "legacy" DD_PROFILING_PROFILERS env variable, defaulting to space + wall
269
171
  // Use a Set to avoid duplicates
@@ -272,26 +174,26 @@ function getProfilers ({
272
174
  // snapshots the space profile won't include memory taken by profiles created
273
175
  // before it in the sequence. That memory is ultimately transient and will be
274
176
  // released when all profiles are subsequently encoded.
275
- const profilers = new Set((DD_PROFILING_PROFILERS ?? 'space,wall').split(','))
177
+ const profilers = new Set(DD_PROFILING_PROFILERS)
276
178
 
277
179
  let spaceExplicitlyEnabled = false
278
180
  // Add/remove space depending on the value of DD_PROFILING_HEAP_ENABLED
279
- if (DD_PROFILING_HEAP_ENABLED != null) {
280
- if (isTrue(DD_PROFILING_HEAP_ENABLED)) {
181
+ if (DD_PROFILING_HEAP_ENABLED !== undefined) {
182
+ if (DD_PROFILING_HEAP_ENABLED) {
281
183
  if (!profilers.has('space')) {
282
184
  profilers.add('space')
283
185
  spaceExplicitlyEnabled = true
284
186
  }
285
- } else if (isFalse(DD_PROFILING_HEAP_ENABLED)) {
187
+ } else {
286
188
  profilers.delete('space')
287
189
  }
288
190
  }
289
191
 
290
192
  // Add/remove wall depending on the value of DD_PROFILING_WALLTIME_ENABLED
291
- if (DD_PROFILING_WALLTIME_ENABLED != null) {
292
- if (isTrue(DD_PROFILING_WALLTIME_ENABLED)) {
193
+ if (DD_PROFILING_WALLTIME_ENABLED !== undefined) {
194
+ if (DD_PROFILING_WALLTIME_ENABLED) {
293
195
  profilers.add('wall')
294
- } else if (isFalse(DD_PROFILING_WALLTIME_ENABLED)) {
196
+ } else {
295
197
  profilers.delete('wall')
296
198
  profilers.delete('cpu') // remove alias too
297
199
  }
@@ -321,22 +223,12 @@ function getExportStrategy (name, options) {
321
223
  }
322
224
 
323
225
  function ensureOOMExportStrategies (strategies, options) {
324
- if (!strategies) {
325
- return []
226
+ const set = new Set()
227
+ for (const strategy of strategies) {
228
+ set.add(getExportStrategy(strategy, options))
326
229
  }
327
230
 
328
- if (typeof strategies === 'string') {
329
- strategies = strategies.split(',')
330
- }
331
-
332
- for (let i = 0; i < strategies.length; i++) {
333
- const strategy = strategies[i]
334
- if (typeof strategy === 'string') {
335
- strategies[i] = getExportStrategy(strategy, options)
336
- }
337
- }
338
-
339
- return [...new Set(strategies)]
231
+ return [...set]
340
232
  }
341
233
 
342
234
  function getExporter (name, options) {
@@ -345,22 +237,13 @@ function getExporter (name, options) {
345
237
  return new AgentExporter(options)
346
238
  case 'file':
347
239
  return new FileExporter(options)
240
+ default:
241
+ options.logger.error(`Unknown exporter "${name}"`)
348
242
  }
349
243
  }
350
244
 
351
245
  function ensureExporters (exporters, options) {
352
- if (typeof exporters === 'string') {
353
- exporters = exporters.split(',')
354
- }
355
-
356
- for (let i = 0; i < exporters.length; i++) {
357
- const exporter = exporters[i]
358
- if (typeof exporter === 'string') {
359
- exporters[i] = getExporter(exporter, options)
360
- }
361
- }
362
-
363
- return exporters
246
+ return exporters.map((exporter) => getExporter(exporter, options))
364
247
  }
365
248
 
366
249
  function getProfiler (name, options) {
@@ -376,30 +259,26 @@ function getProfiler (name, options) {
376
259
  }
377
260
 
378
261
  function ensureProfilers (profilers, options) {
379
- if (typeof profilers === 'string') {
380
- profilers = profilers.split(',')
381
- }
262
+ const filteredProfilers = []
382
263
 
383
264
  for (let i = 0; i < profilers.length; i++) {
384
- const profiler = profilers[i]
385
- if (typeof profiler === 'string') {
386
- profilers[i] = getProfiler(profiler, options)
265
+ const profiler = getProfiler(profilers[i], options)
266
+ if (profiler !== undefined) {
267
+ filteredProfilers.push(profiler)
387
268
  }
388
269
  }
389
270
 
390
271
  // Events profiler is a profiler that produces timeline events. It is only
391
272
  // added if timeline is enabled and there's a wall profiler.
392
- if (options.timelineEnabled && profilers.some(p => p instanceof WallProfiler)) {
393
- profilers.push(new EventsProfiler(options))
273
+ if (options.timelineEnabled && filteredProfilers.some(profiler => profiler instanceof WallProfiler)) {
274
+ filteredProfilers.push(new EventsProfiler(options))
394
275
  }
395
276
 
396
- // Filter out any invalid profilers
397
- return profilers.filter(Boolean)
277
+ return filteredProfilers
398
278
  }
399
279
 
400
280
  function ensureLogger (logger) {
401
- if (typeof logger !== 'object' ||
402
- typeof logger.debug !== 'function' ||
281
+ if (typeof logger?.debug !== 'function' ||
403
282
  typeof logger.info !== 'function' ||
404
283
  typeof logger.warn !== 'function' ||
405
284
  typeof logger.error !== 'function') {
@@ -424,50 +303,3 @@ function buildExportCommand (options) {
424
303
  path.join(__dirname, 'exporter_cli.js'),
425
304
  urls.join(','), tags, 'space']
426
305
  }
427
-
428
- function getProfilingEnvValues () {
429
- return {
430
- DD_INTERNAL_PROFILING_TIMELINE_SAMPLING_ENABLED:
431
- getValueFromEnvSources('DD_INTERNAL_PROFILING_TIMELINE_SAMPLING_ENABLED'),
432
- DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED:
433
- getValueFromEnvSources('DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED'),
434
- DD_PROFILING_CODEHOTSPOTS_ENABLED:
435
- getValueFromEnvSources('DD_PROFILING_CODEHOTSPOTS_ENABLED'),
436
- DD_PROFILING_CPU_ENABLED:
437
- getValueFromEnvSources('DD_PROFILING_CPU_ENABLED'),
438
- DD_PROFILING_DEBUG_SOURCE_MAPS:
439
- getValueFromEnvSources('DD_PROFILING_DEBUG_SOURCE_MAPS'),
440
- DD_PROFILING_DEBUG_UPLOAD_COMPRESSION:
441
- getValueFromEnvSources('DD_PROFILING_DEBUG_UPLOAD_COMPRESSION'),
442
- DD_PROFILING_ENDPOINT_COLLECTION_ENABLED:
443
- getValueFromEnvSources('DD_PROFILING_ENDPOINT_COLLECTION_ENABLED'),
444
- DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES:
445
- getValueFromEnvSources('DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES'),
446
- DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE:
447
- getValueFromEnvSources('DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE'),
448
- DD_PROFILING_EXPERIMENTAL_OOM_MAX_HEAP_EXTENSION_COUNT:
449
- getValueFromEnvSources('DD_PROFILING_EXPERIMENTAL_OOM_MAX_HEAP_EXTENSION_COUNT'),
450
- DD_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED:
451
- getValueFromEnvSources('DD_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED'),
452
- DD_PROFILING_HEAP_ENABLED:
453
- getValueFromEnvSources('DD_PROFILING_HEAP_ENABLED'),
454
- DD_PROFILING_HEAP_SAMPLING_INTERVAL:
455
- getValueFromEnvSources('DD_PROFILING_HEAP_SAMPLING_INTERVAL'),
456
- DD_PROFILING_PPROF_PREFIX:
457
- getValueFromEnvSources('DD_PROFILING_PPROF_PREFIX'),
458
- DD_PROFILING_PROFILERS:
459
- getValueFromEnvSources('DD_PROFILING_PROFILERS'),
460
- DD_PROFILING_TIMELINE_ENABLED:
461
- getValueFromEnvSources('DD_PROFILING_TIMELINE_ENABLED'),
462
- DD_PROFILING_UPLOAD_PERIOD:
463
- getValueFromEnvSources('DD_PROFILING_UPLOAD_PERIOD'),
464
- DD_PROFILING_UPLOAD_TIMEOUT:
465
- getValueFromEnvSources('DD_PROFILING_UPLOAD_TIMEOUT'),
466
- DD_PROFILING_V8_PROFILER_BUG_WORKAROUND:
467
- getValueFromEnvSources('DD_PROFILING_V8_PROFILER_BUG_WORKAROUND'),
468
- DD_PROFILING_WALLTIME_ENABLED:
469
- getValueFromEnvSources('DD_PROFILING_WALLTIME_ENABLED'),
470
- DD_TAGS:
471
- getValueFromEnvSources('DD_TAGS'),
472
- }
473
- }
@@ -17,9 +17,6 @@ function exporterFromURL (url) {
17
17
  if (url.protocol === 'file:') {
18
18
  return new FileExporter({ pprofPrefix: fileURLToPath(url) })
19
19
  }
20
- // TODO: Why is DD_INJECTION_ENABLED a comma separated list?
21
- const injectionEnabled = (getValueFromEnvSources('DD_INJECTION_ENABLED') ?? '').split(',')
22
- const libraryInjected = injectionEnabled.length > 0
23
20
  const profilingEnabled = (getValueFromEnvSources('DD_PROFILING_ENABLED') ?? '').toLowerCase()
24
21
  const activation = ['true', '1'].includes(profilingEnabled)
25
22
  ? 'manual'
@@ -30,7 +27,7 @@ function exporterFromURL (url) {
30
27
  url,
31
28
  logger,
32
29
  uploadTimeout: timeoutMs,
33
- libraryInjected,
30
+ libraryInjected: !!getValueFromEnvSources('DD_INJECTION_ENABLED'),
34
31
  activation,
35
32
  })
36
33
  }
@@ -14,7 +14,7 @@ class EventSerializer {
14
14
  this._host = host
15
15
  this._service = service
16
16
  this._appVersion = version
17
- this._libraryInjected = !!libraryInjected
17
+ this._libraryInjected = libraryInjected
18
18
  this._activation = activation || 'unknown'
19
19
  }
20
20
 
@@ -22,7 +22,7 @@ class EventSerializer {
22
22
  return `${type}.pprof`
23
23
  }
24
24
 
25
- getEventJSON ({ profiles, infos, start, end, tags = {}, endpointCounts }) {
25
+ getEventJSON ({ profiles, infos, start, end, tags = {}, endpointCounts, customAttributes }) {
26
26
  const event = {
27
27
  attachments: Object.keys(profiles).map(t => this.typeToFile(t)),
28
28
  start: start.toISOString(),
@@ -80,6 +80,10 @@ class EventSerializer {
80
80
  },
81
81
  }
82
82
 
83
+ if (customAttributes) {
84
+ event.custom_attributes = customAttributes
85
+ }
86
+
83
87
  if (processTags.serialized) {
84
88
  event[processTags.PROFILING_FIELD_NAME] = processTags.serialized
85
89
  }
@@ -51,6 +51,7 @@ class Profiler extends EventEmitter {
51
51
  #compressionFnInitialized = false
52
52
  #compressionOptions
53
53
  #config
54
+ #customLabelKeys = new Set()
54
55
  #enabled = false
55
56
  #endpointCounts = new Map()
56
57
  #lastStart
@@ -70,56 +71,22 @@ class Profiler extends EventEmitter {
70
71
  return this.#config?.flushInterval
71
72
  }
72
73
 
74
+ /**
75
+ * @param {import('../config/config-base')} config - Tracer configuration
76
+ */
73
77
  start (config) {
74
- const {
75
- service,
76
- version,
77
- env,
78
- url,
79
- hostname,
80
- port,
81
- tags,
82
- repositoryUrl,
83
- commitSHA,
84
- injectionEnabled,
85
- reportHostname,
86
- } = config
87
- const { enabled, sourceMap, exporters } = config.profiling
88
- const { heartbeatInterval } = config.telemetry
89
-
90
78
  // TODO: Unify with main logger and rewrite template strings to use printf formatting.
91
79
  const logger = {
92
- debug (message) { log.debug(message) },
93
- info (message) { log.info(message) },
94
- warn (message) { log.warn(message) },
95
- error (...args) { log.error(...args) },
80
+ debug: log.debug.bind(log),
81
+ info: log.info.bind(log),
82
+ warn: log.warn.bind(log),
83
+ error: log.error.bind(log),
96
84
  }
97
85
 
98
- const libraryInjected = injectionEnabled.length > 0
99
- let activation
100
- if (enabled === 'auto') {
101
- activation = 'auto'
102
- } else if (enabled === 'true') {
103
- activation = 'manual'
104
- } // else activation = undefined
105
-
86
+ // TODO: Rewrite this to not need to copy the config.
106
87
  const options = {
107
- service,
108
- version,
109
- env,
88
+ ...config,
110
89
  logger,
111
- sourceMap,
112
- exporters,
113
- url,
114
- hostname,
115
- port,
116
- tags,
117
- repositoryUrl,
118
- commitSHA,
119
- libraryInjected,
120
- activation,
121
- heartbeatInterval,
122
- reportHostname,
123
90
  }
124
91
 
125
92
  try {
@@ -135,6 +102,45 @@ class Profiler extends EventEmitter {
135
102
  return this.#enabled
136
103
  }
137
104
 
105
+ /**
106
+ * Declares the set of custom label keys that will be used with
107
+ * {@link runWithLabels}. This is used for profile upload metadata and
108
+ * for pprof serialization optimization (low-cardinality deduplication).
109
+ *
110
+ * @param {Iterable<string>} keys - Custom label key names
111
+ */
112
+ setCustomLabelKeys (keys) {
113
+ this.#customLabelKeys.clear()
114
+ for (const key of keys) {
115
+ this.#customLabelKeys.add(key)
116
+ }
117
+ if (this.#config) {
118
+ for (const profiler of this.#config.profilers) {
119
+ profiler.setCustomLabelKeys?.(this.#customLabelKeys)
120
+ }
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Runs a function with custom profiling labels attached to wall profiler samples.
126
+ *
127
+ * @param {Record<string, string | number>} labels - Custom labels to attach
128
+ * @param {function(): T} fn - Function to execute with the labels
129
+ * @returns {T} The return value of fn
130
+ * @template T
131
+ */
132
+ runWithLabels (labels, fn) {
133
+ if (!this.#enabled || !this.#config) {
134
+ return fn()
135
+ }
136
+ for (const profiler of this.#config.profilers) {
137
+ if (profiler.runWithLabels) {
138
+ return profiler.runWithLabels(labels, fn)
139
+ }
140
+ }
141
+ return fn()
142
+ }
143
+
138
144
  #logError (err) {
139
145
  logError(this.#logger, err)
140
146
  }
@@ -182,6 +188,9 @@ class Profiler extends EventEmitter {
182
188
  return this.#compressionFn
183
189
  }
184
190
 
191
+ /**
192
+ * @param {import('../config/config-base')} options - Tracer configuration
193
+ */
185
194
  _start (options) {
186
195
  if (this.enabled) return true
187
196
 
@@ -410,7 +419,10 @@ class Profiler extends EventEmitter {
410
419
 
411
420
  tags.snapshot = snapshotKind
412
421
  tags.profile_seq = this.#profileSeq++
413
- const exportSpec = { profiles, infos, start, end, tags, endpointCounts }
422
+ const customAttributes = this.#customLabelKeys.size > 0
423
+ ? [...this.#customLabelKeys]
424
+ : undefined
425
+ const exportSpec = { profiles, infos, start, end, tags, endpointCounts, customAttributes }
414
426
  const tasks = this.#config.exporters.map(exporter =>
415
427
  exporter.export(exportSpec).catch(err => {
416
428
  if (this.#logger) {
@@ -51,8 +51,7 @@ function labelFromStrStr (stringTable, keyStr, valStr) {
51
51
  }
52
52
 
53
53
  function getMaxSamples (options) {
54
- const flushInterval = options.flushInterval || 65 * 1e3 // 65 seconds
55
- const maxCpuSamples = flushInterval / options.samplingInterval
54
+ const maxCpuSamples = options.flushInterval / options.samplingInterval
56
55
 
57
56
  // The lesser of max parallelism and libuv thread pool size, plus one so we can detect
58
57
  // oversubscription on libuv thread pool, plus another one for GC.
@@ -403,7 +402,7 @@ class EventsProfiler {
403
402
 
404
403
  get type () { return 'events' }
405
404
 
406
- constructor (options = {}) {
405
+ constructor (options) {
407
406
  this.#maxSamples = getMaxSamples(options)
408
407
  this.#timelineSamplingEnabled = !!options.timelineSamplingEnabled
409
408
  this.#eventSerializer = new EventSerializer(this.#maxSamples)