dd-trace 4.18.0 → 4.23.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.
Files changed (137) hide show
  1. package/LICENSE-3rdparty.csv +3 -2
  2. package/README.md +3 -3
  3. package/ext/kinds.d.ts +1 -0
  4. package/ext/kinds.js +2 -1
  5. package/ext/tags.d.ts +2 -1
  6. package/ext/tags.js +6 -1
  7. package/index.d.ts +29 -0
  8. package/package.json +12 -11
  9. package/packages/datadog-core/src/storage/async_resource.js +1 -1
  10. package/packages/datadog-esbuild/index.js +1 -20
  11. package/packages/datadog-instrumentations/src/aerospike.js +47 -0
  12. package/packages/datadog-instrumentations/src/apollo-server-core.js +41 -0
  13. package/packages/datadog-instrumentations/src/apollo-server.js +83 -0
  14. package/packages/datadog-instrumentations/src/child-process.js +4 -5
  15. package/packages/datadog-instrumentations/src/couchbase.js +5 -4
  16. package/packages/datadog-instrumentations/src/crypto.js +2 -1
  17. package/packages/datadog-instrumentations/src/dns.js +2 -1
  18. package/packages/datadog-instrumentations/src/graphql.js +18 -4
  19. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +1 -2
  20. package/packages/datadog-instrumentations/src/helpers/hooks.js +10 -2
  21. package/packages/datadog-instrumentations/src/helpers/instrument.js +9 -4
  22. package/packages/datadog-instrumentations/src/helpers/register.js +19 -3
  23. package/packages/datadog-instrumentations/src/http/client.js +12 -2
  24. package/packages/datadog-instrumentations/src/http/server.js +7 -4
  25. package/packages/datadog-instrumentations/src/http2/client.js +3 -1
  26. package/packages/datadog-instrumentations/src/http2/server.js +3 -1
  27. package/packages/datadog-instrumentations/src/jest.js +12 -6
  28. package/packages/datadog-instrumentations/src/kafkajs.js +27 -0
  29. package/packages/datadog-instrumentations/src/net.js +10 -2
  30. package/packages/datadog-instrumentations/src/next.js +18 -6
  31. package/packages/datadog-instrumentations/src/restify.js +14 -1
  32. package/packages/datadog-instrumentations/src/rhea.js +15 -9
  33. package/packages/datadog-plugin-aerospike/src/index.js +113 -0
  34. package/packages/datadog-plugin-cucumber/src/index.js +34 -2
  35. package/packages/datadog-plugin-cypress/src/plugin.js +60 -8
  36. package/packages/datadog-plugin-graphql/src/resolve.js +26 -18
  37. package/packages/datadog-plugin-http/src/client.js +19 -2
  38. package/packages/datadog-plugin-jest/src/index.js +38 -4
  39. package/packages/datadog-plugin-kafkajs/src/consumer.js +59 -6
  40. package/packages/datadog-plugin-kafkajs/src/producer.js +64 -6
  41. package/packages/datadog-plugin-mocha/src/index.js +32 -1
  42. package/packages/datadog-plugin-next/src/index.js +40 -14
  43. package/packages/datadog-plugin-playwright/src/index.js +17 -1
  44. package/packages/dd-trace/src/appsec/activation.js +29 -0
  45. package/packages/dd-trace/src/appsec/addresses.js +3 -1
  46. package/packages/dd-trace/src/appsec/api_security_sampler.js +48 -0
  47. package/packages/dd-trace/src/appsec/blocked_templates.js +4 -1
  48. package/packages/dd-trace/src/appsec/blocking.js +95 -43
  49. package/packages/dd-trace/src/appsec/channels.js +5 -2
  50. package/packages/dd-trace/src/appsec/graphql.js +146 -0
  51. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  52. package/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js +105 -0
  53. package/packages/dd-trace/src/appsec/iast/iast-log.js +1 -1
  54. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +1 -1
  55. package/packages/dd-trace/src/appsec/iast/index.js +1 -1
  56. package/packages/dd-trace/src/appsec/iast/path-line.js +1 -1
  57. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +1 -1
  58. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/constants.js +7 -0
  59. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +12 -19
  60. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/header-sensitive-analyzer.js +20 -0
  61. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/json-sensitive-analyzer.js +6 -10
  62. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +18 -25
  63. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +79 -85
  64. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +27 -36
  65. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +14 -11
  66. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  67. package/packages/dd-trace/src/appsec/index.js +33 -32
  68. package/packages/dd-trace/src/appsec/recommended.json +1737 -120
  69. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +6 -1
  70. package/packages/dd-trace/src/appsec/remote_config/index.js +40 -15
  71. package/packages/dd-trace/src/appsec/reporter.js +50 -34
  72. package/packages/dd-trace/src/appsec/rule_manager.js +9 -6
  73. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  74. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +28 -13
  75. package/packages/dd-trace/src/appsec/waf/waf_manager.js +0 -1
  76. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +30 -1
  77. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +30 -1
  78. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +17 -1
  79. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +110 -59
  80. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +40 -7
  81. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +26 -1
  82. package/packages/dd-trace/src/ci-visibility/telemetry.js +130 -0
  83. package/packages/dd-trace/src/config.js +145 -63
  84. package/packages/dd-trace/src/datastreams/processor.js +166 -26
  85. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +14 -1
  86. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +14 -0
  87. package/packages/dd-trace/src/exporters/common/agent-info-exporter.js +4 -0
  88. package/packages/dd-trace/src/exporters/common/form-data.js +4 -0
  89. package/packages/dd-trace/src/format.js +6 -1
  90. package/packages/dd-trace/src/id.js +12 -0
  91. package/packages/dd-trace/src/iitm.js +1 -1
  92. package/packages/dd-trace/src/log/channels.js +1 -1
  93. package/packages/dd-trace/src/noop/proxy.js +4 -0
  94. package/packages/dd-trace/src/opentelemetry/span.js +95 -2
  95. package/packages/dd-trace/src/opentelemetry/tracer.js +9 -10
  96. package/packages/dd-trace/src/opentracing/propagation/text_map.js +14 -5
  97. package/packages/dd-trace/src/opentracing/span.js +6 -0
  98. package/packages/dd-trace/src/opentracing/span_context.js +5 -2
  99. package/packages/dd-trace/src/opentracing/tracer.js +2 -2
  100. package/packages/dd-trace/src/plugin_manager.js +1 -1
  101. package/packages/dd-trace/src/plugins/ci_plugin.js +46 -9
  102. package/packages/dd-trace/src/plugins/database.js +1 -1
  103. package/packages/dd-trace/src/plugins/index.js +6 -0
  104. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  105. package/packages/dd-trace/src/plugins/util/ci.js +6 -19
  106. package/packages/dd-trace/src/plugins/util/exec.js +23 -2
  107. package/packages/dd-trace/src/plugins/util/git.js +98 -22
  108. package/packages/dd-trace/src/plugins/util/ip_extractor.js +7 -6
  109. package/packages/dd-trace/src/plugins/util/test.js +3 -2
  110. package/packages/dd-trace/src/plugins/util/url.js +26 -0
  111. package/packages/dd-trace/src/plugins/util/user-provided-git.js +4 -16
  112. package/packages/dd-trace/src/priority_sampler.js +30 -38
  113. package/packages/dd-trace/src/profiler.js +5 -3
  114. package/packages/dd-trace/src/profiling/config.js +26 -2
  115. package/packages/dd-trace/src/profiling/exporters/agent.js +1 -0
  116. package/packages/dd-trace/src/profiling/profiler.js +17 -10
  117. package/packages/dd-trace/src/profiling/profilers/events.js +264 -0
  118. package/packages/dd-trace/src/profiling/profilers/shared.js +39 -0
  119. package/packages/dd-trace/src/profiling/profilers/space.js +2 -1
  120. package/packages/dd-trace/src/profiling/profilers/wall.js +121 -58
  121. package/packages/dd-trace/src/proxy.js +25 -1
  122. package/packages/dd-trace/src/ritm.js +1 -1
  123. package/packages/dd-trace/src/sampling_rule.js +130 -0
  124. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +5 -0
  125. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +4 -0
  126. package/packages/dd-trace/src/span_processor.js +4 -0
  127. package/packages/dd-trace/src/span_sampler.js +6 -64
  128. package/packages/dd-trace/src/spanleak.js +98 -0
  129. package/packages/dd-trace/src/startup-log.js +7 -1
  130. package/packages/dd-trace/src/telemetry/dependencies.js +56 -10
  131. package/packages/dd-trace/src/telemetry/index.js +171 -41
  132. package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
  133. package/packages/dd-trace/src/telemetry/send-data.js +47 -5
  134. package/packages/dd-trace/src/tracer.js +8 -2
  135. package/scripts/install_plugin_modules.js +11 -3
  136. package/packages/diagnostics_channel/index.js +0 -3
  137. package/packages/diagnostics_channel/src/index.js +0 -121
