dd-trace 2.3.1 → 2.4.2

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 (53) hide show
  1. package/ci/init.js +26 -2
  2. package/index.d.ts +51 -0
  3. package/package.json +2 -2
  4. package/packages/datadog-instrumentations/index.js +10 -0
  5. package/packages/datadog-instrumentations/src/amqp10.js +70 -0
  6. package/packages/datadog-instrumentations/src/amqplib.js +58 -0
  7. package/packages/datadog-instrumentations/src/cassandra-driver.js +191 -0
  8. package/packages/datadog-instrumentations/src/cucumber.js +27 -12
  9. package/packages/datadog-instrumentations/src/helpers/hook.js +44 -0
  10. package/packages/datadog-instrumentations/src/helpers/instrument.js +31 -58
  11. package/packages/datadog-instrumentations/src/http/client.js +170 -0
  12. package/packages/datadog-instrumentations/src/http/server.js +61 -0
  13. package/packages/datadog-instrumentations/src/http.js +4 -0
  14. package/packages/datadog-instrumentations/src/mocha.js +139 -0
  15. package/packages/datadog-instrumentations/src/mongodb-core.js +179 -0
  16. package/packages/datadog-instrumentations/src/net.js +117 -0
  17. package/packages/datadog-instrumentations/src/pg.js +75 -0
  18. package/packages/datadog-instrumentations/src/rhea.js +224 -0
  19. package/packages/datadog-instrumentations/src/tedious.js +66 -0
  20. package/packages/datadog-plugin-amqp10/src/index.js +79 -122
  21. package/packages/datadog-plugin-amqplib/src/index.js +77 -142
  22. package/packages/datadog-plugin-cassandra-driver/src/index.js +52 -224
  23. package/packages/datadog-plugin-cucumber/src/index.js +3 -1
  24. package/packages/datadog-plugin-elasticsearch/src/index.js +4 -2
  25. package/packages/datadog-plugin-http/src/client.js +112 -252
  26. package/packages/datadog-plugin-http/src/index.js +29 -3
  27. package/packages/datadog-plugin-http/src/server.js +54 -32
  28. package/packages/datadog-plugin-jest/src/jest-environment.js +3 -3
  29. package/packages/datadog-plugin-jest/src/jest-jasmine2.js +5 -3
  30. package/packages/datadog-plugin-mocha/src/index.js +96 -207
  31. package/packages/datadog-plugin-mongodb-core/src/index.js +119 -3
  32. package/packages/datadog-plugin-net/src/index.js +65 -121
  33. package/packages/datadog-plugin-next/src/index.js +10 -10
  34. package/packages/datadog-plugin-pg/src/index.js +32 -69
  35. package/packages/datadog-plugin-rhea/src/index.js +59 -225
  36. package/packages/datadog-plugin-tedious/src/index.js +38 -86
  37. package/packages/dd-trace/lib/version.js +1 -1
  38. package/packages/dd-trace/src/appsec/recommended.json +235 -315
  39. package/packages/dd-trace/src/config.js +6 -0
  40. package/packages/dd-trace/src/iitm.js +5 -1
  41. package/packages/dd-trace/src/loader.js +6 -4
  42. package/packages/dd-trace/src/noop/tracer.js +4 -0
  43. package/packages/dd-trace/src/opentracing/propagation/text_map.js +34 -1
  44. package/packages/dd-trace/src/opentracing/span.js +34 -0
  45. package/packages/dd-trace/src/plugin_manager.js +4 -0
  46. package/packages/dd-trace/src/plugins/plugin.js +3 -1
  47. package/packages/dd-trace/src/plugins/util/web.js +99 -93
  48. package/packages/dd-trace/src/proxy.js +4 -0
  49. package/packages/dd-trace/src/ritm.js +60 -25
  50. package/packages/dd-trace/src/tracer.js +16 -0
  51. package/packages/datadog-plugin-mongodb-core/src/legacy.js +0 -59
  52. package/packages/datadog-plugin-mongodb-core/src/unified.js +0 -138
  53. package/packages/datadog-plugin-mongodb-core/src/util.js +0 -143
@@ -109,6 +109,11 @@ class Config {
109
109
  process.env.DD_TRACE_EXPERIMENTAL_B3_ENABLED,
110
110
  false
111
111
  )
