dd-trace 5.45.0 → 5.47.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 (86) hide show
  1. package/LICENSE-3rdparty.csv +1 -2
  2. package/ci/init.js +8 -0
  3. package/ext/exporters.d.ts +2 -1
  4. package/ext/exporters.js +2 -1
  5. package/package.json +8 -9
  6. package/packages/datadog-instrumentations/orchestrion.yml +52 -0
  7. package/packages/datadog-instrumentations/src/cucumber.js +2 -1
  8. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  9. package/packages/datadog-instrumentations/src/helpers/register.js +41 -1
  10. package/packages/datadog-instrumentations/src/jest.js +11 -2
  11. package/packages/datadog-instrumentations/src/langchain.js +49 -53
  12. package/packages/datadog-instrumentations/src/mariadb.js +19 -0
  13. package/packages/datadog-instrumentations/src/mocha/main.js +1 -1
  14. package/packages/datadog-instrumentations/src/mocha/utils.js +11 -3
  15. package/packages/datadog-instrumentations/src/orchestrion-config/index.js +5 -0
  16. package/packages/datadog-instrumentations/src/playwright.js +333 -46
  17. package/packages/datadog-instrumentations/src/router.js +1 -7
  18. package/packages/datadog-instrumentations/src/vitest.js +11 -3
  19. package/packages/datadog-plugin-cucumber/src/index.js +11 -4
  20. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +17 -5
  21. package/packages/datadog-plugin-jest/src/index.js +11 -4
  22. package/packages/datadog-plugin-langchain/src/index.js +18 -12
  23. package/packages/datadog-plugin-langchain/src/tracing.js +66 -6
  24. package/packages/datadog-plugin-mocha/src/index.js +17 -5
  25. package/packages/datadog-plugin-mongodb-core/src/index.js +24 -0
  26. package/packages/datadog-plugin-playwright/src/index.js +124 -10
  27. package/packages/datadog-plugin-vitest/src/index.js +13 -8
  28. package/packages/datadog-shimmer/src/shimmer.js +3 -42
  29. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +39 -15
  30. package/packages/dd-trace/src/appsec/iast/taint-tracking/filter.js +3 -3
  31. package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +0 -3
  32. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +25 -12
  33. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-telemetry.js +3 -32
  34. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +99 -57
  35. package/packages/dd-trace/src/appsec/rasp/command_injection.js +1 -1
  36. package/packages/dd-trace/src/appsec/rasp/index.js +4 -2
  37. package/packages/dd-trace/src/appsec/rasp/lfi.js +1 -1
  38. package/packages/dd-trace/src/appsec/rasp/sql_injection.js +1 -1
  39. package/packages/dd-trace/src/appsec/rasp/ssrf.js +1 -1
  40. package/packages/dd-trace/src/appsec/rasp/utils.js +12 -7
  41. package/packages/dd-trace/src/appsec/recommended.json +256 -84
  42. package/packages/dd-trace/src/appsec/reporter.js +6 -4
  43. package/packages/dd-trace/src/appsec/telemetry/index.js +27 -3
  44. package/packages/dd-trace/src/appsec/telemetry/rasp.js +70 -6
  45. package/packages/dd-trace/src/appsec/telemetry/waf.js +0 -30
  46. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +4 -0
  47. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +8 -3
  48. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +6 -4
  49. package/packages/dd-trace/src/config.js +9 -0
  50. package/packages/dd-trace/src/constants.js +1 -0
  51. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +102 -22
  52. package/packages/dd-trace/src/debugger/devtools_client/condition.js +263 -0
  53. package/packages/dd-trace/src/debugger/devtools_client/index.js +69 -36
  54. package/packages/dd-trace/src/debugger/devtools_client/lock.js +8 -0
  55. package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +1 -7
  56. package/packages/dd-trace/src/debugger/devtools_client/send.js +2 -2
  57. package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +15 -10
  58. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +3 -3
  59. package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +69 -62
  60. package/packages/dd-trace/src/debugger/devtools_client/state.js +3 -2
  61. package/packages/dd-trace/src/debugger/index.js +3 -0
  62. package/packages/dd-trace/src/encode/0.4.js +24 -17
  63. package/packages/dd-trace/src/exporter.js +1 -0
  64. package/packages/dd-trace/src/exporters/common/docker.js +37 -7
  65. package/packages/dd-trace/src/exporters/common/request.js +1 -4
  66. package/packages/dd-trace/src/format.js +58 -60
  67. package/packages/dd-trace/src/llmobs/plugins/base.js +2 -2
  68. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +62 -3
  69. package/packages/dd-trace/src/llmobs/plugins/openai.js +1 -0
  70. package/packages/dd-trace/src/llmobs/plugins/vertexai.js +2 -1
  71. package/packages/dd-trace/src/llmobs/writers/spans/base.js +3 -3
  72. package/packages/dd-trace/src/log/index.js +2 -0
  73. package/packages/dd-trace/src/log/writer.js +19 -2
  74. package/packages/dd-trace/src/opentelemetry/span.js +4 -4
  75. package/packages/dd-trace/src/opentracing/propagation/text_map.js +17 -3
  76. package/packages/dd-trace/src/opentracing/span.js +10 -0
  77. package/packages/dd-trace/src/plugin_manager.js +2 -0
  78. package/packages/dd-trace/src/plugins/util/test.js +11 -0
  79. package/packages/dd-trace/src/profiler.js +1 -1
  80. package/packages/dd-trace/src/profiling/config.js +6 -0
  81. package/packages/dd-trace/src/profiling/exporters/agent.js +1 -5
  82. package/packages/dd-trace/src/profiling/profiler.js +4 -3
  83. package/packages/dd-trace/src/profiling/profilers/wall.js +12 -8
  84. package/packages/dd-trace/src/proxy.js +5 -1
  85. package/packages/dd-trace/src/tagger.js +38 -26
  86. package/packages/dd-trace/src/util.js +1 -7
