dd-trace 2.11.0 → 2.12.1

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 (29) hide show
  1. package/LICENSE-3rdparty.csv +0 -1
  2. package/index.d.ts +3 -3
  3. package/package.json +4 -5
  4. package/packages/datadog-core/src/storage/async_hooks.js +4 -4
  5. package/packages/datadog-core/src/storage/async_resource.js +14 -4
  6. package/packages/datadog-instrumentations/src/connect.js +4 -4
  7. package/packages/datadog-instrumentations/src/couchbase.js +166 -61
  8. package/packages/datadog-instrumentations/src/fastify.js +12 -25
  9. package/packages/datadog-instrumentations/src/graphql.js +17 -5
  10. package/packages/datadog-instrumentations/src/koa.js +4 -4
  11. package/packages/datadog-instrumentations/src/mocha.js +88 -18
  12. package/packages/datadog-instrumentations/src/restify.js +4 -8
  13. package/packages/datadog-instrumentations/src/router.js +4 -4
  14. package/packages/datadog-plugin-couchbase/src/index.js +8 -10
  15. package/packages/datadog-plugin-graphql/src/resolve.js +2 -0
  16. package/packages/datadog-plugin-http/src/server.js +3 -8
  17. package/packages/datadog-plugin-mocha/src/index.js +80 -3
  18. package/packages/datadog-plugin-next/src/index.js +1 -1
  19. package/packages/datadog-plugin-router/src/index.js +39 -10
  20. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +111 -15
  21. package/packages/dd-trace/src/plugin_manager.js +49 -33
  22. package/packages/dd-trace/src/plugins/util/test.js +32 -1
  23. package/packages/dd-trace/src/plugins/util/web.js +25 -17
  24. package/packages/dd-trace/src/profiling/config.js +10 -2
  25. package/packages/dd-trace/src/profiling/exporters/agent.js +1 -2
  26. package/packages/dd-trace/src/profiling/exporters/form-data.js +53 -0
  27. package/packages/dd-trace/src/profiling/index.js +2 -0
  28. package/packages/dd-trace/src/profiling/profiler.js +6 -1
  29. package/packages/dd-trace/src/profiling/profilers/cpu.js +126 -0
@@ -6,8 +6,31 @@ const errorCh = channel('ci:mocha:test:error')
6
6
  const skipCh = channel('ci:mocha:test:skip')
7
7
  const testFinishCh = channel('ci:mocha:test:finish')
8
8
  const parameterizedTestCh = channel('ci:mocha:test:parameterize')
9
+
10
+ const testRunStartCh = channel('ci:mocha:run:start')
9
11
  const testRunFinishCh = channel('ci:mocha:run:finish')
10
12
 
13
+ const testSuiteStartCh = channel('ci:mocha:test-suite:start')
14
+ const testSuiteFinishCh = channel('ci:mocha:test-suite:finish')
15
+ const testSuiteErrorCh = channel('ci:mocha:test-suite:error')
16
+
17
+ // TODO: remove when root hooks and fixtures are implemented
18
+ const patched = new WeakSet()
19
+
20
+ const testToAr = new WeakMap()
21
+ const originalFns = new WeakMap()
22
+ const testSuiteToAr = new WeakMap()
23
+
24
+ function getTestStatus (test) {
25
+ if (test.pending) {
26
+ return 'skip'
27
+ }
28
+ if (test.state !== 'failed' && !test.timedOut) {
29
+ return 'pass'
30
+ }
31
+ return 'fail'
32
+ }
33
+
11
34
  function isRetry (test) {
12
35
  return test._currentRetry !== undefined && test._currentRetry !== 0
13
36
  }
@@ -23,12 +46,6 @@ function getTestAsyncResource (test) {
23
46
  return testToAr.get(originalFn)
24
47
  }
25
48
 