112
+ const DD_TRACE_TRACEPARENT_ENABLED = coalesce(
113
+ options.experimental && options.experimental.traceparent,
114
+ process.env.DD_TRACE_EXPERIMENTAL_TRACEPARENT_ENABLED,
115
+ false
116
+ )
112
117
  const DD_TRACE_RUNTIME_ID_ENABLED = coalesce(
113
118
  options.experimental && options.experimental.runtimeId,
114
119
  process.env.DD_TRACE_EXPERIMENTAL_RUNTIME_ID_ENABLED,
@@ -187,6 +192,7 @@ class Config {
187
192
  this.runtimeMetrics = isTrue(DD_RUNTIME_METRICS_ENABLED)
188
193
  this.experimental = {
189
194
  b3: isTrue(DD_TRACE_B3_ENABLED),
195
+ traceparent: isTrue(DD_TRACE_TRACEPARENT_ENABLED),
190
196
  runtimeId: isTrue(DD_TRACE_RUNTIME_ID_ENABLED),
191
197
  exporter: DD_TRACE_EXPORTER,
192
198
  enableGetRumData: isTrue(DD_TRACE_GET_RUM_DATA_ENABLED),
@@ -8,5 +8,9 @@ if (semver.satisfies(process.versions.node, '^12.20.0 || >=14.13.1')) {
8
8
  } else {
9
9
  logger.warn('ESM is not fully supported by this version of Node.js, ' +
10
10
  'so dd-trace will not intercept ESM loading.')
11
- module.exports = () => {}
11
+ module.exports = () => ({
12
+ unhook: () => {}
13
+ })
14
+ module.exports.addHook = () => {}
15
+ module.exports.removeHook = () => {}
12
16
  }
@@ -1,8 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const semver = require('semver')
4
- const hook = require('./ritm')
5
- const esmHook = require('./iitm')
4
+ const Hook = require('../../datadog-instrumentations/src/helpers/hook')
6
5
  const parse = require('module-details-from-path')
7
6
  const path = require('path')
8
7
  const uniq = require('lodash.uniq')
@@ -18,6 +17,7 @@ class Loader {
18
17
 
19
18
  reload (plugins) {
20
19
  this._plugins = plugins
20
+ this._patched = []
21
21
 
22
22
  const instrumentations = Array.from(this._plugins.keys())
23
23
  .reduce((prev, current) => prev.concat(current), [])
@@ -28,8 +28,10 @@ class Loader {
28
28
  this._names = new Set(instrumentations
29
29
  .map(instrumentation => filename(instrumentation)))
30
30
 
31
- hook(instrumentedModules, this._hookModule.bind(this))
32
- esmHook(instrumentedModules, this._hookModule.bind(this))
31
+ this._hook && this._hook.unhook()
32
+ this._hook = Hook(instrumentedModules, (moduleExports, moduleName, moduleBaseDir) => {
33
+ return this._hookModule(moduleExports, moduleName, moduleBaseDir)
34
+ })
33
35
  }
34
36
 
35
37
  load (instrumentation, config) {
@@ -38,6 +38,10 @@ class NoopTracer extends Tracer {
38
38
  _startSpan (name, options) {
39
39
  return this._span
40
40
  }
41
+
42
+ setUser () {
43
+ return this
44
+ }
41
45
  }
42
46
 
43
47
  module.exports = NoopTracer
@@ -26,6 +26,8 @@ const baggageExpr = new RegExp(`^${baggagePrefix}(.+)$`)
26
26
  const ddKeys = [traceKey, spanKey, samplingKey, originKey]
27
27
  const b3Keys = [b3TraceKey, b3SpanKey, b3ParentKey, b3SampledKey, b3FlagsKey, b3HeaderKey]
28
28
  const logKeys = ddKeys.concat(b3Keys)
29
+ const traceparentExpr = /^(\d{2})-([A-Fa-f0-9]{32})-([A-Fa-f0-9]{16})-(\d{2})$/i
30
+ const traceparentKey = 'traceparent'
29
31
 
30
32
  class TextMapPropagator {
31
33
  constructor (config) {
@@ -40,6 +42,7 @@ class TextMapPropagator {
40
42
  this._injectSamplingPriority(spanContext, carrier)
41
43
  this._injectBaggageItems(spanContext, carrier)
42
44
  this._injectB3(spanContext, carrier)
45
+ this._injectTraceparent(spanContext, carrier)
43
46
 
44
47
  log.debug(() => `Inject into carrier: ${JSON.stringify(pick(carrier, logKeys))}.`)
45
48
  }
@@ -92,8 +95,20 @@ class TextMapPropagator {
92
95
  }
93
96
  }
94
97
 
98
+ _injectTraceparent (spanContext, carrier) {
99
+ if (!this._config.experimental.traceparent) return
100
+
101
+ const sampling = spanContext._sampling.priority >= AUTO_KEEP ? '01' : '00'
102
+ const traceId = spanContext._traceId.toString('hex').padStart(32, '0')
103
+ const spanId = spanContext._spanId.toString('hex').padStart(16, '0')
104
+ carrier[traceparentKey] = `01-${traceId}-${spanId}-${sampling}`
105
+ }
106
+
95
107
  _extractSpanContext (carrier) {
96
- return this._extractDatadogContext(carrier) || this._extractB3Context(carrier) || this._extractSqsdContext(carrier)
108
+ return this._extractDatadogContext(carrier) ||
109
+ this._extractTraceparentContext(carrier) ||
110
+ this._extractB3Context(carrier) ||
111
+ this._extractSqsdContext(carrier)
97
112
  }
98
113
 
99
114
  _extractDatadogContext (carrier) {
@@ -146,6 +161,24 @@ class TextMapPropagator {
146
161
  return this._extractDatadogContext(parsed)
147
162
  }
148
163
 
164
+ _extractTraceparentContext (carrier) {
165
+ if (!this._config.experimental.traceparent) return null
166
+
167
+ const headerValue = carrier[traceparentKey]
168
+ if (!headerValue) {
169
+ return null
170
+ }
171
+ const matches = headerValue.match(traceparentExpr)
172
+ if (matches.length) {
173
+ return new DatadogSpanContext({
174
+ traceId: id(matches[2], 16),
175
+ spanId: id(matches[3], 16),
176
+ sampling: { priority: matches[4] === '01' ? 1 : 0 }
177
+ })
178
+ }
179
+ return null
180
+ }
181
+
149
182
  _extractGenericContext (carrier, traceKey, spanKey, radix) {
150
183
  if (carrier[traceKey] && carrier[spanKey]) {
151
184
  return new DatadogSpanContext({
@@ -1,16 +1,23 @@
1
1
  'use strict'
2
2
 
3
+ // TODO (new internal tracer): use DC events for lifecycle metrics and test them
4
+
3
5
  const opentracing = require('opentracing')
4
6
  const now = require('performance-now')
7
+ const semver = require('semver')
5
8
  const Span = opentracing.Span
6
9
  const SpanContext = require('./span_context')
7
10
  const id = require('../id')
8
11
  const tagger = require('../tagger')
12
+ const metrics = require('../metrics')
9
13
  const log = require('../log')
10
14
  const { storage } = require('../../../datadog-core')
11
15
 
12
16
  const { DD_TRACE_EXPERIMENTAL_STATE_TRACKING } = process.env
13
17
 
18
+ const unfinishedRegistry = createRegistry('unfinished')
19
+ const finishedRegistry = createRegistry('finished')
20
+
14
21
  class DatadogSpan extends Span {
15
22
  constructor (tracer, processor, prioritySampler, fields, debug) {
16
23
  super()
@@ -25,6 +32,7 @@ class DatadogSpan extends Span {
25
32
  this._processor = processor
26
33
  this._prioritySampler = prioritySampler
27
34
  this._store = storage.getStore()
35
+ this._name = operationName
28
36
 
29
37
  this._spanContext = this._createContext(parent)
30
38
  this._spanContext._name = operationName
@@ -32,6 +40,13 @@ class DatadogSpan extends Span {
32
40
  this._spanContext._hostname = hostname
33
41
 
34
42
  this._startTime = fields.startTime || this._getTime()
43
+
44
+ if (this._debug && unfinishedRegistry) {
45
+ metrics.increment('runtime.node.spans.unfinished')
46
+ metrics.increment('runtime.node.spans.unfinished.by.name', `span_name:${operationName}`)
47
+
48
+ unfinishedRegistry.register(this, operationName, this)
49
+ }
35
50
  }
36
51
 
37
52
  toString () {
@@ -122,6 +137,16 @@ class DatadogSpan extends Span {
122
137
  }
123
138
  }
124
139
 
140
+ if (this._debug && finishedRegistry) {
141
+ metrics.decrement('runtime.node.spans.unfinished')
142
+ metrics.decrement('runtime.node.spans.unfinished.by.name', `span_name:${this._name}`)
143
+ metrics.increment('runtime.node.spans.finished')
144
+ metrics.increment('runtime.node.spans.finished.by.name', `span_name:${this._name}`)
145
+
146
+ unfinishedRegistry.unregister(this)
147
+ finishedRegistry.register(this, this._name)
148
+ }
149
+
125
150
  finishTime = parseFloat(finishTime) || this._getTime()
126
151
 
127
152
  this._duration = finishTime - this._startTime
@@ -131,4 +156,13 @@ class DatadogSpan extends Span {
131
156
  }
132
157
  }
133
158
 
159
+ function createRegistry (type) {
160
+ if (!semver.satisfies(process.version, '>=14.6')) return
161
+
162
+ return new global.FinalizationRegistry(name => {
163
+ metrics.decrement(`runtime.node.spans.${type}`)
164
+ metrics.decrement(`runtime.node.spans.${type}.by.name`, [`span_name:${name}`])
165
+ })
166
+ }
167
+
134
168
  module.exports = DatadogSpan
@@ -55,6 +55,10 @@ module.exports = class PluginManager {
55
55
  }
56
56
  this.configurePlugin(name, pluginConfig)
57
57
  }
58
+ } else {
59
+ for (const name in this._pluginsByName) {
60
+ this.configurePlugin(name, false)
61
+ }
58
62
  }
59
63
  }
60
64
 
@@ -8,7 +8,6 @@ class Subscription {
8
8
  this._channel = dc.channel(event)
9
9
  this._handler = (message, name) => {
10
10
  const store = storage.getStore()
11
-
12
11
  if (!store || !store.noop) {
13
12
  handler(message, name)
14
13
  }
@@ -58,6 +57,9 @@ module.exports = class Plugin {
58
57
  }
59
58
 
60
59
  configure (config) {
60
+ if (typeof config === 'boolean') {
61
+ config = { enabled: config }
62
+ }
61
63
  this.config = config
62
64
  if (config.enabled && !this._enabled) {
63
65
  this._enabled = true
@@ -52,10 +52,36 @@ const web = {
52
52
  })
53
53
  },
54
54
 
55
+ startSpan (tracer, config, req, res, name) {
56
+ const context = this.patch(req)
57
+ context.config = config
58
+
59
+ let span
60
+
61
+ if (context.span) {
62
+ context.span.context()._name = name
63
+ span = context.span
64
+ } else {
65
+ span = web.startChildSpan(tracer, name, req.headers)
66
+ }
67
+
68
+ context.tracer = tracer
69
+ context.span = span
70
+ context.res = res
71
+
72
+ return span
73
+ },
74
+ wrap (req) {
75
+ const context = contexts.get(req)
76
+ if (!context.instrumented) {
77
+ this.wrapEnd(context)
78
+ this.wrapEvents(context)
79
+ context.instrumented = true
80
+ }
81
+ },
55
82
  // Start a span and activate a scope for a request.
56
83
  instrument (tracer, config, req, res, name, callback) {
57
- const context = this.patch(req)
58
- const span = startSpan(tracer, config, req, res, name)
84
+ const span = this.startSpan(tracer, config, req, res, name)
59
85
 
60
86
  if (!config.filter(req.url)) {
61
87
  span.setTag(MANUAL_DROP, true)
@@ -67,12 +93,7 @@ const web = {
67
93
 
68
94
  analyticsSampler.sample(span, config.measured, true)
69
95
 
70
- if (!context.instrumented) {
71
- wrapEnd(context)
72
- wrapEvents(context)
73
-
74
- context.instrumented = true
75
- }
96
+ this.wrap(req)
76
97
 
77
98
  return callback && tracer.scope().activate(span, () => callback(span))
78
99
  },
@@ -199,6 +220,7 @@ const web = {
199
220
  // Extract the parent span from the headers and start a new span as its child
200
221
  startChildSpan (tracer, name, headers) {
201
222
  const childOf = tracer.scope().active() || tracer.extract(FORMAT_HTTP_HEADERS, headers)
223
+
202
224
  const span = tracer.startSpan(name, { childOf })
203
225
 
204
226
  return span
@@ -221,103 +243,94 @@ const web = {
221
243
  const context = contexts.get(req)
222
244
  context.error = context.error || error
223
245
  }
224
- }
225
- }
226
-
227
- function startSpan (tracer, config, req, res, name) {
228
- const context = contexts.get(req)
229
-
230
- context.config = config
231
-
232
- let span
233
-
234
- if (context.span) {
235
- context.span.context()._name = name
236
- span = context.span
237
- } else {
238
- span = web.startChildSpan(tracer, name, req.headers)
239
- }
240
-
241
- context.tracer = tracer
242
- context.span = span
243
- context.res = res
244
-
245
- return span
246
- }
246
+ },
247
247
 
248
- function finish (context) {
249
- const { req, res } = context
248
+ finishMiddleware (context) {
249
+ if (context.finished) return
250
250
 
251
- if (context.finished && !req.stream) return
251
+ let span
252
252
 
253
- addRequestTags(context)
254
- addResponseTags(context)
253
+ while ((span = context.middleware.pop())) {
254
+ span.finish()
255
+ }
256
+ },
255
257
 
256
- context.config.hooks.request(context.span, req, res)
257
- addResourceTag(context)
258
+ finishSpan (context) {
259
+ const { req, res } = context
258
260
 
259
- context.span.finish()
260
- context.finished = true
261
- }
261
+ if (context.finished && !req.stream) return
262
262
 
263
- function finishMiddleware (context) {
264
- if (context.finished) return
263
+ addRequestTags(context)
264
+ addResponseTags(context)
265
265
 
266
- let span
266
+ context.config.hooks.request(context.span, req, res)
267
+ addResourceTag(context)
267
268
 
268
- while ((span = context.middleware.pop())) {
269
- span.finish()
270
- }
271
- }
269
+ context.span.finish()
270
+ context.finished = true
271
+ },
272
+ wrapWriteHead (context) {
273
+ const { req, res } = context
274
+ const writeHead = res.writeHead
272
275
 
273
- function wrapEnd (context) {
274
- const scope = context.tracer.scope()
275
- const req = context.req
276
- const res = context.res
277
- const end = res.end
276
+ return function (statusCode, statusMessage, headers) {
277
+ headers = typeof statusMessage === 'string' ? headers : statusMessage
278
+ headers = Object.assign(res.getHeaders(), headers)
278
279
 
279
- res.writeHead = wrapWriteHead(context)
280
+ if (req.method.toLowerCase() === 'options' && isOriginAllowed(req, headers)) {
281
+ addAllowHeaders(req, res, headers)
282
+ }
280
283
 
281
- ends.set(res, function () {
282
- for (const beforeEnd of context.beforeEnd) {
283
- beforeEnd()
284
+ return writeHead.apply(this, arguments)
284
285
  }
286
+ },
287
+ getContext (req) {
288
+ return contexts.get(req)
289
+ },
290
+ wrapRes (context, req, res, end) {
291
+ return function () {
292
+ for (const beforeEnd of context.beforeEnd) {
293
+ beforeEnd()
294
+ }
285
295
 
286
- finishMiddleware(context)
287
-
288
- if (incomingHttpRequestEnd.hasSubscribers) incomingHttpRequestEnd.publish({ req, res })
289
-
290
- const returnValue = end.apply(res, arguments)
291
-
292
- finish(context)
293
-
294
- return returnValue
295
- })
296
+ web.finishMiddleware(context)
296
297
 
297
- Object.defineProperty(res, 'end', {
298
- configurable: true,
299
- get () {
300
- return ends.get(this)
301
- },
302
- set (value) {
303
- ends.set(this, scope.bind(value, context.span))
304
- }
305
- })
306
- }
298
+ if (incomingHttpRequestEnd.hasSubscribers) {
299
+ incomingHttpRequestEnd.publish({ req, res })
300
+ }
307
301
 
308
- function wrapWriteHead (context) {
309
- const { req, res } = context
310
- const writeHead = res.writeHead
302
+ const returnValue = end.apply(res, arguments)
311
303
 
312
- return function (statusCode, statusMessage, headers) {
313
- headers = typeof statusMessage === 'string' ? headers : statusMessage
314
- headers = Object.assign(res.getHeaders(), headers)
304
+ web.finishSpan(context)
315
305
 
316
- if (req.method.toLowerCase() === 'options' && isOriginAllowed(req, headers)) {
317
- addAllowHeaders(req, res, headers)
306
+ return returnValue
318
307
  }
308
+ },
309
+ wrapEnd (context) {
310
+ const scope = context.tracer.scope()
311
+ const req = context.req
312
+ const res = context.res
313
+ const end = res.end
314
+
315
+ res.writeHead = web.wrapWriteHead(context)
316
+
317
+ ends.set(res, this.wrapRes(context, req, res, end))
318
+
319
+ Object.defineProperty(res, 'end', {
320
+ configurable: true,
321
+ get () {
322
+ return ends.get(this)
323
+ },
324
+ set (value) {
325
+ ends.set(this, scope.bind(value, context.span))
326
+ }
327
+ })
328
+ },
329
+ wrapEvents (context) {
330
+ const scope = context.tracer.scope()
331
+ const res = context.res
319
332
 
320
- return writeHead.apply(this, arguments)
333
+ scope.bind(res, context.span)
321
334
  }
322
335
  }
323
336
 
@@ -354,13 +367,6 @@ function splitHeader (str) {
354
367
  return typeof str === 'string' ? str.split(/\s*,\s*/) : []
355
368
  }
356
369
 
357
- function wrapEvents (context) {
358
- const scope = context.tracer.scope()
359
- const res = context.res
360
-
361
- scope.bind(res, context.span)
362
- }
363
-
364
370
  function reactivate (req, fn) {
365
371
  const context = contexts.get(req)
366
372
 
@@ -141,6 +141,10 @@ class Tracer extends BaseTracer {
141
141
  getRumData () {
142
142
  return this._tracer.getRumData.apply(this._tracer, arguments)
143
143
  }
144
+
145
+ setUser () {
146
+ return this._tracer.setUser.apply(this.tracer, arguments)
147
+ }
144
148
  }
145
149
 
146
150
  module.exports = Tracer
@@ -10,9 +10,10 @@ const origRequire = Module.prototype.require
10
10
 
11
11
  module.exports = Hook
12
12
 
13
- Hook.reset = function () {
14
- Module.prototype.require = origRequire
15
- }
13
+ let moduleHooks = Object.create(null)
14
+ let cache = Object.create(null)
15
+ let patching = Object.create(null)
16
+ let patchedRequire = null
16
17
 
17
18
  function Hook (modules, options, onrequire) {
18
19
  if (!(this instanceof Hook)) return new Hook(modules, options, onrequire)
@@ -25,35 +26,40 @@ function Hook (modules, options, onrequire) {
25
26
  options = {}
26
27
  }
27
28
 
29
+ modules = modules || []
28
30
  options = options || {}
29
31
 
30
- this.cache = {}
31
- this._unhooked = false
32
- this._origRequire = Module.prototype.require
32
+ this.modules = modules
33
+ this.options = options
34
+ this.onrequire = onrequire
33
35
 
34
- const self = this
35
- const patching = {}
36
+ if (Array.isArray(modules)) {
37
+ for (const mod of modules) {
38
+ const hooks = moduleHooks[mod]
36
39
 
37
- this._require = Module.prototype.require = function (request) {
38
- if (self._unhooked) {
39
- // if the patched require function could not be removed because
40
- // someone else patched it after it was patched here, we just
41
- // abort and pass the request onwards to the original require
42
- return self._origRequire.apply(this, arguments)
40
+ if (hooks) {
41
+ hooks.push(onrequire)
42
+ } else {
43
+ moduleHooks[mod] = [onrequire]
44
+ }
43
45
  }
46
+ }
44
47
 
48
+ if (patchedRequire) return
49
+
50
+ patchedRequire = Module.prototype.require = function (request) {
45
51
  const filename = Module._resolveFilename(request, this)
46
52
  const core = filename.indexOf(path.sep) === -1
47
- let name, basedir
53
+ let name, basedir, hooks
48
54
 
49
55
  // return known patched modules immediately
50
- if (self.cache.hasOwnProperty(filename)) {
56
+ if (cache[filename]) {
51
57
  // require.cache was potentially altered externally
52
- if (require.cache[filename] && require.cache[filename].exports !== self.cache[filename].original) {
58
+ if (require.cache[filename] && require.cache[filename].exports !== cache[filename].original) {
53
59
  return require.cache[filename].exports
54
60
  }
55
61
 
56
- return self.cache[filename].exports
62
+ return cache[filename].exports
57
63
  }
58
64
 
59
65
  // Check if this module has a patcher in-progress already.
@@ -63,7 +69,7 @@ function Hook (modules, options, onrequire) {
63
69
  patching[filename] = true
64
70
  }
65
71
 
66
- const exports = self._origRequire.apply(this, arguments)
72
+ const exports = origRequire.apply(this, arguments)
67
73
 
68
74
  // If it's already patched, just return it as-is.
69
75
  if (patched) return exports
@@ -73,7 +79,8 @@ function Hook (modules, options, onrequire) {
73
79
  delete patching[filename]
74
80
 
75
81
  if (core) {
76
- if (modules && modules.indexOf(filename) === -1) return exports // abort if module name isn't on whitelist
82
+ hooks = moduleHooks[filename]
83
+ if (!hooks) return exports // abort if module name isn't on whitelist
77
84
  name = filename
78
85
  } else {
79
86
  const stat = parse(filename)
@@ -81,7 +88,8 @@ function Hook (modules, options, onrequire) {
81
88
  name = stat.name
82
89
  basedir = stat.basedir
83
90
 
84
- if (modules && modules.indexOf(name) === -1) return exports // abort if module name isn't on whitelist
91
+ hooks = moduleHooks[name]
92
+ if (!hooks) return exports // abort if module name isn't on whitelist
85
93
 
86
94
  // figure out if this is the main module file, or a file inside the module
87
95
  const paths = Module._resolveLookupPaths(name, this, true)
@@ -99,10 +107,37 @@ function Hook (modules, options, onrequire) {
99
107
 
100
108
  // ensure that the cache entry is assigned a value before calling
101
109
  // onrequire, in case calling onrequire requires the same module.
102
- self.cache[filename] = { exports }
103
- self.cache[filename].original = exports
104
- self.cache[filename].exports = onrequire(exports, name, basedir)
110
+ cache[filename] = { exports }
111
+ cache[filename].original = exports
112
+
113
+ for (const hook of hooks) {
114
+ cache[filename].exports = hook(cache[filename].exports, name, basedir)
115
+ }
116
+
117
+ return cache[filename].exports
118
+ }
119
+ }
120
+
121
+ Hook.reset = function () {
122
+ Module.prototype.require = origRequire
123
+ patchedRequire = null
124
+ patching = Object.create(null)
125
+ cache = Object.create(null)
126
+ moduleHooks = Object.create(null)
127
+ }
128
+
129
+ Hook.prototype.unhook = function () {
130
+ for (const mod of this.modules) {
131
+ const hooks = (moduleHooks[mod] || []).filter(hook => hook !== this.onrequire)
132
+
133
+ if (hooks.length > 0) {
134
+ moduleHooks[mod] = hooks
135
+ } else {
136
+ delete moduleHooks[mod]
137
+ }
138
+ }
105
139
 
106
- return self.cache[filename].exports
140
+ if (Object.keys(moduleHooks).length === 0) {
141
+ Hook.reset()
107
142
  }
108
143
  }
@@ -122,6 +122,22 @@ class DatadogTracer extends Tracer {
122
122
  <meta name="dd-trace-id" content="${traceId}" />\
123
123
  <meta name="dd-trace-time" content="${traceTime}" />`
124
124
  }
125
+
126
+ setUser (user) {
127
+ if (!user || !user.id) return this
128
+
129
+ const span = this.scope().active()
130
+ if (!span) return this
131
+
132
+ const rootSpan = span._spanContext._trace.started[0]
133
+ if (!rootSpan) return this
134
+
135
+ for (const k of Object.keys(user)) {
136
+ rootSpan.setTag(`usr.${k}`, '' + user[k])
137
+ }
138
+
139
+ return this
140
+ }
125
141
  }
126
142
 
127
143
  function addError (span, error) {