dd-trace 5.88.0 → 5.90.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 (103) hide show
  1. package/LICENSE-3rdparty.csv +0 -3
  2. package/ext/tags.js +2 -0
  3. package/index.d.ts +40 -0
  4. package/package.json +18 -14
  5. package/packages/datadog-instrumentations/src/azure-durable-functions.js +75 -0
  6. package/packages/datadog-instrumentations/src/cucumber.js +40 -1
  7. package/packages/datadog-instrumentations/src/elasticsearch.js +12 -3
  8. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  9. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +26 -111
  10. package/packages/datadog-instrumentations/src/helpers/rewriter/{compiler.js → orchestrion/compiler.js} +5 -5
  11. package/packages/datadog-instrumentations/src/helpers/rewriter/orchestrion/index.js +43 -0
  12. package/packages/datadog-instrumentations/src/helpers/rewriter/orchestrion/matcher.js +49 -0
  13. package/packages/datadog-instrumentations/src/helpers/rewriter/orchestrion/transformer.js +121 -0
  14. package/packages/datadog-instrumentations/src/helpers/rewriter/{transforms.js → orchestrion/transforms.js} +6 -6
  15. package/packages/datadog-instrumentations/src/jest.js +123 -43
  16. package/packages/datadog-instrumentations/src/mocha/main.js +10 -4
  17. package/packages/datadog-instrumentations/src/mocha/utils.js +6 -0
  18. package/packages/datadog-instrumentations/src/mocha/worker.js +10 -2
  19. package/packages/datadog-instrumentations/src/playwright.js +20 -2
  20. package/packages/datadog-instrumentations/src/prisma.js +4 -2
  21. package/packages/datadog-instrumentations/src/vitest.js +16 -0
  22. package/packages/datadog-plugin-apollo/src/gateway/execute.js +8 -0
  23. package/packages/datadog-plugin-apollo/src/gateway/fetch.js +5 -0
  24. package/packages/datadog-plugin-apollo/src/gateway/plan.js +8 -0
  25. package/packages/datadog-plugin-apollo/src/gateway/postprocessing.js +5 -0
  26. package/packages/datadog-plugin-apollo/src/gateway/request.js +4 -3
  27. package/packages/datadog-plugin-apollo/src/gateway/validate.js +4 -3
  28. package/packages/datadog-plugin-apollo/src/index.js +28 -0
  29. package/packages/datadog-plugin-azure-durable-functions/src/index.js +49 -0
  30. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +47 -6
  31. package/packages/datadog-plugin-cypress/src/source-map-utils.js +297 -0
  32. package/packages/datadog-plugin-cypress/src/support.js +4 -1
  33. package/packages/datadog-plugin-jest/src/index.js +6 -0
  34. package/packages/datadog-plugin-playwright/src/index.js +35 -8
  35. package/packages/dd-trace/src/aiguard/noop.js +1 -1
  36. package/packages/dd-trace/src/aiguard/sdk.js +18 -5
  37. package/packages/dd-trace/src/appsec/api_security_sampler.js +22 -1
  38. package/packages/dd-trace/src/appsec/index.js +11 -1
  39. package/packages/dd-trace/src/appsec/reporter.js +28 -11
  40. package/packages/dd-trace/src/appsec/waf/index.js +1 -1
  41. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +4 -4
  42. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +1 -0
  43. package/packages/dd-trace/src/config/index.js +3 -0
  44. package/packages/dd-trace/src/config/supported-configurations.json +17 -0
  45. package/packages/dd-trace/src/constants.js +1 -0
  46. package/packages/dd-trace/src/datastreams/checkpointer.js +13 -0
  47. package/packages/dd-trace/src/datastreams/index.js +3 -0
  48. package/packages/dd-trace/src/datastreams/manager.js +9 -0
  49. package/packages/dd-trace/src/datastreams/processor.js +126 -3
  50. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +1 -8
  51. package/packages/dd-trace/src/encode/agentless-json.js +82 -23
  52. package/packages/dd-trace/src/exporters/agent/writer.js +7 -8
  53. package/packages/dd-trace/src/exporters/agentless/index.js +58 -15
  54. package/packages/dd-trace/src/exporters/agentless/writer.js +35 -18
  55. package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
  56. package/packages/dd-trace/src/llmobs/plugins/anthropic.js +9 -0
  57. package/packages/dd-trace/src/llmobs/tagger.js +8 -0
  58. package/packages/dd-trace/src/opentracing/propagation/text_map.js +1 -0
  59. package/packages/dd-trace/src/pkg.js +1 -1
  60. package/packages/dd-trace/src/plugins/apollo.js +7 -2
  61. package/packages/dd-trace/src/plugins/index.js +1 -0
  62. package/packages/dd-trace/src/plugins/util/ci.js +95 -3
  63. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +36 -2
  64. package/packages/dd-trace/src/plugins/util/web.js +31 -11
  65. package/packages/dd-trace/src/proxy.js +2 -1
  66. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +7 -0
  67. package/packages/dd-trace/src/service-naming/schemas/v0/serverless.js +4 -0
  68. package/packages/dd-trace/src/service-naming/schemas/v1/serverless.js +4 -0
  69. package/packages/dd-trace/src/standalone/product.js +2 -1
  70. package/packages/dd-trace/src/startup-log.js +52 -18
  71. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  72. package/vendor/dist/@datadog/source-map/index.js +1 -1
  73. package/vendor/dist/@isaacs/ttlcache/index.js +1 -1
  74. package/vendor/dist/@opentelemetry/core/index.js +1 -1
  75. package/vendor/dist/@opentelemetry/resources/index.js +1 -1
  76. package/vendor/dist/astring/index.js +1 -1
  77. package/vendor/dist/crypto-randomuuid/index.js +1 -1
  78. package/vendor/dist/escape-string-regexp/index.js +1 -1
  79. package/vendor/dist/esquery/index.js +1 -1
  80. package/vendor/dist/ignore/index.js +1 -1
  81. package/vendor/dist/istanbul-lib-coverage/index.js +1 -1
  82. package/vendor/dist/jest-docblock/index.js +1 -1
  83. package/vendor/dist/jsonpath-plus/index.js +1 -1
  84. package/vendor/dist/limiter/index.js +1 -1
  85. package/vendor/dist/lodash.sortby/index.js +1 -1
  86. package/vendor/dist/lru-cache/index.js +1 -1
  87. package/vendor/dist/meriyah/index.js +1 -1
  88. package/vendor/dist/module-details-from-path/index.js +1 -1
  89. package/vendor/dist/mutexify/promise/index.js +1 -1
  90. package/vendor/dist/opentracing/index.js +1 -1
  91. package/vendor/dist/path-to-regexp/index.js +1 -1
  92. package/vendor/dist/pprof-format/index.js +1 -1
  93. package/vendor/dist/protobufjs/index.js +1 -1
  94. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  95. package/vendor/dist/retry/index.js +1 -1
  96. package/vendor/dist/rfdc/index.js +1 -1
  97. package/vendor/dist/semifies/index.js +1 -1
  98. package/vendor/dist/shell-quote/index.js +1 -1
  99. package/vendor/dist/source-map/index.js +1 -1
  100. package/vendor/dist/source-map/lib/util/index.js +1 -1
  101. package/vendor/dist/tlhunter-sorted-set/index.js +1 -1
  102. package/vendor/dist/ttl-set/index.js +1 -1
  103. package/packages/datadog-instrumentations/src/helpers/rewriter/transformer.js +0 -21
