dd-trace 5.37.0 → 5.38.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/ci/init.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable no-console */
2
2
  const tracer = require('../packages/dd-trace')
3
- const { isTrue } = require('../packages/dd-trace/src/util')
3
+ const { isTrue, isFalse } = require('../packages/dd-trace/src/util')
4
4
  const log = require('../packages/dd-trace/src/log')
5
5
 
6
6
  const isJestWorker = !!process.env.JEST_WORKER_ID
@@ -23,7 +23,7 @@ const options = {
23
23
  flushInterval: isJestWorker ? 0 : 5000
24
24
  }
25
25
 
26
- let shouldInit = true
26
+ let shouldInit = !isFalse(process.env.DD_CIVISIBILITY_ENABLED)
27
27
 
28
28
  if (isPackageManager()) {
29
29
  log.debug('dd-trace is not initialized in a package manager.')
package/initialize.mjs CHANGED
@@ -35,10 +35,12 @@ ${result.source}`
35
35
  const [NODE_MAJOR, NODE_MINOR] = process.versions.node.split('.').map(x => +x)
36
36
 
37
37
  const brokenLoaders = NODE_MAJOR === 18 && NODE_MINOR === 0
38
+ const iitmExclusions = [/langsmith/, /openai\/_shims/, /openai\/resources\/chat\/completions\/messages/]
38
39
 
39
- export async function load (...args) {
40
- const loadHook = brokenLoaders ? args[args.length - 1] : origLoad
41
- return insertInit(await loadHook(...args))
40
+ export async function load (url, context, nextLoad) {
41
+ const iitmExclusionsMatch = iitmExclusions.some((exclusion) => exclusion.test(url))
42
+ const loadHook = (brokenLoaders || iitmExclusionsMatch) ? nextLoad : origLoad
43
+ return insertInit(await loadHook(url, context, nextLoad))
42
44
  }
43
45
 
44
46
  export const resolve = brokenLoaders ? undefined : origResolve
@@ -53,6 +55,8 @@ if (isMainThread) {
53
55
  const require = Module.createRequire(import.meta.url)
54
56
  require('./init.js')
55
57
  if (Module.register) {
56
- Module.register('./loader-hook.mjs', import.meta.url)
58
+ Module.register('./loader-hook.mjs', import.meta.url, {
59
+ data: { exclude: iitmExclusions }
60
+ })
57
61
  }
58
62
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "5.37.0",
3
+ "version": "5.38.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -131,7 +131,7 @@
131
131
  "checksum": "^1.0.0",
132
132
  "cli-table3": "^0.6.3",
133
133
  "dotenv": "16.3.1",
134
- "esbuild": "0.16.12",
134
+ "esbuild": "^0.25.0",
135
135
  "eslint": "^9.19.0",
136
136
  "eslint-config-standard": "^17.1.0",
137
137
  "eslint-plugin-import": "^2.31.0",
@@ -172,7 +172,7 @@ function getMessage (request, error, result) {
172
172
 
173
173
  function getChannelSuffix (name) {
174
174
  // some resource identifiers have spaces between ex: bedrock runtime
175
- name = name.replaceAll(' ', '')
175
+ name = String(name).replaceAll(' ', '')
176
176
  return [
177
177
  'cloudwatchlogs',
178
178
  'dynamodb',
@@ -19,8 +19,8 @@ module.exports = {
19
19
  '@jest/test-sequencer': () => require('../jest'),
20
20
  '@jest/transform': () => require('../jest'),
21
21
  '@koa/router': () => require('../koa'),
22
- '@langchain/core': () => require('../langchain'),
23
- '@langchain/openai': () => require('../langchain'),
22
+ '@langchain/core': { esmFirst: true, fn: () => require('../langchain') },
23
+ '@langchain/openai': { esmFirst: true, fn: () => require('../langchain') },
24
24
  '@node-redis/client': () => require('../redis'),
25
25
  '@opensearch-project/opensearch': () => require('../opensearch'),
26
26
  '@opentelemetry/sdk-trace-node': () => require('../otel-sdk-trace'),
@@ -100,7 +100,7 @@ module.exports = {
100
100
  'node:vm': () => require('../vm'),
101
101
  nyc: () => require('../nyc'),
102
102
  oracledb: () => require('../oracledb'),
103
- openai: () => require('../openai'),
103
+ openai: { esmFirst: true, fn: () => require('../openai') },
104
104
  paperplane: () => require('../paperplane'),
105
105
  passport: () => require('../passport'),
106
106
  'passport-http': () => require('../passport-http'),
@@ -117,6 +117,11 @@ function patch (http, methodName) {
117
117
  } catch (e) {
118
118
  ctx.error = e
119
119
  errorChannel.publish(ctx)
120
+ // if the initial request failed, ctx.req will be unset, we must close the span here
121
+ // fix for: https://github.com/DataDog/dd-trace-js/issues/5016
122
+ if (!ctx.req) {
123
+ finish()
124
+ }
120
125
  throw e
121
126
  } finally {
122
127
  endChannel.publish(ctx)
@@ -8,98 +8,98 @@ const ch = dc.tracingChannel('apm:openai:request')
8
8
 
9
9
  const V4_PACKAGE_SHIMS = [
10
10
  {
11
- file: 'resources/chat/completions.js',
11
+ file: 'resources/chat/completions',
12
12
  targetClass: 'Completions',
13
13
  baseResource: 'chat.completions',
14
14
  methods: ['create'],
15
15
  streamedResponse: true
16
16
  },
17
17
  {
18
- file: 'resources/completions.js',
18
+ file: 'resources/completions',
19
19
  targetClass: 'Completions',
20
20
  baseResource: 'completions',
21
21
  methods: ['create'],
22
22
  streamedResponse: true
23
23
  },
24
24
  {
25
- file: 'resources/embeddings.js',
25
+ file: 'resources/embeddings',
26
26
  targetClass: 'Embeddings',
27
27
  baseResource: 'embeddings',
28
28
  methods: ['create']
29
29
  },
30
30
  {
31
- file: 'resources/files.js',
31
+ file: 'resources/files',
32
32
  targetClass: 'Files',
33
33
  baseResource: 'files',
34
34
  methods: ['create', 'del', 'list', 'retrieve']
35
35
  },
36
36
  {
37
- file: 'resources/files.js',
37
+ file: 'resources/files',
38
38
  targetClass: 'Files',
39
39
  baseResource: 'files',
40
40
  methods: ['retrieveContent'],
41
41
  versions: ['>=4.0.0 <4.17.1']
42
42
  },
43
43
  {
44
- file: 'resources/files.js',
44
+ file: 'resources/files',
45
45
  targetClass: 'Files',
46
46
  baseResource: 'files',
47
47
  methods: ['content'], // replaced `retrieveContent` in v4.17.1
48
48
  versions: ['>=4.17.1']
49
49
  },
50
50
  {
51
- file: 'resources/images.js',
51
+ file: 'resources/images',
52
52
  targetClass: 'Images',
53
53
  baseResource: 'images',
54
54
  methods: ['createVariation', 'edit', 'generate']
55
55
  },
56
56
  {
57
- file: 'resources/fine-tuning/jobs/jobs.js',
57
+ file: 'resources/fine-tuning/jobs/jobs',
58
58
  targetClass: 'Jobs',
59
59
  baseResource: 'fine_tuning.jobs',
60
60
  methods: ['cancel', 'create', 'list', 'listEvents', 'retrieve'],
61
61
  versions: ['>=4.34.0'] // file location changed in 4.34.0
62
62
  },
63
63
  {
64
- file: 'resources/fine-tuning/jobs.js',
64
+ file: 'resources/fine-tuning/jobs',
65
65
  targetClass: 'Jobs',
66
66
  baseResource: 'fine_tuning.jobs',
67
67
  methods: ['cancel', 'create', 'list', 'listEvents', 'retrieve'],
68
68
  versions: ['>=4.1.0 <4.34.0']
69
69
  },
70
70
  {
71
- file: 'resources/fine-tunes.js', // deprecated after 4.1.0
71
+ file: 'resources/fine-tunes', // deprecated after 4.1.0
72
72
  targetClass: 'FineTunes',
73
73
  baseResource: 'fine-tune',
74
74
  methods: ['cancel', 'create', 'list', 'listEvents', 'retrieve'],
75
75
  versions: ['>=4.0.0 <4.1.0']
76
76
  },
77
77
  {
78
- file: 'resources/models.js',
78
+ file: 'resources/models',
79
79
  targetClass: 'Models',
80
80
  baseResource: 'models',
81
81
  methods: ['del', 'list', 'retrieve']
82
82
  },
83
83
  {
84
- file: 'resources/moderations.js',
84
+ file: 'resources/moderations',
85
85
  targetClass: 'Moderations',
86
86
  baseResource: 'moderations',
87
87
  methods: ['create']
88
88
  },
89
89
  {
90
- file: 'resources/audio/transcriptions.js',
90
+ file: 'resources/audio/transcriptions',
91
91
  targetClass: 'Transcriptions',
92
92
  baseResource: 'audio.transcriptions',
93
93
  methods: ['create']
94
94
  },
95
95
  {
96
- file: 'resources/audio/translations.js',
96
+ file: 'resources/audio/translations',
97
97
  targetClass: 'Translations',
98
98
  baseResource: 'audio.translations',
99
99
  methods: ['create']
100
100
  },
101
101
  {
102
- file: 'resources/chat/completions/completions.js',
102
+ file: 'resources/chat/completions/completions',
103
103
  targetClass: 'Completions',
104
104
  baseResource: 'chat.completions',
105
105
  methods: ['create'],
@@ -267,93 +267,117 @@ function wrapStreamIterator (response, options, n, ctx) {
267
267
  }
268
268
  }
269
269
 
270
- for (const shim of V4_PACKAGE_SHIMS) {
271
- const { file, targetClass, baseResource, methods, versions, streamedResponse } = shim
272
- addHook({ name: 'openai', file, versions: versions || ['>=4'] }, exports => {
273
- const targetPrototype = exports[targetClass].prototype
270
+ const extensions = ['.js', '.mjs']
274
271
 
275
- for (const methodName of methods) {
276
- shimmer.wrap(targetPrototype, methodName, methodFn => function () {
277
- if (!ch.start.hasSubscribers) {
278
- return methodFn.apply(this, arguments)
279
- }
272
+ for (const extension of extensions) {
273
+ for (const shim of V4_PACKAGE_SHIMS) {
274
+ const { file, targetClass, baseResource, methods, versions, streamedResponse } = shim
275
+ addHook({ name: 'openai', file: file + extension, versions: versions || ['>=4'] }, exports => {
276
+ const targetPrototype = exports[targetClass].prototype
280
277
 
281
- // The OpenAI library lets you set `stream: true` on the options arg to any method
282
- // However, we only want to handle streamed responses in specific cases
283
- // chat.completions and completions
284
- const stream = streamedResponse && getOption(arguments, 'stream', false)
285
-
286
- // we need to compute how many prompts we are sending in streamed cases for completions
287
- // not applicable for chat completiond
288
- let n
289
- if (stream) {
290
- n = getOption(arguments, 'n', 1)
291
- const prompt = getOption(arguments, 'prompt')
292
- if (Array.isArray(prompt) && typeof prompt[0] !== 'number') {
293
- n *= prompt.length
278
+ for (const methodName of methods) {
279
+ shimmer.wrap(targetPrototype, methodName, methodFn => function () {
280
+ if (!ch.start.hasSubscribers) {
281
+ return methodFn.apply(this, arguments)
294
282
  }
295
- }
296
283
 
297
- const client = this._client || this.client
284
+ // The OpenAI library lets you set `stream: true` on the options arg to any method
285
+ // However, we only want to handle streamed responses in specific cases
286
+ // chat.completions and completions
287
+ const stream = streamedResponse && getOption(arguments, 'stream', false)
288
+
289
+ // we need to compute how many prompts we are sending in streamed cases for completions
290
+ // not applicable for chat completiond
291
+ let n
292
+ if (stream) {
293
+ n = getOption(arguments, 'n', 1)
294
+ const prompt = getOption(arguments, 'prompt')
295
+ if (Array.isArray(prompt) && typeof prompt[0] !== 'number') {
296
+ n *= prompt.length
297
+ }
298
+ }
298
299
 
299
- const ctx = {
300
- methodName: `${baseResource}.${methodName}`,
301
- args: arguments,
302
- basePath: client.baseURL,
303
- apiKey: client.apiKey
304
- }
300
+ const client = this._client || this.client
305
301
 
306
- return ch.start.runStores(ctx, () => {
307
- const apiProm = methodFn.apply(this, arguments)
308
-
309
- // wrapping `parse` avoids problematic wrapping of `then` when trying to call
310
- // `withResponse` in userland code after. This way, we can return the whole `APIPromise`
311
- shimmer.wrap(apiProm, 'parse', origApiPromParse => function () {
312
- return origApiPromParse.apply(this, arguments)
313
- // the original response is wrapped in a promise, so we need to unwrap it
314
- .then(body => Promise.all([this.responsePromise, body]))
315
- .then(([{ response, options }, body]) => {
316
- if (stream) {
317
- if (body.iterator) {
318
- shimmer.wrap(body, 'iterator', wrapStreamIterator(response, options, n, ctx))
319
- } else {
320
- shimmer.wrap(
321
- body.response.body, Symbol.asyncIterator, wrapStreamIterator(response, options, n, ctx)
322
- )
323
- }
324
- } else {
325
- finish(ctx, {
326
- headers: response.headers,
327
- data: body,
328
- request: {
329
- path: response.url,
330
- method: options.method
331
- }
332
- })
333
- }
302
+ const ctx = {
303
+ methodName: `${baseResource}.${methodName}`,
304
+ args: arguments,
305
+ basePath: client.baseURL,
306
+ apiKey: client.apiKey
307
+ }
334
308
 
335
- return body
336
- })
337
- .catch(error => {
338
- finish(ctx, undefined, error)
309
+ return ch.start.runStores(ctx, () => {
310
+ const apiProm = methodFn.apply(this, arguments)
339
311
 
340
- throw error
341
- })
342
- .finally(() => {
343
- // maybe we don't want to unwrap here in case the promise is re-used?
344
- // other hand: we want to avoid resource leakage
345
- shimmer.unwrap(apiProm, 'parse')
312
+ if (baseResource === 'chat.completions' && typeof apiProm._thenUnwrap === 'function') {
313
+ // this should only ever be invoked from a client.beta.chat.completions.parse call
314
+ shimmer.wrap(apiProm, '_thenUnwrap', origApiPromThenUnwrap => function () {
315
+ // TODO(sam.brenner): I wonder if we can patch the APIPromise prototype instead, although
316
+ // we might not have access to everything we need...
317
+
318
+ // this is a new apipromise instance
319
+ const unwrappedPromise = origApiPromThenUnwrap.apply(this, arguments)
320
+
321
+ shimmer.wrap(unwrappedPromise, 'parse', origApiPromParse => function () {
322
+ const parsedPromise = origApiPromParse.apply(this, arguments)
323
+ .then(body => Promise.all([this.responsePromise, body]))
324
+
325
+ return handleUnwrappedAPIPromise(parsedPromise, ctx, stream, n)
326
+ })
327
+
328
+ return unwrappedPromise
346
329
  })
347
- })
330
+ }
331
+
332
+ // wrapping `parse` avoids problematic wrapping of `then` when trying to call
333
+ // `withResponse` in userland code after. This way, we can return the whole `APIPromise`
334
+ shimmer.wrap(apiProm, 'parse', origApiPromParse => function () {
335
+ const parsedPromise = origApiPromParse.apply(this, arguments)
336
+ .then(body => Promise.all([this.responsePromise, body]))
337
+
338
+ return handleUnwrappedAPIPromise(parsedPromise, ctx, stream, n)
339
+ })
348
340
 
349
- ch.end.publish(ctx)
341
+ ch.end.publish(ctx)
350
342
 
351
- return apiProm
343
+ return apiProm
344
+ })
352
345
  })
353
- })
354
- }
355
- return exports
356
- })
346
+ }
347
+ return exports
348
+ })
349
+ }
350
+ }
351
+
352
+ function handleUnwrappedAPIPromise (apiProm, ctx, stream, n) {
353
+ return apiProm
354
+ .then(([{ response, options }, body]) => {
355
+ if (stream) {
356
+ if (body.iterator) {
357
+ shimmer.wrap(body, 'iterator', wrapStreamIterator(response, options, n, ctx))
358
+ } else {
359
+ shimmer.wrap(
360
+ body.response.body, Symbol.asyncIterator, wrapStreamIterator(response, options, n, ctx)
361
+ )
362
+ }
363
+ } else {
364
+ finish(ctx, {
365
+ headers: response.headers,
366
+ data: body,
367
+ request: {
368
+ path: response.url,
369
+ method: options.method
370
+ }
371
+ })
372
+ }
373
+
374
+ return body
375
+ })
376
+ .catch(error => {
377
+ finish(ctx, undefined, error)
378
+
379
+ throw error
380
+ })
357
381
  }
358
382
 
359
383
  function finish (ctx, response, error) {
@@ -102,7 +102,9 @@ class HttpClientPlugin extends ClientPlugin {
102
102
  addResponseHeaders(res, span, this.config)
103
103
  }
104
104
 
105
- addRequestHeaders(req, span, this.config)
105
+ if (req) {
106
+ addRequestHeaders(req, span, this.config)
107
+ }
106
108
 
107
109
  this.config.hooks.request(span, req, res)
108
110
 
@@ -73,6 +73,9 @@ class Http2ClientPlugin extends ClientPlugin {
73
73
  }
74
74
 
75
75
  bindAsyncStart ({ eventName, eventData, currentStore, parentStore }) {
76
+ // Plugin wasn't enabled when the request started.
77
+ if (!currentStore) return storage('legacy').getStore()
78
+
76
79
  switch (eventName) {
77
80
  case 'response':
78
81
  this._onResponse(currentStore, eventData)
@@ -70,6 +70,7 @@ function getQuery (cmd) {
70
70
  if (!cmd || typeof cmd !== 'object' || Array.isArray(cmd)) return
71
71
  if (cmd.query) return sanitizeBigInt(limitDepth(cmd.query))
72
72
  if (cmd.filter) return sanitizeBigInt(limitDepth(cmd.filter))
73
+ if (cmd.pipeline) return sanitizeBigInt(limitDepth(cmd.pipeline))
73
74
  }
74
75
 
75
76
  function getResource (plugin, ns, query, operationName) {
@@ -1,11 +1,11 @@
1
1
  /* eslint-disable @stylistic/js/max-len */
2
2
  'use strict'
3
3
 
4
- const html = `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>You've been blocked</title><style>a,body,div,html,span{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}body{background:-webkit-radial-gradient(26% 19%,circle,#fff,#f4f7f9);background:radial-gradient(circle at 26% 19%,#fff,#f4f7f9);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-line-pack:center;align-content:center;width:100%;min-height:100vh;line-height:1;flex-direction:column}p{display:block}main{text-align:center;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-line-pack:center;align-content:center;flex-direction:column}p{font-size:18px;line-height:normal;color:#646464;font-family:sans-serif;font-weight:400}a{color:#4842b7}footer{width:100%;text-align:center}footer p{font-size:16px}</style></head><body><main><p>Sorry, you cannot access this page. Please contact the customer service team.</p></main><footer><p>Security provided by <a href="https://www.datadoghq.com/product/security-platform/application-security-monitoring/" target="_blank">Datadog</a></p></footer></body></html>`
4
+ const html = '<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>You\'ve been blocked</title><style>a,body,div,html,span{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}body{background:-webkit-radial-gradient(26% 19%,circle,#fff,#f4f7f9);background:radial-gradient(circle at 26% 19%,#fff,#f4f7f9);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-line-pack:center;align-content:center;width:100%;min-height:100vh;line-height:1;flex-direction:column}p{display:block}main{text-align:center;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-line-pack:center;align-content:center;flex-direction:column}p{font-size:18px;line-height:normal;color:#646464;font-family:sans-serif;font-weight:400}a{color:#4842b7}footer{width:100%;text-align:center}footer p{font-size:16px}</style></head><body><main><p>Sorry, you cannot access this page. Please contact the customer service team.</p></main><footer><p>Security provided by <a href="https://www.datadoghq.com/product/security-platform/application-security-monitoring/" target="_blank">Datadog</a></p></footer></body></html>'
5
5
 
6
- const json = `{"errors":[{"title":"You've been blocked","detail":"Sorry, you cannot access this page. Please contact the customer service team. Security provided by Datadog."}]}`
6
+ const json = '{"errors":[{"title":"You\'ve been blocked","detail":"Sorry, you cannot access this page. Please contact the customer service team. Security provided by Datadog."}]}'
7
7
 
8
- const graphqlJson = `{"errors":[{"message":"You've been blocked","extensions":{"detail":"Sorry, you cannot perform this operation. Please contact the customer service team. Security provided by Datadog."}}]}`
8
+ const graphqlJson = '{"errors":[{"message":"You\'ve been blocked","extensions":{"detail":"Sorry, you cannot perform this operation. Please contact the customer service team. Security provided by Datadog."}}]}'
9
9
 
10
10
  module.exports = {
11
11
  html,
@@ -69,8 +69,12 @@ class TestVisDynamicInstrumentation {
69
69
  // To avoid infinite initialization loops, we're disabling DI and tracing in the worker.
70
70
  env: {
71
71
  ...process.env,
72
+ DD_CIVISIBILITY_ENABLED: 0,
72
73
  DD_TRACE_ENABLED: 0,
73
- DD_TEST_DYNAMIC_INSTRUMENTATION_ENABLED: 0
74
+ DD_TEST_FAILED_TEST_REPLAY_ENABLED: 0,
75
+ DD_CIVISIBILITY_MANUAL_API_ENABLED: 0,
76
+ DD_TRACING_ENABLED: 0,
77
+ DD_TRACE_TELEMETRY_ENABLED: 0
74
78
  },
75
79
  workerData: {
76
80
  config: config.serialize(),
@@ -5,6 +5,7 @@ const FormData = require('../../../exporters/common/form-data')
5
5
  const request = require('../../../exporters/common/request')
6
6
 
7
7
  const log = require('../../../log')
8
+ const { isFalse } = require('../../../util')
8
9
  const {
9
10
  getLatestCommits,
10
11
  getRepositoryUrl,
@@ -284,7 +285,9 @@ function sendGitMetadata (url, { isEvpProxy, evpProxyPrefix }, configRepositoryU
284
285
  }
285
286
  // Otherwise we unshallow and get commits to upload again
286
287
  log.debug('It is shallow clone, unshallowing...')
287
- unshallowRepository()
288
+ if (!isFalse(process.env.DD_CIVISIBILITY_GIT_UNSHALLOW_ENABLED)) {
289
+ unshallowRepository()
290
+ }
288
291
 
289
292
  // The latest commits change after unshallowing
290
293
  latestCommits = getLatestCommits()
@@ -566,7 +566,7 @@ class Config {
566
566
  this._setValue(defaults, 'telemetry.metrics', true)
567
567
  this._setValue(defaults, 'traceEnabled', true)
568
568
  this._setValue(defaults, 'traceId128BitGenerationEnabled', true)
569
- this._setValue(defaults, 'traceId128BitLoggingEnabled', false)
569
+ this._setValue(defaults, 'traceId128BitLoggingEnabled', true)
570
570
  this._setValue(defaults, 'tracePropagationExtractFirst', false)
571
571
  this._setValue(defaults, 'tracePropagationStyle.inject', ['datadog', 'tracecontext', 'baggage'])
572
572
  this._setValue(defaults, 'tracePropagationStyle.extract', ['datadog', 'tracecontext', 'baggage'])
@@ -1144,7 +1144,7 @@ class Config {
1144
1144
  DD_CIVISIBILITY_FLAKY_RETRY_COUNT,
1145
1145
  DD_TEST_SESSION_NAME,
1146
1146
  DD_AGENTLESS_LOG_SUBMISSION_ENABLED,
1147
- DD_TEST_DYNAMIC_INSTRUMENTATION_ENABLED,
1147
+ DD_TEST_FAILED_TEST_REPLAY_ENABLED,
1148
1148
  DD_TEST_MANAGEMENT_ENABLED,
1149
1149
  DD_TEST_MANAGEMENT_ATTEMPT_TO_FIX_RETRIES
1150
1150
  } = process.env
@@ -1164,7 +1164,7 @@ class Config {
1164
1164
  this._setBoolean(calc, 'isManualApiEnabled', !isFalse(this._isCiVisibilityManualApiEnabled()))
1165
1165
  this._setString(calc, 'ciVisibilityTestSessionName', DD_TEST_SESSION_NAME)
1166
1166
  this._setBoolean(calc, 'ciVisAgentlessLogSubmissionEnabled', isTrue(DD_AGENTLESS_LOG_SUBMISSION_ENABLED))
1167
- this._setBoolean(calc, 'isTestDynamicInstrumentationEnabled', isTrue(DD_TEST_DYNAMIC_INSTRUMENTATION_ENABLED))
1167
+ this._setBoolean(calc, 'isTestDynamicInstrumentationEnabled', !isFalse(DD_TEST_FAILED_TEST_REPLAY_ENABLED))
1168
1168
  this._setBoolean(calc, 'isServiceUserProvided', !!this._env.service)
1169
1169
  this._setBoolean(calc, 'isTestManagementEnabled', !isFalse(DD_TEST_MANAGEMENT_ENABLED))
1170
1170
  this._setValue(calc,
@@ -31,9 +31,9 @@ async function addBreakpoint (probe) {
31
31
  probe.nsBetweenSampling = BigInt(1 / snapshotsPerSecond * 1e9)
32
32
  probe.lastCaptureNs = 0n
33
33
 
34
- // TODO: Inbetween `await session.post('Debugger.enable')` and here, the scripts are parsed and cached.
35
- // Maybe there's a race condition here or maybe we're guraenteed that `await session.post('Debugger.enable')` will
36
- // not continue untill all scripts have been parsed?
34
+ // Warning: The code below relies on undocumented behavior of the inspector!
35
+ // It expects that `await session.post('Debugger.enable')` will wait for all loaded scripts to be emitted as
36
+ // `Debugger.scriptParsed` events. If this ever changes, we will have a race condition!
37
37
  const script = findScriptFromPartialPath(file)
38
38
  if (!script) throw new Error(`No loaded script found for ${file} (probe: ${probe.id}, version: ${probe.version})`)
39
39
  const { url, scriptId, sourceMapURL, source } = script
@@ -7,6 +7,7 @@ const { SourceMapConsumer } = require('source-map')
7
7
 
8
8
  const cache = new Map()
9
9
  let cacheTimer = null
10
+ let cacheTimerLastSet = 0
10
11
 
11
12
  const self = module.exports = {
12
13
  async loadSourceMap (dir, url) {
@@ -33,13 +34,26 @@ const self = module.exports = {
33
34
  }
34
35
  }
35
36
 
37
+ // TODO: Remove if-statement around `setTimeout` below once it's safe to do so.
38
+ //
39
+ // This is a workaround for, what seems like a bug in Node.js core, that seems to trigger when, among other things, a
40
+ // lot of timers are being created very rapidly. This makes the call to `setTimeout` throw an error from within
41
+ // `AsyncLocalStorage._propagate` with the following error message:
42
+ //
43
+ // TypeError: Cannot read properties of undefined (reading 'Symbol(kResourceStore)')
44
+ //
45
+ // Source: https://github.com/nodejs/node/blob/v18.20.6/lib/async_hooks.js#L312
36
46
  function cacheIt (key, value) {
37
- clearTimeout(cacheTimer)
38
- cacheTimer = setTimeout(function () {
39
- // Optimize for app boot, where a lot of reads might happen
40
- // Clear cache a few seconds after it was last used
41
- cache.clear()
42
- }, 10_000).unref()
47
+ const now = Date.now()
48
+ if (now > cacheTimerLastSet + 1_000) {
49
+ clearTimeout(cacheTimer)
50
+ cacheTimer = setTimeout(function () {
51
+ // Optimize for app boot, where a lot of reads might happen
52
+ // Clear cache a few seconds after it was last used
53
+ cache.clear()
54
+ }, 10_000).unref()
55
+ cacheTimerLastSet = now
56
+ }
43
57
  cache.set(key, value)
44
58
  return value
45
59
  }
@@ -14,7 +14,8 @@ class LogPropagator {
14
14
  carrier.dd = {}
15
15
 
16
16
  if (spanContext) {
17
- if (this._config.traceId128BitLoggingEnabled && spanContext._trace.tags['_dd.p.tid']) {
17
+ if (this._config.traceId128BitGenerationEnabled &&
18
+ this._config.traceId128BitLoggingEnabled && spanContext._trace.tags['_dd.p.tid']) {
18
19
  carrier.dd.trace_id = spanContext.toTraceId(true)
19
20
  } else {
20
21
  carrier.dd.trace_id = spanContext.toTraceId()
@@ -46,6 +46,7 @@ const tracestateTagKeyFilter = /[^\x21-\x2b\x2d-\x3c\x3e-\x7e]/g
46
46
  const tracestateTagValueFilter = /[^\x20-\x2b\x2d-\x3a\x3c-\x7d]/g
47
47
  const invalidSegment = /^0+$/
48
48
  const zeroTraceId = '0000000000000000'
49
+ const hex16 = /^[0-9A-Fa-f]{16}$/
49
50
 
50
51
  class TextMapPropagator {
51
52
  constructor (config) {
@@ -482,10 +483,20 @@ class TextMapPropagator {
482
483
  }
483
484
  break
484
485
  }
485
- default:
486
+ default: {
486
487
  if (!key.startsWith('t.')) continue
487
- spanContext._trace.tags[`_dd.p.${key.slice(2)}`] = value
488
- .replace(/[\x7e]/gm, '=')
488
+ const subKey = key.slice(2) // e.g. t.tid -> tid
489
+ const transformedValue = value.replace(/[\x7e]/gm, '=')
490
+
491
+ // If subkey is tid then do nothing because trace header tid should always be preserved
492
+ if (subKey === 'tid') {
493
+ if (!hex16.test(value) || spanContext._trace.tags['_dd.p.tid'] !== transformedValue) {
494
+ log.error(`Invalid trace id ${value} in tracestate, skipping`)
495
+ }
496
+ continue
497
+ }
498
+ spanContext._trace.tags[`_dd.p.${subKey}`] = transformedValue
499
+ }
489
500
  }
490
501
  }
491
502
  })
@@ -645,7 +656,11 @@ class TextMapPropagator {
645
656
  log.error('Trace tags from carrier are invalid, skipping extraction.')
646
657
  return
647
658
  }
648
-
659
+ // Check if value is a valid 16 character lower-case hexadecimal encoded number as per spec
660
+ if (key === '_dd.p.tid' && !(hex16.test(value))) {
661
+ log.error(`Invalid _dd.p.tid tag ${value}, skipping`)
662
+ continue
663
+ }
649
664
  tags[key] = value
650
665
  }
651
666
 
@@ -202,15 +202,15 @@ module.exports = class CiPlugin extends Plugin {
202
202
  configure (config, shouldGetEnvironmentData = true) {
203
203
  super.configure(config)
204
204
 
205
+ if (!shouldGetEnvironmentData) {
206
+ return
207
+ }
208
+
205
209
  if (config.isTestDynamicInstrumentationEnabled && !this.di) {
206
210
  const testVisibilityDynamicInstrumentation = require('../ci-visibility/dynamic-instrumentation')
207
211
  this.di = testVisibilityDynamicInstrumentation
208
212
  }
209
213
 
210
- if (!shouldGetEnvironmentData) {
211
- return
212
- }
213
-
214
214
  this.testEnvironmentMetadata = getTestEnvironmentMetadata(this.constructor.id, this.config)
215
215
 
216
216
  const {
@@ -628,6 +628,38 @@ module.exports = {
628
628
  }
629
629
  }
630
630
 
631
+ if (env.DRONE && env.CI) {
632
+ const {
633
+ DRONE_BUILD_NUMBER,
634
+ DRONE_BUILD_LINK,
635
+ DRONE_STEP_NAME,
636
+ DRONE_STAGE_NAME,
637
+ DRONE_WORKSPACE,
638
+ DRONE_GIT_HTTP_URL,
639
+ DRONE_COMMIT_SHA,
640
+ DRONE_BRANCH,
641
+ DRONE_TAG,
642
+ DRONE_COMMIT_AUTHOR_NAME,
643
+ DRONE_COMMIT_AUTHOR_EMAIL,
644
+ DRONE_COMMIT_MESSAGE
645
+ } = env
646
+ tags = {
647
+ [CI_PROVIDER_NAME]: 'drone',
648
+ [CI_PIPELINE_NUMBER]: DRONE_BUILD_NUMBER,
649
+ [CI_PIPELINE_URL]: DRONE_BUILD_LINK,
650
+ [CI_JOB_NAME]: DRONE_STEP_NAME,
651
+ [CI_STAGE_NAME]: DRONE_STAGE_NAME,
652
+ [CI_WORKSPACE_PATH]: DRONE_WORKSPACE,
653
+ [GIT_REPOSITORY_URL]: DRONE_GIT_HTTP_URL,
654
+ [GIT_COMMIT_SHA]: DRONE_COMMIT_SHA,
655
+ [GIT_BRANCH]: DRONE_BRANCH,
656
+ [GIT_TAG]: DRONE_TAG,
657
+ [GIT_COMMIT_AUTHOR_NAME]: DRONE_COMMIT_AUTHOR_NAME,
658
+ [GIT_COMMIT_AUTHOR_EMAIL]: DRONE_COMMIT_AUTHOR_EMAIL,
659
+ [GIT_COMMIT_MESSAGE]: DRONE_COMMIT_MESSAGE
660
+ }
661
+ }
662
+
631
663
  normalizeTag(tags, CI_WORKSPACE_PATH, resolveTilde)
632
664
  normalizeTag(tags, GIT_REPOSITORY_URL, filterSensitiveInfoFromRepository)
633
665
  normalizeTag(tags, GIT_BRANCH, normalizeRef)
@@ -41,10 +41,9 @@ function matcher (pattern, locator) {
41
41
  return new RegExpMatcher(pattern, locator)
42
42
  }
43
43
 
44
- if (typeof pattern === 'string' && pattern !== '*') {
44
+ if (typeof pattern === 'string' && pattern !== '*' && pattern !== '**' && pattern !== '***') {
45
45
  return new GlobMatcher(pattern, locator)
46
46
  }
47
-
48
47
  return new AlwaysMatcher()
49
48
  }
50
49
 
@@ -63,6 +62,12 @@ function serviceLocator (span) {
63
62
  span.tracer()._service
64
63
  }
65
64
 
65
+ function resourceLocator (span) {
66
+ const { _tags: tags } = span.context()
67
+ return tags.resource ||
68
+ tags['resource.name']
69
+ }
70
+
66
71
  class SamplingRule {
67
72
  constructor ({ name, service, resource, tags, sampleRate = 1.0, provenance = undefined, maxPerSecond } = {}) {
68
73
  this.matchers = []
@@ -74,7 +79,7 @@ class SamplingRule {
74
79
  this.matchers.push(matcher(service, serviceLocator))
75
80
  }
76
81
  if (resource) {
77
- this.matchers.push(matcher(resource, makeTagLocator('resource.name')))
82
+ this.matchers.push(matcher(resource, resourceLocator))
78
83
  }
79
84
  for (const [key, value] of Object.entries(tags || {})) {
80
85
  this.matchers.push(matcher(value, makeTagLocator(key)))
@@ -327,7 +327,8 @@ function updateConfig (changes, config) {
327
327
  service: 'DD_SERVICE',
328
328
  clientIpHeader: 'DD_TRACE_CLIENT_IP_HEADER',
329
329
  'grpc.client.error.statuses': 'DD_GRPC_CLIENT_ERROR_STATUSES',
330
- 'grpc.server.error.statuses': 'DD_GRPC_SERVER_ERROR_STATUSES'
330
+ 'grpc.server.error.statuses': 'DD_GRPC_SERVER_ERROR_STATUSES',
331
+ traceId128BitLoggingEnabled: 'DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED'
331
332
  }
332
333
 
333
334
  const namesNeedFormatting = new Set(['DD_TAGS', 'peerServiceMapping', 'serviceMapping'])
@@ -26,6 +26,8 @@ function isError (value) {
26
26
  function globMatch (pattern, subject) {
27
27
  if (typeof pattern === 'string') pattern = pattern.toLowerCase()
28
28
  if (typeof subject === 'string') subject = subject.toLowerCase()
29
+ if (typeof subject === 'number' && Number.isInteger(subject)) subject = String(subject)
30
+
29
31
  let px = 0 // [p]attern inde[x]
30
32
  let sx = 0 // [s]ubject inde[x]
31
33
  let nextPx = 0
package/register.js CHANGED
@@ -1,4 +1,6 @@
1
1
  const { register } = require('node:module')
2
2
  const { pathToFileURL } = require('node:url')
3
3
 
4
- register('./loader-hook.mjs', pathToFileURL(__filename))
4
+ register('./loader-hook.mjs', pathToFileURL(__filename), {
5
+ data: { exclude: [/langsmith/, /openai\/_shims/, /openai\/resources\/chat\/completions\/messages/] }
6
+ })