dd-trace 2.12.2 → 2.13.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.12.2",
3
+ "version": "2.13.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -61,7 +61,7 @@
61
61
  "@datadog/native-appsec": "^1.2.1",
62
62
  "@datadog/native-metrics": "^1.4.2",
63
63
  "@datadog/pprof": "^1.0.2",
64
- "@datadog/sketches-js": "^1.0.5",
64
+ "@datadog/sketches-js": "^2.0.0",
65
65
  "@types/node": ">=12",
66
66
  "crypto-randomuuid": "^1.0.0",
67
67
  "diagnostics_channel": "^1.1.0",
@@ -1,54 +1,3 @@
1
1
  'use strict'
2
2
 
3
- require('./src/amqplib')
4
- require('./src/amqp10')
5
- require('./src/aws-sdk')
6
- require('./src/bluebird')
7
- require('./src/bunyan')
8
- require('./src/cassandra-driver')
9
- require('./src/connect')
10
- require('./src/couchbase')
11
- require('./src/cucumber')
12
- require('./src/dns')
13
- require('./src/elasticsearch')
14
- require('./src/express')
15
- require('./src/fastify')
16
- require('./src/find-my-way')
17
- require('./src/generic-pool')
18
- require('./src/google-cloud-pubsub')
19
- require('./src/graphql')
20
- require('./src/grpc')
21
- require('./src/hapi')
22
- require('./src/http')
23
- require('./src/http2')
24
- require('./src/ioredis')
25
- require('./src/jest')
26
- require('./src/kafkajs')
27
- require('./src/knex')
28
- require('./src/koa')
29
- require('./src/memcached')
30
- require('./src/microgateway-core')
31
- require('./src/moleculer')
32
- require('./src/mongodb-core')
33
- require('./src/mongoose')
34
- require('./src/mysql')
35
- require('./src/mysql2')
36
- require('./src/mocha')
37
- require('./src/net')
38
- require('./src/next')
39
- require('./src/oracledb')
40
- require('./src/paperplane')
41
- require('./src/pino')
42
- require('./src/pg')
43
- require('./src/promise')
44
- require('./src/promise-js')
45
- require('./src/q')
46
- require('./src/redis')
47
- require('./src/restify')
48
- require('./src/router')
49
- require('./src/rhea')
50
- require('./src/sharedb')
51
- require('./src/tedious')
52
- require('./src/when')
53
- require('./src/winston')
54
- require('./src/limitd-client')
3
+ require('./src/helpers/register')
@@ -0,0 +1,68 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ '@cucumber/cucumber': () => require('../cucumber'),
5
+ '@elastic/elasticsearch': () => require('../elasticsearch'),
6
+ '@elastic/transport': () => require('../elasticsearch'),
7
+ '@google-cloud/pubsub': () => require('../google-cloud-pubsub'),
8
+ '@grpc/grpc-js': () => require('../grpc'),
9
+ '@hapi/hapi': () => require('../hapi'),
10
+ '@koa/router': () => require('../koa'),
11
+ '@node-redis/client': () => require('../redis'),
12
+ 'amqp10': () => require('../amqp10'),
13
+ 'amqplib': () => require('../amqplib'),
14
+ 'aws-sdk': () => require('../aws-sdk'),
15
+ 'bluebird': () => require('../bluebird'),
16
+ 'bunyan': () => require('../bunyan'),
17
+ 'cassandra-driver': () => require('../cassandra-driver'),
18
+ 'connect': () => require('../connect'),
19
+ 'couchbase': () => require('../couchbase'),
20
+ 'cypress': () => require('../cypress'),
21
+ 'dns': () => require('../dns'),
22
+ 'elasticsearch': () => require('../elasticsearch'),
23
+ 'express': () => require('../express'),
24
+ 'fastify': () => require('../fastify'),
25
+ 'find-my-way': () => require('../find-my-way'),
26
+ 'graphql': () => require('../graphql'),
27
+ 'grpc': () => require('../grpc'),
28
+ 'hapi': () => require('../hapi'),
29
+ 'http': () => require('../http'),
30
+ 'http2': () => require('../http2'),
31
+ 'https': () => require('../http'),
32
+ 'ioredis': () => require('../ioredis'),
33
+ 'jest-environment-node': () => require('../jest'),
34
+ 'jest-environment-jsdom': () => require('../jest'),
35
+ 'jest-jasmine2': () => require('../jest'),
36
+ 'koa': () => require('../koa'),
37
+ 'koa-router': () => require('../koa'),
38
+ 'kafkajs': () => require('../kafkajs'),
39
+ 'limitd-client': () => require('../limitd-client'),
40
+ 'memcached': () => require('../memcached'),
41
+ 'microgateway-core': () => require('../microgateway-core'),
42
+ 'mocha': () => require('../mocha'),
43
+ 'mocha-each': () => require('../mocha'),
44
+ 'moleculer': () => require('../moleculer'),
45
+ 'mongodb': () => require('../mongodb-core'),
46
+ 'mongodb-core': () => require('../mongodb-core'),
47
+ 'mongoose': () => require('../mongoose'),
48
+ 'mysql': () => require('../mysql'),
49
+ 'mysql2': () => require('../mysql2'),
50
+ 'net': () => require('../net'),
51
+ 'next': () => require('../next'),
52
+ 'oracledb': () => require('../oracledb'),
53
+ 'paperplane': () => require('../paperplane'),
54
+ 'pg': () => require('../pg'),
55
+ 'pino': () => require('../pino'),
56
+ 'pino-pretty': () => require('../pino'),
57
+ 'promise-js': () => require('../promise-js'),
58
+ 'promise': () => require('../promise'),
59
+ 'q': () => require('../q'),
60
+ 'redis': () => require('../redis'),
61
+ 'restify': () => require('../restify'),
62
+ 'rhea': () => require('../rhea'),
63
+ 'router': () => require('../router'),
64
+ 'sharedb': () => require('../sharedb'),
65
+ 'tedious': () => require('../tedious'),
66
+ 'when': () => require('../when'),
67
+ 'winston': () => require('../winston')
68
+ }
@@ -1,16 +1,12 @@
1
1
  'use strict'
