dd-trace 5.89.0 → 5.91.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 (71) hide show
  1. package/LICENSE-3rdparty.csv +0 -3
  2. package/index.d.ts +38 -0
  3. package/package.json +12 -11
  4. package/packages/datadog-instrumentations/src/azure-durable-functions.js +75 -0
  5. package/packages/datadog-instrumentations/src/cucumber.js +58 -4
  6. package/packages/datadog-instrumentations/src/elasticsearch.js +12 -3
  7. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  8. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  9. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langgraph.js +30 -0
  10. package/packages/datadog-instrumentations/src/jest.js +31 -2
  11. package/packages/datadog-instrumentations/src/langgraph.js +7 -0
  12. package/packages/datadog-instrumentations/src/mocha/main.js +41 -12
  13. package/packages/datadog-instrumentations/src/mocha/utils.js +6 -1
  14. package/packages/datadog-instrumentations/src/mocha/worker.js +12 -4
  15. package/packages/datadog-instrumentations/src/playwright.js +20 -2
  16. package/packages/datadog-instrumentations/src/prisma.js +4 -2
  17. package/packages/datadog-instrumentations/src/vitest.js +69 -24
  18. package/packages/datadog-plugin-apollo/src/gateway/execute.js +8 -0
  19. package/packages/datadog-plugin-apollo/src/gateway/fetch.js +5 -0
  20. package/packages/datadog-plugin-apollo/src/gateway/plan.js +8 -0
  21. package/packages/datadog-plugin-apollo/src/gateway/postprocessing.js +5 -0
  22. package/packages/datadog-plugin-apollo/src/gateway/request.js +4 -3
  23. package/packages/datadog-plugin-apollo/src/gateway/validate.js +4 -3
  24. package/packages/datadog-plugin-apollo/src/index.js +28 -0
  25. package/packages/datadog-plugin-azure-durable-functions/src/index.js +49 -0
  26. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +12 -2
  27. package/packages/datadog-plugin-cypress/src/support.js +5 -7
  28. package/packages/datadog-plugin-jest/src/index.js +6 -0
  29. package/packages/datadog-plugin-langgraph/src/index.js +24 -0
  30. package/packages/datadog-plugin-langgraph/src/stream.js +41 -0
  31. package/packages/datadog-plugin-playwright/src/index.js +35 -8
  32. package/packages/dd-trace/src/aiguard/noop.js +1 -1
  33. package/packages/dd-trace/src/aiguard/sdk.js +14 -5
  34. package/packages/dd-trace/src/appsec/api_security_sampler.js +22 -1
  35. package/packages/dd-trace/src/appsec/index.js +11 -1
  36. package/packages/dd-trace/src/appsec/reporter.js +28 -11
  37. package/packages/dd-trace/src/appsec/waf/index.js +1 -1
  38. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +4 -4
  39. package/packages/dd-trace/src/config/defaults.js +1 -0
  40. package/packages/dd-trace/src/config/index.js +9 -1
  41. package/packages/dd-trace/src/config/supported-configurations.json +14 -0
  42. package/packages/dd-trace/src/constants.js +2 -0
  43. package/packages/dd-trace/src/crashtracking/crashtracker.js +1 -1
  44. package/packages/dd-trace/src/debugger/devtools_client/config.js +3 -0
  45. package/packages/dd-trace/src/dogstatsd.js +1 -0
  46. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +1 -8
  47. package/packages/dd-trace/src/encode/agentless-json.js +67 -22
  48. package/packages/dd-trace/src/exporters/agentless/index.js +58 -15
  49. package/packages/dd-trace/src/exporters/agentless/writer.js +35 -18
  50. package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
  51. package/packages/dd-trace/src/llmobs/plugins/anthropic.js +9 -0
  52. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chain.js +11 -0
  53. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +2 -0
  54. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +114 -0
  55. package/packages/dd-trace/src/llmobs/tagger.js +8 -0
  56. package/packages/dd-trace/src/opentracing/propagation/text_map.js +1 -0
  57. package/packages/dd-trace/src/plugins/apollo.js +7 -2
  58. package/packages/dd-trace/src/plugins/ci_plugin.js +2 -2
  59. package/packages/dd-trace/src/plugins/index.js +2 -0
  60. package/packages/dd-trace/src/plugins/util/ci.js +95 -3
  61. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +36 -2
  62. package/packages/dd-trace/src/plugins/util/test.js +7 -10
  63. package/packages/dd-trace/src/plugins/util/web.js +31 -11
  64. package/packages/dd-trace/src/priority_sampler.js +20 -2
  65. package/packages/dd-trace/src/process-tags/index.js +41 -34
  66. package/packages/dd-trace/src/profiling/profilers/wall.js +9 -1
  67. package/packages/dd-trace/src/proxy.js +4 -0
  68. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +7 -0
  69. package/packages/dd-trace/src/service-naming/schemas/v0/serverless.js +4 -0
  70. package/packages/dd-trace/src/service-naming/schemas/v1/serverless.js +4 -0
  71. package/packages/dd-trace/src/standalone/product.js +2 -1
