dd-trace 2.0.1 → 2.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -3,6 +3,7 @@
3
3
  require('./src/dns')
4
4
  require('./src/memcached')
5
5
  require('./src/mysql')
6
+ require('./src/mysql2')
6
7
  require('./src/bluebird')
7
8
  require('./src/when')
8
9
  require('./src/promise')
@@ -0,0 +1,76 @@
1
+ 'use strict'
2
+
3
+ const {
4
+ channel,
5
+ addHook,
6
+ AsyncResource
7
+ } = require('./helpers/instrument')
8
+ const shimmer = require('../../datadog-shimmer')
9
+
10
+ addHook({ name: 'mysql2', file: 'lib/connection.js', versions: ['>=1'] }, Connection => {
11
+ const startCh = channel('apm:mysql:query:start')
12
+ const asyncEndCh = channel('apm:mysql:query:async-end')
13
+ const endCh = channel('apm:mysql:query:end')
14
+ const errorCh = channel('apm:mysql:query:error')
15
+
16
+ shimmer.wrap(Connection.prototype, 'addCommand', addCommand => function (cmd) {
17
+ if (!startCh.hasSubscribers) return addCommand.apply(this, arguments)
18
+
19
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
20
+ const name = cmd && cmd.constructor && cmd.constructor.name
21
+ const isCommand = typeof cmd.execute === 'function'
22
+ const isQuery = isCommand && (name === 'Execute' || name === 'Query')
23
+
24
+ // TODO: consider supporting all commands and not just queries
25
+ cmd.execute = isQuery
26
+ ? wrapExecute(cmd, cmd.execute, asyncResource, this.config)
27
+ : bindExecute(cmd, cmd.execute, asyncResource)
28
+
29
+ return asyncResource.bind(addCommand, this).apply(this, arguments)
30
+ })
31
+
32
+ return Connection
33
+
34
+ function bindExecute (cmd, execute, asyncResource) {
35
+ return asyncResource.bind(function executeWithTrace (packet, connection) {
36
+ if (this.onResult) {
37
+ this.onResult = asyncResource.bind(this.onResult)
38
+ }
39
+
40
+ return execute.apply(this, arguments)
41
+ }, cmd)
42
+ }
43
+
44
+ function wrapExecute (cmd, execute, asyncResource, config) {
45
+ return asyncResource.bind(function executeWithTrace (packet, connection) {
46
+ const sql = cmd.statement ? cmd.statement.query : cmd.sql
47
+
48
+ startCh.publish([sql, config])
49
+
50
+ if (this.onResult) {
51
+ const onResult = asyncResource.bind(this.onResult)
52
+
53
+ this.onResult = AsyncResource.bind(function (error) {
54
+ if (error) {
55
+ errorCh.publish(error)
56
+ }
57
+ asyncEndCh.publish(undefined)
58
+ onResult.apply(this, arguments)
59
+ }, 'bound-anonymous-fn', this)
60
+ } else {
61
+ this.on('error', AsyncResource.bind(error => errorCh.publish(error)))
62
+ this.on('end', AsyncResource.bind(() => asyncEndCh.publish(undefined)))
63
+ }
64
+
65
+ this.execute = execute
66
+
67
+ try {
68
+ return execute.apply(this, arguments)
69
+ } catch (err) {
70
+ errorCh.publish(err)
71
+ } finally {
72
+ endCh.publish(undefined)
73
+ }
74
+ }, cmd)
75
+ }
76
+ })
@@ -10,7 +10,8 @@ const services = {
10
10
  s3: getService(require('./services/s3')),
11
11
  redshift: getService(require('./services/redshift')),
12
12
  sns: getService(require('./services/sns')),
13
- sqs: getService(require('./services/sqs'))
13
+ sqs: getService(require('./services/sqs')),
14
+ eventbridge: getService(require('./services/eventbridge'))
14
15
  }
15
16
 
