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 +1 -1
- package/packages/datadog-instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/mysql2.js +76 -0
- package/packages/datadog-plugin-aws-sdk/src/helpers.js +4 -3
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +48 -0
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +56 -6
- package/packages/datadog-plugin-aws-sdk/src/services/sns.js +33 -6
- package/packages/datadog-plugin-mysql/src/index.js +1 -0
- package/packages/datadog-plugin-next/src/index.js +10 -6
- package/packages/dd-trace/lib/version.js +1 -1
- package/packages/dd-trace/src/plugins/index.js +1 -1
- package/packages/datadog-plugin-mysql2/src/index.js +0 -94
package/package.json
CHANGED
|
@@ -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
|
|
82
|
-
if (
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
if (!params || !params.StreamName) return tags
|
|
5
|
+
if (!params || !params.StreamName) return {}
|
|
8
6
|
|
|
9
|
-
return
|
|
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
|
-
|
|
6
|
+
if (!params) return {}
|
|
6
7
|
|
|
7
|
-
if (!params) return
|
|
8
|
+
if (!params.TopicArn && !(response.data && response.data.TopicArn)) return {}
|
|
8
9
|
|
|
9
|
-
|
|
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(
|
|
119
|
-
span
|
|
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
|
+
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-
|
|
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
|
-
]
|