@@ -1,10 +1,31 @@
1
1
  const cp = require('child_process')
2
2
  const log = require('../../log')
3
+ const { distributionMetric, incrementCountMetric } = require('../../ci-visibility/telemetry')
3
4
 
4
- const sanitizedExec = (cmd, flags, options = { stdio: 'pipe' }) => {
5
+ const sanitizedExec = (
6
+ cmd,
7
+ flags,
8
+ operationMetric,
9
+ durationMetric,
10
+ errorMetric
11
+ ) => {
12
+ let startTime
13
+ if (operationMetric) {
14
+ incrementCountMetric(operationMetric.name, operationMetric.tags)
15
+ }
16
+ if (durationMetric) {
17
+ startTime = Date.now()
18
+ }
5
19
  try {
6
- return cp.execFileSync(cmd, flags, options).toString().replace(/(\r\n|\n|\r)/gm, '')
20
+ const result = cp.execFileSync(cmd, flags, { stdio: 'pipe' }).toString().replace(/(\r\n|\n|\r)/gm, '')
21
+ if (durationMetric) {
22
+ distributionMetric(durationMetric.name, durationMetric.tags, Date.now() - startTime)
23
+ }
24
+ return result
7
25
  } catch (e) {
26
+ if (errorMetric) {
27
+ incrementCountMetric(errorMetric.name, { ...errorMetric.tags, exitCode: e.status })
28
+ }
8
29
  log.error(e)
9
30
  return ''
10
31
  }
@@ -1,10 +1,9 @@
1
- const { execFileSync } = require('child_process')
1
+ const cp = require('child_process')
2
2
  const os = require('os')
3
3
  const path = require('path')
4
4
  const fs = require('fs')
5
5
 
6
6
  const log = require('../../log')
7
- const { sanitizedExec } = require('./exec')
8
7
  const {
9
8
  GIT_COMMIT_SHA,
10
9
  GIT_BRANCH,
@@ -19,9 +18,46 @@ const {
19
18
  GIT_COMMIT_AUTHOR_NAME,
20
19
  CI_WORKSPACE_PATH
21
20
  } = require('./tags')
21
+ const {
22
+ incrementCountMetric,
23
+ distributionMetric,
24
+ TELEMETRY_GIT_COMMAND,
25
+ TELEMETRY_GIT_COMMAND_MS,
26
+ TELEMETRY_GIT_COMMAND_ERRORS
27
+ } = require('../../ci-visibility/telemetry')
28
+ const { filterSensitiveInfoFromRepository } = require('./url')
22
29
 
23
30
  const GIT_REV_LIST_MAX_BUFFER = 8 * 1024 * 1024 // 8MB
24
31
 
32
+ function sanitizedExec (
33
+ cmd,
34
+ flags,
35
+ operationMetric,
36
+ durationMetric,
37
+ errorMetric
38
+ ) {
39
+ let startTime
40
+ if (operationMetric) {
41
+ incrementCountMetric(operationMetric.name, operationMetric.tags)
42
+ }
43
+ if (durationMetric) {
44
+ startTime = Date.now()
45
+ }
46
+ try {
47
+ const result = cp.execFileSync(cmd, flags, { stdio: 'pipe' }).toString().replace(/(\r\n|\n|\r)/gm, '')
48
+ if (durationMetric) {
49
+ distributionMetric(durationMetric.name, durationMetric.tags, Date.now() - startTime)
50
+ }
51
+ return result
52
+ } catch (e) {
53
+ if (errorMetric) {
54
+ incrementCountMetric(errorMetric.name, { ...errorMetric.tags, exitCode: e.status })
55
+ }
56
+ log.error(e)
57
+ return ''
58
+ }
59
+ }
60
+
25
61
  function isDirectory (path) {
26
62
  try {
27
63
  const stats = fs.statSync(path)
@@ -32,7 +68,13 @@ function isDirectory (path) {
32
68
  }
33
69
 
34
70
  function isShallowRepository () {
35
- return sanitizedExec('git', ['rev-parse', '--is-shallow-repository']) === 'true'
71
+ return sanitizedExec(
72
+ 'git',
73
+ ['rev-parse', '--is-shallow-repository'],
74
+ { name: TELEMETRY_GIT_COMMAND, tags: { command: 'check_shallow' } },
75
+ { name: TELEMETRY_GIT_COMMAND_MS, tags: { command: 'check_shallow' } },
76
+ { name: TELEMETRY_GIT_COMMAND_ERRORS, tags: { command: 'check_shallow' } }
77
+ ) === 'true'
36
78
  }
37
79
 
38
80
  function getGitVersion () {
@@ -71,50 +113,76 @@ function unshallowRepository () {
71
113
  defaultRemoteName
72
114
  ]
73
115
 
116
+ incrementCountMetric(TELEMETRY_GIT_COMMAND, { command: 'unshallow' })
117
+ const start = Date.now()
74
118
  try {
75
- execFileSync('git', [
119
+ cp.execFileSync('git', [
76
120
  ...baseGitOptions,
77
121
  revParseHead
78
122
  ], { stdio: 'pipe' })
79
- } catch (e) {
123
+ } catch (err) {
80
124
  // If the local HEAD is a commit that has not been pushed to the remote, the above command will fail.
81
- log.error(e)
125
+ log.error(err)
126
+ incrementCountMetric(TELEMETRY_GIT_COMMAND_ERRORS, { command: 'unshallow', exitCode: err.status })
82
127
  const upstreamRemote = sanitizedExec('git', ['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{upstream}'])
83
128
  try {
84
- execFileSync('git', [
129
+ cp.execFileSync('git', [
85
130
  ...baseGitOptions,
86
131
  upstreamRemote
87
132
  ], { stdio: 'pipe' })
88
- } catch (e) {
133
+ } catch (err) {
89
134
  // If the CI is working on a detached HEAD or branch tracking hasn’t been set up, the above command will fail.
90
- log.error(e)
135
+ log.error(err)
136
+ incrementCountMetric(TELEMETRY_GIT_COMMAND_ERRORS, { command: 'unshallow', exitCode: err.status })
91
137
  // We use sanitizedExec here because if this last option fails, we'll give up.
92
- sanitizedExec('git', baseGitOptions)
138
+ sanitizedExec(
139
+ 'git',
140
+ baseGitOptions,
141
+ null,
142
+ null,
143
+ { name: TELEMETRY_GIT_COMMAND_ERRORS, tags: { command: 'unshallow' } } // we log the error in sanitizedExec
144
+ )
93
145
  }
94
146
  }
147
+ distributionMetric(TELEMETRY_GIT_COMMAND_MS, { command: 'unshallow' }, Date.now() - start)
95
148
  }
96
149
 
97
150
  function getRepositoryUrl () {
98
- return sanitizedExec('git', ['config', '--get', 'remote.origin.url'])
151
+ return sanitizedExec(
152
+ 'git',
153
+ ['config', '--get', 'remote.origin.url'],
154
+ { name: TELEMETRY_GIT_COMMAND, tags: { command: 'get_repository' } },
155
+ { name: TELEMETRY_GIT_COMMAND_MS, tags: { command: 'get_repository' } },
156
+ { name: TELEMETRY_GIT_COMMAND_ERRORS, tags: { command: 'get_repository' } }
157
+ )
99
158
  }
100
159
 
101
160
  function getLatestCommits () {
161
+ incrementCountMetric(TELEMETRY_GIT_COMMAND, { command: 'get_local_commits' })
162
+ const startTime = Date.now()
102
163
  try {
103
- return execFileSync('git', ['log', '--format=%H', '-n 1000', '--since="1 month ago"'], { stdio: 'pipe' })
164
+ const result = cp.execFileSync('git', ['log', '--format=%H', '-n 1000', '--since="1 month ago"'], { stdio: 'pipe' })
104
165
  .toString()
105
166
  .split('\n')
106
167
  .filter(commit => commit)
168
+ distributionMetric(TELEMETRY_GIT_COMMAND_MS, { command: 'get_local_commits' }, Date.now() - startTime)
169
+ return result
107
170
  } catch (err) {
108
171
  log.error(`Get latest commits failed: ${err.message}`)
172
+ incrementCountMetric(TELEMETRY_GIT_COMMAND_ERRORS, { command: 'get_local_commits', errorType: err.status })
109
173
  return []
110
174
  }
111
175
  }
112
176
 
113
- function getCommitsToUpload (commitsToExclude, commitsToInclude) {
177
+ function getCommitsRevList (commitsToExclude, commitsToInclude) {
178
+ let result = []
179
+
114
180
  const commitsToExcludeString = commitsToExclude.map(commit => `^${commit}`)
115
181
 
182
+ incrementCountMetric(TELEMETRY_GIT_COMMAND, { command: 'get_objects' })
183
+ const startTime = Date.now()
116
184
  try {
117
- return execFileSync(
185
+ result = cp.execFileSync(
118
186
  'git',
119
187
  [
120
188
  'rev-list',
@@ -131,11 +199,14 @@ function getCommitsToUpload (commitsToExclude, commitsToInclude) {
131
199
  .filter(commit => commit)
132
200
  } catch (err) {
133
201
  log.error(`Get commits to upload failed: ${err.message}`)
134
- return []
202
+ incrementCountMetric(TELEMETRY_GIT_COMMAND_ERRORS, { command: 'get_objects', errorType: err.status })
135
203
  }
204
+ distributionMetric(TELEMETRY_GIT_COMMAND_MS, { command: 'get_objects' }, Date.now() - startTime)
205
+ return result
136
206
  }
137
207
 
138
208
  function generatePackFilesForCommits (commitsToUpload) {
209
+ let result = []
139
210
  const tmpFolder = os.tmpdir()
140
211
 
141
212
  if (!isDirectory(tmpFolder)) {
@@ -147,10 +218,12 @@ function generatePackFilesForCommits (commitsToUpload) {
147
218
  const temporaryPath = path.join(tmpFolder, randomPrefix)
148
219
  const cwdPath = path.join(process.cwd(), randomPrefix)
149
220
 
221
+ incrementCountMetric(TELEMETRY_GIT_COMMAND, { command: 'pack_objects' })
222
+ const startTime = Date.now()
150
223
  // Generates pack files to upload and
151
224
  // returns the ordered list of packfiles' paths
152
225
  function execGitPackObjects (targetPath) {
153
- return execFileSync(
226
+ return cp.execFileSync(
154
227
  'git',
155
228
  [
156
229
  'pack-objects',
@@ -163,9 +236,10 @@ function generatePackFilesForCommits (commitsToUpload) {
163
236
  }
164
237
 
165
238
  try {
166
- return execGitPackObjects(temporaryPath)
239
+ result = execGitPackObjects(temporaryPath)
167
240
  } catch (err) {
168
241
  log.error(err)
242
+ incrementCountMetric(TELEMETRY_GIT_COMMAND_ERRORS, { command: 'pack_objects', errorType: err.status })
169
243
  /**
170
244
  * The generation of pack files in the temporary folder (from `os.tmpdir()`)
171
245
  * sometimes fails in certain CI setups with the error message
@@ -179,13 +253,15 @@ function generatePackFilesForCommits (commitsToUpload) {
179
253
  * TODO: fix issue and remove workaround.
180
254
  */
181
255
  try {
182
- return execGitPackObjects(cwdPath)
256
+ result = execGitPackObjects(cwdPath)
183
257
  } catch (err) {
184
258
  log.error(err)
259
+ incrementCountMetric(TELEMETRY_GIT_COMMAND_ERRORS, { command: 'pack_objects', errorType: err.status })
185
260
  }
186
-
187
- return []
188
261
  }
262
+ distributionMetric(TELEMETRY_GIT_COMMAND_MS, { command: 'pack_objects' }, Date.now() - startTime)
263
+
264
+ return result
189
265
  }
190
266
 
191
267
  // If there is ciMetadata, it takes precedence.
@@ -214,7 +290,7 @@ function getGitMetadata (ciMetadata) {
214
290
 
215
291
  return {
216
292
  [GIT_REPOSITORY_URL]:
217
- repositoryUrl || sanitizedExec('git', ['ls-remote', '--get-url']),
293
+ filterSensitiveInfoFromRepository(repositoryUrl || sanitizedExec('git', ['ls-remote', '--get-url'])),
218
294
  [GIT_COMMIT_MESSAGE]:
219
295
  commitMessage || sanitizedExec('git', ['show', '-s', '--format=%s']),
220
296
  [GIT_COMMIT_AUTHOR_DATE]: authorDate,
@@ -235,7 +311,7 @@ module.exports = {
235
311
  getLatestCommits,
236
312
  getRepositoryUrl,
237
313
  generatePackFilesForCommits,
238
- getCommitsToUpload,
314
+ getCommitsRevList,
239
315
  GIT_REV_LIST_MAX_BUFFER,
240
316
  isShallowRepository,
241
317
  unshallowRepository
@@ -48,8 +48,8 @@ function extractIp (config, req) {
48
48
 
49
49
  let firstPrivateIp
50
50
  if (headers) {
51
- for (let i = 0; i < ipHeaderList.length; i++) {
52
- const firstIp = findFirstIp(headers[ipHeaderList[i]])
51
+ for (const ipHeaderName of ipHeaderList) {
52
+ const firstIp = findFirstIp(headers[ipHeaderName])
53
53
 
54
54
  if (firstIp.public) {
55
55
  return firstIp.public
@@ -59,7 +59,7 @@ function extractIp (config, req) {
59
59
  }
60
60
  }
61
61
 
62
- return firstPrivateIp || (req.socket && req.socket.remoteAddress)
62
+ return firstPrivateIp || req.socket?.remoteAddress
63
63
  }
64
64
 
65
65
  function findFirstIp (str) {
@@ -68,8 +68,8 @@ function findFirstIp (str) {
68
68
 
69
69
  const splitted = str.split(',')
70
70
 
71
- for (let i = 0; i < splitted.length; i++) {
72
- const chunk = splitted[i].trim()
71
+ for (const part of splitted) {
72
+ const chunk = part.trim()
73
73
 
74
74
  // TODO: strip port and interface data ?
75
75
 
@@ -90,5 +90,6 @@ function findFirstIp (str) {
90
90
  }
91
91
 
92
92
  module.exports = {
93
- extractIp
93
+ extractIp,
94
+ ipHeaderList
94
95
  }
@@ -397,8 +397,9 @@ function addIntelligentTestRunnerSpanTags (
397
397
  testModuleSpan.setTag(TEST_ITR_FORCED_RUN, 'true')
398
398
  }
399
399
 
400
- // If suites have been skipped we don't want to report the total coverage, as it will be wrong
401
- if (testCodeCoverageLinesTotal !== undefined && !isSuitesSkipped) {
400
+ // This will not be reported unless the user has manually added code coverage.
401
+ // This is always the case for Mocha and Cucumber, but not for Jest.
402
+ if (testCodeCoverageLinesTotal !== undefined) {
402
403
  testSessionSpan.setTag(TEST_CODE_COVERAGE_LINES_PCT, testCodeCoverageLinesTotal)
403
404
  testModuleSpan.setTag(TEST_CODE_COVERAGE_LINES_PCT, testCodeCoverageLinesTotal)
404
405
  }
@@ -0,0 +1,26 @@
1
+ const { URL } = require('url')
2
+
3
+ function filterSensitiveInfoFromRepository (repositoryUrl) {
4
+ if (!repositoryUrl) {
5
+ return ''
6
+ }
7
+ if (repositoryUrl.startsWith('git@')) {
8
+ return repositoryUrl
9
+ }
10
+
11
+ // Remove the username from ssh URLs
12
+ if (repositoryUrl.startsWith('ssh://')) {
13
+ const sshRegex = /^(ssh:\/\/)[^@/]*@/
14
+ return repositoryUrl.replace(sshRegex, '$1')
15
+ }
16
+
17
+ try {
18
+ const { protocol, host, pathname } = new URL(repositoryUrl)
19
+
20
+ return `${protocol}//${host}${pathname === '/' ? '' : pathname}`
21
+ } catch (e) {
22
+ return ''
23
+ }
24
+ }
25
+
26
+ module.exports = { filterSensitiveInfoFromRepository }
@@ -13,7 +13,7 @@ const {
13
13
  } = require('./tags')
14
14
 
15
15
  const { normalizeRef } = require('./ci')
16
- const { URL } = require('url')
16
+ const { filterSensitiveInfoFromRepository } = require('./url')
17
17
 
18
18
  function removeEmptyValues (tags) {
19
19
  return Object.keys(tags).reduce((filteredTags, tag) => {
@@ -27,23 +27,11 @@ function removeEmptyValues (tags) {
27
27
  }, {})
28
28
  }
29
29
 
30
- function filterSensitiveInfoFromRepository (repositoryUrl) {
31
- try {
32
- if (repositoryUrl.startsWith('git@')) {
33
- return repositoryUrl
34
- }
35
- const { protocol, hostname, pathname } = new URL(repositoryUrl)
36
-
37
- return `${protocol}//${hostname}${pathname}`
38
- } catch (e) {
39
- return repositoryUrl
40
- }
41
- }
42
-
43
- // The regex is extracted from
30
+ // The regex is inspired by
44
31
  // https://github.com/jonschlinkert/is-git-url/blob/396965ffabf2f46656c8af4c47bef1d69f09292e/index.js#L9C15-L9C87
32
+ // The `.git` suffix is optional in this version
45
33
  function validateGitRepositoryUrl (repoUrl) {
46
- return /(?:git|ssh|https?|git@[-\w.]+):(\/\/)?(.*?)(\.git)(\/?|#[-\d\w._]+?)$/.test(repoUrl)
34
+ return /(?:git|ssh|https?|git@[-\w.]+):(\/\/)?(.*?)(\/?|#[-\d\w._]+?)$/.test(repoUrl)
47
35
  }
48
36
 
49
37
  function validateGitCommitSha (gitCommitSha) {
@@ -1,9 +1,8 @@
1
1
  'use strict'
2
2
 
3
- const RateLimiter = require('./rate_limiter')
4
3
  const Sampler = require('./sampler')
5
- const ext = require('../../../ext')
6
4
  const { setSamplingRules } = require('./startup-log')
5
+ const SamplingRule = require('./sampling_rule')
7
6
 
8
7
  const {
9
8
  SAMPLING_MECHANISM_DEFAULT,
@@ -16,14 +15,21 @@ const {
16
15
  DECISION_MAKER_KEY
17
16
  } = require('./constants')
18
17
 
19
- const SERVICE_NAME = ext.tags.SERVICE_NAME
20
- const SAMPLING_PRIORITY = ext.tags.SAMPLING_PRIORITY
21
- const MANUAL_KEEP = ext.tags.MANUAL_KEEP
22
- const MANUAL_DROP = ext.tags.MANUAL_DROP
23
- const USER_REJECT = ext.priority.USER_REJECT
24
- const AUTO_REJECT = ext.priority.AUTO_REJECT
25
- const AUTO_KEEP = ext.priority.AUTO_KEEP
26
- const USER_KEEP = ext.priority.USER_KEEP
18
+ const {
19
+ tags: {
20
+ MANUAL_KEEP,
21
+ MANUAL_DROP,
22
+ SAMPLING_PRIORITY,
23
+ SERVICE_NAME
24
+ },
25
+ priority: {
26
+ AUTO_REJECT,
27
+ AUTO_KEEP,
28
+ USER_REJECT,
29
+ USER_KEEP
30
+ }
31
+ } = require('../../../ext')
32
+
27
33
  const DEFAULT_KEY = 'service:,env:'
28
34
 
29
35
  const defaultSampler = new Sampler(AUTO_KEEP)
@@ -36,8 +42,7 @@ class PrioritySampler {
36
42
 
37
43
  configure (env, { sampleRate, rateLimit = 100, rules = [] } = {}) {
38
44
  this._env = env
39
- this._rules = this._normalizeRules(rules, sampleRate)
40
- this._limiter = new RateLimiter(rateLimit)
45
+ this._rules = this._normalizeRules(rules, sampleRate, rateLimit)
41
46
 
42
47
  setSamplingRules(this._rules)
43
48
  }
@@ -104,7 +109,7 @@ class PrioritySampler {
104
109
 
105
110
  _getPriorityFromAuto (span) {
106
111
  const context = this._getContext(span)
107
- const rule = this._findRule(context)
112
+ const rule = this._findRule(span)
108
113
 
109
114
  return rule
110
115
  ? this._getPriorityByRule(context, rule)
@@ -131,15 +136,14 @@ class PrioritySampler {
131
136
  context._trace[SAMPLING_RULE_DECISION] = rule.sampleRate
132
137
  context._sampling.mechanism = SAMPLING_MECHANISM_RULE
133
138
 
134
- return rule.sampler.isSampled(context) && this._isSampledByRateLimit(context) ? USER_KEEP : USER_REJECT
135
- }
139
+ const sampled = rule.sample()
140
+ const priority = sampled ? USER_KEEP : USER_REJECT
136
141
 
137
- _isSampledByRateLimit (context) {
138
- const allowed = this._limiter.isAllowed()
139
-
140
- context._trace[SAMPLING_LIMIT_DECISION] = this._limiter.effectiveRate()
142
+ if (sampled) {
143
+ context._trace[SAMPLING_LIMIT_DECISION] = rule.effectiveRate
144
+ }
141
145
 
142
- return allowed
146
+ return priority
143
147
  }
144
148
 
145
149
  _getPriorityByAgent (context) {
@@ -172,33 +176,21 @@ class PrioritySampler {
172
176
  }
173
177
  }
174
178
 
175
- _normalizeRules (rules, sampleRate) {
179
+ _normalizeRules (rules, sampleRate, rateLimit) {
176
180
  rules = [].concat(rules || [])
177
181
 
178
182
  return rules
179
- .concat({ sampleRate })
183
+ .concat({ sampleRate, maxPerSecond: rateLimit })
180
184
  .map(rule => ({ ...rule, sampleRate: parseFloat(rule.sampleRate) }))
181
185
  .filter(rule => !isNaN(rule.sampleRate))
182
- .map(rule => ({ ...rule, sampler: new Sampler(rule.sampleRate) }))
186
+ .map(SamplingRule.from)
183
187
  }
184
188
 
185
- _findRule (context) {
186
- for (let i = 0, l = this._rules.length; i < l; i++) {
187
- if (this._matchRule(context, this._rules[i])) return this._rules[i]
189
+ _findRule (span) {
190
+ for (const rule of this._rules) {
191
+ if (rule.match(span)) return rule
188
192
  }
189
193
  }
190
-
191
- _matchRule (context, rule) {
192
- const name = context._name
193
- const service = context._tags['service.name']
194
-
195
- if (rule.name instanceof RegExp && !rule.name.test(name)) return false
196
- if (typeof rule.name === 'string' && rule.name !== name) return false
197
- if (rule.service instanceof RegExp && !rule.service.test(service)) return false
198
- if (typeof rule.service === 'string' && rule.service !== service) return false
199
-
200
- return true
201
- }
202
194
  }
203
195
 
204
196
  function hasOwn (object, prop) {
@@ -8,7 +8,7 @@ process.once('beforeExit', () => { profiler.stop() })
8
8
 
9
9
  module.exports = {
10
10
  start: config => {
11
- const { service, version, env, url, hostname, port, tags } = config
11
+ const { service, version, env, url, hostname, port, tags, repositoryUrl, commitSHA } = config
12
12
  const { enabled, sourceMap, exporters } = config.profiling
13
13
  const logger = {
14
14
  debug: (message) => log.debug(message),
@@ -17,7 +17,7 @@ module.exports = {
17
17
  error: (message) => log.error(message)
18
18
  }
19
19
 
20
- profiler.start({
20
+ return profiler.start({
21
21
  enabled,
22
22
  service,
23
23
  version,
@@ -28,7 +28,9 @@ module.exports = {
28
28
  url,
29
29
  hostname,
30
30
  port,
31
- tags
31
+ tags,
32
+ repositoryUrl,
33
+ commitSHA
32
34
  })
33
35
  },
34
36
 
@@ -9,7 +9,9 @@ const { FileExporter } = require('./exporters/file')
9
9
  const { ConsoleLogger } = require('./loggers/console')
10
10
  const WallProfiler = require('./profilers/wall')
11
11
  const SpaceProfiler = require('./profilers/space')
12
+ const EventsProfiler = require('./profilers/events')
12
13
  const { oomExportStrategies, snapshotKinds } = require('./constants')
14
+ const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../plugins/util/tags')
13
15
  const { tagger } = require('./tagger')
14
16
  const { isFalse, isTrue } = require('../util')
15
17
 
@@ -37,6 +39,7 @@ class Config {
37
39
  DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE,
38
40
  DD_PROFILING_EXPERIMENTAL_OOM_MAX_HEAP_EXTENSION_COUNT,
39
41
  DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES,
42
+ DD_PROFILING_EXPERIMENTAL_TIMELINE_ENABLED,
40
43
  DD_PROFILING_CODEHOTSPOTS_ENABLED,
41
44
  DD_PROFILING_ENDPOINT_COLLECTION_ENABLED,
42
45
  DD_PROFILING_EXPERIMENTAL_CODEHOTSPOTS_ENABLED,
@@ -70,6 +73,13 @@ class Config {
70
73
  tagger.parse(options.tags),
71
74
  tagger.parse({ env, host, service, version, functionname })
72
75
  )
76
+
77
+ // Add source code integration tags if available
78
+ if (options.repositoryUrl && options.commitSHA) {
79
+ this.tags[GIT_REPOSITORY_URL] = options.repositoryUrl
80
+ this.tags[GIT_COMMIT_SHA] = options.commitSHA
81
+ }
82
+
73
83
  this.logger = ensureLogger(options.logger)
74
84
  const logger = this.logger
75
85
  function logExperimentalVarDeprecation (shortVarName) {
@@ -126,7 +136,14 @@ class Config {
126
136
 
127
137
  const profilers = options.profilers
128
138
  ? options.profilers
129
- : getProfilers({ DD_PROFILING_HEAP_ENABLED, DD_PROFILING_WALLTIME_ENABLED, DD_PROFILING_PROFILERS })
139
+ : getProfilers({
140
+ DD_PROFILING_HEAP_ENABLED,
141
+ DD_PROFILING_WALLTIME_ENABLED,
142
+ DD_PROFILING_PROFILERS
143
+ })
144
+
145
+ this.timelineEnabled = isTrue(coalesce(options.timelineEnabled,
146
+ DD_PROFILING_EXPERIMENTAL_TIMELINE_ENABLED, false))
130
147
 
131
148
  this.codeHotspotsEnabled = isTrue(coalesce(options.codeHotspotsEnabled,
132
149
  DD_PROFILING_CODEHOTSPOTS_ENABLED,
@@ -139,7 +156,9 @@ class Config {
139
156
 
140
157
  module.exports = { Config }
141
158
 
142
- function getProfilers ({ DD_PROFILING_HEAP_ENABLED, DD_PROFILING_WALLTIME_ENABLED, DD_PROFILING_PROFILERS }) {
159
+ function getProfilers ({
160
+ DD_PROFILING_HEAP_ENABLED, DD_PROFILING_WALLTIME_ENABLED, DD_PROFILING_PROFILERS
161
+ }) {
143
162
  // First consider "legacy" DD_PROFILING_PROFILERS env variable, defaulting to wall + space
144
163
  // Use a Set to avoid duplicates
145
164
  const profilers = new Set(coalesce(DD_PROFILING_PROFILERS, 'wall,space').split(','))
@@ -240,6 +259,11 @@ function ensureProfilers (profilers, options) {
240
259
  }
241
260
  }
242
261
 
262
+ // Events profiler is a profiler for timeline events
263
+ if (options.timelineEnabled) {
264
+ profilers.push(new EventsProfiler(options))
265
+ }
266
+
243
267
  // Filter out any invalid profilers
244
268
  return profilers.filter(v => v)
245
269
  }
@@ -75,6 +75,7 @@ class AgentExporter {
75
75
  ['tags[]', 'language:javascript'],
76
76
  ['tags[]', 'runtime:nodejs'],
77
77
  ['tags[]', `runtime_version:${process.version}`],
78
+ ['tags[]', `process_id:${process.pid}`],
78
79
  ['tags[]', `profiler_version:${version}`],
79
80
  ['tags[]', 'format:pprof'],
80
81
  ...Object.entries(tags).map(([key, value]) => ['tags[]', `${key}:${value}`])