dd-trace 5.25.0 → 5.26.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 (67) hide show
  1. package/LICENSE-3rdparty.csv +2 -0
  2. package/index.d.ts +10 -8
  3. package/init.js +60 -47
  4. package/package.json +5 -2
  5. package/packages/datadog-core/index.js +1 -3
  6. package/packages/datadog-core/src/storage.js +21 -0
  7. package/packages/datadog-instrumentations/src/express.js +1 -1
  8. package/packages/datadog-instrumentations/src/handlebars.js +40 -0
  9. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  10. package/packages/datadog-instrumentations/src/jest.js +6 -2
  11. package/packages/datadog-instrumentations/src/pug.js +23 -0
  12. package/packages/datadog-instrumentations/src/router.js +2 -3
  13. package/packages/datadog-plugin-amqplib/src/consumer.js +2 -1
  14. package/packages/datadog-plugin-aws-sdk/src/base.js +5 -0
  15. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +9 -7
  16. package/packages/datadog-plugin-aws-sdk/src/services/s3.js +34 -0
  17. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +10 -9
  18. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +59 -45
  19. package/packages/datadog-plugin-cypress/src/support.js +1 -0
  20. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -1
  21. package/packages/datadog-plugin-grpc/src/server.js +2 -1
  22. package/packages/datadog-plugin-http/src/client.js +42 -1
  23. package/packages/datadog-plugin-http2/src/client.js +26 -1
  24. package/packages/datadog-plugin-jest/src/index.js +2 -1
  25. package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -1
  26. package/packages/datadog-plugin-mocha/src/index.js +1 -1
  27. package/packages/datadog-plugin-moleculer/src/server.js +2 -2
  28. package/packages/datadog-plugin-rhea/src/consumer.js +2 -1
  29. package/packages/datadog-plugin-vitest/src/index.js +2 -1
  30. package/packages/dd-trace/src/appsec/api_security_sampler.js +50 -27
  31. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  32. package/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js +33 -16
  33. package/packages/dd-trace/src/appsec/iast/analyzers/template-injection-analyzer.js +18 -0
  34. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +3 -2
  35. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  36. package/packages/dd-trace/src/appsec/index.js +6 -6
  37. package/packages/dd-trace/src/appsec/recommended.json +353 -155
  38. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +1 -1
  39. package/packages/dd-trace/src/appsec/remote_config/index.js +0 -7
  40. package/packages/dd-trace/src/appsec/reporter.js +1 -0
  41. package/packages/dd-trace/src/config.js +13 -4
  42. package/packages/dd-trace/src/constants.js +6 -1
  43. package/packages/dd-trace/src/crashtracking/crashtracker.js +98 -0
  44. package/packages/dd-trace/src/crashtracking/index.js +15 -0
  45. package/packages/dd-trace/src/crashtracking/noop.js +8 -0
  46. package/packages/dd-trace/src/llmobs/sdk.js +1 -1
  47. package/packages/dd-trace/src/llmobs/span_processor.js +1 -1
  48. package/packages/dd-trace/src/llmobs/writers/spans/base.js +3 -0
  49. package/packages/dd-trace/src/log/index.js +10 -13
  50. package/packages/dd-trace/src/log/log.js +52 -0
  51. package/packages/dd-trace/src/log/writer.js +50 -19
  52. package/packages/dd-trace/src/noop/span.js +1 -0
  53. package/packages/dd-trace/src/opentelemetry/span.js +15 -0
  54. package/packages/dd-trace/src/opentracing/propagation/text_map.js +35 -22
  55. package/packages/dd-trace/src/opentracing/span.js +14 -0
  56. package/packages/dd-trace/src/opentracing/span_context.js +1 -0
  57. package/packages/dd-trace/src/plugins/tracing.js +3 -3
  58. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +121 -0
  59. package/packages/dd-trace/src/plugins/util/ip_extractor.js +0 -1
  60. package/packages/dd-trace/src/plugins/util/web.js +39 -11
  61. package/packages/dd-trace/src/proxy.js +5 -0
  62. package/packages/dd-trace/src/telemetry/logs/index.js +16 -11
  63. package/packages/dd-trace/src/telemetry/logs/log-collector.js +3 -8
  64. package/packages/dd-trace/src/telemetry/metrics.js +6 -1
  65. package/packages/dd-trace/src/util.js +16 -1
  66. package/version.js +4 -2
  67. /package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/{code-injection-sensitive-analyzer.js → tainted-range-based-sensitive-analyzer.js} +0 -0