16
17
  function getService (Service) {
@@ -78,8 +79,8 @@ const helpers = {
78
79
  requestInject (span, request, serviceName, tracer) {
79
80
  if (!span) return
80
81
 
81
- const inject = services[serviceName] && services[serviceName].requestInject
82
- if (inject) inject(span, request, tracer)
82
+ const service = services[serviceName] && services[serviceName]
83
+ if (service.requestInject) service.requestInject(span, request, tracer)
83
84
  },
84
85
 
85
86
  wrapCb (cb, serviceName, tags, request, tracer, childOf) {
@@ -0,0 +1,48 @@
1
+ 'use strict'
2
+ const log = require('../../../dd-trace/src/log')
3
+ class EventBridge {
4
+ generateTags (params, operation, response) {
5
+ if (!params || !params.source) return {}
6
+
7
+ return {
8
+ 'resource.name': `${operation} ${params.source}`,
9
+ 'aws.eventbridge.source': params.source
10
+ }
11
+ }
12
+
13
+ /**
14
+ * requestInject
15
+ * @param {*} span
16
+ * @param {*} request
17
+ * @param {*} tracer
18
+ *
19
+ * Docs: https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_PutEventsRequestEntry.html
20
+ * We cannot use the traceHeader field as that's reserved for X-Ray.
21
+ * Detail must be a valid JSON string
22
+ * Max size per event is 256kb (https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-putevent-size.html)
23
+ */
24
+ requestInject (span, request, tracer) {
25
+ const operation = request.operation
26
+ if (operation === 'putEvents' &&
27
+ request.params &&
28
+ request.params.Entries &&
29
+ request.params.Entries.length > 0 &&
30
+ request.params.Entries[0].Detail) {
31
+ try {
32
+ const details = JSON.parse(request.params.Entries[0].Detail)
33
+ details._datadog = {}
34
+ tracer.inject(span, 'text_map', details._datadog)
35
+ const finalData = JSON.stringify(details)
36
+ const byteSize = Buffer.byteLength(finalData)
37
+ if (byteSize >= (1024 * 256)) {
38
+ log.info('Payload size too large to pass context')
39
+ return
40
+ }
41
+ request.params.Entries[0].Detail = finalData
42
+ } catch (e) {
43
+ log.error(e)
44
+ }
45
+ }
46
+ }
47
+ }
48
+ module.exports = EventBridge
@@ -1,15 +1,65 @@
1
1
  'use strict'
2
-
2
+ const log = require('../../../dd-trace/src/log')
3
3
  class Kinesis {
4
4
  generateTags (params, operation, response) {
5
- const tags = {}
6
-
7
- if (!params || !params.StreamName) return tags
5
+ if (!params || !params.StreamName) return {}
8
6
 
9
- return Object.assign(tags, {
7
+ return {
10
8
  'resource.name': `${operation} ${params.StreamName}`,
11
9
  'aws.kinesis.stream_name': params.StreamName
12
- })
10
+ }
11
+ }
12
+
13
+ // AWS-SDK will b64 kinesis payloads
14
+ // or will accept an already b64 encoded payload
15
+ // This method handles both
16
+ _tryParse (body) {
17
+ try {
18
+ return JSON.parse(body)
19
+ } catch (e) {
20
+ log.info('Not JSON string. Trying Base64 encoded JSON string')
21
+ }
22
+ try {
23
+ return JSON.parse(Buffer.from(body, 'base64').toString('ascii'), true)
24
+ } catch (e) {
25
+ return null
26
+ }
27
+ }
28
+
29
+ requestInject (span, request, tracer) {
30
+ const operation = request.operation
31
+ if (operation === 'putRecord' || operation === 'putRecords') {
32
+ if (!request.params) {
33
+ return
34
+ }
35
+
36
+ const traceData = {}
37
+ tracer.inject(span, 'text_map', traceData)
38
+ let injectPath
39
+ if (request.params.Records && request.params.Records.length > 0) {
40
+ injectPath = request.params.Records[0]
41
+ } else if (request.params.Data) {
42
+ injectPath = request.params
43
+ } else {
44
+ log.error('No valid payload passed, unable to pass trace context')
45
+ return
46
+ }
47
+ const parsedData = this._tryParse(injectPath.Data)
48
+ if (parsedData) {
49
+ parsedData._datadog = traceData
50
+ const finalData = JSON.stringify(parsedData)
51
+ const byteSize = Buffer.byteLength(finalData, 'ascii')
52
+ // Kinesis max payload size is 1MB
53
+ // So we must ensure adding DD context won't go over that (512b is an estimate)
54
+ if (byteSize >= 1048576) {
55
+ log.info('Payload size too large to pass context')
56
+ return
57
+ }
58
+ injectPath.Data = finalData
59
+ } else {
60
+ log.error('Unable to parse payload, unable to pass trace context')
61
+ }
62
+ }
13
63
  }
14
64
  }
15
65
 
@@ -1,21 +1,48 @@
1
1
  'use strict'
2
+ const log = require('../../../dd-trace/src/log')
2
3
 
3
4
  class Sns {
4
5
  generateTags (params, operation, response) {
5
- const tags = {}
6
+ if (!params) return {}
6
7
 
7
- if (!params) return tags
8
+ if (!params.TopicArn && !(response.data && response.data.TopicArn)) return {}
8
9
 
9
- if (!params.TopicArn && !(response.data && response.data.TopicArn)) return tags
10
-
11
- return Object.assign(tags, {
10
+ return {
12
11
  'resource.name': `${operation} ${params.TopicArn || response.data.TopicArn}`,
13
12
  'aws.sns.topic_arn': params.TopicArn || response.data.TopicArn
14
- })
13
+ }
15
14
 
16
15
  // TODO: should arn be sanitized or quantized in some way here,
17
16
  // for example if it contains a phone number?
18
17
  }
18
+
19
+ requestInject (span, request, tracer) {
20
+ const operation = request.operation
21
+ if (operation === 'publish' || operation === 'publishBatch') {
22
+ if (!request.params) {
23
+ request.params = {}
24
+ }
25
+ let injectPath
26
+ if (request.params.PublishBatchRequestEntries && request.params.PublishBatchRequestEntries.length > 0) {
27
+ injectPath = request.params.PublishBatchRequestEntries[0]
28
+ } else if (request.params.Message) {
29
+ injectPath = request.params
30
+ }
31
+ if (!injectPath.MessageAttributes) {
32
+ injectPath.MessageAttributes = {}
33
+ }
34
+ if (Object.keys(injectPath.MessageAttributes).length >= 10) { // SNS quota
35
+ log.info('Message attributes full, skipping trace context injection')
36
+ return
37
+ }
38
+ const ddInfo = {}
39
+ tracer.inject(span, 'text_map', ddInfo)
40
+ injectPath.MessageAttributes._datadog = {
41
+ DataType: 'String',
42
+ StringValue: JSON.stringify(ddInfo)
43
+ }
44
+ }
45
+ }
19
46
  }
20
47
 
21
48
  module.exports = Sns
@@ -4,6 +4,7 @@ const Plugin = require('../../dd-trace/src/plugins/plugin')
4
4
  const { storage } = require('../../datadog-core')
5
5
  const analyticsSampler = require('../../dd-trace/src/analytics_sampler')
6
6
 
7
+ // This plugin supports both mysql and mysql2
7
8
  class MySQLPlugin extends Plugin {
8
9
  static get name () {
9
10
  return 'mysql'
@@ -115,10 +115,10 @@ function trace (tracer, config, req, res, handler) {
115
115
  // TODO: Use CLS when it will be available in core.
116
116
  span._nextReq = req
117
117
 
118
- promise.then(() => finish(span, config, req, res), err => {
119
- span.setTag('error', err)
120
- finish(span, config, req, res)
121
- })
118
+ promise.then(
119
+ () => finish(span, config, req, res),
120
+ err => finish(span, config, req, res, err)
121
+ )
122
122
 
123
123
  return promise
124
124
  }
@@ -132,7 +132,8 @@ function addPage (req, page) {
132
132
  })
133
133
  }
134
134
 
135
- function finish (span, config, req, res) {
135
+ function finish (span, config, req, res, err) {
136
+ span.setTag('error', err || !config.validateStatus(res.statusCode))
136
137
  span.addTags({
137
138
  'http.status_code': res.statusCode
138
139
  })
@@ -142,8 +143,11 @@ function finish (span, config, req, res) {
142
143
 
143
144
  function normalizeConfig (config) {
144
145
  const hooks = getHooks(config)
146
+ const validateStatus = typeof config.validateStatus === 'function'
147
+ ? config.validateStatus
148
+ : code => code < 500
145
149
 
146
- return Object.assign({}, config, { hooks })
150
+ return Object.assign({}, config, { hooks, validateStatus })
147
151
  }
148
152
 
149
153
  function getHooks (config) {
@@ -1 +1 @@
1
- module.exports = '2.0.1'
1
+ module.exports = '2.1.0'
@@ -35,7 +35,7 @@ module.exports = {
35
35
  'mongodb-core': require('../../../datadog-plugin-mongodb-core/src'),
36
36
  'mongoose': require('../../../datadog-plugin-mongoose/src'),
37
37
  'mysql': require('../../../datadog-plugin-mysql/src'),
38
- 'mysql2': require('../../../datadog-plugin-mysql2/src'),
38
+ 'mysql2': require('../../../datadog-plugin-mysql/src'),
39
39
  'net': require('../../../datadog-plugin-net/src'),
40
40
  'next': require('../../../datadog-plugin-next/src'),
41
41
  'oracledb': require('../../../datadog-plugin-oracledb/src'),
@@ -1,94 +0,0 @@
1
- 'use strict'
2
-
3
- const Tags = require('opentracing').Tags
4
- const analyticsSampler = require('../../dd-trace/src/analytics_sampler')
5
-
6
- function createWrapAddCommand (tracer, config) {
7
- return function wrapAddCommand (addCommand) {
8
- return function addCommandWithTrace (cmd) {
9
- const name = cmd && cmd.constructor && cmd.constructor.name
10
- const isCommand = typeof cmd.execute === 'function'
11
- const isSupported = name === 'Execute' || name === 'Query'
12
-
13
- if (isCommand && isSupported) {
14
- cmd.execute = wrapExecute(tracer, config, cmd.execute)
15
- }
16
-
17
- return addCommand.apply(this, arguments)
18
- }
19
- }
20
- }
21
-
22
- function wrapExecute (tracer, config, execute) {
23
- const scope = tracer.scope()
24
- const childOf = scope.active()
25
-
26
- return function executeWithTrace (packet, connection) {
27
- const connectionConfig = (connection && connection.config) || {}
28
- const sql = this.statement ? this.statement.query : this.sql
29
- const span = tracer.startSpan('mysql.query', {
30
- childOf,
31
- tags: {
32
- [Tags.SPAN_KIND]: Tags.SPAN_KIND_RPC_CLIENT,
33
- 'service.name': config.service || `${tracer._service}-mysql`,
34
- 'resource.name': sql,
35
- 'span.type': 'sql',
36
- 'span.kind': 'client',
37
- 'db.type': 'mysql',
38
- 'db.user': connectionConfig.user,
39
- 'db.name': connectionConfig.database,
40
- 'out.host': connectionConfig.host,
41
- 'out.port': connectionConfig.port
42
- }
43
- })
44
-
45
- analyticsSampler.sample(span, config.measured)
46
-
47
- if (typeof this.onResult === 'function') {
48
- this.onResult = wrapCallback(tracer, span, childOf, this.onResult)
49
- } else {
50
- this.on('error', error => span.addTags({ error }))
51
- this.on('end', () => span.finish())
52
- }
53
-
54
- this.execute = execute
55
-
56
- return scope.bind(execute, span).apply(this, arguments)
57
- }
58
- }
59
-
60
- function wrapCallback (tracer, span, parent, done) {
61
- return tracer.scope().bind((...args) => {
62
- const [ error ] = args
63
- span.addTags({ error })
64
-
65
- span.finish()
66
-
67
- done(...args)
68
- }, parent)
69
- }
70
-
71
- module.exports = [
72
- {
73
- name: 'mysql2',
74
- file: 'lib/connection.js',
75
- versions: ['>=1'],
76
- patch (Connection, tracer, config) {
77
- this.wrap(Connection.prototype, 'addCommand', createWrapAddCommand(tracer, config))
78
- },
79
- unpatch (Connection) {
80
- this.unwrap(Connection.prototype, 'addCommand')
81
- }
82
- },
83
- {
84
- name: 'mysql2',
85
- file: 'lib/commands/command.js',
86
- versions: ['>=1'],
87
- patch (Command, tracer, config) {
88
- tracer.scope().bind(Command.prototype)
89
- },
90
- unpatch (Command, tracer) {
91
- tracer.scope().unbind(Command.prototype)
92
- }
93
- }
94
- ]