dd-trace 5.92.0 → 5.93.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "5.92.0",
3
+ "version": "5.93.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -138,7 +138,7 @@
138
138
  "import-in-the-middle": "^3.0.0"
139
139
  },
140
140
  "optionalDependencies": {
141
- "@datadog/libdatadog": "0.8.1",
141
+ "@datadog/libdatadog": "0.9.2",
142
142
  "@datadog/native-appsec": "11.0.1",
143
143
  "@datadog/native-iast-taint-tracking": "4.1.0",
144
144
  "@datadog/native-metrics": "3.1.1",
@@ -154,6 +154,11 @@ class BaseAwsSdkPlugin extends ClientPlugin {
154
154
  })
155
155
 
156
156
  this.finish(ctx)
157
+
158
+ if (IS_SERVERLESS) {
159
+ const peerStore = storage('peerServerless').getStore()
160
+ if (peerStore) delete peerStore.peerHostname
161
+ }
157
162
  })
158
163
 
159
164
  this.addBind(`apm:aws:response:start:${this.serviceIdentifier}`, ctx => {
@@ -744,9 +744,9 @@
744
744
  ],
745
745
  "DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED": [
746
746
  {
747
- "implementation": "A",
747
+ "implementation": "B",
748
748
  "type": "boolean",
749
- "default": "false",
749
+ "default": "true",
750
750
  "configurationNames": [
751
751
  "propagateProcessTags.enabled"
752
752
  ]
@@ -1,9 +1,15 @@
1
1
  'use strict'
2
2
 
3
+ const { existsSync } = require('node:fs')
3
4
  const { isMainThread } = require('worker_threads')
4
5
  const log = require('../log')
5
6
 
6
- if (isMainThread) {
7
+ // libdatadog v29 crashtracker segfaults during init on ARM64 musl (Alpine).
8
+ // The segfault bypasses JS try/catch so we must avoid loading it entirely.
9
+ // See: https://github.com/DataDog/libdatadog-nodejs/issues/114
10
+ const isArm64Musl = process.arch === 'arm64' && existsSync('/etc/alpine-release')
11
+
12
+ if (isMainThread && !isArm64Musl) {
7
13
  try {
8
14
  module.exports = require('./crashtracker')
9
15
  } catch (e) {
@@ -35,6 +35,7 @@ if (inodePath) {
35
35
  const entityId = containerId ? `ci-${containerId}` : inode && `in-${inode}`
36
36
 
37
37
  module.exports = {
38
+ containerId,
38
39
  entityId,
39
40
 
40
41
  inject (carrier) {
@@ -14,9 +14,9 @@ const { urlToHttpOptions } = require('./url-to-http-options-polyfill')
14
14
  const docker = require('./docker')
15
15
  const { httpAgent, httpsAgent } = require('./agents')
16
16
 
17
- const maxActiveRequests = 8
17
+ const maxActiveBufferSize = 1024 * 1024 * 64
18
18
 
19
- let activeRequests = 0
19
+ let activeBufferSize = 0
20
20
 
21
21
  function parseUrl (urlObjOrString) {
22
22
  if (urlObjOrString !== null && typeof urlObjOrString === 'object') return urlToHttpOptions(urlObjOrString)
@@ -50,7 +50,22 @@ function request (data, options, callback) {
50
50
  }
51
51
  }
52
52
 
53
- const isReadable = data instanceof Readable
53
+ if (data instanceof Readable) {
54
+ const chunks = []
55
+
56
+ data
57
+ .on('data', (data) => {
58
+ chunks.push(data)
59
+ })
60
+ .on('end', () => {
61
+ request(Buffer.concat(chunks), options, callback)
62
+ })
63
+ .on('error', (err) => {
64
+ callback(err)
65
+ })
66
+
67
+ return
68
+ }
54
69
 
55
70
  // The timeout should be kept low to avoid excessive queueing.
56
71
  const timeout = options.timeout || 2000
@@ -58,12 +73,10 @@ function request (data, options, callback) {
58
73
  const client = isSecure ? https : http
59
74
  let dataArray = data
60
75
 
61
- if (!isReadable) {
62
- if (!Array.isArray(data)) {
63
- dataArray = [data]
64
- }
65
- options.headers['Content-Length'] = byteLength(dataArray)
76
+ if (!Array.isArray(data)) {
77
+ dataArray = [data]
66
78
  }
79
+ options.headers['Content-Length'] = byteLength(dataArray)
67
80
 
68
81
  docker.inject(options.headers)
69
82
 
@@ -126,14 +139,14 @@ function request (data, options, callback) {
126
139
  return callback(null)
127
140
  }
128
141
 
129
- activeRequests++
142
+ activeBufferSize += options.headers['Content-Length'] ?? 0
130
143
 
131
144
  storage('legacy').run({ noop: true }, () => {
132
145
  let finished = false
133
146
  const finalize = () => {
134
147
  if (finished) return
135
148
  finished = true
136
- activeRequests--
149
+ activeBufferSize -= options.headers['Content-Length'] ?? 0
137
150
  }
138
151
 
139
152
  const req = client.request(options, (res) => onResponse(res, finalize))
@@ -158,12 +171,8 @@ function request (data, options, callback) {
158
171
  }
159
172
  })
160
173
 
161
- if (isReadable) {
162
- data.pipe(req) // TODO: Validate whether this is actually retriable.
163
- } else {
164
- for (const buffer of dataArray) req.write(buffer)
165
- req.end()
166
- }
174
+ for (const buffer of dataArray) req.write(buffer)
175
+ req.end()
167
176
  })
168
177
  }
169
178
 
@@ -183,7 +192,7 @@ function byteLength (data) {
183
192
 
184
193
  Object.defineProperty(request, 'writable', {
185
194
  get () {
186
- return activeRequests < maxActiveRequests
195
+ return activeBufferSize < maxActiveBufferSize
187
196
  },
188
197
  })
189
198
 
@@ -37,13 +37,16 @@ const {
37
37
  const DEFAULT_KEY = 'service:,env:'
38
38
 
39
39
  /**
40
- * Formats a sampling rate as a string with up to 6 significant digits and no trailing zeros.
40
+ * Formats a sampling rate as a string with up to 6 decimal digits and no trailing zeros.
41
41
  *
42
42
  * @param {number} rate
43
- * @returns {string}
44
43
  */
45
44
  function formatKnuthRate (rate) {
46
- return Number(rate.toPrecision(6)).toString()
45
+ const string = Number(rate).toFixed(6)
46
+ for (let i = string.length - 1; i > 0; i--) {
47
+ if (string[i] === '0') continue
48
+ return string.slice(0, i + (string[i] === '.' ? 0 : 1))
49
+ }
47
50
  }
48
51
 
49
52
  const defaultSampler = new Sampler(AUTO_KEEP)
@@ -1,8 +1,6 @@
1
1
  'use strict'
2
2
 
3
3
  const { EventEmitter } = require('events')
4
- const { promisify } = require('util')
5
- const zlib = require('zlib')
6
4
  const dc = require('dc-polyfill')
7
5
  const crashtracker = require('../crashtracking')
8
6
  const log = require('../log')
@@ -34,6 +32,14 @@ function findWebSpan (startedSpans, spanId) {
34
32
  return false
35
33
  }
36
34
 
35
+ const MISSING_SOURCE_MAPS_TOKEN = 'dd:has-missing-map-files'
36
+
37
+ function profileHasMissingSourceMaps (profile) {
38
+ const strings = profile?.stringTable?.strings
39
+ if (!strings) return false
40
+ return profile.comment?.some(idx => strings[idx] === MISSING_SOURCE_MAPS_TOKEN) ?? false
41
+ }
42
+
37
43
  function processInfo (infos, info, type) {
38
44
  if (Object.keys(info).length > 0) {
39
45
  infos[type] = info
@@ -42,6 +48,7 @@ function processInfo (infos, info, type) {
42
48
 
43
49
  class Profiler extends EventEmitter {
44
50
  #compressionFn
51
+ #compressionFnInitialized = false
45
52
  #compressionOptions
46
53
  #config
47
54
  #enabled = false
@@ -50,7 +57,6 @@ class Profiler extends EventEmitter {
50
57
  #logger
51
58
  #profileSeq = 0
52
59
  #spanFinishListener
53
- #sourceMapCount = 0
54
60
  #timer
55
61
 
56
62
  constructor () {
@@ -116,11 +122,13 @@ class Profiler extends EventEmitter {
116
122
  reportHostname,
117
123
  }
118
124
 
119
- return this._start(options).catch((err) => {
125
+ try {
126
+ return this._start(options)
127
+ } catch (err) {
120
128
  logError(logger, 'Error starting profiler. For troubleshooting tips, see ' +
121
129
  '<https://dtdg.co/nodejs-profiler-troubleshooting>', err)
122
130
  return false
123
- })
131
+ }
124
132
  }
125
133
 
126
134
  get enabled () {
@@ -131,7 +139,50 @@ class Profiler extends EventEmitter {
131
139
  logError(this.#logger, err)
132
140
  }
133
141
 
134
- async _start (options) {
142
+ #getCompressionFn () {
143
+ if (!this.#compressionFnInitialized) {
144
+ this.#compressionFnInitialized = true
145
+ try {
146
+ const { promisify } = require('util')
147
+ const zlib = require('zlib')
148
+ const { method, level: clevel } = this.#config.uploadCompression
149
+ switch (method) {
150
+ case 'gzip':
151
+ this.#compressionFn = promisify(zlib.gzip)
152
+ if (clevel !== undefined) {
153
+ this.#compressionOptions = {
154
+ level: clevel,
155
+ }
156
+ }
157
+ break
158
+ case 'zstd':
159
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
160
+ if (typeof zlib.zstdCompress === 'function') {
161
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
162
+ this.#compressionFn = promisify(zlib.zstdCompress)
163
+ if (clevel !== undefined) {
164
+ this.#compressionOptions = {
165
+ params: {
166
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
167
+ [zlib.constants.ZSTD_c_compressionLevel]: clevel,
168
+ },
169
+ }
170
+ }
171
+ } else {
172
+ const zstdCompress = require('@datadog/libdatadog').load('datadog-js-zstd').zstd_compress
173
+ const level = clevel ?? 0 // 0 is zstd default compression level
174
+ this.#compressionFn = (buffer) => Promise.resolve(Buffer.from(zstdCompress(buffer, level)))
175
+ }
176
+ break
177
+ }
178
+ } catch (err) {
179
+ this.#logError(err)
180
+ }
181
+ }
182
+ return this.#compressionFn
183
+ }
184
+
185
+ _start (options) {
135
186
  if (this.enabled) return true
136
187
 
137
188
  const config = this.#config = new Config(options)
@@ -148,45 +199,21 @@ class Profiler extends EventEmitter {
148
199
  setLogger(config.logger)
149
200
 
150
201
  if (config.sourceMap) {
151
- mapper = await SourceMapper.create([process.cwd()], config.debugSourceMaps)
152
- this.#sourceMapCount = mapper.infoMap.size
153
- if (config.debugSourceMaps) {
154
- this.#logger.debug(() => {
155
- return this.#sourceMapCount === 0
156
- ? 'Found no source maps'
157
- : `Found source maps for following files: [${[...mapper.infoMap.keys()].join(', ')}]`
158
- })
159
- }
160
- }
161
-
162
- const clevel = config.uploadCompression.level
163
- switch (config.uploadCompression.method) {
164
- case 'gzip':
165
- this.#compressionFn = promisify(zlib.gzip)
166
- if (clevel !== undefined) {
167
- this.#compressionOptions = {
168
- level: clevel,
169
- }
170
- }
171
- break
172
- case 'zstd':
173
- if (typeof zlib.zstdCompress === 'function') { // eslint-disable-line n/no-unsupported-features/node-builtins
174
- // eslint-disable-next-line n/no-unsupported-features/node-builtins
175
- this.#compressionFn = promisify(zlib.zstdCompress)
176
- if (clevel !== undefined) {
177
- this.#compressionOptions = {
178
- params: {
179
- // eslint-disable-next-line n/no-unsupported-features/node-builtins
180
- [zlib.constants.ZSTD_c_compressionLevel]: clevel,
181
- },
182
- }
202
+ mapper = new SourceMapper(config.debugSourceMaps)
203
+ mapper.loadDirectory(process.cwd())
204
+ .then(() => {
205
+ if (config.debugSourceMaps) {
206
+ const count = mapper.infoMap.size
207
+ this.#logger.debug(() => {
208
+ return count === 0
209
+ ? 'Found no source maps'
210
+ : `Found source maps for following files: [${[...mapper.infoMap.keys()].join(', ')}]`
211
+ })
183
212
  }
184
- } else {
185
- const zstdCompress = require('@datadog/libdatadog').load('datadog-js-zstd').zstd_compress
186
- const level = clevel ?? 0 // 0 is zstd default compression level
187
- this.#compressionFn = (buffer) => Promise.resolve(Buffer.from(zstdCompress(buffer, level)))
188
- }
189
- break
213
+ })
214
+ .catch((err) => {
215
+ this.#logError(err)
216
+ })
190
217
  }
191
218
  } catch (err) {
192
219
  this.#logError(err)
@@ -295,7 +322,7 @@ class Profiler extends EventEmitter {
295
322
  return {
296
323
  serverless: this.serverless,
297
324
  settings: this.#config.systemInfoReport,
298
- sourceMapCount: this.#sourceMapCount,
325
+ hasMissingSourceMaps: false,
299
326
  }
300
327
  }
301
328
 
@@ -332,15 +359,19 @@ class Profiler extends EventEmitter {
332
359
 
333
360
  const encodedProfiles = {}
334
361
  const infos = this.#createInitialInfos()
362
+ const compressionFn = this.#getCompressionFn()
335
363
 
336
364
  // encode and export asynchronously
337
365
  await Promise.all(profiles.map(async ({ profiler, profile, info }) => {
338
366
  try {
339
367
  const encoded = await profiler.encode(profile)
340
- const compressed = encoded instanceof Buffer && this.#compressionFn !== undefined
341
- ? await this.#compressionFn(encoded, this.#compressionOptions)
368
+ const compressed = encoded instanceof Buffer && compressionFn !== undefined
369
+ ? await compressionFn(encoded, this.#compressionOptions)
342
370
  : encoded
343
371
  encodedProfiles[profiler.type] = compressed
372
+ if (profileHasMissingSourceMaps(profile)) {
373
+ infos.hasMissingSourceMaps = true
374
+ }
344
375
  processInfo(infos, info, profiler.type)
345
376
  this.#logger.debug(() => {
346
377
  const profileJson = JSON.stringify(profile, (_, value) => {
@@ -180,7 +180,7 @@ class Tracer extends NoopProxy {
180
180
  if (config.profiling.enabled === 'true') {
181
181
  this._profilerStarted = this._startProfiler(config)
182
182
  } else {
183
- this._profilerStarted = Promise.resolve(false)
183
+ this._profilerStarted = false
184
184
  if (config.profiling.enabled === 'auto') {
185
185
  const { SSIHeuristics } = require('./profiling/ssi-heuristics')
186
186
  const ssiHeuristics = new SSIHeuristics(config)
@@ -252,6 +252,7 @@ class Tracer extends NoopProxy {
252
252
  'Error starting profiler. For troubleshooting tips, see <https://dtdg.co/nodejs-profiler-troubleshooting>',
253
253
  e
254
254
  )
255
+ return false
255
256
  }
256
257
  }
257
258
 
@@ -329,11 +330,11 @@ class Tracer extends NoopProxy {
329
330
  * @override
330
331
  */
331
332
  profilerStarted () {
332
- if (!this._profilerStarted) {
333
+ if (this._profilerStarted === undefined) {
333
334
  // injection hardening: this is only ever invoked from tests.
334
335
  throw new Error('profilerStarted() must be called after init()')
335
336
  }
336
- return this._profilerStarted
337
+ return Promise.resolve(this._profilerStarted)
337
338
  }
338
339
 
339
340
  /**
@@ -11,13 +11,22 @@ function storeConfig (config) {
11
11
  return
12
12
  }
13
13
 
14
+ const { containerId } = require('./exporters/common/docker')
15
+ const processTags = require('./process-tags')
16
+
17
+ const processTagsSerialized = config.propagateProcessTags?.enabled
18
+ ? (processTags.serialized || null)
19
+ : null
20
+
14
21
  const metadata = new processDiscovery.TracerMetadata(
15
22
  config.tags['runtime-id'],
16
23
  tracerVersion,
17
24
  config.hostname,
18
25
  config.service || null,
19
26
  config.env || null,
20
- config.version || null
27
+ config.version || null,
28
+ processTagsSerialized,
29
+ containerId || null
21
30
  )
22
31
 
23
32
  return processDiscovery.storeMetadata(metadata)