@@ -2,34 +2,64 @@
2
2
 
3
3
  const fs = require('fs')
4
4
 
5
+ const { DD_EXTERNAL_ENV } = process.env
6
+
5
7
  // The second part is the PCF / Garden regexp. We currently assume no suffix($) to avoid matching pod UIDs
6
8
  // See https://github.com/DataDog/datadog-agent/blob/7.40.x/pkg/util/cgroups/reader.go#L50
7
9
  const uuidSource =
8
10
  '[0-9a-f]{8}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{12}|[0-9a-f]{8}(?:-[0-9a-f]{4}){4}$'
9
11
  const containerSource = '[0-9a-f]{64}'
10
12
  const taskSource = '[0-9a-f]{32}-\\d+'
13
+ const lineReg = /^(\d+):([^:]*):(.+)$/
11
14
  const entityReg = new RegExp(`.*(${uuidSource}|${containerSource}|${taskSource})(?:\\.scope)?$`, 'm')
12
15
 
16
+ const cgroup = readControlGroup()
13
17
  const entityId = getEntityId()
18
+ const inode = getInode()
14
19
 
15
20
  function getEntityId () {
16
- const cgroup = readControlGroup() || ''
17
- const match = cgroup.trim().match(entityReg) || []
21
+ const match = cgroup.match(entityReg) || []
18
22
 
19
23
  return match[1]
20
24
  }
21
25
 