@@ -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
 
@@ -150,7 +150,6 @@ const DD_CAPABILITIES_FAILED_TEST_REPLAY = '_dd.library_capabilities.failed_test
150
150
  const DD_CI_LIBRARY_CONFIGURATION_ERROR = '_dd.ci.library_configuration_error'
151
151
 
152
152
  const UNSUPPORTED_TIA_FRAMEWORKS = new Set(['playwright', 'vitest'])
153
- const UNSUPPORTED_TIA_FRAMEWORKS_PARALLEL_MODE = new Set(['cucumber', 'mocha'])
154
153
  const MINIMUM_FRAMEWORK_VERSION_FOR_EFD = {
155
154
  playwright: '>=1.38.0',
156
155
  }
@@ -170,7 +169,6 @@ const MINIMUM_FRAMEWORK_VERSION_FOR_FAILED_TEST_REPLAY = {
170
169
  playwright: '>=1.38.0',
171
170
  }
172
171
 
173
- const UNSUPPORTED_ATTEMPT_TO_FIX_FRAMEWORKS_PARALLEL_MODE = new Set(['mocha'])
174
172
  const NOT_SUPPORTED_GRANULARITY_IMPACTED_TESTS_FRAMEWORKS = new Set(['mocha', 'playwright', 'vitest'])
175
173
 
