dd-trace 4.18.0 → 4.22.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 (110) hide show
  1. package/LICENSE-3rdparty.csv +3 -2
  2. package/README.md +3 -3
  3. package/ext/kinds.d.ts +1 -0
  4. package/ext/kinds.js +2 -1
  5. package/ext/tags.d.ts +2 -1
  6. package/ext/tags.js +6 -1
  7. package/index.d.ts +29 -0
  8. package/package.json +11 -10
  9. package/packages/datadog-core/src/storage/async_resource.js +1 -1
  10. package/packages/datadog-esbuild/index.js +1 -20
  11. package/packages/datadog-instrumentations/src/aerospike.js +47 -0
  12. package/packages/datadog-instrumentations/src/apollo-server-core.js +41 -0
  13. package/packages/datadog-instrumentations/src/apollo-server.js +83 -0
  14. package/packages/datadog-instrumentations/src/graphql.js +18 -4
  15. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +1 -2
  16. package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
  17. package/packages/datadog-instrumentations/src/helpers/instrument.js +1 -1
  18. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  19. package/packages/datadog-instrumentations/src/http/client.js +10 -0
  20. package/packages/datadog-instrumentations/src/jest.js +11 -5
  21. package/packages/datadog-instrumentations/src/kafkajs.js +27 -0
  22. package/packages/datadog-instrumentations/src/next.js +18 -6
  23. package/packages/datadog-instrumentations/src/restify.js +14 -1
  24. package/packages/datadog-instrumentations/src/rhea.js +15 -9
  25. package/packages/datadog-plugin-aerospike/src/index.js +113 -0
  26. package/packages/datadog-plugin-graphql/src/resolve.js +26 -18
  27. package/packages/datadog-plugin-http/src/client.js +19 -2
  28. package/packages/datadog-plugin-kafkajs/src/consumer.js +59 -6
  29. package/packages/datadog-plugin-kafkajs/src/producer.js +64 -6
  30. package/packages/datadog-plugin-next/src/index.js +40 -14
  31. package/packages/dd-trace/src/appsec/activation.js +29 -0
  32. package/packages/dd-trace/src/appsec/addresses.js +3 -1
  33. package/packages/dd-trace/src/appsec/api_security_sampler.js +48 -0
  34. package/packages/dd-trace/src/appsec/blocked_templates.js +4 -1
  35. package/packages/dd-trace/src/appsec/blocking.js +95 -43
  36. package/packages/dd-trace/src/appsec/channels.js +5 -2
  37. package/packages/dd-trace/src/appsec/graphql.js +146 -0
  38. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  39. package/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js +105 -0
  40. package/packages/dd-trace/src/appsec/iast/iast-log.js +1 -1
  41. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +1 -1
  42. package/packages/dd-trace/src/appsec/iast/index.js +1 -1
  43. package/packages/dd-trace/src/appsec/iast/path-line.js +1 -1
  44. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +1 -1
  45. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/constants.js +7 -0
  46. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +12 -19
  47. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/header-sensitive-analyzer.js +20 -0
  48. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/json-sensitive-analyzer.js +6 -10
  49. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +18 -25
  50. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +79 -85
  51. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +27 -36
  52. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +14 -11
  53. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  54. package/packages/dd-trace/src/appsec/index.js +33 -32
  55. package/packages/dd-trace/src/appsec/recommended.json +1737 -120
  56. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
  57. package/packages/dd-trace/src/appsec/remote_config/index.js +36 -15
  58. package/packages/dd-trace/src/appsec/reporter.js +50 -34
  59. package/packages/dd-trace/src/appsec/rule_manager.js +9 -6
  60. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  61. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +28 -13
  62. package/packages/dd-trace/src/appsec/waf/waf_manager.js +0 -1
  63. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +17 -1
  64. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +75 -56
  65. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +22 -6
  66. package/packages/dd-trace/src/config.js +48 -7
  67. package/packages/dd-trace/src/datastreams/processor.js +166 -26
  68. package/packages/dd-trace/src/format.js +6 -1
  69. package/packages/dd-trace/src/id.js +12 -0
  70. package/packages/dd-trace/src/iitm.js +1 -1
  71. package/packages/dd-trace/src/log/channels.js +1 -1
  72. package/packages/dd-trace/src/noop/proxy.js +4 -0
  73. package/packages/dd-trace/src/opentelemetry/span.js +95 -2
  74. package/packages/dd-trace/src/opentelemetry/tracer.js +9 -10
  75. package/packages/dd-trace/src/opentracing/propagation/text_map.js +14 -5
  76. package/packages/dd-trace/src/opentracing/span.js +6 -0
  77. package/packages/dd-trace/src/opentracing/span_context.js +5 -2
  78. package/packages/dd-trace/src/plugin_manager.js +1 -1
  79. package/packages/dd-trace/src/plugins/ci_plugin.js +2 -1
  80. package/packages/dd-trace/src/plugins/database.js +1 -1
  81. package/packages/dd-trace/src/plugins/index.js +1 -0
  82. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  83. package/packages/dd-trace/src/plugins/util/ci.js +6 -19
  84. package/packages/dd-trace/src/plugins/util/git.js +4 -3
  85. package/packages/dd-trace/src/plugins/util/ip_extractor.js +7 -6
  86. package/packages/dd-trace/src/plugins/util/test.js +3 -2
  87. package/packages/dd-trace/src/plugins/util/url.js +26 -0
  88. package/packages/dd-trace/src/plugins/util/user-provided-git.js +4 -16
  89. package/packages/dd-trace/src/profiler.js +5 -3
  90. package/packages/dd-trace/src/profiling/config.js +26 -2
  91. package/packages/dd-trace/src/profiling/profiler.js +17 -10
  92. package/packages/dd-trace/src/profiling/profilers/events.js +264 -0
  93. package/packages/dd-trace/src/profiling/profilers/shared.js +39 -0
  94. package/packages/dd-trace/src/profiling/profilers/space.js +2 -1
  95. package/packages/dd-trace/src/profiling/profilers/wall.js +121 -58
  96. package/packages/dd-trace/src/proxy.js +25 -1
  97. package/packages/dd-trace/src/ritm.js +1 -1
  98. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +5 -0
  99. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +4 -0
  100. package/packages/dd-trace/src/span_processor.js +4 -0
  101. package/packages/dd-trace/src/spanleak.js +98 -0
  102. package/packages/dd-trace/src/startup-log.js +7 -1
  103. package/packages/dd-trace/src/telemetry/dependencies.js +56 -10
  104. package/packages/dd-trace/src/telemetry/index.js +136 -44
  105. package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
  106. package/packages/dd-trace/src/telemetry/send-data.js +47 -5
  107. package/packages/dd-trace/src/tracer.js +8 -2
  108. package/scripts/install_plugin_modules.js +11 -3
  109. package/packages/diagnostics_channel/index.js +0 -3
  110. package/packages/diagnostics_channel/src/index.js +0 -121