@@ -9,20 +9,22 @@ const BaseWriter = require('../common/writer')
9
9
  const { AgentlessJSONEncoder } = require('../../encode/agentless-json')
10
10
 
11
11
  /**
12
- * Writer for agentless APM span intake.
13
- * Sends spans directly to the Datadog intake endpoint without an agent.
12
+ * Writer for agentless APM trace intake.
13
+ * Sends traces directly to the Datadog intake endpoint without an agent.
14
14
  */
15
15
  class AgentlessWriter extends BaseWriter {
16
16
  #apiKeyMissing = false
17
+ #urlMissing = false
17
18
 
18
19
  /**
19
20
  * @param {object} options - Writer options
20
21
  * @param {URL} [options.url] - The intake URL. If not provided, constructed from site.
21
22
  * @param {string} [options.site='datadoghq.com'] - The Datadog site
23
+ * @param {object} [options.metadata={}] - Metadata to pass to the encoder (hostname, env, etc.)
22
24
  */
23
- constructor ({ url, site = 'datadoghq.com' }) {
25
+ constructor ({ url, site = 'datadoghq.com', metadata = {} }) {
24
26
  super({ url })
25
- this._encoder = new AgentlessJSONEncoder()
27
+ this._encoder = new AgentlessJSONEncoder(this, metadata)
26
28
 
27
29
  if (!url) {
28
30
  try {
@@ -39,17 +41,27 @@ class AgentlessWriter extends BaseWriter {
39
41
 
40
42
  if (!getValueFromEnvSources('DD_API_KEY')) {
41
43
  this.#apiKeyMissing = true
42
- log.error('DD_API_KEY is required for agentless span intake. Set DD_API_KEY. Spans will not be sent.')
44
+ log.error('DD_API_KEY is required for agentless trace intake. Set DD_API_KEY. Traces will not be sent.')
45
+ }
46
+ }
47
+
48
+ setUrl (url) {
49
+ super.setUrl(url)
50
+ if (url) {
51
+ this.#urlMissing = false
43
52
  }
44
53
  }
45
54
 
46
55
  /**
47
- * Flushes the current trace. Since we flush after each trace, this sends
48
- * a single request.
56
+ * Flushes accumulated traces to the intake as a single request.
49
57
  * @param {Function} [done] - Callback when send completes
50
58
  */
51
59
  flush (done = () => {}) {
52
60
  if (!request.writable) {
61
+ const count = this._encoder.count()
62
+ if (count > 0) {
63
+ log.error('Maximum number of active requests reached. Dropping %d trace(s).', count)
64
+ }
53
65
  this._encoder.reset()
54
66
  done()
55
67
  return
@@ -76,7 +88,7 @@ class AgentlessWriter extends BaseWriter {
76
88
  /**
77
89
  * Sends the encoded payload to the intake endpoint.
78
90
  * @param {Buffer} data - The encoded JSON payload
79
- * @param {number} count - Number of spans in the payload
91
+ * @param {number} count - Number of traces in the payload
80
92
  * @param {Function} done - Callback when complete
81
93
  */
82
94
  _sendPayload (data, count, done) {
@@ -87,7 +99,11 @@ class AgentlessWriter extends BaseWriter {
87
99
  }
88
100
 
89
101
  if (!this._url) {
90
- log.debug('Skipping send due to invalid URL configuration')
102
+ if (!this.#urlMissing) {
103
+ this.#urlMissing = true
104
+ log.error('No valid URL configured for agentless trace intake. Traces will not be sent.')
105
+ }
106
+ log.debug('Dropping %d trace(s) due to missing URL', count)
91
107
  done()
92
108
  return
93
109
  }
@@ -96,9 +112,9 @@ class AgentlessWriter extends BaseWriter {
96
112
  if (!apiKey) {
97
113
  if (!this.#apiKeyMissing) {
98
114
  this.#apiKeyMissing = true
99
- log.error('DD_API_KEY is required for agentless span intake. Set DD_API_KEY. Spans will not be sent.')
115
+ log.error('DD_API_KEY is required for agentless trace intake. Set DD_API_KEY. Traces will not be sent.')
100
116
  }
101
- log.debug('Dropping %d span(s) due to missing DD_API_KEY', count)
117
+ log.debug('Dropping %d trace(s) due to missing DD_API_KEY', count)
102
118
  done()
103
119
  return
104
120
  }
@@ -110,6 +126,7 @@ class AgentlessWriter extends BaseWriter {
110
126
  headers: {
111
127
  'Content-Type': 'application/json',
112
128
  'dd-api-key': apiKey,
129
+ 'X-Datadog-Trace-Count': String(count),
113
130
  'Datadog-Meta-Lang': 'nodejs',
114
131
  'Datadog-Meta-Lang-Version': process.version,
115
132
  'Datadog-Meta-Lang-Interpreter': process.versions.bun ? 'JavaScriptCore' : 'v8',
@@ -137,42 +154,42 @@ class AgentlessWriter extends BaseWriter {
137
154
  * Logs request errors with status-specific guidance.
138
155
  * @param {Error} err - The error object
139
156
  * @param {number} statusCode - HTTP status code (if available)
140
- * @param {number} count - Number of spans that were being sent
157
+ * @param {number} count - Number of traces that were being sent
141
158
  */
142
159
  _logRequestError (err, statusCode, count) {
143
160
  if (statusCode === 401 || statusCode === 403) {
144
161
  log.error(
145
- 'Authentication failed sending %d span(s) (status %s). Verify DD_API_KEY is valid.',
162
+ 'Authentication failed sending %d trace(s) (status %s). Verify DD_API_KEY is valid.',
146
163
  count,
147
164
  statusCode
148
165
  )
149
166
  } else if (statusCode === 404) {
150
167
  log.error(
151
- 'Span intake endpoint not found (status %s). Verify DD_SITE is correctly configured. %d span(s) dropped.',
168
+ 'Trace intake endpoint not found (status %s). Verify DD_SITE is correctly configured. %d trace(s) dropped.',
152
169
  statusCode,
153
170
  count
154
171
  )
155
172
  } else if (statusCode === 429) {
156
173
  log.error(
157
- 'Rate limited by span intake (status 429). %d span(s) dropped.',
174
+ 'Rate limited by trace intake (status 429). %d trace(s) dropped.',
158
175
  count
159
176
  )
160
177
  } else if (statusCode >= 500) {
161
178
  log.error(
162
- 'Span intake server error (status %s). %d span(s) dropped. This may be transient.',
179
+ 'Trace intake server error (status %s). %d trace(s) dropped. This may be transient.',
163
180
  statusCode,
164
181
  count
165
182
  )
166
183
  } else if (statusCode) {
167
184
  log.error(
168
- 'Error sending agentless payload (status %s): %s. %d span(s) dropped.',
185
+ 'Error sending agentless payload (status %s): %s. %d trace(s) dropped.',
169
186
  statusCode,
170
187
  err.message,
171
188
  count
172
189
  )
173
190
  } else {
174
191
  log.error(
175
- 'Network error sending %d span(s) to %s: %s',
192
+ 'Network error sending %d trace(s) to %s: %s',
176
193
  count,
177
194
  this._url?.hostname || 'unknown',
178
195
  err.message
@@ -38,6 +38,8 @@ module.exports = {
38
38
  TOTAL_TOKENS_METRIC_KEY: 'total_tokens',
39
39
  CACHE_READ_INPUT_TOKENS_METRIC_KEY: 'cache_read_input_tokens',
40
40
  CACHE_WRITE_INPUT_TOKENS_METRIC_KEY: 'cache_write_input_tokens',
41
+ CACHE_WRITE_5M_INPUT_TOKENS_METRIC_KEY: 'ephemeral_5m_input_tokens',
42
+ CACHE_WRITE_1H_INPUT_TOKENS_METRIC_KEY: 'ephemeral_1h_input_tokens',
41
43
  REASONING_OUTPUT_TOKENS_METRIC_KEY: 'reasoning_output_tokens',
42
44
 
43
45
  DROPPED_IO_COLLECTION_ERROR: 'dropped_io',
@@ -253,6 +253,15 @@ class AnthropicLLMObsPlugin extends LLMObsPlugin {
253
253
  if (cacheWriteTokens != null) metrics.cacheWriteTokens = cacheWriteTokens
254
254
  if (cacheReadTokens != null) metrics.cacheReadTokens = cacheReadTokens
255
255
 
256
+ const cacheCreation = usage.cache_creation
257
+ if (cacheCreation) {
258
+ metrics.cacheWrite5mTokens = cacheCreation.ephemeral_5m_input_tokens ?? 0
259
+ metrics.cacheWrite1hTokens = cacheCreation.ephemeral_1h_input_tokens ?? 0
260
+ } else if (cacheWriteTokens != null) {
261
+ metrics.cacheWrite5mTokens = cacheWriteTokens
262
+ metrics.cacheWrite1hTokens = 0
263
+ }
264
+
256
265
  this._tagger.tagMetrics(span, metrics)
257
266
  }
258
267
 
@@ -22,6 +22,8 @@ const {
22
22
  ROOT_PARENT_ID,
23
23
  CACHE_READ_INPUT_TOKENS_METRIC_KEY,
24
24
  CACHE_WRITE_INPUT_TOKENS_METRIC_KEY,
25
+ CACHE_WRITE_5M_INPUT_TOKENS_METRIC_KEY,
26
+ CACHE_WRITE_1H_INPUT_TOKENS_METRIC_KEY,
25
27
  INPUT_TOKENS_METRIC_KEY,
26
28
  OUTPUT_TOKENS_METRIC_KEY,
27
29
  TOTAL_TOKENS_METRIC_KEY,
@@ -185,6 +187,12 @@ class LLMObsTagger {
185
187
  case 'cacheWriteTokens':
186
188
  processedKey = CACHE_WRITE_INPUT_TOKENS_METRIC_KEY
187
189
  break
190
+ case 'cacheWrite5mTokens':
191
+ processedKey = CACHE_WRITE_5M_INPUT_TOKENS_METRIC_KEY
192
+ break
193
+ case 'cacheWrite1hTokens':
194
+ processedKey = CACHE_WRITE_1H_INPUT_TOKENS_METRIC_KEY
195
+ break
188
196
  case 'reasoningOutputTokens':
189
197
  processedKey = REASONING_OUTPUT_TOKENS_METRIC_KEY
190
198
  break
@@ -667,6 +667,7 @@ class TextMapPropagator {
667
667
  }
668
668
 
669
669
  _extractBaggageItems (carrier, spanContext) {
670
+ removeAllBaggageItems()
670
671
  if (!this._hasPropagationStyle('extract', 'baggage')) return
671
672
  if (!carrier?.baggage) return
672
673
  const baggages = carrier.baggage.split(',')
@@ -23,7 +23,7 @@ function findPkg () {
23
23
  if (filePath === undefined) return {}
24
24
 
25
25
  try {
26
- return require(filePath)
26
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'))
27
27
  } catch {
28
28
  return {}
29
29
  }
@@ -27,16 +27,21 @@ class ApolloBasePlugin extends TracingPlugin {
27
27
  }
28
28
 
29
29
  end (ctx) {
30
- // Only synchronous operations would have `result` or `error` on `end`.
31
30
  if (!ctx.hasOwnProperty('result') && !ctx.hasOwnProperty('error')) return
31
+ this.onEnd(ctx)
32
32
  ctx?.currentStore?.span?.finish()
33
33
  }
34
34
 
35
35
  asyncStart (ctx) {
36
- ctx?.currentStore?.span.finish()
36
+ this.onAsyncStart(ctx)
37
+ ctx?.currentStore?.span?.finish()
37
38
  return ctx.parentStore
38
39
  }
39
40
 
41
+ onEnd (ctx) {}
42
+
43
+ onAsyncStart (ctx) {}
44
+
40
45
  getServiceName () {
41
46
  return this.serviceName({
42
47
  id: `${this.constructor.id}.${this.constructor.operation}`,
@@ -6,6 +6,7 @@ const plugins = {
6
6
  get '@aws-sdk/smithy-client' () { return require('../../../datadog-plugin-aws-sdk/src') },
7
7
  get '@azure/event-hubs' () { return require('../../../datadog-plugin-azure-event-hubs/src') },
8
8
  get '@azure/functions' () { return require('../../../datadog-plugin-azure-functions/src') },
9
+ get 'durable-functions' () { return require('../../../datadog-plugin-azure-durable-functions/src') },
9
10
  get '@azure/service-bus' () { return require('../../../datadog-plugin-azure-service-bus/src') },
10
11
  get '@cucumber/cucumber' () { return require('../../../datadog-plugin-cucumber/src') },
11
12
  get '@playwright/test' () { return require('../../../datadog-plugin-playwright/src') },
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
- const { readFileSync } = require('fs')
3
+ const { readFileSync, readdirSync, existsSync } = require('fs')
4
+ const path = require('path')
4
5
  const { getEnvironmentVariable, getEnvironmentVariables, getValueFromEnvSources } = require('../../config/helper')
5
6
  const {
6
7
  GIT_BRANCH,
@@ -102,8 +103,92 @@ function getGitHubEventPayload () {
102
103
  return JSON.parse(readFileSync(path, 'utf8'))
103
104
  }
104
105
 
106
+ function getJobIDFromDiagFile (runnerTemp) {
107
+ if (!runnerTemp || !existsSync(runnerTemp)) { return null }
108
+
109
+ // RUNNER_TEMP usually looks like:
110
+ // Linux/mac hosted: /home/runner/work/_temp
111
+ // Windows hosted: C:\actions-runner\_work\_temp
112
+ // Self-hosted (unix): /opt/actions-runner/_work/_temp
113
+
114
+ const workDir = path.dirname(runnerTemp) // .../work or .../_work
115
+ const runnerRoot = path.dirname(workDir) // /home/runner/ (runner root)
116
+
117
+ const dirs = [
118
+ path.join(runnerRoot, 'cached', '_diag'),
119
+ path.join(runnerRoot, '_diag'),
120
+ path.join(runnerRoot, 'actions-runner', 'cached', '_diag'),
121
+ path.join(runnerRoot, 'actions-runner', '_diag'),
122
+ ]
123
+
124
+ const isWin = process.platform === 'win32'
125
+
126
+ // Hardcoded fallbacks
127
+ if (isWin) {
128
+ dirs.push(
129
+ 'C:/actions-runner/cached/_diag',
130
+ 'C:/actions-runner/_diag',
131
+ )
132
+ } else {
133
+ dirs.push(
134
+ '/home/runner/actions-runner/cached/_diag',
135
+ '/home/runner/actions-runner/_diag',
136
+ '/opt/actions-runner/_diag',
137
+ )
138
+ }
139
+
140
+ // Remove duplicates
141
+ const possibleDiagsPaths = [...new Set(dirs)]
142
+
143
+ // This will hold the names of the worker log files that (potentially) contain the Job ID
144
+ let workerLogFiles = []
145
+
146
+ // This will hold the chosen diagnostics path (between the ones that are contemplated in possibleDiagsPath)
147
+ let chosenDiagPath = ''
148
+
149
+ for (const diagPath of possibleDiagsPaths) {
150
+ try {
151
+ // Obtain a list of fs.Dirent objects of the files in diagPath
152
+ const files = readdirSync(diagPath, { withFileTypes: true })
153
+
154
+ // Check if there are valid potential log files
155
+ const potentialLogs = files
156
+ .filter((file) => file.isFile() && file.name.startsWith('Worker_'))
157
+ .map((file) => file.name)
158
+
159
+ if (potentialLogs.length > 0) {
160
+ chosenDiagPath = diagPath
161
+ workerLogFiles = potentialLogs
162
+ break // No need to keep looking for more log files
163
+ }
164
+ } catch (error) {
165
+ // If the directory was not found, just look in the next one
166
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
167
+ continue
168
+ }
169
+
170
+ // Any other kind of error must force a return
171
+ return null
172
+ }
173
+ }
174
+
175
+ // Get the job ID via regex
176
+ for (const logFile of workerLogFiles) {
177
+ const filePath = path.posix.join(chosenDiagPath, logFile)
178
+ const content = readFileSync(filePath, 'utf8')
179
+
180
+ const match = content.match(/"job":\s*{[\s\S]*?"v"\s*:\s*(\d+)(?:\.0)?/)
181
+
182
+ // match[1] is the captured group with the display name
183
+ if (match && match[1]) { return match[1] }
184
+ }
185
+
186
+ return null
187
+ }
188
+
105
189
  module.exports = {
106
190
  normalizeRef,
191
+ getJobIDFromDiagFile,
107
192
  getCIMetadata () {
108
193
  const env = getEnvironmentVariables()
109
194
 
@@ -281,6 +366,8 @@ module.exports = {
281
366
  GITHUB_RUN_ATTEMPT,
282
367
  GITHUB_JOB,
283
368
  GITHUB_BASE_REF,
369
+ RUNNER_TEMP,
370
+ JOB_CHECK_RUN_ID,
284
371
  } = env
285
372
 
286
373
  const repositoryURL = `${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git`
@@ -290,7 +377,12 @@ module.exports = {
290
377
  pipelineURL = `${pipelineURL}/attempts/${GITHUB_RUN_ATTEMPT}`
291
378
  }
292
379
 
293
- const jobUrl = `${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/${GITHUB_SHA}/checks`
380
+ // Build the job url extracting the job ID. If extraction fails, job url is constructed as a generalized url
381
+ const GITHUB_JOB_ID = JOB_CHECK_RUN_ID ?? getJobIDFromDiagFile(RUNNER_TEMP)
382
+ const jobUrl =
383
+ GITHUB_JOB_ID === null
384
+ ? `${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/${GITHUB_SHA}/checks`
385
+ : `${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/job/${GITHUB_JOB_ID}`
294
386
 
295
387
  const ref = GITHUB_HEAD_REF || GITHUB_REF || ''
296
388
  const refKey = ref.includes('tags/') ? GIT_TAG : GIT_BRANCH
@@ -315,7 +407,7 @@ module.exports = {
315
407
  GITHUB_RUN_ID,
316
408
  GITHUB_RUN_ATTEMPT,
317
409
  }),
318
- [CI_JOB_ID]: GITHUB_JOB,
410
+ [CI_JOB_ID]: GITHUB_JOB_ID ?? GITHUB_JOB,
319
411
  }
320
412
  if (GITHUB_BASE_REF) { // `pull_request` or `pull_request_target` event
321
413
  tags[GIT_PULL_REQUEST_BASE_BRANCH] = GITHUB_BASE_REF
@@ -5,8 +5,10 @@ const tags = require('../../../../../ext/tags')
5
5
 
6
6
  const RESOURCE_NAME = tags.RESOURCE_NAME
7
7
  const SPAN_TYPE = tags.SPAN_TYPE
8
+ const SPAN_KIND = tags.SPAN_KIND
8
9
  const HTTP_URL = tags.HTTP_URL
9
10
  const HTTP_METHOD = tags.HTTP_METHOD
11
+ const HTTP_ROUTE = tags.HTTP_ROUTE
10
12
 
11
13
  const PROXY_HEADER_SYSTEM = 'x-dd-proxy'
12
14
  const PROXY_HEADER_START_TIME_MS = 'x-dd-proxy-request-time-ms'
@@ -15,12 +17,20 @@ const PROXY_HEADER_HTTPMETHOD = 'x-dd-proxy-httpmethod'
15
17
  const PROXY_HEADER_DOMAIN = 'x-dd-proxy-domain-name'
16
18
  const PROXY_HEADER_STAGE = 'x-dd-proxy-stage'
17
19
  const PROXY_HEADER_REGION = 'x-dd-proxy-region'
20
+ const PROXY_HEADER_RESOURCE_PATH = 'x-dd-proxy-resource-path'
21
+ const PROXY_HEADER_ACCOUNT_ID = 'x-dd-proxy-account-id'
22
+ const PROXY_HEADER_API_ID = 'x-dd-proxy-api-id'
23
+ const PROXY_HEADER_AWS_USER = 'x-dd-proxy-user'
18
24
 
19
25
  const supportedProxies = {
20
26
  'aws-apigateway': {
21
27
  spanName: 'aws.apigateway',
22
28
  component: 'aws-apigateway',
23
29
  },
30
+ 'aws-httpapi': {
31
+ spanName: 'aws.httpapi',
32
+ component: 'aws-httpapi',
33
+ },
24
34
  'azure-apim': {
25
35
  spanName: 'azure.apim',
26
36
  component: 'azure-apim',
@@ -55,10 +65,16 @@ function createInferredProxySpan (headers, childOf, tracer, reqCtx, traceCtx, co
55
65
  service: proxyContext.domainName || tracer._config.service,
56
66
  component: proxySpanInfo.component,
57
67
  [SPAN_TYPE]: 'web',
68
+ [SPAN_KIND]: 'server',
58
69
  [HTTP_METHOD]: proxyContext.method,
59
- [HTTP_URL]: proxyContext.domainName + proxyContext.path,
70
+ [HTTP_URL]: 'https://' + proxyContext.domainName + proxyContext.path,
60
71
  stage: proxyContext.stage,
61
72
  region: proxyContext.region,
73
+ ...(proxyContext.resourcePath && { [HTTP_ROUTE]: proxyContext.resourcePath }),
74
+ ...(proxyContext.accountId && { account_id: proxyContext.accountId }),
75
+ ...(proxyContext.apiId && { apiid: proxyContext.apiId }),
76
+ ...(proxyContext.region && { region: proxyContext.region }),
77
+ ...(proxyContext.awsUser && { aws_user: proxyContext.awsUser }),
62
78
  },
63
79
  }, traceCtx, config)
64
80
 
@@ -73,8 +89,22 @@ function createInferredProxySpan (headers, childOf, tracer, reqCtx, traceCtx, co
73
89
  }
74
90
 
75
91
  function setInferredProxySpanTags (span, proxyContext) {
76
- span.setTag(RESOURCE_NAME, `${proxyContext.method} ${proxyContext.path}`)
92
+ const resourcePath = proxyContext.resourcePath || proxyContext.path
93
+ span.setTag(RESOURCE_NAME, `${proxyContext.method} ${resourcePath}`)
77
94
  span.setTag('_dd.inferred_span', 1)
95
+
96
+ // Set dd_resource_key as API Gateway ARN if we have the required components
97
+ if (proxyContext.apiId && proxyContext.region) {
98
+ const partition = 'aws'
99
+ // API Gateway v1 (REST): arn:{partition}:apigateway:{region}::/restapis/{api-id}
100
+ // API Gateway v2 (HTTP): arn:{partition}:apigateway:{region}::/apis/{api-id}
101
+ const apiType = proxyContext.proxySystemName === 'aws-httpapi' ? 'apis' : 'restapis'
102
+ span.setTag(
103
+ 'dd_resource_key',
104
+ `arn:${partition}:apigateway:${proxyContext.region}::/${apiType}/${proxyContext.apiId}`
105
+ )
106
+ }
107
+
78
108
  return span
79
109
  }
80
110
 
@@ -98,6 +128,10 @@ function extractInferredProxyContext (headers) {
98
128
  domainName: headers[PROXY_HEADER_DOMAIN],
99
129
  proxySystemName: headers[PROXY_HEADER_SYSTEM],
100
130
  region: headers[PROXY_HEADER_REGION],
131
+ resourcePath: headers[PROXY_HEADER_RESOURCE_PATH],
132
+ accountId: headers[PROXY_HEADER_ACCOUNT_ID],
133
+ apiId: headers[PROXY_HEADER_API_ID],
134
+ awsUser: headers[PROXY_HEADER_AWS_USER],
101
135
  }
102
136
  }
103
137
 
@@ -410,6 +410,13 @@ const web = {
410
410
  },
411
411
  })
412
412
  },
413
+ setRouteOrEndpointTag (req) {
414
+ const context = contexts.get(req)
415
+
416
+ if (!context) return
417
+
418
+ applyRouteOrEndpointTag(context)
419
+ },
413
420
  }
414
421
 
415
422
  function addAllowHeaders (req, res, headers) {
@@ -481,18 +488,9 @@ function addRequestTags (context, spanType) {
481
488
  }
482
489
 
483
490
  function addResponseTags (context) {
484
- const { req, res, paths, span, inferredProxySpan, config } = context
491
+ const { req, res, inferredProxySpan, span } = context
485
492
 
486
- const route = paths.join('')
487
- if (route) {
488
- // Use http.route from trusted framework instrumentation
489
- span.setTag(HTTP_ROUTE, route)
490
- } else if (config.resourceRenamingEnabled) {
491
- // Route is unavailable, compute http.endpoint instead
492
- const url = span.context()._tags[HTTP_URL]
493
- const endpoint = url ? calculateHttpEndpoint(url) : '/'
494
- span.setTag(HTTP_ENDPOINT, endpoint)
495
- }
493
+ applyRouteOrEndpointTag(context)
496
494
 
497
495
  span.addTags({
498
496
  [HTTP_STATUS_CODE]: res.statusCode,
@@ -504,6 +502,28 @@ function addResponseTags (context) {
504
502
  web.addStatusError(req, res.statusCode)
505
503
  }
506
504
 
505
+ function applyRouteOrEndpointTag (context) {
506
+ const { paths, span, config } = context
507
+ if (!span) return
508
+ const tags = span.context()._tags
509
+ const route = paths.join('')
510
+
511
+ if (route) {
512
+ // Use http.route from trusted framework instrumentation.
513
+ span.setTag(HTTP_ROUTE, route)
514
+ return
515
+ }
516
+
517
+ if (!config.resourceRenamingEnabled || tags[HTTP_ENDPOINT]) {
518
+ return
519
+ }
520
+
521
+ // Route is unavailable, compute http.endpoint once.
522
+ const url = tags[HTTP_URL]
523
+ const endpoint = url ? calculateHttpEndpoint(url) : '/'
524
+ span.setTag(HTTP_ENDPOINT, endpoint)
525
+ }
526
+
507
527
  function addResourceTag (context) {
508
528
  const { req, span } = context
509
529
  const tags = span.context()._tags
@@ -6,7 +6,7 @@ const DatadogTracer = require('./tracer')
6
6
  const getConfig = require('./config')
7
7
  const runtimeMetrics = require('./runtime_metrics')
8
8
  const log = require('./log')
9
- const { setStartupLogPluginManager } = require('./startup-log')
9
+ const { setStartupLogPluginManager, startupLog } = require('./startup-log')
10
10
  const DynamicInstrumentation = require('./debugger')
11
11
  const telemetry = require('./telemetry')
12
12
  const nomenclature = require('./service-naming')
@@ -292,6 +292,7 @@ class Tracer extends NoopProxy {
292
292
  this._pluginManager.configure(config)
293
293
  DynamicInstrumentation.configure(config)
294
294
  setStartupLogPluginManager(this._pluginManager)
295
+ startupLog()
295
296
  }
296
297
  }
297
298
 
@@ -11,6 +11,7 @@ const log = require('../log')
11
11
  const { getValueFromEnvSources } = require('../config/helper')
12
12
 
13
13
  const { NODE_MAJOR } = require('../../../../version')
14
+ const processTags = require('../process-tags')
14
15
  // TODO: This environment variable may not be changed, since the agent expects a flush every ten seconds.
15
16
  // It is only a variable for testing. Think about alternatives.
16
17
  const DD_RUNTIME_METRICS_FLUSH_INTERVAL = getValueFromEnvSources('DD_RUNTIME_METRICS_FLUSH_INTERVAL') ?? '10000'
@@ -38,6 +39,12 @@ module.exports = {
38
39
  this.stop()
39
40
  const clientConfig = DogStatsDClient.generateClientConfig(config)
40
41
 
42
+ if (config.propagateProcessTags?.enabled) {
43
+ for (const tag of processTags.tagsArray) {
44
+ clientConfig.tags.push(tag)
45
+ }
46
+ }
47
+
41
48
  const trackEventLoop = config.runtimeMetrics.eventLoop !== false
42
49
  const trackGc = config.runtimeMetrics.gc !== false
43
50
 
@@ -8,6 +8,10 @@ const serverless = {
8
8
  opName: () => 'azure.functions.invoke',
9
9
  serviceName: identityService,
10
10
  },
11
+ 'azure-durable-functions': {
12
+ opName: () => 'azure.functions.invoke',
13
+ serviceName: identityService,
14
+ },
11
15
  },
12
16
  }
13
17
 
@@ -8,6 +8,10 @@ const serverless = {
8
8
  opName: () => 'azure.functions.invoke',
9
9
  serviceName: identityService,
10
10
  },
11
+ 'azure-durable-functions': {
12
+ opName: () => 'azure.functions.invoke',
13
+ serviceName: identityService,
14
+ },
11
15
  },
12
16
  }
13
17
 
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const { SAMPLING_MECHANISM_APPSEC } = require('../constants')
3
+ const { SAMPLING_MECHANISM_APPSEC, SAMPLING_MECHANISM_AI_GUARD } = require('../constants')
4
4
  const RateLimiter = require('../rate_limiter')
5
5
 
6
6
  /**
@@ -26,6 +26,7 @@ const PRODUCTS = {
26
26
  DSM: { id: 1 << 2 },
27
27
  DJM: { id: 1 << 3 },
28
28
  DBM: { id: 1 << 4 },
29
+ AI_GUARD: { id: 1 << 5, mechanism: SAMPLING_MECHANISM_AI_GUARD },
29
30
  }
30
31
 
31
32
  module.exports = {