@@ -180,6 +180,20 @@ class DatadogSpan {
180
180
  })
181
181
  }
182
182
 
183
+ addSpanPointer (ptrKind, ptrDir, ptrHash) {
184
+ const zeroContext = new SpanContext({
185
+ traceId: id('0'),
186
+ spanId: id('0')
187
+ })
188
+ const attributes = {
189
+ 'ptr.kind': ptrKind,
190
+ 'ptr.dir': ptrDir,
191
+ 'ptr.hash': ptrHash,
192
+ 'link.kind': 'span-pointer'
193
+ }
194
+ this.addLink(zeroContext, attributes)
195
+ }
196
+
183
197
  addEvent (name, attributesOrStartTime, startTime) {
184
198
  const event = { name }
185
199
  if (attributesOrStartTime) {
@@ -18,6 +18,7 @@ class DatadogSpanContext {
18
18
  this._tags = props.tags || {}
19
19
  this._sampling = props.sampling || {}
20
20
  this._spanSampling = undefined
21
+ this._links = props.links || []
21
22
  this._baggageItems = props.baggageItems || {}
22
23
  this._traceparent = props.traceparent
23
24
  this._tracestate = props.tracestate
@@ -101,9 +101,8 @@ class TracingPlugin extends Plugin {
101
101
  }
102
102
  }
103
103
 
104
- startSpan (name, { childOf, kind, meta, metrics, service, resource, type } = {}, enter = true) {
104
+ startSpan (name, { childOf, kind, meta, metrics, service, resource, type, extractedLinks } = {}, enter = true) {
105
105
  const store = storage.getStore()
106
-
107
106
  if (store && childOf === undefined) {
108
107
  childOf = store.span
109
108
  }
@@ -119,7 +118,8 @@ class TracingPlugin extends Plugin {
119
118
  ...meta,
120
119
  ...metrics
121
120
  },
122
- integrationName: type
121
+ integrationName: type,
122
+ links: extractedLinks
123
123
  })
124
124
 
125
125
  analyticsSampler.sample(span, this.config.measured)
@@ -0,0 +1,121 @@
1
+ const log = require('../../log')
2
+ const tags = require('../../../../../ext/tags')
3
+
4
+ const RESOURCE_NAME = tags.RESOURCE_NAME
5
+ const HTTP_ROUTE = tags.HTTP_ROUTE
6
+ const SPAN_KIND = tags.SPAN_KIND
7
+ const SPAN_TYPE = tags.SPAN_TYPE
8
+ const HTTP_URL = tags.HTTP_URL
9
+ const HTTP_METHOD = tags.HTTP_METHOD
10
+
11
+ const PROXY_HEADER_SYSTEM = 'x-dd-proxy'
12
+ const PROXY_HEADER_START_TIME_MS = 'x-dd-proxy-request-time-ms'
13
+ const PROXY_HEADER_PATH = 'x-dd-proxy-path'
14
+ const PROXY_HEADER_HTTPMETHOD = 'x-dd-proxy-httpmethod'
15
+ const PROXY_HEADER_DOMAIN = 'x-dd-proxy-domain-name'
16
+ const PROXY_HEADER_STAGE = 'x-dd-proxy-stage'
17
+
18
+ const supportedProxies = {
19
+ 'aws-apigateway': {
20
+ spanName: 'aws.apigateway',
21
+ component: 'aws-apigateway'
22
+ }
23
+ }
24
+
25
+ function createInferredProxySpan (headers, childOf, tracer, context) {
26
+ if (!headers) {
27
+ return null
28
+ }
29
+
30
+ if (!tracer._config?.inferredProxyServicesEnabled) {
31
+ return null
32
+ }
33
+
34
+ const proxyContext = extractInferredProxyContext(headers)
35
+
36
+ if (!proxyContext) {
37
+ return null
38
+ }
39
+
40
+ const proxySpanInfo = supportedProxies[proxyContext.proxySystemName]
41
+
42
+ log.debug(`Successfully extracted inferred span info ${proxyContext} for proxy: ${proxyContext.proxySystemName}`)
43
+
44
+ const span = tracer.startSpan(
45
+ proxySpanInfo.spanName,
46
+ {
47
+ childOf,
48
+ type: 'web',
49
+ startTime: proxyContext.requestTime,
50
+ tags: {
51
+ service: proxyContext.domainName || tracer._config.service,
52
+ component: proxySpanInfo.component,
53
+ [SPAN_KIND]: 'internal',
54
+ [SPAN_TYPE]: 'web',
55
+ [HTTP_METHOD]: proxyContext.method,
56
+ [HTTP_URL]: proxyContext.domainName + proxyContext.path,
57
+ [HTTP_ROUTE]: proxyContext.path,
58
+ stage: proxyContext.stage
59
+ }
60
+ }
61
+ )
62
+
63
+ tracer.scope().activate(span)
64
+ context.inferredProxySpan = span
65
+ childOf = span
66
+
67
+ log.debug('Successfully created inferred proxy span.')
68
+
69
+ setInferredProxySpanTags(span, proxyContext)
70
+
71
+ return childOf
72
+ }
73
+
74
+ function setInferredProxySpanTags (span, proxyContext) {
75
+ span.setTag(RESOURCE_NAME, `${proxyContext.method} ${proxyContext.path}`)
76
+ span.setTag('_dd.inferred_span', '1')
77
+ return span
78
+ }
79
+
80
+ function extractInferredProxyContext (headers) {
81
+ if (!(PROXY_HEADER_START_TIME_MS in headers)) {
82
+ return null
83
+ }
84
+
85
+ if (!(PROXY_HEADER_SYSTEM in headers && headers[PROXY_HEADER_SYSTEM] in supportedProxies)) {
86
+ log.debug(`Received headers to create inferred proxy span but headers include an unsupported proxy type ${headers}`)
87
+ return null
88
+ }
89
+
90
+ return {
91
+ requestTime: headers[PROXY_HEADER_START_TIME_MS]
92
+ ? parseInt(headers[PROXY_HEADER_START_TIME_MS], 10)
93
+ : null,
94
+ method: headers[PROXY_HEADER_HTTPMETHOD],
95
+ path: headers[PROXY_HEADER_PATH],
96
+ stage: headers[PROXY_HEADER_STAGE],
97
+ domainName: headers[PROXY_HEADER_DOMAIN],
98
+ proxySystemName: headers[PROXY_HEADER_SYSTEM]
99
+ }
100
+ }
101
+
102
+ function finishInferredProxySpan (context) {
103
+ const { req } = context
104
+
105
+ if (!context.inferredProxySpan) return
106
+
107
+ if (context.inferredProxySpanFinished && !req.stream) return
108
+
109
+ // context.config.hooks.request(context.inferredProxySpan, req, res) # TODO: Do we need this??
110
+
111
+ // Only close the inferred span if one was created
112
+ if (context.inferredProxySpan) {
113
+ context.inferredProxySpan.finish()
114
+ context.inferredProxySpanFinished = true
115
+ }
116
+ }
117
+
118
+ module.exports = {
119
+ createInferredProxySpan,
120
+ finishInferredProxySpan
121
+ }
@@ -8,7 +8,6 @@ const ipHeaderList = [
8
8
  'x-real-ip',
9
9
  'true-client-ip',
10
10
  'x-client-ip',
11
- 'x-forwarded',
12
11
  'forwarded-for',
13
12
  'x-cluster-client-ip',
14
13
  'fastly-client-ip',
@@ -10,6 +10,7 @@ const kinds = require('../../../../../ext/kinds')
10
10
  const urlFilter = require('./urlfilter')
11
11
  const { extractIp } = require('./ip_extractor')
12
12
  const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../../constants')
13
+ const { createInferredProxySpan, finishInferredProxySpan } = require('./inferred_proxy')
13
14
 
14
15
  const WEB = types.WEB
15
16
  const SERVER = kinds.SERVER
@@ -97,7 +98,7 @@ const web = {
97
98
  context.span.context()._name = name
98
99
  span = context.span
99
100
  } else {
100
- span = web.startChildSpan(tracer, name, req.headers)
101
+ span = web.startChildSpan(tracer, name, req)
101
102
  }
102
103
 
103
104
  context.tracer = tracer
@@ -253,9 +254,20 @@ const web = {
253
254
  },
254
255
 
255
256
  // Extract the parent span from the headers and start a new span as its child
256
- startChildSpan (tracer, name, headers) {
257
- const childOf = tracer.extract(FORMAT_HTTP_HEADERS, headers)
258
- const span = tracer.startSpan(name, { childOf })
257
+ startChildSpan (tracer, name, req) {
258
+ const headers = req.headers
259
+ const context = contexts.get(req)
260
+ let childOf = tracer.extract(FORMAT_HTTP_HEADERS, headers)
261
+
262
+ // we may have headers signaling a router proxy span should be created (such as for AWS API Gateway)
263
+ if (tracer._config?.inferredProxyServicesEnabled) {
264
+ const proxySpan = createInferredProxySpan(headers, childOf, tracer, context)
265
+ if (proxySpan) {
266
+ childOf = proxySpan
267
+ }
268
+ }
269
+
270
+ const span = tracer.startSpan(name, { childOf, extractedLinks: childOf?.links })
259
271
 
260
272
  return span
261
273
  },
@@ -263,13 +275,21 @@ const web = {
263
275
  // Validate a request's status code and then add error tags if necessary
264
276
  addStatusError (req, statusCode) {
265
277
  const context = contexts.get(req)
266
- const span = context.span
267
- const error = context.error
268
- const hasExistingError = span.context()._tags.error || span.context()._tags[ERROR_MESSAGE]
278
+ const { span, inferredProxySpan, error } = context
269
279
 
270
- if (!hasExistingError && !context.config.validateStatus(statusCode)) {
280
+ const spanHasExistingError = span.context()._tags.error || span.context()._tags[ERROR_MESSAGE]
281
+ const inferredSpanContext = inferredProxySpan?.context()
282
+ const inferredSpanHasExistingError = inferredSpanContext?._tags.error || inferredSpanContext?._tags[ERROR_MESSAGE]
283
+
284
+ const isValidStatusCode = context.config.validateStatus(statusCode)
285
+
286
+ if (!spanHasExistingError && !isValidStatusCode) {
271
287
  span.setTag(ERROR, error || true)
272
288
  }
289
+
290
+ if (inferredProxySpan && !inferredSpanHasExistingError && !isValidStatusCode) {
291
+ inferredProxySpan.setTag(ERROR, error || true)
292
+ }
273
293
  },
274
294
 
275
295
  // Add an error to the request
@@ -316,6 +336,8 @@ const web = {
316
336
  web.finishMiddleware(context)
317
337
 
318
338
  web.finishSpan(context)
339
+
340
+ finishInferredProxySpan(context)
319
341
  },
320
342
 
321
343
  obfuscateQs (config, url) {
@@ -426,7 +448,7 @@ function reactivate (req, fn) {
426
448
  }
427
449
 
428
450
  function addRequestTags (context, spanType) {
429
- const { req, span, config } = context
451
+ const { req, span, inferredProxySpan, config } = context
430
452
  const url = extractURL(req)
431
453
 
432
454
  span.addTags({
@@ -443,6 +465,7 @@ function addRequestTags (context, spanType) {
443
465
 
444
466
  if (clientIp) {
445
467
  span.setTag(HTTP_CLIENT_IP, clientIp)
468
+ inferredProxySpan?.setTag(HTTP_CLIENT_IP, clientIp)
446
469
  }
447
470
  }
448
471
 
@@ -450,7 +473,7 @@ function addRequestTags (context, spanType) {
450
473
  }
451
474
 
452
475
  function addResponseTags (context) {
453
- const { req, res, paths, span } = context
476
+ const { req, res, paths, span, inferredProxySpan } = context
454
477
 
455
478
  if (paths.length > 0) {
456
479
  span.setTag(HTTP_ROUTE, paths.join(''))
@@ -459,6 +482,9 @@ function addResponseTags (context) {
459
482
  span.addTags({
460
483
  [HTTP_STATUS_CODE]: res.statusCode
461
484
  })
485
+ inferredProxySpan?.addTags({
486
+ [HTTP_STATUS_CODE]: res.statusCode
487
+ })
462
488
 
463
489
  web.addStatusError(req, res.statusCode)
464
490
  }
@@ -477,7 +503,7 @@ function addResourceTag (context) {
477
503
  }
478
504
 
479
505
  function addHeaders (context) {
480
- const { req, res, config, span } = context
506
+ const { req, res, config, span, inferredProxySpan } = context
481
507
 
482
508
  config.headers.forEach(([key, tag]) => {
483
509
  const reqHeader = req.headers[key]
@@ -485,10 +511,12 @@ function addHeaders (context) {
485
511
 
486
512
  if (reqHeader) {
487
513
  span.setTag(tag || `${HTTP_REQUEST_HEADERS}.${key}`, reqHeader)
514
+ inferredProxySpan?.setTag(tag || `${HTTP_REQUEST_HEADERS}.${key}`, reqHeader)
488
515
  }
489
516
 
490
517
  if (resHeader) {
491
518
  span.setTag(tag || `${HTTP_RESPONSE_HEADERS}.${key}`, resHeader)
519
+ inferredProxySpan?.setTag(tag || `${HTTP_RESPONSE_HEADERS}.${key}`, resHeader)
492
520
  }
493
521
  })
494
522
  }
@@ -59,6 +59,11 @@ class Tracer extends NoopProxy {
59
59
 
60
60
  try {
61
61
  const config = new Config(options) // TODO: support dynamic code config
62
+
63
+ if (config.crashtracking.enabled) {
64
+ require('./crashtracking').start(config)
65
+ }
66
+
62
67
  telemetry.start(config, this._pluginManager)
63
68
 
64
69
  if (config.dogstatsd) {
@@ -35,18 +35,23 @@ function onLog (log) {
35
35
  }
36
36
 
37
37
  function onErrorLog (msg) {
38
- if (msg instanceof Error) {
39
- onLog({
40
- level: 'ERROR',
41
- message: msg.message,
42
- stack_trace: msg.stack
43
- })
44
- } else if (typeof msg === 'string') {
45
- onLog({
46
- level: 'ERROR',
47
- message: msg
48
- })
38
+ const { message, cause } = msg
39
+ if (!message && !cause) return
40
+
41
+ const telLog = {
42
+ level: 'ERROR',
43
+
44
+ // existing log.error(err) without message will be reported as 'Generic Error'
45
+ message: message ?? 'Generic Error'
49
46
  }
47
+
48
+ if (cause) {
49
+ telLog.stack_trace = cause.stack
50
+ const errorType = cause.name ?? 'Error'
51
+ telLog.message = `${errorType}: ${telLog.message}`
52
+ }
53
+
54
+ onLog(telLog)
50
55
  }
51
56
 
52
57
  function start (config) {
@@ -48,16 +48,11 @@ function sanitize (logEntry) {
48
48
  .map(line => line.replace(ddBasePath, ''))
49
49
 
50
50
  logEntry.stack_trace = stackLines.join(EOL)
51
- if (logEntry.stack_trace === '') {
52
- // If entire stack was removed, we'd just have a message saying "omitted"
53
- // in which case we'd rather not log it at all.
51
+ if (logEntry.stack_trace === '' && !logEntry.message) {
52
+ // If entire stack was removed and there is no message we'd rather not log it at all.
54
53
  return null
55
54
  }
56
55
 
57
- if (!isDDCode) {
58
- logEntry.message = 'omitted'
59
- }
60
-
61
56
  return logEntry
62
57
  }
63
58
 
@@ -82,7 +77,7 @@ const logCollector = {
82
77
  return true
83
78
  }
84
79
  } catch (e) {
85
- log.error(`Unable to add log to logCollector: ${e.message}`)
80
+ log.error('Unable to add log to logCollector: %s', e.message)
86
81
  }
87
82
  return false
88
83
  },
@@ -27,13 +27,18 @@ function hasPoints (metric) {
27
27
  return metric.points.length > 0
28
28
  }
29
29
 
30
+ let versionTag
31
+
30
32
  class Metric {
31
33
  constructor (namespace, metric, common, tags) {
32
34
  this.namespace = namespace.toString()
33
35
  this.metric = common ? metric : `nodejs.${metric}`
34
36
  this.tags = tagArray(tags)
35
37
  if (common) {
36
- this.tags.push(`version:${process.version}`)
38
+ if (versionTag === undefined) {
39
+ versionTag = `version:${process.version}`
40
+ }
41
+ this.tags.push(versionTag)
37
42
  }
38
43
  this.common = common
39
44
 
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const crypto = require('crypto')
3
4
  const path = require('path')
4
5
 
5
6
  function isTrue (str) {
@@ -73,11 +74,25 @@ function hasOwn (object, prop) {
73
74
  return Object.prototype.hasOwnProperty.call(object, prop)
74
75
  }
75
76
 
77
+ /**
78
+ * Generates a unique hash from an array of strings by joining them with | before hashing.
79
+ * Used to uniquely identify AWS requests for span pointers.
80
+ * @param {string[]} components - Array of strings to hash
81
+ * @returns {string} A 32-character hash uniquely identifying the components
82
+ */
83
+ function generatePointerHash (components) {
84
+ // If passing S3's ETag as a component, make sure any quotes have already been removed!
85
+ const dataToHash = components.join('|')
86
+ const hash = crypto.createHash('sha256').update(dataToHash).digest('hex')
87
+ return hash.substring(0, 32)
88
+ }
89
+
76
90
  module.exports = {
77
91
  isTrue,
78
92
  isFalse,
79
93
  isError,
80
94
  globMatch,
81
95
  calculateDDBasePath,
82
- hasOwn
96
+ hasOwn,
97
+ generatePointerHash
83
98
  }
package/version.js CHANGED
@@ -1,7 +1,9 @@
1
1
  'use strict'
2
2
 
3
- const ddMatches = require('./package.json').version.match(/^(\d+)\.(\d+)\.(\d+)/)
4
- const nodeMatches = process.versions.node.match(/^(\d+)\.(\d+)\.(\d+)/)
3
+ /* eslint-disable no-var */
4
+
5
+ var ddMatches = require('./package.json').version.match(/^(\d+)\.(\d+)\.(\d+)/)
6
+ var nodeMatches = process.versions.node.match(/^(\d+)\.(\d+)\.(\d+)/)
5
7
 
6
8
  module.exports = {
7
9
  DD_MAJOR: parseInt(ddMatches[1]),