26
- // TODO: remove when root hooks and fixtures are implemented
27
- const patched = new WeakSet()
28
-
29
- const testToAr = new WeakMap()
30
- const originalFns = new WeakMap()
31
-
32
49
  function mochaHook (Runner) {
33
50
  if (patched.has(Runner)) return Runner
34
51
 
@@ -39,6 +56,59 @@ function mochaHook (Runner) {
39
56
  return run.apply(this, arguments)
40
57
  }
41
58
 
59
+ const testRunAsyncResource = new AsyncResource('bound-anonymous-fn')
60
+
61
+ this.once('end', testRunAsyncResource.bind(function () {
62
+ let status = 'pass'
63
+ if (this.stats) {
64
+ status = this.stats.failures === 0 ? 'pass' : 'fail'
65
+ } else if (this.failures !== 0) {
66
+ status = 'fail'
67
+ }
68
+ testRunFinishCh.publish(status)
69
+ }))
70
+
71
+ this.once('start', testRunAsyncResource.bind(function () {
72
+ const processArgv = process.argv.slice(2).join(' ')
73
+ const command = `mocha ${processArgv}`
74
+ testRunStartCh.publish(command)
75
+ }))
76
+
77
+ this.on('suite', function (suite) {
78
+ if (suite.root) {
79
+ return
80
+ }
81
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
82
+
83
+ testSuiteToAr.set(suite, asyncResource)
84
+
85
+ asyncResource.runInAsyncScope(() => {
86
+ testSuiteStartCh.publish(suite)
87
+ })
88
+ })
89
+
90
+ this.on('suite end', function (suite) {
91
+ if (suite.root) {
92
+ return
93
+ }
94
+ let status = 'pass'
95
+ if (suite.pending) {
96
+ status = 'skip'
97
+ } else {
98
+ suite.eachTest(test => {
99
+ if (test.state === 'failed' || test.timedOut) {
100
+ status = 'fail'
101
+ }
102
+ })
103
+ }
104
+
105
+ const asyncResource = testSuiteToAr.get(suite)
106
+ asyncResource.runInAsyncScope(() => {
107
+ // get suite status
108
+ testSuiteFinishCh.publish(status)
109
+ })
110
+ })
111
+
42
112
  this.on('test', (test) => {
43
113
  if (isRetry(test)) {
44
114
  return
@@ -52,14 +122,7 @@ function mochaHook (Runner) {
52
122
 
53
123
  this.on('test end', (test) => {
54
124
  const asyncResource = getTestAsyncResource(test)
55
- let status
56
- if (test.pending) {
57
- status = 'skip'
58
- } else if (test.state !== 'failed' && !test.timedOut) {
59
- status = 'pass'
60
- } else {
61
- status = 'fail'
62
- }
125
+ const status = getTestStatus(test)
63
126
 
64
127
  // if there are afterEach to be run, we don't finish the test yet
65
128
  if (!test.parent._afterEach.length) {
@@ -75,9 +138,10 @@ function mochaHook (Runner) {
75
138
  if (test && hook.parent._afterEach.includes(hook)) { // only if it's an afterEach
76
139
  const isLastAfterEach = hook.parent._afterEach.indexOf(hook) === hook.parent._afterEach.length - 1
77
140
  if (isLastAfterEach) {
141
+ const status = getTestStatus(test)
78
142
  const asyncResource = getTestAsyncResource(test)
79
143
  asyncResource.runInAsyncScope(() => {
80
- testFinishCh.publish('pass')
144
+ testFinishCh.publish(status)
81
145
  })
82
146
  }
83
147
  }
@@ -100,6 +164,15 @@ function mochaHook (Runner) {
100
164
  } else {
101
165
  errorCh.publish(err)
102
166
  }
167
+ // we propagate the error to the suite
168
+ const testSuiteAsyncResource = testSuiteToAr.get(test.parent)
169
+ if (testSuiteAsyncResource) {
170
+ const testSuiteError = new Error(`Test "${test.fullTitle()}" failed with message "${err.message}"`)
171
+ testSuiteError.stack = err.stack
172
+ testSuiteAsyncResource.runInAsyncScope(() => {
173
+ testSuiteErrorCh.publish(testSuiteError)
174
+ })
175
+ }
103
176
  })
104
177
  }
105
178
  })
@@ -127,9 +200,6 @@ function mochaHook (Runner) {
127
200
  if (!testRunFinishCh.hasSubscribers) {
128
201
  return runTests.apply(this, arguments)
129
202
  }
130
- this.once('end', AsyncResource.bind(() => {
131
- testRunFinishCh.publish()
132
- }))
133
203
  return runTests.apply(this, arguments)
134
204
  })
135
205
 
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const shimmer = require('../../datadog-shimmer')
4
- const { addHook, channel, AsyncResource } = require('./helpers/instrument')
4
+ const { addHook, channel } = require('./helpers/instrument')
5
5
  const handlers = ['use', 'pre']
6
6
  const methods = ['del', 'get', 'head', 'opts', 'post', 'put', 'patch']
7
7
 
@@ -9,14 +9,14 @@ const handleChannel = channel('apm:restify:request:handle')
9
9
  const routeChannel = channel('apm:restify:request:route')
10
10
 
11
11
  function wrapSetupRequest (setupRequest) {
12
- return function setupRequestWithTrace (req, res) {
12
+ return function (req, res) {
13
13
  handleChannel.publish({ req, res })
14
14
  return setupRequest.apply(this, arguments)
15
15
  }
16
16
  }
17
17
 
18
18
  function wrapMethod (method) {
19
- return function methodWithTrace (path) {
19
+ return function (path) {
20
20
  const middleware = wrapMiddleware(Array.prototype.slice.call(arguments, 1))
21
21
 
22
22
  return method.apply(this, [path].concat(middleware))
@@ -24,7 +24,7 @@ function wrapMethod (method) {
24
24
  }
25
25
 
26
26
  function wrapHandler (method) {
27
- return function methodWithTrace () {
27
+ return function () {
28
28
  return method.apply(this, wrapMiddleware(arguments))
29
29
  }
30
30
  }
@@ -37,10 +37,6 @@ function wrapFn (fn) {
37
37
  if (Array.isArray(fn)) return wrapMiddleware(fn)
38
38
 
39
39
  return function (req, res, next) {
40
- if (typeof next === 'function') {
41
- arguments[2] = AsyncResource.bind(next)
42
- }
43
-
44
40
  if (req.route) {
45
41
  routeChannel.publish({ req, route: req.route })
46
42
  }
@@ -49,12 +49,12 @@ function createWrapRouterMethod (name) {
49
49
 
50
50
  try {
51
51
  return original.apply(this, arguments)
52
- } catch (e) {
53
- errorChannel.publish(e)
52
+ } catch (error) {
53
+ errorChannel.publish({ req, error })
54
54
  nextChannel.publish({ req })
55
55
  exitChannel.publish({ req })
56
56
 
57
- throw e
57
+ throw error
58
58
  }
59
59
  })
60
60
  })
@@ -91,7 +91,7 @@ function createWrapRouterMethod (name) {
91
91
  function wrapNext (req, next) {
92
92
  return function (error) {
93
93
  if (error) {
94
- errorChannel.publish(error)
94
+ errorChannel.publish({ req, error })
95
95
  }
96
96
 
97
97
  nextChannel.publish({ req })
@@ -11,11 +11,11 @@ class CouchBasePlugin extends Plugin {
11
11
 
12
12
  addSubs (func, start, finish = defaultFinish) {
13
13
  this.addSub(`apm:couchbase:${func}:start`, start)
14
- this.addSub(`apm:couchbase:${func}:error`, errorHandler)
14
+ this.addSub(`apm:couchbase:${func}:error`, this.addError)
15
15
  this.addSub(`apm:couchbase:${func}:finish`, finish)
16
16
  }
17
17
 
18
- startSpan (operation, customTags, store, bucket) {
18
+ startSpan (operation, customTags, store, { bucket, collection }) {
19
19
  const tags = {
20
20
  'db.type': 'couchbase',
21
21
  'component': 'couchbase',
@@ -32,7 +32,8 @@ class CouchBasePlugin extends Plugin {
32
32
  tags
33
33
  })
34
34
 
35
- span.setTag('couchbase.bucket.name', bucket.name || bucket._name)
35
+ if (bucket) span.setTag(`couchbase.bucket.name`, bucket.name)
36
+ if (collection) span.setTag(`couchbase.collection.name`, collection.name)
36
37
 
37
38
  analyticsSampler.sample(span, this.config.measured)
38
39
  return span
@@ -43,7 +44,8 @@ class CouchBasePlugin extends Plugin {
43
44
 
44
45
  this.addSubs('query', ({ resource, bucket }) => {
45
46
  const store = storage.getStore()
46
- const span = this.startSpan('query', { 'span.type': 'sql', 'resource.name': resource }, store, bucket)
47
+ const span = this.startSpan('query', { 'span.type': 'sql', 'resource.name': resource },
48
+ store, { bucket })
47
49
  this.enter(span, store)
48
50
  })
49
51
 
@@ -54,9 +56,9 @@ class CouchBasePlugin extends Plugin {
54
56
  this._addCommandSubs('prepend')
55
57
  }
56
58
  _addCommandSubs (name) {
57
- this.addSubs(name, ({ bucket }) => {
59
+ this.addSubs(name, ({ bucket, collection }) => {
58
60
  const store = storage.getStore()
59
- const span = this.startSpan(name, {}, store, bucket)
61
+ const span = this.startSpan(name, {}, store, { bucket, collection })
60
62
  this.enter(span, store)
61
63
  })
62
64
  }
@@ -66,8 +68,4 @@ function defaultFinish () {
66
68
  storage.getStore().span.finish()
67
69
  }
68
70
 
69
- function errorHandler (error) {
70
- storage.getStore().span.setTag('error', error)
71
- }
72
-
73
71
  module.exports = CouchBasePlugin
@@ -77,6 +77,8 @@ class GraphQLResolvePlugin extends Plugin {
77
77
  })
78
78
  })
79
79
 
80
+ this.addSub('apm:graphql:resolve:error', this.addError)
81
+
80
82
  this.addSub('apm:graphql:resolve:finish', finishTime => {
81
83
  const span = storage.getStore().span
82
84
  span.finish(finishTime)
@@ -17,7 +17,7 @@ class HttpServerPlugin extends Plugin {
17
17
  const store = storage.getStore()
18
18
  const span = web.startSpan(this.tracer, this.config, req, res, 'http.request')
19
19
 
20
- this.enter(span, store)
20
+ this.enter(span, { ...store, req })
21
21
 
22
22
  const context = web.getContext(req)
23
23
 
@@ -32,12 +32,7 @@ class HttpServerPlugin extends Plugin {
32
32
  })
33
33
 
34
34
  this.addSub('apm:http:server:request:error', (error) => {
35
- const span = storage.getStore().span
36
- span.addTags({
37
- 'error.type': error.name,
38
- 'error.msg': error.message,
39
- 'error.stack': error.stack
40
- })
35
+ web.addError(error)
41
36
  })
42
37
 
43
38
  this.addSub('apm:http:server:request:finish', ({ req }) => {
@@ -45,7 +40,7 @@ class HttpServerPlugin extends Plugin {
45
40
 
46
41
  if (!context || !context.res) return // Not created by a http.Server instance.
47
42
 
48
- web.wrapRes(context, context.req, context.res, context.res.end)()
43
+ web.finishAll(context)
49
44
  })
50
45
  }
51
46
 
@@ -16,7 +16,12 @@ const {
16
16
  getTestParametersString,
17
17
  getCodeOwnersFileEntries,
18
18
  getCodeOwnersForFilename,
19
- getTestCommonTags
19
+ getTestCommonTags,
20
+ getTestSessionCommonTags,
21
+ getTestSuiteCommonTags,
22
+ TEST_SUITE_ID,
23
+ TEST_SESSION_ID,
24
+ TEST_COMMAND
20
25
  } = require('../../dd-trace/src/plugins/util/test')
21
26
 
22
27
  function getTestSpanMetadata (tracer, test, sourceRoot) {
@@ -42,11 +47,63 @@ class MochaPlugin extends Plugin {
42
47
  constructor (...args) {
43
48
  super(...args)
44
49
 
50
+ this._testSuites = new WeakMap()
45
51
  this._testNameToParams = {}
46
52
  this.testEnvironmentMetadata = getTestEnvironmentMetadata('mocha', this.config)
47
53
  this.sourceRoot = process.cwd()
48
54
  this.codeOwnersEntries = getCodeOwnersFileEntries(this.sourceRoot)
49
55
 
56
+ this.addSub('ci:mocha:run:start', (command) => {
57
+ if (!this.config.isAgentlessEnabled) {
58
+ return
59
+ }
60
+ const childOf = getTestParentSpan(this.tracer)
61
+ const testSessionSpanMetadata = getTestSessionCommonTags(command, this.tracer._version)
62
+
63
+ this.command = command
64
+ this.testSessionSpan = this.tracer.startSpan('mocha.test_session', {
65
+ childOf,
66
+ tags: {
67
+ ...this.testEnvironmentMetadata,
68
+ ...testSessionSpanMetadata
69
+ }
70
+ })
71
+ })
72
+
73
+ this.addSub('ci:mocha:test-suite:start', (suite) => {
74
+ if (!this.config.isAgentlessEnabled) {
75
+ return
76
+ }
77
+ const store = storage.getStore()
78
+ const testSuiteMetadata = getTestSuiteCommonTags(this.command, this.tracer._version, suite.fullTitle())
79
+ const testSuiteSpan = this.tracer.startSpan('mocha.test_suite', {
80
+ childOf: this.testSessionSpan,
81
+ tags: {
82
+ ...this.testEnvironmentMetadata,
83
+ ...testSuiteMetadata
84
+ }
85
+ })
86
+ this.enter(testSuiteSpan, store)
87
+ this._testSuites.set(suite, testSuiteSpan)
88
+ })
89
+
90
+ this.addSub('ci:mocha:test-suite:finish', (status) => {
91
+ if (!this.config.isAgentlessEnabled) {
92
+ return
93
+ }
94
+ const span = storage.getStore().span
95
+ span.setTag(TEST_STATUS, status)
96
+ span.finish()
97
+ })
98
+
99
+ this.addSub('ci:mocha:test-suite:error', (err) => {
100
+ if (!this.config.isAgentlessEnabled) {
101
+ return
102
+ }
103
+ const span = storage.getStore().span
104
+ span.setTag('error', err)
105
+ })
106
+
50
107
  this.addSub('ci:mocha:test:start', (test) => {
51
108
  const store = storage.getStore()
52
109
  const span = this.startTestSpan(test)
@@ -89,12 +146,31 @@ class MochaPlugin extends Plugin {
89
146
  this._testNameToParams[name] = params
90
147
  })
91
148
 
92
- this.addSub('ci:mocha:run:finish', () => {
149
+ this.addSub('ci:mocha:run:finish', (status) => {
150
+ if (this.testSessionSpan) {
151
+ this.testSessionSpan.setTag(TEST_STATUS, status)
152
+ this.testSessionSpan.finish()
153
+ finishAllTraceSpans(this.testSessionSpan)
154
+ }
93
155
  this.tracer._exporter._writer.flush()
94
156
  })
95
157
  }
96
158
 
97
159
  startTestSpan (test) {
160
+ const testSuiteTags = {}
161
+ const testSuiteSpan = this._testSuites.get(test.parent)
162
+
163
+ if (testSuiteSpan) {
164
+ const testSuiteId = testSuiteSpan.context()._spanId.toString('hex')
165
+ testSuiteTags[TEST_SUITE_ID] = testSuiteId
166
+ }
167
+
168
+ if (this.testSessionSpan) {
169
+ const testSessionId = this.testSessionSpan.context()._traceId.toString('hex')
170
+ testSuiteTags[TEST_SESSION_ID] = testSessionId
171
+ testSuiteTags[TEST_COMMAND] = this.command
172
+ }
173
+
98
174
  const { childOf, ...testSpanMetadata } = getTestSpanMetadata(this.tracer, test, this.sourceRoot)
99
175
 
100
176
  const testParametersString = getTestParametersString(this._testNameToParams, test.title)
@@ -112,7 +188,8 @@ class MochaPlugin extends Plugin {
112
188
  childOf,
113
189
  tags: {
114
190
  ...this.testEnvironmentMetadata,
115
- ...testSpanMetadata
191
+ ...testSpanMetadata,
192
+ ...testSuiteTags
116
193
  }
117
194
  })
118
195
  testSpan.context()._trace.origin = CI_APP_ORIGIN
@@ -21,7 +21,7 @@ class NextPlugin extends Plugin {
21
21
  childOf,
22
22
  tags: {
23
23
  'service.name': this.config.service || this.tracer._service,
24
- 'resource.name': 'test',
24
+ 'resource.name': req.method,
25
25
  'span.type': 'web',
26
26
  'span.kind': 'server',
27
27
  'http.method': req.method
@@ -16,11 +16,18 @@ class RouterPlugin extends WebPlugin {
16
16
  this._contexts = new WeakMap()
17
17
 
18
18
  this.addSub(`apm:${this.constructor.name}:middleware:enter`, ({ req, name, route }) => {
19
- const store = storage.getStore()
20
- const context = this._createContext(req, route)
21
- const span = this._getMiddlewareSpan(context, store, name)
19
+ const childOf = this._getActive(req) || this._getStoreSpan()
22
20
 
23
- this.enter(span, store)
21
+ if (!childOf) return
22
+
23
+ const span = this._getMiddlewareSpan(name, childOf)
24
+ const context = this._createContext(req, route, childOf)
25
+
26
+ if (childOf !== span) {
27
+ context.middleware.push(span)
28
+ }
29
+
30
+ this.enter(span)
24
31
 
25
32
  web.patch(req)
26
33
  web.setRoute(req, context.route)
@@ -42,7 +49,17 @@ class RouterPlugin extends WebPlugin {
42
49
  context.middleware.pop().finish()
43
50
  })
44
51
 
45
- this.addSub(`apm:${this.constructor.name}:middleware:error`, this.addError)
52
+ this.addSub(`apm:${this.constructor.name}:middleware:error`, ({ req, error }) => {
53
+ web.addError(req, error)
54
+
55
+ if (!this.config.middleware) return
56
+
57
+ const span = this._getActive(req)
58
+
59
+ if (!span) return
60
+
61
+ span.setTag('error', error)
62
+ })
46
63
 
47
64
  this.addSub(`apm:http:server:request:finish`, ({ req }) => {
48
65
  const context = this._contexts.get(req)
@@ -57,9 +74,22 @@ class RouterPlugin extends WebPlugin {
57
74
  })
58
75
  }
59
76
 
60
- _getMiddlewareSpan (context, store, name) {
61
- const childOf = store && store.span
77
+ _getActive (req) {
78
+ const context = this._contexts.get(req)
79
+
80
+ if (!context) return
81
+ if (context.middleware.length === 0) return context.span
82
+
83
+ return context.middleware[context.middleware.length - 1]
84
+ }
85
+
86
+ _getStoreSpan () {
87
+ const store = storage.getStore()
88
+
89
+ return store && store.span
90
+ }
62
91
 
92
+ _getMiddlewareSpan (name, childOf) {
63
93
  if (this.config.middleware === false) {
64
94
  return childOf
65
95
  }
@@ -71,14 +101,12 @@ class RouterPlugin extends WebPlugin {
71
101
  }
72
102
  })
73
103
 
74
- context.middleware.push(span)
75
-
76
104
  analyticsSampler.sample(span, this.config.measured)
77
105
 
78
106
  return span
79
107
  }
80
108
 
81
- _createContext (req, route) {
109
+ _createContext (req, route, span) {
82
110
  let context = this._contexts.get(req)
83
111
 
84
112
  if (!route || route === '/' || route === '*') {
@@ -96,6 +124,7 @@ class RouterPlugin extends WebPlugin {
96
124
  }
97
125
  } else {
98
126
  context = {
127
+ span,
99
128
  stack: [route],
100
129
  route,
101
130
  middleware: []