@@ -65,7 +65,7 @@ function wrapRenderToHTML (renderToHTML) {
65
65
 
66
66
  function wrapRenderErrorToHTML (renderErrorToHTML) {
67
67
  return function (err, req, res, pathname, query) {
68
- return instrument(req, res, () => renderErrorToHTML.apply(this, arguments))
68
+ return instrument(req, res, err, () => renderErrorToHTML.apply(this, arguments))
69
69
  }
70
70
  }
71
71
 
@@ -76,8 +76,8 @@ function wrapRenderToResponse (renderToResponse) {
76
76
  }
77
77
 
78
78
  function wrapRenderErrorToResponse (renderErrorToResponse) {
79
- return function (ctx) {
80
- return instrument(ctx.req, ctx.res, () => renderErrorToResponse.apply(this, arguments))
79
+ return function (ctx, err) {
80
+ return instrument(ctx.req, ctx.res, err, () => renderErrorToResponse.apply(this, arguments))
81
81
  }
82
82
  }
83
83
 
@@ -111,13 +111,23 @@ function getPageFromPath (page, dynamicRoutes = []) {
111
111
  return getPagePath(page)
112
112
  }
113
113
 
114
- function instrument (req, res, handler) {
114
+ function instrument (req, res, error, handler) {
115
+ if (typeof error === 'function') {
116
+ handler = error
117
+ error = null
118
+ }
119
+
115
120
  req = req.originalRequest || req
116
121
  res = res.originalResponse || res
117
122
 
118
123
  // TODO support middleware properly in the future?
119
124
  const isMiddleware = req.headers[MIDDLEWARE_HEADER]
120
- if (isMiddleware || requests.has(req)) return handler()
125
+ if (isMiddleware || requests.has(req)) {
126
+ if (error) {
127
+ errorChannel.publish({ error })
128
+ }
129
+ return handler()
130
+ }
121
131
 
122
132
  requests.add(req)
123
133
 
@@ -144,7 +154,9 @@ function instrument (req, res, handler) {
144
154
  function wrapServeStatic (serveStatic) {
145
155
  return function (req, res, path) {
146
156
  return instrument(req, res, () => {
147
- if (pageLoadChannel.hasSubscribers && path) pageLoadChannel.publish({ page: path })
157
+ if (pageLoadChannel.hasSubscribers && path) {
158
+ pageLoadChannel.publish({ page: path, isStatic: true })
159
+ }
148
160
 
149
161
  return serveStatic.apply(this, arguments)
150
162
  })
@@ -50,7 +50,20 @@ function wrapFn (fn) {
50
50
  enterChannel.publish({ req, route })
51
51
 
52
52
  try {
53
- return fn.apply(this, arguments)
53
+ const result = fn.apply(this, arguments)
54
+ if (result && typeof result === 'object' && typeof result.then === 'function') {
55
+ return result.then(function () {
56
+ nextChannel.publish({ req })
57
+ finishChannel.publish({ req })
58
+ return arguments[0]
59
+ }).catch(function (error) {
60
+ errorChannel.publish({ req, error })
61
+ nextChannel.publish({ req })
62
+ finishChannel.publish({ req })
63
+ throw error
64
+ })
65
+ }
66
+ return result
54
67
  } catch (error) {
55
68
  errorChannel.publish({ req, error })
56
69
  nextChannel.publish({ req })
@@ -22,7 +22,7 @@ const dispatchReceiveCh = channel('apm:rhea:receive:dispatch')
22
22
  const errorReceiveCh = channel('apm:rhea:receive:error')
23
23
  const finishReceiveCh = channel('apm:rhea:receive:finish')
24
24
 
25
- const contexts = new WeakMap()
25
+ const contexts = new WeakMap() // key: delivery Fn, val: context
26
26
 
27
27
  addHook({ name: 'rhea', versions: ['>=1'] }, rhea => {
28
28
  shimmer.wrap(rhea.message, 'encode', encode => function (msg) {
@@ -52,7 +52,8 @@ addHook({ name: 'rhea', versions: ['>=1'], file: 'lib/link.js' }, obj => {
52
52
  startSendCh.publish({ targetAddress, host, port, msg })
53
53
  const delivery = send.apply(this, arguments)
54
54
  const context = {
55
- asyncResource
55
+ asyncResource,
56
+ connection: this.connection
56
57
  }
57
58
  contexts.set(delivery, context)
58
59
 
@@ -80,7 +81,8 @@ addHook({ name: 'rhea', versions: ['>=1'], file: 'lib/link.js' }, obj => {
80
81
 
81
82
  if (msgObj.delivery) {
82
83
  const context = {
83
- asyncResource
84
+ asyncResource,
85
+ connection: this.connection
84
86
  }
85
87
  contexts.set(msgObj.delivery, context)
86
88
  msgObj.delivery.update = wrapDeliveryUpdate(msgObj.delivery, msgObj.delivery.update)
@@ -114,7 +116,7 @@ addHook({ name: 'rhea', versions: ['>=1'], file: 'lib/connection.js' }, Connecti
114
116
 
115
117
  asyncResource.runInAsyncScope(() => {
116
118
  errorReceiveCh.publish(error)
117
- beforeFinish(delivery, null)
119
+ exports.beforeFinish(delivery, null)
118
120
  finishReceiveCh.publish()
119
121
  })
120
122
  })
@@ -187,7 +189,7 @@ function patchCircularBuffer (proto, Session) {
187
189
  const state = remoteState && remoteState.constructor
188
190
  ? entry.remote_state.constructor.composite_type : undefined
189
191
  asyncResource.runInAsyncScope(() => {
190
- beforeFinish(entry, state)
192
+ exports.beforeFinish(entry, state)
191
193
  finishSendCh.publish()
192
194
  })
193
195
  }
@@ -217,13 +219,13 @@ function addToInFlightDeliveries (connection, delivery) {
217
219
  }
218
220
 
219
221
  function beforeFinish (delivery, state) {
220
- const obj = contexts.get(delivery)
221
- if (obj) {
222
+ const context = contexts.get(delivery)
223
+ if (context) {
222
224
  if (state) {
223
225
  dispatchReceiveCh.publish({ state })
224
226
  }
225
- if (obj.connection && obj.connection[inFlightDeliveries]) {
226
- obj.connection[inFlightDeliveries].delete(delivery)
227
+ if (context.connection && context.connection[inFlightDeliveries]) {
228
+ context.connection[inFlightDeliveries].delete(delivery)
227
229
  }
228
230
  }
229
231
  }
@@ -238,3 +240,7 @@ function getStateFromData (stateData) {
238
240
  }
239
241
  }
240
242
  }
243
+
244
+ module.exports.inFlightDeliveries = inFlightDeliveries
245
+ module.exports.beforeFinish = beforeFinish
246
+ module.exports.contexts = contexts
@@ -0,0 +1,113 @@
1
+ 'use strict'
2
+
3
+ const { storage } = require('../../datadog-core')
4
+ const DatabasePlugin = require('../../dd-trace/src/plugins/database')
5
+
6
+ const AEROSPIKE_PEER_SERVICE = 'aerospike.namespace'
7
+
8
+ class AerospikePlugin extends DatabasePlugin {
9
+ static get id () { return 'aerospike' }
10
+ static get operation () { return 'command' }
11
+ static get system () { return 'aerospike' }
12
+ static get prefix () {
13
+ return 'tracing:apm:aerospike:command'
14
+ }
15
+
16
+ static get peerServicePrecursors () {
17
+ return [AEROSPIKE_PEER_SERVICE]
18
+ }
19
+
20
+ bindStart (ctx) {
21
+ const { commandName, commandArgs } = ctx
22
+ const resourceName = commandName.slice(0, commandName.indexOf('Command'))
23
+ const store = storage.getStore()
24
+ const childOf = store ? store.span : null
25
+ const meta = getMeta(resourceName, commandArgs)
26
+
27
+ const span = this.startSpan(this.operationName(), {
28
+ childOf,
29
+ service: this.serviceName({ pluginConfig: this.config }),
30
+ type: 'aerospike',
31
+ kind: 'client',
32
+ resource: resourceName,
33
+ meta
34
+ }, false)
35
+
36
+ ctx.parentStore = store
37
+ ctx.currentStore = { ...store, span }
38
+
39
+ return ctx.currentStore
40
+ }
41
+
42
+ bindAsyncStart (ctx) {
43
+ if (ctx.currentStore) {
44
+ // have to manually trigger peer service calculation when using tracing channel
45
+ this.tagPeerService(ctx.currentStore.span)
46
+ ctx.currentStore.span.finish()
47
+ }
48
+ return ctx.parentStore
49
+ }
50
+
51
+ end (ctx) {
52
+ if (ctx.result) {
53
+ // have to manually trigger peer service calculation when using tracing channel
54
+ this.tagPeerService(ctx.currentStore.span)
55
+ ctx.currentStore.span.finish()
56
+ }
57
+ }
58
+
59
+ error (ctx) {
60
+ if (ctx.error) {
61
+ const error = ctx.error
62
+ const span = ctx.currentStore.span
63
+ span.setTag('error', error)
64
+ }
65
+ }
66
+ }
67
+
68
+ function getMeta (resourceName, commandArgs) {
69
+ let meta = {}
70
+ if (resourceName.includes('Index')) {
71
+ const [ns, set, bin, index] = commandArgs
72
+ meta = getMetaForIndex(ns, set, bin, index)
73
+ } else if (resourceName === 'Query') {
74
+ const { ns, set } = commandArgs[2]
75
+ meta = getMetaForQuery({ ns, set })
76
+ } else if (isKeyObject(commandArgs[0])) {
77
+ const { ns, set, key } = commandArgs[0]
78
+ meta = getMetaForKey(ns, set, key)
79
+ }
80
+ return meta
81
+ }
82
+
83
+ function getMetaForIndex (ns, set, bin, index) {
84
+ return {
85
+ [AEROSPIKE_PEER_SERVICE]: ns,
86
+ 'aerospike.setname': set,
87
+ 'aerospike.bin': bin,
88
+ 'aerospike.index': index
89
+ }
90
+ }
91
+
92
+ function getMetaForKey (ns, set, key) {
93
+ return {
94
+ 'aerospike.key': `${ns}:${set}:${key}`,
95
+ [AEROSPIKE_PEER_SERVICE]: ns,
96
+ 'aerospike.setname': set,
97
+ 'aerospike.userkey': key
98
+ }
99
+ }
100
+
101
+ function getMetaForQuery (queryObj) {
102
+ const { ns, set } = queryObj
103
+ return {
104
+ [AEROSPIKE_PEER_SERVICE]: ns,
105
+ 'aerospike.setname': set
106
+ }
107
+ }
108
+
109
+ function isKeyObject (obj) {
110
+ return obj && obj.ns !== undefined && obj.set !== undefined && obj.key !== undefined
111
+ }
112
+
113
+ module.exports = AerospikePlugin
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
4
+ const dc = require('dc-polyfill')
4
5
 
5
6
  const collapsedPathSym = Symbol('collapsedPaths')
6
7
 
@@ -14,8 +15,6 @@ class GraphQLResolvePlugin extends TracingPlugin {
14
15
  if (!shouldInstrument(this.config, path)) return
15
16
  const computedPathString = path.join('.')
16
17
 
17
- addResolver(context, info, args)
18
-
19
18
  if (this.config.collapse) {
20
19
  if (!context[collapsedPathSym]) {
21
20
  context[collapsedPathSym] = {}
@@ -55,6 +54,10 @@ class GraphQLResolvePlugin extends TracingPlugin {
55
54
  span.setTag(`graphql.variables.${name}`, variables[name])
56
55
  })
57
56
  }
57
+
58
+ if (this.resolverStartCh.hasSubscribers) {
59
+ this.resolverStartCh.publish({ context, resolverInfo: getResolverInfo(info, args) })
60
+ }
58
61
  }
59
62
 
60
63
  constructor (...args) {
@@ -69,6 +72,8 @@ class GraphQLResolvePlugin extends TracingPlugin {
69
72
  field.finishTime = span._getTime ? span._getTime() : 0
70
73
  field.error = field.error || err
71
74
  })
75
+
76
+ this.resolverStartCh = dc.channel('datadog:graphql:resolver:start')
72
77
  }
73
78
 
74
79
  configure (config) {
@@ -109,28 +114,31 @@ function withCollapse (responsePathAsArray) {
109
114
  }
110
115
  }
111
116
 
112
- function addResolver (context, info, args) {
113
- if (info.rootValue && !info.rootValue[info.fieldName]) {
114
- return
115
- }
117
+ function getResolverInfo (info, args) {
118
+ let resolverInfo = null
119
+ const resolverVars = {}
116
120
 
117
- if (!context.resolvers) {
118
- context.resolvers = {}
121
+ if (args && Object.keys(args).length) {
122
+ Object.assign(resolverVars, args)
119
123
  }
120
124
 
121
- const resolvers = context.resolvers
122
-
123
- if (!resolvers[info.fieldName]) {
124
- if (args && Object.keys(args).length) {
125
- resolvers[info.fieldName] = [args]
126
- } else {
127
- resolvers[info.fieldName] = []
125
+ const directives = info.fieldNodes[0].directives
126
+ for (const directive of directives) {
127
+ const argList = {}
128
+ for (const argument of directive['arguments']) {
129
+ argList[argument.name.value] = argument.value.value
128
130
  }
129
- } else {
130
- if (args && Object.keys(args).length) {
131
- resolvers[info.fieldName].push(args)
131
+
132
+ if (Object.keys(argList).length) {
133
+ resolverVars[directive.name.value] = argList
132
134
  }
133
135
  }
136
+
137
+ if (Object.keys(resolverVars).length) {
138
+ resolverInfo = { [info.fieldName]: resolverVars }
139
+ }
140
+
141
+ return resolverInfo
134
142
  }
135
143
 
136
144
  module.exports = GraphQLResolvePlugin
@@ -58,7 +58,7 @@ class HttpClientPlugin extends ClientPlugin {
58
58
  span._spanContext._trace.record = false
59
59
  }
60
60
 
61
- if (!(hasAmazonSignature(options) || !this.config.propagationFilter(uri))) {
61
+ if (this.shouldInjectTraceHeaders(options, uri)) {
62
62
  this.tracer.inject(span, HTTP_HEADERS, options.headers)
63
63
  }
64
64
 
@@ -71,6 +71,18 @@ class HttpClientPlugin extends ClientPlugin {
71
71
  return message.currentStore
72
72
  }
73
73
 
74
+ shouldInjectTraceHeaders (options, uri) {
75
+ if (hasAmazonSignature(options) && !this.config.enablePropagationWithAmazonHeaders) {
76
+ return false
77
+ }
78
+
79
+ if (!this.config.propagationFilter(uri)) {
80
+ return false
81
+ }
82
+
83
+ return true
84
+ }
85
+
74
86
  bindAsyncStart ({ parentStore }) {
75
87
  return parentStore
76
88
  }
@@ -98,7 +110,7 @@ class HttpClientPlugin extends ClientPlugin {
98
110
  span.finish()
99
111
  }
100
112
 
101
- error ({ span, error }) {
113
+ error ({ span, error, args, customRequestTimeout }) {
102
114
  if (!span) return
103
115
  if (error) {
104
116
  span.addTags({
@@ -107,6 +119,11 @@ class HttpClientPlugin extends ClientPlugin {
107
119
  [ERROR_STACK]: error.stack
108
120
  })
109
121
  } else {
122
+ // conditions for no error:
123
+ // 1. not using a custom agent instance with custom timeout specified
124
+ // 2. no invocation of `req.setTimeout`
125
+ if (!args.options.agent?.options.timeout && !customRequestTimeout) return
126
+
110
127
  span.setTag('error', 1)
111
128
  }
112
129
  }
@@ -1,19 +1,66 @@
1
1
  'use strict'
2
2
 
3
+ const { getMessageSize, CONTEXT_PROPAGATION_KEY } = require('../../dd-trace/src/datastreams/processor')
3
4
  const ConsumerPlugin = require('../../dd-trace/src/plugins/consumer')
4
5
 
5
6
  class KafkajsConsumerPlugin extends ConsumerPlugin {
6
7
  static get id () { return 'kafkajs' }
7
8
  static get operation () { return 'consume' }
8
9
 
9
- start ({ topic, partition, message, groupId }) {
10
- if (this.config.dsmEnabled) {
11
- this.tracer.decodeDataStreamsContext(message.headers['dd-pathway-ctx'])
12
- this.tracer
13
- .setCheckpoint(['direction:in', `group:${groupId}`, `topic:${topic}`, 'type:kafka'])
10
+ constructor () {
11
+ super(...arguments)
12
+ this.addSub('apm:kafkajs:consume:commit', message => this.commit(message))
13
+ }
14
+
15
+ /**
16
+ * Transform individual commit details sent by kafkajs' event reporter
17
+ * into actionable backlog items for DSM
18
+ *
19
+ * @typedef {object} ConsumerBacklog
20
+ * @property {number} type
21
+ * @property {string} consumer_group
22
+ * @property {string} topic
23
+ * @property {number} partition
24
+ * @property {number} offset
25
+ *
26
+ * @typedef {object} CommitEventItem
27
+ * @property {string} groupId
28
+ * @property {string} topic
29
+ * @property {number} partition
30
+ * @property {import('kafkajs/utils/long').Long} offset
31
+ *
32
+ * @param {CommitEventItem} commit
33
+ * @returns {ConsumerBacklog}
34
+ */
35
+ transformCommit (commit) {
36
+ const { groupId, partition, offset, topic } = commit
37
+ return {
38
+ partition,
39
+ topic,
40
+ type: 'kafka_commit',
41
+ offset: Number(offset),
42
+ consumer_group: groupId
43
+ }
44
+ }
45
+
46
+ commit (commitList) {
47
+ if (!this.config.dsmEnabled) return
48
+ const keys = [
49
+ 'consumer_group',
50
+ 'type',
51
+ 'partition',
52
+ 'offset',
53
+ 'topic'
54
+ ]
55
+ for (const commit of commitList.map(this.transformCommit)) {
56
+ if (keys.some(key => !commit.hasOwnProperty(key))) continue
57
+ this.tracer.setOffset(commit)
14
58
  }
59
+ }
60
+
61
+ start ({ topic, partition, message, groupId }) {
15
62
  const childOf = extract(this.tracer, message.headers)
16
- this.startSpan({
63
+ const span = this.startSpan({
17
64
  childOf,
18
65
  resource: topic,
19
66
  type: 'worker',
@@ -26,6 +73,12 @@ class KafkajsConsumerPlugin extends ConsumerPlugin {
26
73
  'kafka.partition': partition
27
74
  }
28
75
  })
76
+ if (this.config.dsmEnabled) {
77
+ const payloadSize = getMessageSize(message)
78
+ this.tracer.decodeDataStreamsContext(message.headers[CONTEXT_PROPAGATION_KEY])
79
+ this.tracer
80
+ .setCheckpoint(['direction:in', `group:${groupId}`, `topic:${topic}`, 'type:kafka'], span, payloadSize)
81
+ }
29
82
  }
30
83
  }
31
84
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  const ProducerPlugin = require('../../dd-trace/src/plugins/producer')
4
4
  const { encodePathwayContext } = require('../../dd-trace/src/datastreams/pathway')
5
+ const { getMessageSize, CONTEXT_PROPAGATION_KEY } = require('../../dd-trace/src/datastreams/processor')
6
+
5
7
  const BOOTSTRAP_SERVERS_KEY = 'messaging.kafka.bootstrap.servers'
6
8
 
7
9
  class KafkajsProducerPlugin extends ProducerPlugin {
@@ -9,13 +11,63 @@ class KafkajsProducerPlugin extends ProducerPlugin {
9
11
  static get operation () { return 'produce' }
10
12
  static get peerServicePrecursors () { return [BOOTSTRAP_SERVERS_KEY] }
11
13
 
14
+ constructor () {
15
+ super(...arguments)
16
+ this.addSub('apm:kafkajs:produce:commit', message => this.commit(message))
17
+ }
18
+
19
+ /**
20
+ * Transform individual commit details sent by kafkajs' event reporter
21
+ * into actionable backlog items for DSM
22
+ *
23
+ * @typedef {object} ProducerBacklog
24
+ * @property {number} type
25
+ * @property {string} topic
26
+ * @property {number} partition
27
+ * @property {number} offset
28
+ *
29
+ * @typedef {object} ProducerResponseItem
30
+ * @property {string} topic
31
+ * @property {number} partition
32
+ * @property {import('kafkajs/utils/long').Long} [offset]
33
+ * @property {import('kafkajs/utils/long').Long} [baseOffset]
34
+ *
35
+ * @param {ProducerResponseItem} response
36
+ * @returns {ProducerBacklog}
37
+ */
38
+ transformProduceResponse (response) {
39
+ // In produce protocol >=v3, the offset key changes from `offset` to `baseOffset`
40
+ const { topicName: topic, partition, offset, baseOffset } = response
41
+ const offsetAsLong = offset || baseOffset
42
+ return {
43
+ type: 'kafka_produce',
44
+ partition,
45
+ offset: offsetAsLong ? Number(offsetAsLong) : undefined,
46
+ topic
47
+ }
48
+ }
49
+
50
+ /**
51
+ *
52
+ * @param {ProducerResponseItem[]} commitList
53
+ * @returns {void}
54
+ */
55
+ commit (commitList) {
56
+ if (!this.config.dsmEnabled) return
57
+ const keys = [
58
+ 'type',
59
+ 'partition',
60
+ 'offset',
61
+ 'topic'
62
+ ]
63
+ for (const commit of commitList.map(this.transformProduceResponse)) {
64
+ if (keys.some(key => !commit.hasOwnProperty(key))) continue
65
+ this.tracer.setOffset(commit)
66
+ }
67
+ }
68
+
12
69
  start ({ topic, messages, bootstrapServers }) {
13
70
  let pathwayCtx
14
- if (this.config.dsmEnabled) {
15
- const dataStreamsContext = this.tracer
16
- .setCheckpoint(['direction:out', `topic:${topic}`, 'type:kafka'])
17
- pathwayCtx = encodePathwayContext(dataStreamsContext)
18
- }
19
71
  const span = this.startSpan({
20
72
  resource: topic,
21
73
  meta: {
@@ -31,8 +83,14 @@ class KafkajsProducerPlugin extends ProducerPlugin {
31
83
  }
32
84
  for (const message of messages) {
33
85
  if (typeof message === 'object') {
34
- if (this.config.dsmEnabled) message.headers['dd-pathway-ctx'] = pathwayCtx
35
86
  this.tracer.inject(span, 'text_map', message.headers)
87
+ if (this.config.dsmEnabled) {
88
+ const payloadSize = getMessageSize(message)
89
+ const dataStreamsContext = this.tracer
90
+ .setCheckpoint(['direction:out', `topic:${topic}`, 'type:kafka'], span, payloadSize)
91
+ pathwayCtx = encodePathwayContext(dataStreamsContext)
92
+ message.headers[CONTEXT_PROPAGATION_KEY] = pathwayCtx
93
+ }
36
94
  }
37
95
  }
38
96
  }
@@ -6,6 +6,8 @@ const analyticsSampler = require('../../dd-trace/src/analytics_sampler')
6
6
  const { COMPONENT } = require('../../dd-trace/src/constants')
7
7
  const web = require('../../dd-trace/src/plugins/util/web')
8
8
 
9
+ const errorPages = ['/404', '/500', '/_error', '/_not-found']
10
+
9
11
  class NextPlugin extends ServerPlugin {
10
12
  static get id () {
11
13
  return 'next'
@@ -40,6 +42,13 @@ class NextPlugin extends ServerPlugin {
40
42
  }
41
43
 
42
44
  error ({ span, error }) {
45
+ if (!span) {
46
+ const store = storage.getStore()
47
+ if (!store) return
48
+
49
+ span = store.span
50
+ }
51
+
43
52
  this.addError(error, span)
44
53
  }
45
54
 
@@ -50,10 +59,20 @@ class NextPlugin extends ServerPlugin {
50
59
 
51
60
  const span = store.span
52
61
  const error = span.context()._tags['error']
53
-
54
- if (!this.config.validateStatus(res.statusCode) && !error) {
55
- span.setTag('error', req.error || nextRequest.error || true)
56
- web.addError(req, req.error || nextRequest.error || true)
62
+ const requestError = req.error || nextRequest.error
63
+
64
+ if (requestError) {
65
+ // prioritize user-set errors from API routes
66
+ span.setTag('error', requestError)
67
+ web.addError(req, requestError)
68
+ } else if (error) {
69
+ // general error handling
70
+ span.setTag('error', error)
71
+ web.addError(req, requestError || error)
72
+ } else if (!this.config.validateStatus(res.statusCode)) {
73
+ // where there's no error, we still need to validate status
74
+ span.setTag('error', true)
75
+ web.addError(req, true)
57
76
  }
58
77
 
59
78
  span.addTags({
@@ -65,7 +84,7 @@ class NextPlugin extends ServerPlugin {
65
84
  span.finish()
66
85
  }
67
86
 
68
- pageLoad ({ page, isAppPath = false }) {
87
+ pageLoad ({ page, isAppPath = false, isStatic = false }) {
69
88
  const store = storage.getStore()
70
89
 
71
90
  if (!store) return
@@ -73,21 +92,28 @@ class NextPlugin extends ServerPlugin {
73
92
  const span = store.span
74
93
  const req = this._requests.get(span)
75
94
 
95
+ // safeguard against missing req in complicated timeout scenarios
96
+ if (!req) return
97
+
76
98
  // Only use error page names if there's not already a name
77
99
  const current = span.context()._tags['next.page']
78
- if (current && ['/404', '/500', '/_error', '/_not-found'].includes(page)) {
100
+ const isErrorPage = errorPages.includes(page)
101
+
102
+ if (current && isErrorPage) {
79
103
  return
80
104
  }
81
105
 
82
106
  // remove ending /route or /page for appDir projects
83
- if (isAppPath) page = page.substring(0, page.lastIndexOf('/'))
84
-
85
- // This is for static files whose 'page' includes the whole file path
86
- // For normal page matches, like /api/hello/[name] and a req.url like /api/hello/world,
87
- // nothing should happen
88
- // For page matches like /User/something/public/text.txt and req.url like /text.txt,
89
- // it should disregard the extra absolute path Next.js sometimes sets
90
- if (page.includes(req.url)) page = req.url
107
+ // need to check if not an error page too, as those are marked as app directory
108
+ // in newer versions
109
+ if (isAppPath && !isErrorPage) page = page.substring(0, page.lastIndexOf('/'))
110
+
111
+ // handle static resource
112
+ if (isStatic) {
113
+ page = req.url.includes('_next/static')
114
+ ? '/_next/static/*'
115
+ : '/public/*'
116
+ }
91
117
 
92
118
  span.addTags({
93
119
  [COMPONENT]: this.constructor.id,