2
2
 
3
3
  const dc = require('diagnostics_channel')
4
- const path = require('path')
5
4
  const semver = require('semver')
6
- const Hook = require('./hook')
7
- const requirePackageJson = require('../../../dd-trace/src/require-package-json')
5
+ const instrumentations = require('./instrumentations')
8
6
  const { AsyncResource } = require('async_hooks')
9
- const log = require('../../../dd-trace/src/log')
10
7
 
11
- const pathSepExpr = new RegExp(`\\${path.sep}`, 'g')
12
8
  const channelMap = {}
13
- exports.channel = function channel (name) {
9
+ exports.channel = function (name) {
14
10
  const maybe = channelMap[name]
15
11
  if (maybe) return maybe
16
12
  const ch = dc.channel(name)
@@ -19,36 +15,11 @@ exports.channel = function channel (name) {
19
15
  }
20
16
 
21
17
  exports.addHook = function addHook ({ name, versions, file }, hook) {
22
- const fullFilename = filename(name, file)
23
-
24
- Hook([name], (moduleExports, moduleName, moduleBaseDir) => {
25
- moduleName = moduleName.replace(pathSepExpr, '/')
26
-
27
- if (moduleName !== fullFilename || !matchVersion(getVersion(moduleBaseDir), versions)) {
28
- return moduleExports
29
- }
30
-
31
- try {
32
- return hook(moduleExports)
33
- } catch (e) {
34
- log.error(e)
35
- return moduleExports
36
- }
37
- })
38
- }
39
-
40
- function matchVersion (version, ranges) {
41
- return !version || (ranges && ranges.some(range => semver.satisfies(semver.coerce(version), range)))
42
- }
43
-
44
- function getVersion (moduleBaseDir) {
45
- if (moduleBaseDir) {
46
- return requirePackageJson(moduleBaseDir, module).version
18
+ if (!instrumentations[name]) {
19
+ instrumentations[name] = []
47
20
  }
48
- }
49
21
 
50
- function filename (name, file) {
51
- return [name, file].filter(val => val).join('/')
22
+ instrumentations[name].push({ name, versions, file, hook })
52
23
  }
53
24
 
54
25
  // AsyncResource.bind exists and binds `this` properly only from 17.8.0 and up.
@@ -0,0 +1,3 @@
1
+ 'use strict'
2
+
3
+ module.exports = {}
@@ -0,0 +1,59 @@
1
+ 'use strict'
2
+
3
+ const { channel } = require('diagnostics_channel')
4
+ const path = require('path')
5
+ const semver = require('semver')
6
+ const Hook = require('./hook')
7
+ const requirePackageJson = require('../../../dd-trace/src/require-package-json')
8
+ const log = require('../../../dd-trace/src/log')
9
+
10
+ const hooks = require('./hooks')
11
+ const instrumentations = require('./instrumentations')
12
+ const names = Object.keys(hooks)
13
+ const pathSepExpr = new RegExp(`\\${path.sep}`, 'g')
14
+
15
+ const loadChannel = channel('dd-trace:instrumentation:load')
16
+
17
+ // TODO: make this more efficient
18
+
19
+ for (const packageName of names) {
20
+ Hook([packageName], (moduleExports, moduleName, moduleBaseDir) => {
21
+ moduleName = moduleName.replace(pathSepExpr, '/')
22
+
23
+ hooks[packageName]()
24
+
25
+ for (const { name, file, versions, hook } of instrumentations[packageName]) {
26
+ const fullFilename = filename(name, file)
27
+
28
+ if (moduleName === fullFilename) {
29
+ const version = getVersion(moduleBaseDir)
30
+
31
+ if (matchVersion(version, versions)) {
32
+ try {
33
+ loadChannel.publish({ name, version, file })
34
+
35
+ moduleExports = hook(moduleExports)
36
+ } catch (e) {
37
+ log.error(e)
38
+ }
39
+ }
40
+ }
41
+ }
42
+
43
+ return moduleExports
44
+ })
45
+ }
46
+
47
+ function matchVersion (version, ranges) {
48
+ return !version || (ranges && ranges.some(range => semver.satisfies(semver.coerce(version), range)))
49
+ }
50
+
51
+ function getVersion (moduleBaseDir) {
52
+ if (moduleBaseDir) {
53
+ return requirePackageJson(moduleBaseDir, module).version
54
+ }
55
+ }
56
+
57
+ function filename (name, file) {
58
+ return [name, file].filter(val => val).join('/')
59
+ }
@@ -161,12 +161,12 @@ class MochaPlugin extends Plugin {
161
161
  const testSuiteSpan = this._testSuites.get(test.parent)
162
162
 
163
163
  if (testSuiteSpan) {
164
- const testSuiteId = testSuiteSpan.context()._spanId.toString('hex')
164
+ const testSuiteId = testSuiteSpan.context()._spanId.toString(16)
165
165
  testSuiteTags[TEST_SUITE_ID] = testSuiteId
166
166
  }
167
167
 
168
168
  if (this.testSessionSpan) {
169
- const testSessionId = this.testSessionSpan.context()._traceId.toString('hex')
169
+ const testSessionId = this.testSessionSpan.context()._traceId.toString(16)
170
170
  testSuiteTags[TEST_SESSION_ID] = testSessionId
171
171
  testSuiteTags[TEST_COMMAND] = this.command
172
172
  }
@@ -0,0 +1,220 @@
1
+
2
+ const fs = require('fs')
3
+ const https = require('https')
4
+ const path = require('path')
5
+
6
+ const FormData = require('../../../exporters/common/form-data')
7
+
8
+ const log = require('../../../log')
9
+ const {
10
+ getLatestCommits,
11
+ getRepositoryUrl,
12
+ generatePackFilesForCommits,
13
+ getCommitsToUpload
14
+ } = require('../../../plugins/util/git')
15
+
16
+ const isValidSha = (sha) => /[0-9a-f]{40}/.test(sha)
17
+
18
+ function sanitizeCommits (commits) {
19
+ return commits.map(({ id: commitSha, type }) => {
20
+ if (type !== 'commit') {
21
+ throw new Error('Invalid commit response')
22
+ }
23
+ const sanitizedCommit = commitSha.replace(/[^0-9a-f]+/g, '')
24
+ if (sanitizedCommit !== commitSha || !isValidSha(sanitizedCommit)) {
25
+ throw new Error('Invalid commit format')
26
+ }
27
+ return sanitizedCommit
28
+ })
29
+ }
30
+
31
+ function getCommonRequestOptions (url) {
32
+ return {
33
+ method: 'POST',
34
+ headers: {
35
+ 'dd-api-key': process.env.DATADOG_API_KEY || process.env.DD_API_KEY
36
+ },
37
+ timeout: 15000,
38
+ protocol: url.protocol,
39
+ hostname: url.hostname,
40
+ port: url.port
41
+ }
42
+ }
43
+
44
+ /**
45
+ * This function posts the SHAs of the commits of the last month
46
+ * The response are the commits for which the backend already has information
47
+ * This response is used to know which commits can be ignored from there on
48
+ */
49
+ function getCommitsToExclude ({ url, repositoryUrl }, callback) {
50
+ const latestCommits = getLatestCommits()
51
+ const [headCommit] = latestCommits
52
+
53
+ const commonOptions = getCommonRequestOptions(url)
54
+
55
+ const options = {
56
+ ...commonOptions,
57
+ headers: {
58
+ ...commonOptions.headers,
59
+ 'Content-Type': 'application/json'
60
+ },
61
+ path: '/api/v2/git/repository/search_commits'
62
+ }
63
+
64
+ const localCommitData = JSON.stringify({
65
+ meta: {
66
+ repository_url: repositoryUrl
67
+ },
68
+ data: latestCommits.map(commit => ({
69
+ id: commit,
70
+ type: 'commit'
71
+ }))
72
+ })
73
+
74
+ const request = https.request(options, (res) => {
75
+ let responseData = ''
76
+
77
+ res.on('data', chunk => { responseData += chunk })
78
+ res.on('end', () => {
79
+ if (res.statusCode === 200) {
80
+ let commitsToExclude
81
+ try {
82
+ commitsToExclude = sanitizeCommits(JSON.parse(responseData).data)
83
+ } catch (e) {
84
+ callback(new Error(`Can't parse response: ${e.message}`))
85
+ return
86
+ }
87
+ callback(null, commitsToExclude, headCommit)
88
+ } else {
89
+ const error = new Error(`Error getting commits: ${res.statusCode} ${res.statusMessage}`)
90
+ callback(error)
91
+ }
92
+ })
93
+ })
94
+
95
+ request.write(localCommitData)
96
+ request.on('error', callback)
97
+
98
+ request.end()
99
+
100
+ return request
101
+ }
102
+
103
+ /**
104
+ * This function uploads a git packfile
105
+ */
106
+ function uploadPackFile ({ url, packFileToUpload, repositoryUrl, headCommit }, callback) {
107
+ const form = new FormData()
108
+
109
+ const pushedSha = JSON.stringify({
110
+ data: {
111
+ id: headCommit,
112
+ type: 'commit'
113
+ },
114
+ meta: {
115
+ repository_url: repositoryUrl
116
+ }
117
+ })
118
+
119
+ form.append('pushedSha', pushedSha, { contentType: 'application/json' })
120
+
121
+ try {
122
+ const packFileContent = fs.readFileSync(packFileToUpload)
123
+ // The original filename includes a random prefix, so we remove it here
124
+ const [, filename] = path.basename(packFileToUpload).split('-')
125
+ form.append('packfile', packFileContent, {
126
+ filename,
127
+ contentType: 'application/octet-stream'
128
+ })
129
+ } catch (e) {
130
+ callback(new Error(`Error reading packfile: ${packFileToUpload}`))
131
+ return
132
+ }
133
+
134
+ const commonOptions = getCommonRequestOptions(url)
135
+
136
+ const options = {
137
+ ...commonOptions,
138
+ path: '/api/v2/git/repository/packfile',
139
+ headers: {
140
+ ...commonOptions.headers,
141
+ ...form.getHeaders()
142
+ }
143
+ }
144
+
145
+ const req = https.request(options, res => {
146
+ res.on('data', () => {})
147
+ res.on('end', () => {
148
+ if (res.statusCode === 204) {
149
+ callback(null)
150
+ } else {
151
+ const error = new Error(`Error uploading packfiles: ${res.statusCode} ${res.statusMessage}`)
152
+ error.status = res.statusCode
153
+
154
+ callback(error)
155
+ }
156
+ })
157
+ })
158
+
159
+ req.on('error', err => {
160
+ callback(err)
161
+ })
162
+ form.pipe(req)
163
+ }
164
+
165
+ /**
166
+ * This function uploads git metadata to CI Visibility's backend.
167
+ */
168
+ function sendGitMetadata (site, callback) {
169
+ const url = new URL(`https://api.${site}`)
170
+
171
+ const repositoryUrl = getRepositoryUrl()
172
+
173
+ getCommitsToExclude({ url, repositoryUrl }, (err, commitsToExclude, headCommit) => {
174
+ if (err) {
175
+ callback(err)
176
+ return
177
+ }
178
+ const commitsToUpload = getCommitsToUpload(commitsToExclude)
179
+
180
+ if (!commitsToUpload.length) {
181
+ log.debug('No commits to upload')
182
+ callback(null)
183
+ return
184
+ }
185
+
186
+ const packFilesToUpload = generatePackFilesForCommits(commitsToUpload)
187
+
188
+ let packFileIndex = 0
189
+ // This uploads packfiles sequentially
190
+ const uploadPackFileCallback = (err) => {
191
+ if (err || packFileIndex === packFilesToUpload.length) {
192
+ callback(err)
193
+ return
194
+ }
195
+ return uploadPackFile(
196
+ {
197
+ packFileToUpload: packFilesToUpload[packFileIndex++],
198
+ url,
199
+ repositoryUrl,
200
+ headCommit
201
+ },
202
+ uploadPackFileCallback
203
+ )
204
+ }
205
+
206
+ uploadPackFile(
207
+ {
208
+ url,
209
+ packFileToUpload: packFilesToUpload[packFileIndex++],
210
+ repositoryUrl,
211
+ headCommit
212
+ },
213
+ uploadPackFileCallback
214
+ )
215
+ })
216
+ }
217
+
218
+ module.exports = {
219
+ sendGitMetadata
220
+ }
@@ -174,6 +174,11 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
174
174
  |[\\-]{5}BEGIN[a-z\\s]+PRIVATE\\sKEY[\\-]{5}[^\\-]+[\\-]{5}END[a-z\\s]+PRIVATE\\sKEY|ssh-rsa\\s*[a-z0-9\\/\\.+]{100,}`
175
175
  )
176
176
 
177
+ const DD_CIVISIBILITY_GIT_UPLOAD_ENABLED = coalesce(
178
+ process.env.DD_CIVISIBILITY_GIT_UPLOAD_ENABLED,
179
+ false
180
+ )
181
+
177
182
  const sampler = (options.experimental && options.experimental.sampler) || {}
178
183
  const ingestion = options.ingestion || {}
179
184
  const dogstatsd = coalesce(options.dogstatsd, {})
@@ -248,6 +253,7 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
248
253
  obfuscatorKeyRegex: DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP,
249
254
  obfuscatorValueRegex: DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP
250
255
  }
256
+ this.isGitUploadEnabled = isTrue(DD_CIVISIBILITY_GIT_UPLOAD_ENABLED)
251
257
 
252
258
  tagger.add(this.tags, {
253
259
  service: this.service,
@@ -14,17 +14,17 @@ let batch = 0
14
14
 
15
15
  // Internal representation of a trace or span ID.
16
16
  class Identifier {
17
- constructor (value, radix) {
17
+ constructor (value, radix = 16) {
18
18
  this._isUint64BE = true // msgpack-lite compatibility
19
- this._buffer = typeof radix === 'number'
20
- ? fromString(value, radix)
21
- : createBuffer(value)
19
+ this._buffer = radix === 16
20
+ ? createBuffer(value)
21
+ : fromString(value, radix)
22
22
  }
23
23
 
24
- toString (radix) {
25
- return typeof radix === 'number'
26
- ? toNumberString(this._buffer, radix)
27
- : toHexString(this._buffer)
24
+ toString (radix = 16) {
25
+ return radix === 16
26
+ ? toHexString(this._buffer)
27
+ : toNumberString(this._buffer, radix)
28
28
  }
29
29
 
30
30
  toBuffer () {
@@ -49,10 +49,13 @@ function createBuffer (value) {
49
49
  if (value === '0') return zeroId
50
50
  if (!value) return pseudoRandom()
51
51
 
52
- const size = Math.ceil(value.length / 2)
53
- const buffer = new Array(size)
52
+ const size = Math.ceil(value.length / 16) * 16
53
+ const bytes = size / 2
54
+ const buffer = new Array(bytes)
54
55
 
55
- for (let i = 0; i < size; i++) {
56
+ value = value.padStart(size, '0')
57
+
58
+ for (let i = 0; i < bytes; i++) {
56
59
  buffer[i] = parseInt(value.substring(i * 2, i * 2 + 2), 16)
57
60
  }
58
61
 
@@ -100,8 +103,8 @@ function fromString (str, raddix) {
100
103
 
101
104
  // Convert a buffer to a numerical string.
102
105
  function toNumberString (buffer, radix) {
103
- let high = readInt32(buffer, 0)
104
- let low = readInt32(buffer, 4)
106
+ let high = readInt32(buffer, buffer.length - 8)
107
+ let low = readInt32(buffer, buffer.length - 4)
105
108
  let str = ''
106
109
 
107
110
  radix = radix || 10
@@ -82,8 +82,8 @@ class TextMapPropagator {
82
82
  _injectB3 (spanContext, carrier) {
83
83
  if (!this._config.experimental.b3) return
84
84
 
85
- carrier[b3TraceKey] = spanContext._traceId.toString('hex')
86
- carrier[b3SpanKey] = spanContext._spanId.toString('hex')
85
+ carrier[b3TraceKey] = spanContext._traceId.toString(16)
86
+ carrier[b3SpanKey] = spanContext._spanId.toString(16)
87
87
  carrier[b3SampledKey] = spanContext._sampling.priority >= AUTO_KEEP ? '1' : '0'
88
88
 
89
89
  if (spanContext._sampling.priority > AUTO_KEEP) {
@@ -91,7 +91,7 @@ class TextMapPropagator {
91
91
  }
92
92
 
93
93
  if (spanContext._parentId) {
94
- carrier[b3ParentKey] = spanContext._parentId.toString('hex')
94
+ carrier[b3ParentKey] = spanContext._parentId.toString(16)
95
95
  }
96
96
  }
97
97
 
@@ -99,8 +99,8 @@ class TextMapPropagator {
99
99
  if (!this._config.experimental.traceparent) return
100
100
 
101
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')
102
+ const traceId = spanContext._traceId.toString(16).padStart(32, '0')
103
+ const spanId = spanContext._spanId.toString(16).padStart(16, '0')
104
104
  carrier[traceparentKey] = `01-${traceId}-${spanId}-${sampling}`
105
105
  }
106
106
 
@@ -129,7 +129,7 @@ class TextMapPropagator {
129
129
  const b3 = this._extractB3Headers(carrier)
130
130
  const debug = b3[b3FlagsKey] === '1'
131
131
  const priority = this._getPriority(b3[b3SampledKey], debug)
132
- const spanContext = this._extractGenericContext(b3, b3TraceKey, b3SpanKey)
132
+ const spanContext = this._extractGenericContext(b3, b3TraceKey, b3SpanKey, 16)
133
133
 
134
134
  if (priority !== undefined) {
135
135
  if (!spanContext) {