26
+ function getInode () {
27
+ const match = cgroup.match(lineReg) || []
28
+
29
+ return readInode(match[3])
30
+ }
31
+
22
32
  function readControlGroup () {
23
33
  try {
24
- return fs.readFileSync('/proc/self/cgroup').toString()
34
+ return fs.readFileSync('/proc/self/cgroup').toString().trim()
35
+ } catch (err) {
36
+ return ''
37
+ }
38
+ }
39
+
40
+ function readInode (path) {
41
+ if (!path) return 0
42
+
43
+ const strippedPath = path.replace(/^\//, '').replace(/\/$/, '')
44
+
45
+ try {
46
+ return fs.statSync(`/sys/fs/cgroup/${strippedPath}`).ino
25
47
  } catch (err) {
26
- // ignore
48
+ return 0
27
49
  }
28
50
  }
29
51
 
30
52
  module.exports = {
31
- // can be the container ID but not always depending on the orchestrator
32
- id () {
33
- return entityId
53
+ inject (carrier) {
54
+ if (entityId) {
55
+ carrier['Datadog-Container-Id'] = entityId
56
+ carrier['Datadog-Entity-ID'] = `ci-${entityId}`
57
+ } else if (inode) {
58
+ carrier['Datadog-Entity-ID'] = `in-${inode}`
59
+ }
60
+
61
+ if (DD_EXTERNAL_ENV) {
62
+ carrier['Datadog-External-Env'] = DD_EXTERNAL_ENV
63
+ }
34
64
  }
35
65
  }
@@ -15,7 +15,6 @@ const { storage } = require('../../../../datadog-core')
15
15
  const log = require('../../log')
16
16
 
17
17
  const maxActiveRequests = 8
18
- const containerId = docker.id()
19
18
 
20
19
  let activeRequests = 0
21
20
 
@@ -63,9 +62,7 @@ function request (data, options, callback) {
63
62
  options.headers['Content-Length'] = byteLength(dataArray)
64
63
  }
65
64
 
66
- if (containerId) {
67
- options.headers['Datadog-Container-ID'] = containerId
68
- }
65
+ docker.inject(options.headers)
69
66
 
70
67
  options.agent = isSecure ? httpsAgent : httpAgent
71
68
 
@@ -22,7 +22,9 @@ const PROCESS_ID = constants.PROCESS_ID
22
22
  const ERROR_MESSAGE = constants.ERROR_MESSAGE
23
23
  const ERROR_STACK = constants.ERROR_STACK
24
24
  const ERROR_TYPE = constants.ERROR_TYPE
25
+ const { IGNORE_OTEL_ERROR } = constants
25
26
 
27
+ // TODO(BridgeAR)[31.03.2025]: Should these land in the constants file?
26
28
  const map = {
27
29
  'operation.name': 'name',
28
30
  'service.name': 'service',
@@ -69,48 +71,46 @@ function setSingleSpanIngestionTags (span, options) {
69
71
  }
70
72
 
71
73
  function extractSpanLinks (formattedSpan, span) {
72
- const links = []
73
- if (span._links) {
74
- for (const link of span._links) {
75
- const { context, attributes } = link
76
- const formattedLink = {}
77
-
78
- formattedLink.trace_id = context.toTraceId(true)
79
- formattedLink.span_id = context.toSpanId(true)
80
-
81
- if (attributes && Object.keys(attributes).length > 0) {
82
- formattedLink.attributes = attributes
83
- }
84
- if (context?._sampling?.priority >= 0) formattedLink.flags = context._sampling.priority > 0 ? 1 : 0
85
- if (context?._tracestate) formattedLink.tracestate = context._tracestate.toString()
74
+ if (!span._links?.length) {
75
+ return
76
+ }
77
+ const links = span._links.map(link => {
78
+ const { context, attributes } = link
79
+ const formattedLink = {
80
+ trace_id: context.toTraceId(true),
81
+ span_id: context.toSpanId(true)
82
+ }
86
83
 
87
- links.push(formattedLink)
84
+ if (attributes && Object.keys(attributes).length > 0) {
85
+ formattedLink.attributes = attributes
88
86
  }
89
- }
90
- if (links.length > 0) { formattedSpan.meta['_dd.span_links'] = JSON.stringify(links) }
87
+ if (context?._sampling?.priority >= 0) formattedLink.flags = context._sampling.priority > 0 ? 1 : 0
88
+ if (context?._tracestate) formattedLink.tracestate = context._tracestate.toString()
89
+
90
+ return formattedLink
91
+ })
92
+ formattedSpan.meta['_dd.span_links'] = JSON.stringify(links)
91
93
  }
92
94
 
93
95
  function extractSpanEvents (formattedSpan, span) {
94
- const events = []
95
- if (span._events) {
96
- for (const event of span._events) {
97
- const formattedEvent = {
98
- name: event.name,
99
- time_unix_nano: Math.round(event.startTime * 1e6),
100
- attributes: event.attributes && Object.keys(event.attributes).length > 0 ? event.attributes : undefined
101
- }
102
-
103
- events.push(formattedEvent)
104
- }
105
- }
106
- if (events.length > 0) {
107
- formattedSpan.span_events = events
96
+ if (!span._events?.length) {
97
+ return
108
98
  }
99
+ const events = span._events.map(event => {
100
+ return {
101
+ name: event.name,
102
+ time_unix_nano: Math.round(event.startTime * 1e6),
103
+ attributes: event.attributes && Object.keys(event.attributes).length > 0 ? event.attributes : undefined
104
+ }
105
+ })
106
+ formattedSpan.span_events = events
109
107
  }
110
108
 
111
109
  function extractTags (formattedSpan, span) {
112
110
  const context = span.context()
113
111
  const origin = context._trace.origin
112
+ // TODO(BridgeAR)[31.03.2025]: Look into changing the way we store tags. Using
113
+ // a map is likely faster short term.
114
114
  const tags = context._tags
115
115
  const hostname = context._hostname
116
116
  const priority = context._sampling.priority
@@ -126,43 +126,48 @@ function extractTags (formattedSpan, span) {
126
126
  registerExtraService(tags['service.name'])
127
127
  }
128
128
 
129
- for (const tag in tags) {
129
+ for (const [tag, value] of Object.entries(tags)) {
130
+ // TODO(BridgeAR)[31.03.2025]: Check how many tags are defined in average.
131
+ // In case there are more than 2 tags in average, check for all special
132
+ // cases up front and loop over the tags afterwards, skipping the already
133
+ // visited property names by checking a map with these keys.
130
134
  switch (tag) {
131
135
  case 'service.name':
132
136
  case 'span.type':
133
137
  case 'resource.name':
134
- addTag(formattedSpan, {}, map[tag], tags[tag])
138
+ addTag(formattedSpan, {}, map[tag], value)
135
139
  break
136
140
  // HACK: remove when Datadog supports numeric status code
137
141
  case 'http.status_code':
138
- addTag(formattedSpan.meta, {}, tag, tags[tag] && String(tags[tag]))
142
+ addTag(formattedSpan.meta, {}, tag, value && String(value))
139
143
  break
140
144
  case 'analytics.event':
141
- addTag({}, formattedSpan.metrics, ANALYTICS, tags[tag] === undefined || tags[tag] ? 1 : 0)
145
+ addTag({}, formattedSpan.metrics, ANALYTICS, value === undefined || value ? 1 : 0)
142
146
  break
143
147
  case HOSTNAME_KEY:
144
148
  case MEASURED:
145
- addTag({}, formattedSpan.metrics, tag, tags[tag] === undefined || tags[tag] ? 1 : 0)
149
+ addTag({}, formattedSpan.metrics, tag, value === undefined || value ? 1 : 0)
146
150
  break
151
+ // TODO(BridgeAR)[31.03.2025]: How come we use two different ways to pass
152
+ // through errors? Can we just unify the behavior to always use one way?
147
153
  case 'error':
148
154
  if (context._name !== 'fs.operation') {
149
- extractError(formattedSpan, tags[tag])
155
+ extractError(formattedSpan, value)
150
156
  }
151
157
  break
152
158
  case ERROR_TYPE:
153
159
  case ERROR_MESSAGE:
154
160
  case ERROR_STACK:
155
161
  // HACK: remove when implemented in the backend
156
- if (context._name !== 'fs.operation') {
157
- // HACK: to ensure otel.recordException does not influence formattedSpan.error
158
- if (tags.setTraceError) {
159
- formattedSpan.error = 1
160
- }
161
- } else {
162
+ if (context._name === 'fs.operation') {
162
163
  break
163
164
  }
165
+ // otel.recordException should not influence trace.error
166
+ if (!tags[IGNORE_OTEL_ERROR]) {
167
+ formattedSpan.error = 1
168
+ }
164
169
  default: // eslint-disable-line no-fallthrough
165
- addTag(formattedSpan.meta, formattedSpan.metrics, tag, tags[tag])
170
+ addTag(formattedSpan.meta, formattedSpan.metrics, tag, value)
166
171
  }
167
172
  }
168
173
  setSingleSpanIngestionTags(formattedSpan, context._spanSampling)
@@ -193,8 +198,8 @@ function extractChunkTags (formattedSpan, span) {
193
198
 
194
199
  if (!isLocalRoot) return
195
200
 
196
- for (const key in context._trace.tags) {
197
- addTag(formattedSpan.meta, formattedSpan.metrics, key, context._trace.tags[key])
201
+ for (const [key, value] of Object.entries(context._trace.tags)) {
202
+ addTag(formattedSpan.meta, formattedSpan.metrics, key, value)
198
203
  }
199
204
  }
200
205
 
@@ -205,6 +210,8 @@ function extractError (formattedSpan, error) {
205
210
 
206
211
  if (isError(error)) {
207
212
  // AggregateError only has a code and no message.
213
+ // TODO(BridgeAR)[31.03.2025]: An AggregateError can have a message. Should
214
+ // the code just generally be added, if available?
208
215
  addTag(formattedSpan.meta, formattedSpan.metrics, ERROR_MESSAGE, error.message || error.code)
209
216
  addTag(formattedSpan.meta, formattedSpan.metrics, ERROR_TYPE, error.name)
210
217
  addTag(formattedSpan.meta, formattedSpan.metrics, ERROR_STACK, error.stack)
@@ -223,30 +230,21 @@ function addTag (meta, metrics, key, value, nested) {
223
230
  case 'boolean':
224
231
  metrics[key] = value ? 1 : 0
225
232
  break
226
- case 'undefined':
227
- break
228
- case 'object':
229
- if (value === null) break
233
+ default:
234
+ if (value == null) break
230
235
 
231
236
  // Special case for Node.js Buffer and URL
237
+ // TODO(BridgeAR)[31.03.2025]: Figure out if all typed arrays should be treated as buffers.
232
238
  if (isNodeBuffer(value) || isUrl(value)) {
233
239
  metrics[key] = value.toString()
234
240
  } else if (!Array.isArray(value) && !nested) {
235
- for (const prop in value) {
236
- if (!hasOwn(value, prop)) continue
237
-
238
- addTag(meta, metrics, `${key}.${prop}`, value[prop], true)
241
+ for (const [prop, val] of Object.entries(value)) {
242
+ addTag(meta, metrics, `${key}.${prop}`, val, true)
239
243
  }
240
244
  }
241
-
242
- break
243
245
  }
244
246
  }
245
247
 
246
- function hasOwn (object, prop) {
247
- return Object.prototype.hasOwnProperty.call(object, prop)
248
- }
249
-
250
248
  function isNodeBuffer (obj) {
251
249
  return obj.constructor && obj.constructor.name === 'Buffer' &&
252
250
  typeof obj.readInt8 === 'function' &&
@@ -37,13 +37,13 @@ class LLMObsPlugin extends TracingPlugin {
37
37
  // register options may not be set for operations we do not trace with llmobs
38
38
  // ie OpenAI fine tuning jobs, file jobs, etc.
39
39
  if (registerOptions) {
40
- telemetry.incrementLLMObsSpanStartCount({ autoinstrumented: true, integration: this.constructor.id })
40
+ telemetry.incrementLLMObsSpanStartCount({ autoinstrumented: true, integration: this.constructor.integration })
41
41
 
42
42
  ctx.llmobs = {} // initialize context-based namespace
43
43
  llmobsStorage.enterWith({ span })
44
44
  ctx.llmobs.parent = parent
45
45
 
46
- this._tagger.registerLLMObsSpan(span, { parent, integration: this.constructor.id, ...registerOptions })
46
+ this._tagger.registerLLMObsSpan(span, { parent, integration: this.constructor.integration, ...registerOptions })
47
47
  }
48
48
  }
49
49
 
@@ -20,7 +20,8 @@ const ChatModelHandler = require('./handlers/chat_model')
20
20
  const LlmHandler = require('./handlers/llm')
21
21
  const EmbeddingHandler = require('./handlers/embedding')
22
22
 
23
- class LangChainLLMObsPlugin extends LLMObsPlugin {
23
+ class BaseLangChainLLMObsPlugin extends LLMObsPlugin {
24
+ static get integration () { return 'langchain' }
24
25
  static get id () { return 'langchain' }
25
26
  static get prefix () {
26
27
  return 'tracing:apm:langchain:invoke'
@@ -55,8 +56,11 @@ class LangChainLLMObsPlugin extends LLMObsPlugin {
55
56
  }
56
57
 
57
58
  setLLMObsTags (ctx) {
59
+ ctx.args = ctx.arguments
60
+ ctx.instance = ctx.self
61
+
58
62
  const span = ctx.currentStore?.span
59
- const type = ctx.type // langchain operation type (oneof chain,chat_model,llm,embedding)
63
+ const type = ctx.type = this.constructor.lcType // langchain operation type (oneof chain,chat_model,llm,embedding)
60
64
 
61
65
  if (!Object.keys(this._handlers).includes(type)) {
62
66
  log.warn(`Unsupported LangChain operation type: ${type}`)
@@ -129,4 +133,59 @@ class LangChainLLMObsPlugin extends LLMObsPlugin {
129
133
  }
130
134
  }
131
135
 
132
- module.exports = LangChainLLMObsPlugin
136
+ class RunnableSequenceInvokePlugin extends BaseLangChainLLMObsPlugin {
137
+ static get id () { return 'llmobs_langchain_rs_invoke' }
138
+ static get lcType () { return 'chain' }
139
+ static get prefix () {
140
+ return 'tracing:orchestrion:@langchain/core:RunnableSequence_invoke'
141
+ }
142
+ }
143
+
144
+ class RunnableSequenceBatchPlugin extends BaseLangChainLLMObsPlugin {
145
+ static get id () { return 'llmobs_langchain_rs_batch' }
146
+ static get lcType () { return 'chain' }
147
+ static get prefix () {
148
+ return 'tracing:orchestrion:@langchain/core:RunnableSequence_batch'
149
+ }
150
+ }
151
+
152
+ class BaseChatModelGeneratePlugin extends BaseLangChainLLMObsPlugin {
153
+ static get id () { return 'llmobs_langchain_chat_model_generate' }
154
+ static get lcType () { return 'chat_model' }
155
+ static get prefix () {
156
+ return 'tracing:orchestrion:@langchain/core:BaseChatModel_generate'
157
+ }
158
+ }
159
+
160
+ class BaseLLMGeneratePlugin extends BaseLangChainLLMObsPlugin {
161
+ static get id () { return 'llmobs_langchain_llm_generate' }
162
+ static get lcType () { return 'llm' }
163
+ static get prefix () {
164
+ return 'tracing:orchestrion:@langchain/core:BaseLLM_generate'
165
+ }
166
+ }
167
+
168
+ class EmbeddingsEmbedQueryPlugin extends BaseLangChainLLMObsPlugin {
169
+ static get id () { return 'llmobs_langchain_embeddings_embed_query' }
170
+ static get lcType () { return 'embedding' }
171
+ static get prefix () {
172
+ return 'tracing:apm:@langchain/core:Embeddings_embedQuery'
173
+ }
174
+ }
175
+
176
+ class EmbeddingsEmbedDocumentsPlugin extends BaseLangChainLLMObsPlugin {
177
+ static get id () { return 'llmobs_langchain_embeddings_embed_documents' }
178
+ static get lcType () { return 'embedding' }
179
+ static get prefix () {
180
+ return 'tracing:apm:@langchain/core:Embeddings_embedDocuments'
181
+ }
182
+ }
183
+
184
+ module.exports = [
185
+ RunnableSequenceInvokePlugin,
186
+ RunnableSequenceBatchPlugin,
187
+ BaseChatModelGeneratePlugin,
188
+ BaseLLMGeneratePlugin,
189
+ EmbeddingsEmbedQueryPlugin,
190
+ EmbeddingsEmbedDocumentsPlugin
191
+ ]
@@ -11,6 +11,7 @@ function isIterable (obj) {
11
11
 
12
12
  class OpenAiLLMObsPlugin extends LLMObsPlugin {
13
13
  static get id () { return 'openai' }
14
+ static get integration () { return 'openai' }
14
15
  static get prefix () {
15
16
  return 'tracing:apm:openai:request'
16
17
  }
@@ -7,7 +7,8 @@ const {
7
7
  } = require('../../../../datadog-plugin-google-cloud-vertexai/src/utils')
8
8
 
9
9
  class VertexAILLMObsPlugin extends LLMObsPlugin {
10
- static get id () { return 'vertexai' } // used for llmobs telemetry
10
+ static get integration () { return 'vertexai' } // used for llmobs telemetry
11
+ static get id () { return 'vertexai' }
11
12
  static get prefix () {
12
13
  return 'tracing:apm:vertexai:request'
13
14
  }
@@ -41,12 +41,12 @@ class LLMObsSpanWriter extends BaseWriter {
41
41
  }
42
42
 
43
43
  makePayload (events) {
44
- return {
44
+ return events.map(event => ({
45
45
  '_dd.stage': 'raw',
46
46
  '_dd.tracer_version': tracerVersion,
47
47
  event_type: this._eventType,
48
- spans: events
49
- }
48
+ spans: [event]
49
+ }))
50
50
  }
51
51
 
52
52
  _truncateSpanEvent (event) {
@@ -121,6 +121,8 @@ const log = {
121
121
  }
122
122
  }
123
123
 
124
+ logWriter.setStackTraceLimitFunction(log.error)
125
+
124
126
  log.reset()
125
127
 
126
128
  log.toggle(log.isEnabled(), log.getLogLevel())
@@ -13,6 +13,7 @@ const defaultLogger = {
13
13
  let enabled = false
14
14
  let logger = defaultLogger
15
15
  let logChannel = new LogChannel()
16
+ let stackTraceLimitFunction = onError
16
17
 
17
18
  function withNoop (fn) {
18
19
  const store = storage('legacy').getStore()
@@ -61,12 +62,28 @@ function getErrorLog (err) {
61
62
  }
62
63
  }
63
64
 
65
+ function setStackTraceLimitFunction (fn) {
66
+ if (typeof fn !== 'function') {
67
+ throw new TypeError('stackTraceLimitFunction must be a function')
68
+ }
69
+ stackTraceLimitFunction = fn
70
+ }
71
+
64
72
  function onError (err) {
65
73
  const { formatted, cause } = getErrorLog(err)
66
74
 
67
75
  // calling twice logger.error() because Error cause is only available in nodejs v16.9.0
68
76
  // TODO: replace it with Error(message, { cause }) when cause has broad support
69
- if (formatted) withNoop(() => logger.error(new Error(formatted)))
77
+ if (formatted) {
78
+ withNoop(() => {
79
+ const l = Error.stackTraceLimit
80
+ Error.stackTraceLimit = 0
81
+ const e = new Error(formatted)
82
+ Error.stackTraceLimit = l
83
+ Error.captureStackTrace(e, stackTraceLimitFunction)
84
+ logger.error(e)
85
+ })
86
+ }
70
87
  if (cause) withNoop(() => logger.error(cause))
71
88
  }
72
89
 
@@ -122,4 +139,4 @@ function trace (...args) {
122
139
  onTrace(Log.parse(...args))
123
140
  }
124
141
 
125
- module.exports = { use, toggle, reset, error, warn, info, debug, trace }
142
+ module.exports = { use, toggle, reset, error, warn, info, debug, trace, setStackTraceLimitFunction }
@@ -9,7 +9,7 @@ const { timeInputToHrTime } = require('@opentelemetry/core')
9
9
 
10
10
  const tracer = require('../../')
11
11
  const DatadogSpan = require('../opentracing/span')
12
- const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../constants')
12
+ const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK, IGNORE_OTEL_ERROR } = require('../constants')
13
13
  const { SERVICE_NAME, RESOURCE_NAME } = require('../../../../ext/tags')
14
14
  const kinds = require('../../../../ext/kinds')
15
15
 
@@ -237,7 +237,8 @@ class Span {
237
237
  this._hasStatus = true
238
238
  if (code === 2) {
239
239
  this._ddSpan.addTags({
240
- [ERROR_MESSAGE]: message
240
+ [ERROR_MESSAGE]: message,
241
+ [IGNORE_OTEL_ERROR]: false
241
242
  })
242
243
  }
243
244
  }
@@ -278,12 +279,11 @@ class Span {
278
279
  }
279
280
 
280
281
  recordException (exception, timeInput) {
281
- // HACK: identifier is added so that trace.error remains unchanged after a call to otel.recordException
282
282
  this._ddSpan.addTags({
283
283
  [ERROR_TYPE]: exception.name,
284
284
  [ERROR_MESSAGE]: exception.message,
285
285
  [ERROR_STACK]: exception.stack,
286
- doNotSetTraceError: true
286
+ [IGNORE_OTEL_ERROR]: this._ddSpan.context()._tags[IGNORE_OTEL_ERROR] ?? true
287
287
  })
288
288
  const attributes = {}
289
289
  if (exception.message) attributes['exception.message'] = exception.message
@@ -298,6 +298,7 @@ class TextMapPropagator {
298
298
 
299
299
  _extractSpanContext (carrier) {
300
300
  let context = null
301
+ let style = ''
301
302
  for (const extractor of this._config.tracePropagationStyle.extract) {
302
303
  let extractedContext = null
303
304
  switch (extractor) {
@@ -331,9 +332,9 @@ class TextMapPropagator {
331
332
 
332
333
  if (context === null) {
333
334
  context = extractedContext
335
+ style = extractor
334
336
  if (this._config.tracePropagationExtractFirst) {
335
- this._extractBaggageItems(carrier, context)
336
- return context
337
+ break
337
338
  }
338
339
  } else {
339
340
  // If extractor is tracecontext, add tracecontext specific information to the context
@@ -342,7 +343,7 @@ class TextMapPropagator {
342
343
  this._extractTraceparentContext(carrier), context, carrier)
343
344
  }
344
345
  if (extractedContext._traceId && extractedContext._spanId &&
345
- extractedContext.toTraceId(true) !== context.toTraceId(true)) {
346
+ extractedContext.toTraceId(true) !== context.toTraceId(true)) {
346
347
  const link = {
347
348
  context: extractedContext,
348
349
  attributes: { reason: 'terminated_context', context_headers: extractor }
@@ -354,6 +355,19 @@ class TextMapPropagator {
354
355
 
355
356
  this._extractBaggageItems(carrier, context)
356
357
 
358
+ if (this._config.tracePropagationBehaviorExtract === 'ignore') {
359
+ context._links = []
360
+ } else if (this._config.tracePropagationBehaviorExtract === 'restart') {
361
+ context._links = []
362
+ context._links.push({
363
+ context,
364
+ attributes:
365
+ {
366
+ reason: 'propagation_behavior_extract', context_headers: style
367
+ }
368
+ })
369
+ }
370
+
357
371
  return context || this._extractSqsdContext(carrier)
358
372
  }
359
373
 
@@ -319,6 +319,12 @@ class DatadogSpan {
319
319
  let spanContext
320
320
  let startTime
321
321
 
322
+ let baggage = {}
323
+ if (parent && parent._isRemote && this._parentTracer?._config?.tracePropagationBehaviorExtract !== 'continue') {
324
+ baggage = parent._baggageItems
325
+ parent = null
326
+ }
327
+
322
328
  if (fields.context) {
323
329
  spanContext = fields.context
324
330
  if (!spanContext._trace.startTime) {
@@ -352,6 +358,10 @@ class DatadogSpan {
352
358
  .padStart(8, '0')
353
359
  .padEnd(16, '0')
354
360
  }
361
+
362
+ if (this._parentTracer?._config?.tracePropagationBehaviorExtract === 'restart') {
363
+ spanContext._baggageItems = baggage
364
+ }
355
365
  }
356
366
 
357
367
  spanContext._trace.ticks = spanContext._trace.ticks || now()
@@ -133,6 +133,7 @@ module.exports = class PluginManager {
133
133
  dbmPropagationMode,
134
134
  dsmEnabled,
135
135
  clientIpEnabled,
136
+ clientIpHeader,
136
137
  memcachedCommandEnabled,
137
138
  ciVisibilityTestSessionName,
138
139
  ciVisAgentlessLogSubmissionEnabled,
@@ -148,6 +149,7 @@ module.exports = class PluginManager {
148
149
  site,
149
150
  url,
150
151
  headers: headerTags || [],
152
+ clientIpHeader,
151
153
  ciVisibilityTestSessionName,
152
154
  ciVisAgentlessLogSubmissionEnabled,
153
155
  isTestDynamicInstrumentationEnabled,
@@ -96,6 +96,9 @@ const CUCUMBER_WORKER_TRACE_PAYLOAD_CODE = 70
96
96
  // mocha worker variables
97
97
  const MOCHA_WORKER_TRACE_PAYLOAD_CODE = 80
98
98
 
99
+ // playwright worker variables
100
+ const PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE = 90
101
+
99
102
  // Early flake detection util strings
100
103
  const EFD_STRING = "Retried by Datadog's Early Flake Detection"
101
104
  const EFD_TEST_NAME_REGEX = new RegExp(EFD_STRING + ' \\(#\\d+\\): ', 'g')
@@ -117,6 +120,12 @@ const TEST_LEVEL_EVENT_TYPES = [
117
120
  'test_module_end',
118
121
  'test_session_end'
119
122
  ]
123
+ const TEST_RETRY_REASON_TYPES = {
124
+ efd: 'early_flake_detection',
125
+ atr: 'auto_test_retry',
126
+ atf: 'attempt_to_fix',
127
+ ext: 'external'
128
+ }
120
129
 
121
130
  const DD_TEST_IS_USER_PROVIDED_SERVICE = '_dd.test.is_user_provided_service'
122
131
 
@@ -162,6 +171,7 @@ module.exports = {
162
171
  JEST_WORKER_LOGS_PAYLOAD_CODE,
163
172
  CUCUMBER_WORKER_TRACE_PAYLOAD_CODE,
164
173
  MOCHA_WORKER_TRACE_PAYLOAD_CODE,
174
+ PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE,
165
175
  TEST_SOURCE_START,
166
176
  TEST_SKIPPED_BY_ITR,
167
177
  TEST_IS_NEW,
@@ -223,6 +233,7 @@ module.exports = {
223
233
  DD_CAPABILITIES_TEST_MANAGEMENT_DISABLE,
224
234
  DD_CAPABILITIES_TEST_MANAGEMENT_ATTEMPT_TO_FIX,
225
235
  TEST_LEVEL_EVENT_TYPES,
236
+ TEST_RETRY_REASON_TYPES,
226
237
  getNumFromKnownTests,
227
238
  getFileAndLineNumberFromError,
228
239
  DI_ERROR_DEBUG_INFO_CAPTURED,
@@ -14,7 +14,7 @@ module.exports = {
14
14
  debug: (message) => log.debug(message),
15
15
  info: (message) => log.info(message),
16
16
  warn: (message) => log.warn(message),
17
- error: (message) => log.error(message)
17
+ error: (...args) => log.error(...args)
18
18
  }
19
19
 
20
20
  const libraryInjected = injectionEnabled.length > 0