176
174
  const TEST_LEVEL_EVENT_TYPES = [
@@ -987,9 +985,8 @@ function getFormattedError (error, repositoryRoot) {
987
985
  return newError
988
986
  }
989
987
 
990
- function isTiaSupported (testFramework, isParallel) {
991
- return !(UNSUPPORTED_TIA_FRAMEWORKS.has(testFramework) ||
992
- (isParallel && UNSUPPORTED_TIA_FRAMEWORKS_PARALLEL_MODE.has(testFramework)))
988
+ function isTiaSupported (testFramework) {
989
+ return !UNSUPPORTED_TIA_FRAMEWORKS.has(testFramework)
993
990
  }
994
991
 
995
992
  function isEarlyFlakeDetectionSupported (testFramework, frameworkVersion) {
@@ -1016,12 +1013,12 @@ function isDisableSupported (testFramework, frameworkVersion) {
1016
1013
  : true
1017
1014
  }
1018
1015
 
1019
- function isAttemptToFixSupported (testFramework, isParallel, frameworkVersion) {
1016
+ function isAttemptToFixSupported (testFramework, frameworkVersion) {
1020
1017
  if (testFramework === 'playwright') {
1021
1018
  return satisfies(frameworkVersion, MINIMUM_FRAMEWORK_VERSION_FOR_ATTEMPT_TO_FIX[testFramework])
1022
1019
  }
1023
1020
 
1024
- return !(isParallel && UNSUPPORTED_ATTEMPT_TO_FIX_FRAMEWORKS_PARALLEL_MODE.has(testFramework))
1021
+ return true
1025
1022
  }
1026
1023
 
1027
1024
  function isFailedTestReplaySupported (testFramework, frameworkVersion) {
@@ -1030,9 +1027,9 @@ function isFailedTestReplaySupported (testFramework, frameworkVersion) {
1030
1027
  : true
1031
1028
  }
1032
1029
 
1033
- function getLibraryCapabilitiesTags (testFramework, isParallel, frameworkVersion) {
1030
+ function getLibraryCapabilitiesTags (testFramework, frameworkVersion) {
1034
1031
  return {
1035
- [DD_CAPABILITIES_TEST_IMPACT_ANALYSIS]: isTiaSupported(testFramework, isParallel)
1032
+ [DD_CAPABILITIES_TEST_IMPACT_ANALYSIS]: isTiaSupported(testFramework)
1036
1033
  ? '1'
1037
1034
  : undefined,
1038
1035
  [DD_CAPABILITIES_EARLY_FLAKE_DETECTION]: isEarlyFlakeDetectionSupported(testFramework, frameworkVersion)
@@ -1049,7 +1046,7 @@ function getLibraryCapabilitiesTags (testFramework, isParallel, frameworkVersion
1049
1046
  ? '1'
1050
1047
  : undefined,
1051
1048
  [DD_CAPABILITIES_TEST_MANAGEMENT_ATTEMPT_TO_FIX]:
1052
- isAttemptToFixSupported(testFramework, isParallel, frameworkVersion)
1049
+ isAttemptToFixSupported(testFramework, frameworkVersion)
1053
1050
  ? '5'
1054
1051
  : undefined,
1055
1052
  [DD_CAPABILITIES_FAILED_TEST_REPLAY]: isFailedTestReplaySupported(testFramework, frameworkVersion)
@@ -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
@@ -31,10 +31,21 @@ const {
31
31
  SAMPLING_LIMIT_DECISION,
32
32
  SAMPLING_AGENT_DECISION,
33
33
  DECISION_MAKER_KEY,
34
+ SAMPLING_KNUTH_RATE,
34
35
  } = require('./constants')
35
36
 
36
37
  const DEFAULT_KEY = 'service:,env:'
37
38
 
39
+ /**
40
+ * Formats a sampling rate as a string with up to 6 significant digits and no trailing zeros.
41
+ *
42
+ * @param {number} rate
43
+ * @returns {string}
44
+ */
45
+ function formatKnuthRate (rate) {
46
+ return Number(rate.toPrecision(6)).toString()
47
+ }
48
+
38
49
  const defaultSampler = new Sampler(AUTO_KEEP)
39
50
 
40
51
  /**
@@ -254,6 +265,7 @@ class PrioritySampler {
254
265
  */
255
266
  #getPriorityByRule (context, rule) {
256
267
  context._trace[SAMPLING_RULE_DECISION] = rule.sampleRate
268
+ context._trace.tags[SAMPLING_KNUTH_RATE] = formatKnuthRate(rule.sampleRate)
257
269
  context._sampling.mechanism = SAMPLING_MECHANISM_RULE
258
270
  if (rule.provenance === 'customer') context._sampling.mechanism = SAMPLING_MECHANISM_REMOTE_USER
259
271
  if (rule.provenance === 'dynamic') context._sampling.mechanism = SAMPLING_MECHANISM_REMOTE_DYNAMIC
@@ -290,9 +302,15 @@ class PrioritySampler {
290
302
  // TODO: Change underscored properties to private ones.
291
303
  const sampler = this._samplers[key] || this._samplers[DEFAULT_KEY]
292
304
 
293
- context._trace[SAMPLING_AGENT_DECISION] = sampler.rate()
305
+ const rate = sampler.rate()
306
+ context._trace[SAMPLING_AGENT_DECISION] = rate
294
307
 
295
- context._sampling.mechanism = sampler === defaultSampler ? SAMPLING_MECHANISM_DEFAULT : SAMPLING_MECHANISM_AGENT
308
+ if (sampler === defaultSampler) {
309
+ context._sampling.mechanism = SAMPLING_MECHANISM_DEFAULT
310
+ } else {
311
+ context._trace.tags[SAMPLING_KNUTH_RATE] = formatKnuthRate(rate)
312
+ context._sampling.mechanism = SAMPLING_MECHANISM_AGENT
313
+ }
296
314
 
297
315
  return sampler.isSampled(context) ? AUTO_KEEP : AUTO_REJECT
298
316
  }
@@ -12,8 +12,19 @@ const ENTRYPOINT_PATH = require.main?.filename || ''
12
12
  // entrypoint.basedir = baz
13
13
  // package.json.name = <from package.json>
14
14
 
15
- // process tags are constant throughout the lifetime of a process
16
- function getProcessTags () {
15
+ /**
16
+ * Sanitize a process tag value
17
+ *
18
+ * @param {string} value
19
+ * @returns {string}
20
+ */
21
+ function sanitize (value) {
22
+ return String(value)
23
+ .toLowerCase()
24
+ .replaceAll(/[^a-zA-Z0-9/_.-]+/g, '_')
25
+ }
26
+
27
+ function buildProcessTags (config) {
17
28
  // Lazy load pkg to avoid issues with require.main during test initialization
18
29
  const pkg = require('../pkg')
19
30
 
@@ -35,6 +46,13 @@ function getProcessTags () {
35
46
  ['package.json.name', pkg.name || undefined],
36
47
  ]
37
48
 
49
+ // If config dependent tags keep growing, we should consider moving this into a function
50
+ if (config?.isServiceNameInferred) {
51
+ tags.push(['svc.auto', config.service])
52
+ } else if (config) {
53
+ tags.push(['svc.user', true])
54
+ }
55
+
38
56
  const tagsArray = []
39
57
  const tagsObject = {}
40
58
 
@@ -46,38 +64,27 @@ function getProcessTags () {
46
64
  }
47
65
  }
48
66
 
49
- const serialized = tagsArray.join(',')
50
-
51
- return {
52
- tags,
53
- serialized,
54
- tagsObject,
55
- tagsArray,
56
- }
67
+ processTags.tags = tags
68
+ processTags.serialized = tagsArray.join(',')
69
+ processTags.tagsObject = tagsObject
70
+ processTags.tagsArray = tagsArray
57
71
  }
58
72
 
59
- // Export the singleton
60
- module.exports = getProcessTags()
61
-
62
- module.exports.TRACING_FIELD_NAME = '_dd.tags.process'
63
- module.exports.DSM_FIELD_NAME = 'ProcessTags'
64
- module.exports.PROFILING_FIELD_NAME = 'process_tags'
65
- module.exports.DYNAMIC_INSTRUMENTATION_FIELD_NAME = 'process_tags'
66
- module.exports.TELEMETRY_FIELD_NAME = 'process_tags'
67
- module.exports.REMOTE_CONFIG_FIELD_NAME = 'process_tags'
68
- module.exports.CRASH_TRACKING_FIELD_NAME = 'process_tags'
69
- module.exports.CLIENT_TRACE_STATISTICS_FIELD_NAME = 'ProcessTags'
70
-
71
- /**
72
- * Sanitize a process tag value
73
- *
74
- * @param {string} value
75
- * @returns {string}
76
- */
77
- function sanitize (value) {
78
- return String(value)
79
- .toLowerCase()
80
- .replaceAll(/[^a-zA-Z0-9/_.-]+/g, '_')
73
+ // Singleton with constant defaults so pre-init reads don't blow up
74
+ const processTags = module.exports = {
75
+ initialize (config) {
76
+ // check if one of the properties added during build exist and if so return
77
+ if (processTags.tags) return
78
+ buildProcessTags(config)
79
+ },
80
+
81
+ TRACING_FIELD_NAME: '_dd.tags.process',
82
+ DSM_FIELD_NAME: 'ProcessTags',
83
+ PROFILING_FIELD_NAME: 'process_tags',
84
+ DYNAMIC_INSTRUMENTATION_FIELD_NAME: 'process_tags',
85
+ TELEMETRY_FIELD_NAME: 'process_tags',
86
+ REMOTE_CONFIG_FIELD_NAME: 'process_tags',
87
+ CRASH_TRACKING_FIELD_NAME: 'process_tags',
88
+ CLIENT_TRACE_STATISTICS_FIELD_NAME: 'ProcessTags',
89
+ sanitize,
81
90
  }
82
-
83
- module.exports.sanitize = sanitize
@@ -290,7 +290,15 @@ class NativeWallProfiler {
290
290
  }
291
291
 
292
292
  profilingContext = { spanId, rootSpanId, webTags }
293
- span[ProfilingContext] = profilingContext
293
+ // Don't cache if endpoint collection is enabled and webTags is undefined but
294
+ // the span's type hasn't been set yet. TracingPlugin.startSpan() calls
295
+ // enterWith() before the plugin sets span.type='web' via addRequestTags(),
296
+ // so the first enterCh event fires before the type is known. Without this
297
+ // guard we'd cache webTags=undefined and then serve that stale value on the
298
+ // subsequent activation (when span.type='web' is already set).
299
+ if (!this.#endpointCollectionEnabled || webTags !== undefined || context._tags['span.type']) {
300
+ span[ProfilingContext] = profilingContext
301
+ }
294
302
  }
295
303
  return profilingContext
296
304
  }
@@ -13,6 +13,7 @@ const nomenclature = require('./service-naming')
13
13
  const PluginManager = require('./plugin_manager')
14
14
  const NoopDogStatsDClient = require('./noop/dogstatsd')
15
15
  const { IS_SERVERLESS } = require('./serverless')
16
+ const processTags = require('./process-tags')
16
17
  const {
17
18
  setBaggageItem,
18
19
  getBaggageItem,
@@ -102,6 +103,9 @@ class Tracer extends NoopProxy {
102
103
  try {
103
104
  const config = getConfig(options) // TODO: support dynamic code config
104
105
 
106
+ // Add config dependent process tags
107
+ processTags.initialize(config)
108
+
105
109
  // Configure propagation hash manager for process tags + container tags
106
110
  const propagationHash = require('./propagation-hash')
107
111
  propagationHash.configure(config)
@@ -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 = {