dd-trace 2.8.0 → 2.9.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.
package/ci/init.js CHANGED
@@ -1,3 +1,5 @@
1
+ /* eslint-disable no-console */
2
+
1
3
  const path = require('path')
2
4
  const tracer = require('../packages/dd-trace')
3
5
  const { ORIGIN_KEY } = require('../packages/dd-trace/src/constants')
@@ -11,9 +13,22 @@ const options = {
11
13
  }
12
14
  }
13
15
 
14
- if (process.env.DD_CIVISIBILITY_AGENTLESS_ENABLED && (process.env.DATADOG_API_KEY || process.env.DD_API_KEY)) {
15
- options.experimental = {
16
- exporter: 'datadog'
16
+ let shouldInit = true
17
+
18
+ const isAgentlessEnabled = process.env.DD_CIVISIBILITY_AGENTLESS_ENABLED &&
19
+ process.env.DD_CIVISIBILITY_AGENTLESS_ENABLED !== 'false' &&
20
+ process.env.DD_CIVISIBILITY_AGENTLESS_ENABLED !== '0'
21
+
22
+ if (isAgentlessEnabled) {
23
+ if (process.env.DATADOG_API_KEY || process.env.DD_API_KEY) {
24
+ options.experimental = {
25
+ exporter: 'datadog'
26
+ }
27
+ } else {
28
+ console.error(`DD_CIVISIBILITY_AGENTLESS_ENABLED is set, \
29
+ but neither DD_API_KEY nor DATADOG_API_KEY are set in your environment, \
30
+ so dd-trace will not be initialized.`)
31
+ shouldInit = false
17
32
  }
18
33
  }
19
34
 
@@ -36,8 +51,9 @@ try {
36
51
  // ignore error and let the tracer initialize anyway
37
52
  }
38
53
 
39
- tracer.init(options)
40
-
41
- tracer.use('fs', false)
54
+ if (shouldInit) {
55
+ tracer.init(options)
56
+ tracer.use('fs', false)
57
+ }
42
58
 
43
59
  module.exports = tracer
package/ci/jest/env.js CHANGED
@@ -1,3 +1,5 @@
1
+ /* eslint-disable no-console */
2
+
1
3
  const tracer = require('../../packages/dd-trace')
2
4
  const { ORIGIN_KEY } = require('../../packages/dd-trace/src/constants')
3
5
 
@@ -8,13 +10,23 @@ const options = {
8
10
  }
9
11
  }
10
12
 
11
- if (process.env.DD_CIVISIBILITY_AGENTLESS_ENABLED && (process.env.DATADOG_API_KEY || process.env.DD_API_KEY)) {
12
- tracer.init({
13
- ...options,
14
- experimental: {
15
- exporter: 'datadog'
16
- }
17
- })
13
+ const isAgentlessEnabled = process.env.DD_CIVISIBILITY_AGENTLESS_ENABLED &&
14
+ process.env.DD_CIVISIBILITY_AGENTLESS_ENABLED !== 'false' &&
15
+ process.env.DD_CIVISIBILITY_AGENTLESS_ENABLED !== '0'
16
+
17
+ if (isAgentlessEnabled) {
18
+ if (process.env.DATADOG_API_KEY || process.env.DD_API_KEY) {
19
+ tracer.init({
20
+ ...options,
21
+ experimental: {
22
+ exporter: 'datadog'
23
+ }
24
+ })
25
+ } else {
26
+ console.error(`DD_CIVISIBILITY_AGENTLESS_ENABLED is set, \
27
+ but neither DD_API_KEY nor DATADOG_API_KEY are set in your environment, \
28
+ so dd-trace will not be initialized.`)
29
+ }
18
30
  } else {
19
31
  tracer.init({
20
32
  ...options,
package/ext/tags.d.ts CHANGED
@@ -15,6 +15,7 @@ declare const tags: {
15
15
  HTTP_ROUTE: 'http.route'
16
16
  HTTP_REQUEST_HEADERS: 'http.request.headers'
17
17
  HTTP_RESPONSE_HEADERS: 'http.response.headers'
18
+ HTTP_USERAGENT: 'http.useragent'
18
19
  }
19
20
 
20
21
  export = tags
package/ext/tags.js CHANGED
@@ -19,7 +19,8 @@ const tags = {
19
19
  HTTP_STATUS_CODE: 'http.status_code',
20
20
  HTTP_ROUTE: 'http.route',
21
21
  HTTP_REQUEST_HEADERS: 'http.request.headers',
22
- HTTP_RESPONSE_HEADERS: 'http.response.headers'
22
+ HTTP_RESPONSE_HEADERS: 'http.response.headers',
23
+ HTTP_USERAGENT: 'http.useragent'
23
24
  }
24
25
 
25
26
  // Deprecated
package/index.d.ts CHANGED
@@ -1210,13 +1210,15 @@ declare namespace plugins {
1210
1210
  * This plugin automatically instruments the
1211
1211
  * [mysql](https://github.com/mysqljs/mysql) module.
1212
1212
  */
1213
- interface mysql extends Instrumentation {}
1213
+ interface mysql extends Instrumentation {
1214
+ service?: string | ((params: any) => string);
1215
+ }
1214
1216
 
1215
1217
  /**
1216
1218
  * This plugin automatically instruments the
1217
1219
  * [mysql2](https://github.com/brianmario/mysql2) module.
1218
1220
  */
1219
- interface mysql2 extends Instrumentation {}
1221
+ interface mysql2 extends mysql {}
1220
1222
 
1221
1223
  /**
1222
1224
  * This plugin automatically instruments the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "2.8.0",
3
+ "version": "2.9.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -69,7 +69,7 @@ class AsyncResourceStorage {
69
69
  }
70
70
 
71
71
  _executionAsyncResource () {
72
- return executionAsyncResource()
72
+ return executionAsyncResource() || {}
73
73
  }
74
74
  }
75
75
 
@@ -15,13 +15,14 @@ function wrapRequest (send) {
15
15
  const channelSuffix = getChannelSuffix(serviceIdentifier)
16
16
  const startCh = channel(`apm:aws:request:start:${channelSuffix}`)
17
17
  if (!startCh.hasSubscribers) return send.apply(this, arguments)
18
+ const innerAr = new AsyncResource('apm:aws:request:inner')
18
19
  const outerAr = new AsyncResource('apm:aws:request:outer')
19
20
 
20
- this.on('complete', response => {
21
- channel(`apm:aws:request:complete:${channelSuffix}`).publish({ response })
22
- })
21
+ return innerAr.runInAsyncScope(() => {
22
+ this.on('complete', innerAr.bind(response => {
23
+ channel(`apm:aws:request:complete:${channelSuffix}`).publish({ response })
24
+ }))
23
25
 
24
- return new AsyncResource('apm:aws:request:inner').runInAsyncScope(() => {
25
26
  startCh.publish({
26
27
  serviceIdentifier,
27
28
  operation: this.operation,
@@ -1,6 +1,5 @@
1
1
  'use strict'
2
2
 
3
- const methods = require('methods').concat('all')
4
3
  const shimmer = require('../../datadog-shimmer')
5
4
  const { addHook, channel, AsyncResource } = require('./helpers/instrument')
6
5
 
@@ -8,26 +7,28 @@ const errorChannel = channel('apm:fastify:middleware:error')
8
7
  const handleChannel = channel('apm:fastify:request:handle')
9
8
 
10
9
  const requestResources = new WeakMap()
10
+ const parsingResources = new WeakMap()
11
11
 
12
- function wrapFastify (fastify) {
12
+ function wrapFastify (fastify, hasParsingEvents) {
13
13
  if (typeof fastify !== 'function') return fastify
14
14
 
15
15
  return function fastifyWithTrace () {
16
16
  const app = fastify.apply(this, arguments)
17
17
 
18
- if (!app) return app
18
+ if (!app || typeof app.addHook !== 'function') return app
19
19
 
20
- if (typeof app.addHook === 'function') {
21
- app.addHook('onRequest', onRequest)
22
- app.addHook = wrapAddHook(app.addHook)
23
- app.addHook('preHandler', preHandler)
24
- }
20
+ app.addHook('onRequest', onRequest)
21
+ app.addHook('preHandler', preHandler)
25
22
 
26
- methods.forEach(method => {
27
- app[method] = wrapMethod(app[method])
28
- })
23
+ if (hasParsingEvents) {
24
+ app.addHook('preParsing', preParsing)
25
+ app.addHook('preValidation', preValidation)
26
+ } else {
27
+ app.addHook('onRequest', preParsing)
28
+ app.addHook('preHandler', preValidation)
29
+ }
29
30
 
30
- app.route = wrapRoute(app.route)
31
+ app.addHook = wrapAddHook(app.addHook)
31
32
 
32
33
  return app
33
34
  }
@@ -45,36 +46,39 @@ function wrapAddHook (addHook) {
45
46
 
46
47
  if (!requestResource) return fn.apply(this, arguments)
47
48
 
48
- return requestResource.runInAsyncScope(() => {
49
- const hookResource = new AsyncResource('bound-anonymous-fn')
49
+ try {
50
+ if (typeof done === 'function') {
51
+ done = arguments[arguments.length - 1]
50
52
 
51
- try {
52
- if (typeof done === 'function') {
53
- done = arguments[arguments.length - 1]
53
+ arguments[arguments.length - 1] = function (err) {
54
+ publishError(err, requestResource)
54
55
 
55
- arguments[arguments.length - 1] = hookResource.bind(function (err) {
56
- errorChannel.publish(err)
57
- return done.apply(this, arguments)
58
- })
56
+ if (name === 'onRequest' || name === 'preParsing') {
57
+ const parsingResource = new AsyncResource('bound-anonymous-fn')
59
58
 
60
- return hookResource.bind(fn).apply(this, arguments)
61
- } else {
62
- const promise = hookResource.bind(fn).apply(this, arguments)
59
+ parsingResources.set(req, parsingResource)
63
60
 
64
- if (promise && typeof promise.catch === 'function') {
65
- return promise.catch(err => {
66
- errorChannel.publish(err)
67
- throw err
61
+ return parsingResource.runInAsyncScope(() => {
62
+ return done.apply(this, arguments)
68
63
  })
64
+ } else {
65
+ return done.apply(this, arguments)
69
66
  }
67
+ }
70
68
 
71
- return promise
69
+ return fn.apply(this, arguments)
70
+ } else {
71
+ const promise = fn.apply(this, arguments)
72
+
73
+ if (promise && typeof promise.catch === 'function') {
74
+ return promise.catch(err => publishError(err, requestResource))
72
75
  }
73
- } catch (e) {
74
- errorChannel.publish(e)
75
- throw e
76
+
77
+ return promise
76
78
  }
77
- })
79
+ } catch (e) {
80
+ throw publishError(e, requestResource)
81
+ }
78
82
  })
79
83
 
80
84
  return addHook.apply(this, arguments)
@@ -86,11 +90,14 @@ function onRequest (request, reply, next) {
86
90
 
87
91
  const req = getReq(request)
88
92
  const res = getRes(reply)
93
+ const requestResource = new AsyncResource('bound-anonymous-fn')
89
94
 
90
- requestResources.set(req, new AsyncResource('bound-anonymous-fn'))
91
- handleChannel.publish({ req, res })
95
+ requestResources.set(req, requestResource)
92
96
 
93
- return next()
97
+ return requestResource.runInAsyncScope(() => {
98
+ handleChannel.publish({ req, res })
99
+ return next()
100
+ })
94
101
  }
95
102
 
96
103
  function preHandler (request, reply, next) {
@@ -98,61 +105,39 @@ function preHandler (request, reply, next) {
98
105
  if (!reply || typeof reply.send !== 'function') return next()
99
106
 
100
107
  const req = getReq(request)
108
+ const requestResource = requestResources.get(req)
101
109
 
102
- reply.send = requestResources.get(req).bind(wrapSend(reply.send))
110
+ reply.send = wrapSend(reply.send, requestResource)
103
111
 
104
112
  next()
105
113
  }
106
114
 
107
- function wrapSend (send) {
108
- return function sendWithTrace (payload) {
109
- if (payload instanceof Error) {
110
- errorChannel.publish(payload)
111
- }
112
-
113
- return send.apply(this, arguments)
114
- }
115
- }
116
-
117
- function wrapRoute (route) {
118
- if (typeof route !== 'function') return route
115
+ function preValidation (request, reply, next) {
116
+ const req = getReq(request)
117
+ const parsingResource = parsingResources.get(req)
119
118
 
120
- return function routeWithTrace (opts) {
121
- opts.handler = wrapHandler(opts.handler)
119
+ if (!parsingResource) return next()
122
120
 
123
- return route.apply(this, arguments)
124
- }
121
+ parsingResource.runInAsyncScope(() => next())
125
122
  }
126
123
 
127
- function wrapMethod (method) {
128
- if (typeof method !== 'function') return method
129
-
130
- return function methodWithTrace (url, opts, handler) {
131
- const lastIndex = arguments.length - 1
132
-
133
- handler = arguments[lastIndex]
134
-
135
- if (typeof handler === 'function') {
136
- arguments[lastIndex] = wrapHandler(handler)
137
- } else if (handler) {
138
- arguments[lastIndex].handler = wrapHandler(handler.handler)
139
- }
124
+ function preParsing (request, reply, next) {
125
+ const req = getReq(request)
126
+ const parsingResource = new AsyncResource('bound-anonymous-fn')
140
127
 
141
- return method.apply(this, arguments)
142
- }
128
+ parsingResources.set(req, parsingResource)
129
+ parsingResource.runInAsyncScope(() => next())
143
130
  }
144
131
 
145
- function wrapHandler (handler) {
146
- if (!handler || typeof handler !== 'function' || handler.name === 'handlerWithTrace') {
147
- return handler
148
- }
149
-
150
- return function handlerWithTrace (request, reply) {
151
- const req = getReq(request)
132
+ function wrapSend (send, resource) {
133
+ return function sendWithTrace (payload) {
134
+ if (payload instanceof Error) {
135
+ resource.runInAsyncScope(() => {
136
+ errorChannel.publish(payload)
137
+ })
138
+ }
152
139
 
153
- return requestResources.get(req).runInAsyncScope(() => {
154
- return handler.apply(this, arguments)
155
- })
140
+ return send.apply(this, arguments)
156
141
  }
157
142
  }
158
143
 
@@ -164,16 +149,15 @@ function getRes (reply) {
164
149
  return reply && (reply.raw || reply.res || reply)
165
150
  }
166
151
 
167
- addHook({ name: 'fastify', versions: ['>=3.25.2'] }, fastify => {
168
- const wrapped = shimmer.wrap(fastify, wrapFastify(fastify, false))
169
-
170
- wrapped.fastify = wrapped
171
- wrapped.default = wrapped
152
+ function publishError (error, resource) {
153
+ resource.runInAsyncScope(() => {
154
+ errorChannel.publish(error)
155
+ })
172
156
 
173
- return wrapped
174
- })
157
+ return error
158
+ }
175
159
 
176
- addHook({ name: 'fastify', versions: ['3 - 3.25.1'] }, fastify => {
160
+ addHook({ name: 'fastify', versions: ['>=3'] }, fastify => {
177
161
  const wrapped = shimmer.wrap(fastify, wrapFastify(fastify, true))
178
162
 
179
163
  wrapped.fastify = wrapped
@@ -182,6 +166,10 @@ addHook({ name: 'fastify', versions: ['3 - 3.25.1'] }, fastify => {
182
166
  return wrapped
183
167
  })
184
168
 
185
- addHook({ name: 'fastify', versions: ['1 - 2'] }, fastify => {
169
+ addHook({ name: 'fastify', versions: ['2'] }, fastify => {
186
170
  return shimmer.wrap(fastify, wrapFastify(fastify, true))
187
171
  })
172
+
173
+ addHook({ name: 'fastify', versions: ['1'] }, fastify => {
174
+ return shimmer.wrap(fastify, wrapFastify(fastify, false))
175
+ })
@@ -89,7 +89,10 @@ function wrapMiddleware (fn, layer) {
89
89
  const req = ctx.req
90
90
 
91
91
  return middlewareResource.runInAsyncScope(() => {
92
- enterChannel.publish({ req, name })
92
+ const path = layer && layer.path
93
+ const route = path !== '(.*)' && path !== '([^/]*)' && path
94
+
95
+ enterChannel.publish({ req, name, route })
93
96
 
94
97
  if (typeof next === 'function') {
95
98
  arguments[1] = next
@@ -101,33 +104,33 @@ function wrapMiddleware (fn, layer) {
101
104
  if (result && typeof result.then === 'function') {
102
105
  return result.then(
103
106
  result => {
104
- fulfill(ctx, layer)
107
+ fulfill(ctx)
105
108
  return result
106
109
  },
107
110
  err => {
108
- fulfill(ctx, layer, err)
111
+ fulfill(ctx, err)
109
112
  throw err
110
113
  }
111
114
  )
112
115
  } else {
113
- fulfill(ctx, layer)
116
+ fulfill(ctx)
114
117
  return result
115
118
  }
116
119
  } catch (e) {
117
- fulfill(ctx, layer, e)
120
+ fulfill(ctx, e)
118
121
  throw e
119
122
  }
120
123
  })
121
124
  }
122
125
  }
123
126
 
124
- function fulfill (ctx, layer, error) {
127
+ function fulfill (ctx, error) {
125
128
  if (error) {
126
129
  errorChannel.publish(error)
127
130
  }
128
131
 
129
132
  const req = ctx.req
130
- const route = ctx.routePath || (layer && layer.path)
133
+ const route = ctx.routePath
131
134
 
132
135
  // TODO: make sure that the parent class cannot override this in `enter`
133
136
  if (route) {
@@ -22,6 +22,7 @@ const HTTP_STATUS_CODE = tags.HTTP_STATUS_CODE
22
22
  const HTTP_ROUTE = tags.HTTP_ROUTE
23
23
  const HTTP_REQUEST_HEADERS = tags.HTTP_REQUEST_HEADERS
24
24
  const HTTP_RESPONSE_HEADERS = tags.HTTP_RESPONSE_HEADERS
25
+ const HTTP_USERAGENT = tags.HTTP_USERAGENT
25
26
  const MANUAL_DROP = tags.MANUAL_DROP
26
27
 
27
28
  const HTTP_STATUS_OK = 200
@@ -141,7 +142,8 @@ function addRequestTags (stream, headers) {
141
142
  [HTTP_METHOD]: headers[HTTP2_HEADER_METHOD],
142
143
  [HTTP_URL]: url.split('?')[0],
143
144
  [SPAN_KIND]: SERVER,
144
- [SPAN_TYPE]: WEB
145
+ [SPAN_TYPE]: WEB,
146
+ [HTTP_USERAGENT]: headers['user-agent']
145
147
  })
146
148
  }
147
149
 
@@ -12,25 +12,26 @@ class MySQLPlugin extends Plugin {
12
12
  constructor (...args) {
13
13
  super(...args)
14
14
 
15
- this.addSub(`apm:${this.constructor.name}:query:start`, ({ sql, conf }) => {
15
+ this.addSub(`apm:${this.constructor.name}:query:start`, ({ sql, conf: dbConfig }) => {
16
+ const service = getServiceName(this.tracer, this.config, dbConfig)
16
17
  const store = storage.getStore()
17
18
  const childOf = store ? store.span : store
18
19
  const span = this.tracer.startSpan('mysql.query', {
19
20
  childOf,
20
21
  tags: {
21
- 'service.name': this.config.service || `${this.tracer._service}-mysql`,
22
+ 'service.name': service,
22
23
  'span.type': 'sql',
23
24
  'span.kind': 'client',
24
25
  'db.type': 'mysql',
25
- 'db.user': conf.user,
26
- 'out.host': conf.host,
27
- 'out.port': conf.port,
26
+ 'db.user': dbConfig.user,
27
+ 'out.host': dbConfig.host,
28
+ 'out.port': dbConfig.port,
28
29
  'resource.name': sql
29
30
  }
30
31
  })
31
32
 
32
- if (conf.database) {
33
- span.setTag('db.name', conf.database)
33
+ if (dbConfig.database) {
34
+ span.setTag('db.name', dbConfig.database)
34
35
  }
35
36
 
36
37
  analyticsSampler.sample(span, this.config.measured)
@@ -51,4 +52,14 @@ class MySQLPlugin extends Plugin {
51
52
  }
52
53
  }
53
54
 
55
+ function getServiceName (tracer, config, dbConfig) {
56
+ if (typeof config.service === 'function') {
57
+ return config.service(dbConfig)
58
+ } else if (config.service) {
59
+ return config.service
60
+ } else {
61
+ return `${tracer._service}-mysql`
62
+ }
63
+ }
64
+
54
65
  module.exports = MySQLPlugin
@@ -1 +1 @@
1
- module.exports = '2.8.0'
1
+ module.exports = '2.9.0'
@@ -47,15 +47,15 @@ function incomingHttpStartTranslator (data) {
47
47
  '_dd.runtime_family': 'nodejs'
48
48
  })
49
49
  }
50
+ }
50
51
 
52
+ function incomingHttpEndTranslator (data) {
51
53
  const store = Gateway.startContext()
52
54
 
53
55
  store.set('req', data.req)
54
56
  store.set('res', data.res)
55
- }
56
57
 
57
- function incomingHttpEndTranslator (data) {
58
- const context = Gateway.getContext()
58
+ const context = store.get('context')
59
59
 
60
60
  if (!context) return
61
61
 
@@ -23,6 +23,7 @@ const HTTP_STATUS_CODE = tags.HTTP_STATUS_CODE
23
23
  const HTTP_ROUTE = tags.HTTP_ROUTE
24
24
  const HTTP_REQUEST_HEADERS = tags.HTTP_REQUEST_HEADERS
25
25
  const HTTP_RESPONSE_HEADERS = tags.HTTP_RESPONSE_HEADERS
26
+ const HTTP_USERAGENT = tags.HTTP_USERAGENT
26
27
  const MANUAL_DROP = tags.MANUAL_DROP
27
28
 
28
29
  const HTTP2_HEADER_AUTHORITY = ':authority'
@@ -412,7 +413,8 @@ function addRequestTags (context) {
412
413
  [HTTP_URL]: url.split('?')[0],
413
414
  [HTTP_METHOD]: req.method,
414
415
  [SPAN_KIND]: SERVER,
415
- [SPAN_TYPE]: WEB
416
+ [SPAN_TYPE]: WEB,
417
+ [HTTP_USERAGENT]: req.headers['user-agent']
416
418
  })
417
419
 
418
420
  addHeaders(context)
@@ -78,15 +78,13 @@ async function assertVersions () {
78
78
  const externalNames = Object.keys(externals).filter(name => ~names.indexOf(name))
79
79
  for (const name of externalNames) {
80
80
  for (const inst of [].concat(externals[name])) {
81
- if (!inst.dep) {
82
- await assertInstrumentation(inst, true)
83
- }
81
+ await assertInstrumentation(inst, true)
84
82
  }
85
83
  }
86
84
  }
87
85
 
88
86
  async function assertInstrumentation (instrumentation, external) {
89
- const versions = [].concat(instrumentation.versions)
87
+ const versions = [].concat(instrumentation.versions || [])
90
88
  for (const version of versions) {
91
89
  if (version) {
92
90
  await assertModules(instrumentation.name, semver.coerce(version).version, external)