dd-trace 4.18.0 → 5.6.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 (209) hide show
  1. package/CONTRIBUTING.md +98 -0
  2. package/LICENSE-3rdparty.csv +4 -5
  3. package/MIGRATING.md +15 -0
  4. package/README.md +20 -140
  5. package/ci/cypress/after-run.js +1 -0
  6. package/ci/cypress/after-spec.js +1 -0
  7. package/ci/init.js +1 -4
  8. package/ext/kinds.d.ts +1 -0
  9. package/ext/kinds.js +2 -1
  10. package/ext/tags.d.ts +2 -1
  11. package/ext/tags.js +6 -1
  12. package/index.d.ts +1523 -1460
  13. package/package.json +19 -19
  14. package/packages/datadog-core/src/storage/async_resource.js +1 -1
  15. package/packages/datadog-core/src/utils/src/get.js +11 -0
  16. package/packages/datadog-core/src/utils/src/has.js +14 -0
  17. package/packages/datadog-core/src/utils/src/kebabcase.js +16 -0
  18. package/packages/datadog-core/src/utils/src/pick.js +11 -0
  19. package/packages/datadog-core/src/utils/src/set.js +16 -0
  20. package/packages/datadog-core/src/utils/src/uniq.js +5 -0
  21. package/packages/datadog-esbuild/index.js +1 -20
  22. package/packages/datadog-instrumentations/src/aerospike.js +47 -0
  23. package/packages/datadog-instrumentations/src/amqplib.js +2 -2
  24. package/packages/datadog-instrumentations/src/apollo-server-core.js +41 -0
  25. package/packages/datadog-instrumentations/src/apollo-server.js +83 -0
  26. package/packages/datadog-instrumentations/src/child_process.js +150 -0
  27. package/packages/datadog-instrumentations/src/couchbase.js +5 -4
  28. package/packages/datadog-instrumentations/src/crypto.js +2 -1
  29. package/packages/datadog-instrumentations/src/cucumber.js +163 -46
  30. package/packages/datadog-instrumentations/src/dns.js +2 -1
  31. package/packages/datadog-instrumentations/src/express.js +20 -0
  32. package/packages/datadog-instrumentations/src/graphql.js +18 -4
  33. package/packages/datadog-instrumentations/src/grpc/client.js +56 -36
  34. package/packages/datadog-instrumentations/src/grpc/server.js +3 -1
  35. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +1 -2
  36. package/packages/datadog-instrumentations/src/helpers/hooks.js +12 -3
  37. package/packages/datadog-instrumentations/src/helpers/instrument.js +9 -4
  38. package/packages/datadog-instrumentations/src/helpers/register.js +19 -3
  39. package/packages/datadog-instrumentations/src/http/client.js +12 -2
  40. package/packages/datadog-instrumentations/src/http/server.js +7 -4
  41. package/packages/datadog-instrumentations/src/http2/client.js +3 -1
  42. package/packages/datadog-instrumentations/src/http2/server.js +3 -1
  43. package/packages/datadog-instrumentations/src/jest.js +239 -52
  44. package/packages/datadog-instrumentations/src/kafkajs.js +27 -0
  45. package/packages/datadog-instrumentations/src/mocha.js +154 -18
  46. package/packages/datadog-instrumentations/src/mongodb-core.js +34 -3
  47. package/packages/datadog-instrumentations/src/mongoose.js +23 -10
  48. package/packages/datadog-instrumentations/src/mquery.js +65 -0
  49. package/packages/datadog-instrumentations/src/net.js +10 -2
  50. package/packages/datadog-instrumentations/src/next.js +35 -9
  51. package/packages/datadog-instrumentations/src/playwright.js +110 -16
  52. package/packages/datadog-instrumentations/src/restify.js +14 -1
  53. package/packages/datadog-instrumentations/src/rhea.js +15 -9
  54. package/packages/datadog-plugin-aerospike/src/index.js +113 -0
  55. package/packages/datadog-plugin-amqplib/src/consumer.js +14 -1
  56. package/packages/datadog-plugin-amqplib/src/producer.js +13 -1
  57. package/packages/datadog-plugin-aws-sdk/src/base.js +3 -2
  58. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +163 -27
  59. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +46 -8
  60. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +129 -22
  61. package/packages/datadog-plugin-child_process/src/index.js +91 -0
  62. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +125 -0
  63. package/packages/datadog-plugin-cucumber/src/index.js +70 -13
  64. package/packages/datadog-plugin-cypress/src/after-run.js +3 -0
  65. package/packages/datadog-plugin-cypress/src/after-spec.js +3 -0
  66. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +625 -0
  67. package/packages/datadog-plugin-cypress/src/plugin.js +6 -454
  68. package/packages/datadog-plugin-cypress/src/support.js +50 -3
  69. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -0
  70. package/packages/datadog-plugin-graphql/src/index.js +1 -6
  71. package/packages/datadog-plugin-graphql/src/resolve.js +28 -18
  72. package/packages/datadog-plugin-grpc/src/client.js +16 -2
  73. package/packages/datadog-plugin-grpc/src/util.js +1 -1
  74. package/packages/datadog-plugin-http/src/client.js +19 -2
  75. package/packages/datadog-plugin-jest/src/index.js +118 -12
  76. package/packages/datadog-plugin-jest/src/util.js +38 -16
  77. package/packages/datadog-plugin-kafkajs/src/consumer.js +76 -6
  78. package/packages/datadog-plugin-kafkajs/src/producer.js +64 -8
  79. package/packages/datadog-plugin-mocha/src/index.js +87 -17
  80. package/packages/datadog-plugin-next/src/index.js +40 -14
  81. package/packages/datadog-plugin-playwright/src/index.js +71 -8
  82. package/packages/datadog-plugin-rhea/src/consumer.js +16 -1
  83. package/packages/datadog-plugin-rhea/src/producer.js +10 -0
  84. package/packages/dd-trace/src/appsec/activation.js +29 -0
  85. package/packages/dd-trace/src/appsec/addresses.js +5 -1
  86. package/packages/dd-trace/src/appsec/api_security_sampler.js +61 -0
  87. package/packages/dd-trace/src/appsec/blocked_templates.js +4 -1
  88. package/packages/dd-trace/src/appsec/blocking.js +95 -43
  89. package/packages/dd-trace/src/appsec/channels.js +7 -3
  90. package/packages/dd-trace/src/appsec/graphql.js +146 -0
  91. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +2 -0
  92. package/packages/dd-trace/src/appsec/iast/analyzers/command-injection-analyzer.js +1 -1
  93. package/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js +105 -0
  94. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +22 -17
  95. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +7 -28
  96. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +10 -6
  97. package/packages/dd-trace/src/appsec/iast/analyzers/weak-randomness-analyzer.js +19 -0
  98. package/packages/dd-trace/src/appsec/iast/context/context-plugin.js +90 -0
  99. package/packages/dd-trace/src/appsec/iast/context/kafka-ctx-plugin.js +14 -0
  100. package/packages/dd-trace/src/appsec/iast/iast-log.js +1 -1
  101. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +13 -2
  102. package/packages/dd-trace/src/appsec/iast/index.js +15 -5
  103. package/packages/dd-trace/src/appsec/iast/overhead-controller.js +1 -1
  104. package/packages/dd-trace/src/appsec/iast/path-line.js +1 -1
  105. package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +2 -0
  106. package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +10 -0
  107. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +53 -0
  108. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +10 -46
  109. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +13 -9
  110. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugins/kafka.js +47 -0
  111. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +19 -6
  112. package/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +3 -1
  113. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +41 -3
  114. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/constants.js +7 -0
  115. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +12 -19
  116. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/header-sensitive-analyzer.js +20 -0
  117. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/json-sensitive-analyzer.js +6 -10
  118. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +18 -25
  119. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +79 -85
  120. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +27 -36
  121. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +14 -11
  122. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  123. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +2 -0
  124. package/packages/dd-trace/src/appsec/index.js +49 -33
  125. package/packages/dd-trace/src/appsec/recommended.json +1763 -106
  126. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +7 -1
  127. package/packages/dd-trace/src/appsec/remote_config/index.js +42 -16
  128. package/packages/dd-trace/src/appsec/remote_config/manager.js +9 -8
  129. package/packages/dd-trace/src/appsec/reporter.js +51 -34
  130. package/packages/dd-trace/src/appsec/rule_manager.js +11 -8
  131. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  132. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +28 -13
  133. package/packages/dd-trace/src/appsec/waf/waf_manager.js +0 -1
  134. package/packages/dd-trace/src/ci-visibility/{intelligent-test-runner/get-itr-configuration.js → early-flake-detection/get-known-tests.js} +17 -22
  135. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +25 -6
  136. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +30 -1
  137. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +2 -0
  138. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +30 -1
  139. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +95 -37
  140. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +134 -61
  141. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +37 -4
  142. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +131 -0
  143. package/packages/dd-trace/src/ci-visibility/telemetry.js +130 -0
  144. package/packages/dd-trace/src/config.js +561 -470
  145. package/packages/dd-trace/src/data_streams_context.js +1 -1
  146. package/packages/dd-trace/src/datastreams/pathway.js +58 -1
  147. package/packages/dd-trace/src/datastreams/processor.js +196 -27
  148. package/packages/dd-trace/src/datastreams/writer.js +11 -5
  149. package/packages/dd-trace/src/dogstatsd.js +3 -5
  150. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +44 -6
  151. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +14 -0
  152. package/packages/dd-trace/src/exporters/common/agent-info-exporter.js +4 -0
  153. package/packages/dd-trace/src/exporters/common/form-data.js +4 -0
  154. package/packages/dd-trace/src/exporters/common/request.js +21 -3
  155. package/packages/dd-trace/src/format.js +30 -2
  156. package/packages/dd-trace/src/id.js +12 -0
  157. package/packages/dd-trace/src/iitm.js +1 -1
  158. package/packages/dd-trace/src/log/channels.js +1 -1
  159. package/packages/dd-trace/src/noop/proxy.js +4 -0
  160. package/packages/dd-trace/src/noop/span.js +1 -0
  161. package/packages/dd-trace/src/opentelemetry/span.js +104 -4
  162. package/packages/dd-trace/src/opentelemetry/tracer.js +9 -10
  163. package/packages/dd-trace/src/opentracing/propagation/text_map.js +16 -7
  164. package/packages/dd-trace/src/opentracing/span.js +48 -4
  165. package/packages/dd-trace/src/opentracing/span_context.js +15 -6
  166. package/packages/dd-trace/src/opentracing/tracer.js +4 -3
  167. package/packages/dd-trace/src/plugin_manager.js +1 -1
  168. package/packages/dd-trace/src/plugins/ci_plugin.js +78 -19
  169. package/packages/dd-trace/src/plugins/database.js +1 -1
  170. package/packages/dd-trace/src/plugins/index.js +7 -0
  171. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  172. package/packages/dd-trace/src/plugins/util/ci.js +6 -19
  173. package/packages/dd-trace/src/plugins/util/git.js +104 -22
  174. package/packages/dd-trace/src/plugins/util/ip_extractor.js +7 -6
  175. package/packages/dd-trace/src/plugins/util/test.js +60 -10
  176. package/packages/dd-trace/src/plugins/util/url.js +26 -0
  177. package/packages/dd-trace/src/plugins/util/user-provided-git.js +4 -16
  178. package/packages/dd-trace/src/plugins/util/web.js +1 -1
  179. package/packages/dd-trace/src/priority_sampler.js +30 -38
  180. package/packages/dd-trace/src/profiler.js +5 -3
  181. package/packages/dd-trace/src/profiling/config.js +77 -24
  182. package/packages/dd-trace/src/profiling/exporters/agent.js +77 -31
  183. package/packages/dd-trace/src/profiling/exporters/file.js +2 -1
  184. package/packages/dd-trace/src/profiling/profiler.js +33 -22
  185. package/packages/dd-trace/src/profiling/profilers/events.js +270 -0
  186. package/packages/dd-trace/src/profiling/profilers/shared.js +45 -0
  187. package/packages/dd-trace/src/profiling/profilers/space.js +18 -2
  188. package/packages/dd-trace/src/profiling/profilers/wall.js +146 -70
  189. package/packages/dd-trace/src/proxy.js +56 -24
  190. package/packages/dd-trace/src/ritm.js +1 -1
  191. package/packages/dd-trace/src/sampling_rule.js +130 -0
  192. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +5 -0
  193. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +4 -0
  194. package/packages/dd-trace/src/span_processor.js +9 -1
  195. package/packages/dd-trace/src/span_sampler.js +6 -64
  196. package/packages/dd-trace/src/spanleak.js +98 -0
  197. package/packages/dd-trace/src/startup-log.js +7 -1
  198. package/packages/dd-trace/src/telemetry/dependencies.js +56 -10
  199. package/packages/dd-trace/src/telemetry/index.js +182 -53
  200. package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
  201. package/packages/dd-trace/src/telemetry/send-data.js +65 -7
  202. package/packages/dd-trace/src/tracer.js +12 -5
  203. package/register.js +4 -0
  204. package/scripts/install_plugin_modules.js +11 -3
  205. package/scripts/st.js +105 -0
  206. package/packages/datadog-instrumentations/src/child-process.js +0 -30
  207. package/packages/dd-trace/src/plugins/util/exec.js +0 -13
  208. package/packages/diagnostics_channel/index.js +0 -3
  209. package/packages/diagnostics_channel/src/index.js +0 -121
@@ -1,67 +1,16 @@
1
1
  'use strict'
2
- const { globMatch } = require('../src/util')
3
- const { USER_KEEP, AUTO_KEEP } = require('../../../ext').priority
4
- const RateLimiter = require('./rate_limiter')
5
- const Sampler = require('./sampler')
6
-
7
- class SpanSamplingRule {
8
- constructor ({ service, name, sampleRate = 1.0, maxPerSecond } = {}) {
9
- this.service = service
10
- this.name = name
11
-
12
- this._sampler = new Sampler(sampleRate)
13
- this._limiter = undefined
14
-
15
- if (Number.isFinite(maxPerSecond)) {
16
- this._limiter = new RateLimiter(maxPerSecond)
17
- }
18
- }
19
-
20
- get sampleRate () {
21
- return this._sampler.rate()
22
- }
23
-
24
- get maxPerSecond () {
25
- return this._limiter && this._limiter._rateLimit
26
- }
27
-
28
- static from (config) {
29
- return new SpanSamplingRule(config)
30
- }
31
-
32
- match (service, name) {
33
- if (this.service && !globMatch(this.service, service)) {
34
- return false
35
- }
36
-
37
- if (this.name && !globMatch(this.name, name)) {
38
- return false
39
- }
40
-
41
- return true
42
- }
43
-
44
- sample () {
45
- if (!this._sampler.isSampled()) {
46
- return false
47
- }
48
2
 
49
- if (this._limiter) {
50
- return this._limiter.isAllowed()
51
- }
52
-
53
- return true
54
- }
55
- }
3
+ const { USER_KEEP, AUTO_KEEP } = require('../../../ext').priority
4
+ const SamplingRule = require('./sampling_rule')
56
5
 
57
6
  class SpanSampler {
58
7
  constructor ({ spanSamplingRules = [] } = {}) {
59
- this._rules = spanSamplingRules.map(SpanSamplingRule.from)
8
+ this._rules = spanSamplingRules.map(SamplingRule.from)
60
9
  }
61
10
 
62
- findRule (service, name) {
11
+ findRule (context) {
63
12
  for (const rule of this._rules) {
64
- if (rule.match(service, name)) {
13
+ if (rule.match(context)) {
65
14
  return rule
66
15
  }
67
16
  }
@@ -73,14 +22,7 @@ class SpanSampler {
73
22
 
74
23
  const { started } = spanContext._trace
75
24
  for (const span of started) {
76
- const context = span.context()
77
- const tags = context._tags || {}
78
- const name = context._name
79
- const service = tags.service ||
80
- tags['service.name'] ||
81
- span.tracer()._service
82
-
83
- const rule = this.findRule(service, name)
25
+ const rule = this.findRule(span)
84
26
  if (rule && rule.sample()) {
85
27
  span.context()._spanSampling = {
86
28
  sampleRate: rule.sampleRate,
@@ -0,0 +1,98 @@
1
+ 'use strict'
2
+
3
+ /* eslint-disable no-console */
4
+
5
+ const SortedSet = require('tlhunter-sorted-set')
6
+
7
+ const INTERVAL = 1000 // look for expired spans every 1s
8
+ const LIFETIME = 60 * 1000 // all spans have a max lifetime of 1m
9
+
10
+ const MODES = {
11
+ DISABLED: 0,
12
+ // METRICS_ONLY
13
+ LOG: 1,
14
+ GC_AND_LOG: 2
15
+ // GC
16
+ }
17
+
18
+ module.exports.MODES = MODES
19
+
20
+ const spans = new SortedSet()
21
+
22
+ // TODO: should these also be delivered as runtime metrics?
23
+
24
+ // const registry = new FinalizationRegistry(name => {
25
+ // spans.del(span) // there is no span
26
+ // })
27
+
28
+ let interval
29
+ let mode = MODES.DISABLED
30
+
31
+ module.exports.disable = function () {
32
+ mode = MODES.DISABLED
33
+ }
34
+
35
+ module.exports.enableLogging = function () {
36
+ mode = MODES.LOG
37
+ }
38
+
39
+ module.exports.enableGarbageCollection = function () {
40
+ mode = MODES.GC_AND_LOG
41
+ }
42
+
43
+ module.exports.startScrubber = function () {
44
+ if (!isEnabled()) return
45
+
46
+ interval = setInterval(() => {
47
+ const now = Date.now()
48
+ const expired = spans.rangeByScore(0, now)
49
+
50
+ if (!expired.length) return
51
+
52
+ const gc = isGarbageCollecting()
53
+
54
+ const expirationsByType = Object.create(null) // { [spanType]: count }
55
+
56
+ for (const wrapped of expired) {
57
+ spans.del(wrapped)
58
+ const span = wrapped.deref()
59
+
60
+ if (!span) continue // span has already been garbage collected
61
+
62
+ // TODO: Should we also do things like record the route to help users debug leaks?
63
+ if (!expirationsByType[span._name]) expirationsByType[span._name] = 0
64
+ expirationsByType[span._name]++
65
+
66
+ if (!gc) continue // everything after this point is related to manual GC
67
+
68
+ // TODO: what else can we do to alleviate memory usage
69
+ span.context()._tags = Object.create(null)
70
+ }
71
+
72
+ console.log('expired spans:' +
73
+ Object.keys(expirationsByType).reduce((a, c) => `${a} ${c}: ${expirationsByType[c]}`, ''))
74
+ }, INTERVAL)
75
+ }
76
+
77
+ module.exports.stopScrubber = function () {
78
+ clearInterval(interval)
79
+ }
80
+
81
+ module.exports.addSpan = function (span) {
82
+ if (!isEnabled()) return
83
+
84
+ const now = Date.now()
85
+ const expiration = now + LIFETIME
86
+ // eslint-disable-next-line no-undef
87
+ const wrapped = new WeakRef(span)
88
+ spans.add(wrapped, expiration)
89
+ // registry.register(span, span._name)
90
+ }
91
+
92
+ function isEnabled () {
93
+ return mode > MODES.DISABLED
94
+ }
95
+
96
+ function isGarbageCollecting () {
97
+ return mode >= MODES.GC_AND_LOG
98
+ }
@@ -6,6 +6,7 @@ const os = require('os')
6
6
  const { inspect } = require('util')
7
7
  const tracerVersion = require('../../../package.json').version
8
8
 
9
+ const errors = {}
9
10
  let config
10
11
  let pluginManager
11
12
  let samplingRules = []
@@ -89,6 +90,10 @@ function startupLog ({ agentError } = {}) {
89
90
  info('DATADOG TRACER CONFIGURATION - ' + out)
90
91
  if (agentError) {
91
92
  warn('DATADOG TRACER DIAGNOSTIC - Agent Error: ' + agentError.message)
93
+ errors.agentError = {
94
+ code: agentError.code ? agentError.code : '',
95
+ message: `Agent Error:${agentError.message}`
96
+ }
92
97
  }
93
98
 
94
99
  config = undefined
@@ -112,5 +117,6 @@ module.exports = {
112
117
  startupLog,
113
118
  setStartupLogConfig,
114
119
  setStartupLogPluginManager,
115
- setSamplingRules
120
+ setSamplingRules,
121
+ errors
116
122
  }
@@ -4,8 +4,9 @@ const path = require('path')
4
4
  const parse = require('module-details-from-path')
5
5
  const requirePackageJson = require('../require-package-json')
6
6
  const { sendData } = require('./send-data')
7
- const dc = require('../../../diagnostics_channel')
7
+ const dc = require('dc-polyfill')
8
8
  const { fileURLToPath } = require('url')
9
+ const { isTrue } = require('../../src/util')
9
10
 
10
11
  const savedDependenciesToSend = new Set()
11
12
  const detectedDependencyKeys = new Set()
@@ -14,20 +15,57 @@ const detectedDependencyVersions = new Set()
14
15
  const FILE_URI_START = `file://`
15
16
  const moduleLoadStartChannel = dc.channel('dd-trace:moduleLoadStart')
16
17
 
17
- let immediate, config, application, host
18
+ let immediate, config, application, host, initialLoad
18
19
  let isFirstModule = true
20
+ let getRetryData
21
+ let updateRetryData
19
22
 
23
+ function createBatchPayload (payload) {
24
+ const batchPayload = []
25
+ payload.map(item => {
26
+ batchPayload.push({
27
+ request_type: item.reqType,
28
+ payload: item.payload
29
+ })
30
+ })
31
+
32
+ return batchPayload
33
+ }
20
34
  function waitAndSend (config, application, host) {
21
35
  if (!immediate) {
22
36
  immediate = setImmediate(() => {
23
37
  immediate = null
24
38
  if (savedDependenciesToSend.size > 0) {
25
- const dependencies = Array.from(savedDependenciesToSend.values()).splice(0, 1000).map(pair => {
26
- savedDependenciesToSend.delete(pair)
27
- const [name, version] = pair.split(' ')
28
- return { name, version }
29
- })
30
- sendData(config, application, host, 'app-dependencies-loaded', { dependencies })
39
+ const dependencies = Array.from(savedDependenciesToSend.values())
40
+ // if a depencdency is from the initial load, *always* send the event
41
+ // Otherwise, only send if dependencyCollection is enabled
42
+ .filter(dep => {
43
+ const initialLoadModule = isTrue(dep.split(' ')[2])
44
+ const sendModule = initialLoadModule || (config.telemetry?.dependencyCollection)
45
+
46
+ if (!sendModule) savedDependenciesToSend.delete(dep) // we'll never send it
47
+ return sendModule
48
+ })
49
+ .splice(0, 2000) // v2 documentation specifies up to 2000 dependencies can be sent at once
50
+ .map(pair => {
51
+ savedDependenciesToSend.delete(pair)
52
+ const [name, version] = pair.split(' ')
53
+ return { name, version }
54
+ })
55
+ let currPayload
56
+ const retryData = getRetryData()
57
+ if (retryData) {
58
+ currPayload = { reqType: 'app-dependencies-loaded', payload: { dependencies } }
59
+ } else {
60
+ if (!dependencies.length) return // no retry data and no dependencies, nothing to send
61
+ currPayload = { dependencies }
62
+ }
63
+
64
+ const payload = retryData ? createBatchPayload([currPayload, retryData]) : currPayload
65
+ const reqType = retryData ? 'message-batch' : 'app-dependencies-loaded'
66
+
67
+ sendData(config, application, host, reqType, payload, updateRetryData)
68
+
31
69
  if (savedDependenciesToSend.size > 0) {
32
70
  waitAndSend(config, application, host)
33
71
  }
@@ -76,7 +114,7 @@ function onModuleLoad (data) {
76
114
  const dependencyAndVersion = `${name} ${version}`
77
115
 
78
116
  if (!detectedDependencyVersions.has(dependencyAndVersion)) {
79
- savedDependenciesToSend.add(dependencyAndVersion)
117
+ savedDependenciesToSend.add(`${dependencyAndVersion} ${initialLoad}`)
80
118
  detectedDependencyVersions.add(dependencyAndVersion)
81
119
 
82
120
  waitAndSend(config, application, host)
@@ -89,11 +127,19 @@ function onModuleLoad (data) {
89
127
  }
90
128
  }
91
129
  }
92
- function start (_config, _application, _host) {
130
+ function start (_config = {}, _application, _host, getRetryDataFunction, updateRetryDatafunction) {
93
131
  config = _config
94
132
  application = _application
95
133
  host = _host
134
+ initialLoad = true
135
+ getRetryData = getRetryDataFunction
136
+ updateRetryData = updateRetryDatafunction
96
137
  moduleLoadStartChannel.subscribe(onModuleLoad)
138
+
139
+ // try and capture intially loaded modules in the first tick
140
+ // since, ideally, the tracer (and this module) should be loaded first,
141
+ // this should capture any first-tick dependencies
142
+ queueMicrotask(() => { initialLoad = false })
97
143
  }
98
144
 
99
145
  function isDependency (filename, request) {
@@ -1,11 +1,10 @@
1
1
  'use strict'
2
-
3
2
  const tracerVersion = require('../../../../package.json').version
4
- const dc = require('../../../diagnostics_channel')
3
+ const dc = require('dc-polyfill')
5
4
  const os = require('os')
6
5
  const dependencies = require('./dependencies')
7
6
  const { sendData } = require('./send-data')
8
-
7
+ const { errors } = require('../startup-log')
9
8
  const { manager: metricsManager } = require('./metrics')
10
9
  const logs = require('./logs')
11
10
 
@@ -17,11 +16,54 @@ let pluginManager
17
16
 
18
17
  let application
19
18
  let host
20
- let interval
21
19
  let heartbeatTimeout
22
20
  let heartbeatInterval
21
+ let extendedInterval
22
+ let integrations
23
+ let configWithOrigin = []
24
+ let retryData = null
25
+ const extendedHeartbeatPayload = {}
26
+
23
27
  const sentIntegrations = new Set()
24
28
 
29
+ function getRetryData () {
30
+ return retryData
31
+ }
32
+
33
+ function updateRetryData (error, retryObj) {
34
+ if (error) {
35
+ if (retryObj.reqType === 'message-batch') {
36
+ const payload = retryObj.payload[0].payload
37
+ const reqType = retryObj.payload[0].request_type
38
+ retryData = { payload: payload, reqType: reqType }
39
+
40
+ // Since this payload failed twice it now gets save in to the extended heartbeat
41
+ const failedPayload = retryObj.payload[1].payload
42
+ const failedReqType = retryObj.payload[1].request_type
43
+
44
+ // save away the dependencies and integration request for extended heartbeat.
45
+ if (failedReqType === 'app-integrations-change') {
46
+ if (extendedHeartbeatPayload['integrations']) {
47
+ extendedHeartbeatPayload['integrations'].push(failedPayload)
48
+ } else {
49
+ extendedHeartbeatPayload['integrations'] = [failedPayload]
50
+ }
51
+ }
52
+ if (failedReqType === 'app-dependencies-loaded') {
53
+ if (extendedHeartbeatPayload['dependencies']) {
54
+ extendedHeartbeatPayload['dependencies'].push(failedPayload)
55
+ } else {
56
+ extendedHeartbeatPayload['dependencies'] = [failedPayload]
57
+ }
58
+ }
59
+ } else {
60
+ retryData = retryObj
61
+ }
62
+ } else {
63
+ retryData = null
64
+ }
65
+ }
66
+
25
67
  function getIntegrations () {
26
68
  const newIntegrations = []
27
69
  for (const pluginName in pluginManager._pluginsByName) {
@@ -38,43 +80,66 @@ function getIntegrations () {
38
80
  return newIntegrations
39
81
  }
40
82
 
41
- function flatten (input, result = [], prefix = [], traversedObjects = null) {
42
- traversedObjects = traversedObjects || new WeakSet()
43
- if (traversedObjects.has(input)) {
44
- return
83
+ function getProducts (config) {
84
+ const products = {
85
+ appsec: {
86
+ enabled: config.appsec.enabled
87
+ },
88
+ profiler: {
89
+ version: tracerVersion,
90
+ enabled: config.profiling.enabled
91
+ }
45
92
  }
46
- traversedObjects.add(input)
47
- for (const [key, value] of Object.entries(input)) {
48
- if (typeof value === 'object' && value !== null) {
49
- flatten(value, result, [...prefix, key], traversedObjects)
50
- } else {
51
- result.push({ name: [...prefix, key].join('.'), value })
93
+ if (errors.profilingError) {
94
+ products.profiler.error = errors.profilingError
95
+ errors.profilingError = {}
96
+ }
97
+ return products
98
+ }
99
+
100
+ function getInstallSignature (config) {
101
+ const { installSignature: sig } = config
102
+ if (sig && (sig.id || sig.time || sig.type)) {
103
+ return {
104
+ install_id: sig.id,
105
+ install_time: sig.time,
106
+ install_type: sig.type
52
107
  }
53
108
  }
54
- return result
55
109
  }
56
110
 
57
- function appStarted () {
58
- return {
59
- integrations: getIntegrations(),
60
- dependencies: [],
61
- configuration: flatten(formatConfig(config)),
62
- additional_payload: []
111
+ function appStarted (config) {
112
+ const app = {
113
+ products: getProducts(config),
114
+ configuration: configWithOrigin
115
+ }
116
+ const installSignature = getInstallSignature(config)
117
+ if (installSignature) {
118
+ app.install_signature = installSignature
63
119
  }
120
+ // TODO: add app.error with correct error codes
121
+ // if (errors.agentError) {
122
+ // app.error = errors.agentError
123
+ // errors.agentError = {}
124
+ // }
125
+ return app
64
126
  }
65
127
 
66
- function formatConfig (config) {
67
- // format peerServiceMapping from an object to a string map in order for
68
- // telemetry intake to accept the configuration
69
- config.peerServiceMapping = config.peerServiceMapping
70
- ? Object.entries(config.peerServiceMapping).map(([key, value]) => `${key}:${value}`).join(',')
71
- : ''
72
- return config
128
+ function appClosing () {
129
+ if (!config?.telemetry?.enabled) {
130
+ return
131
+ }
132
+ const { reqType, payload } = createPayload('app-closing')
133
+ sendData(config, application, host, reqType, payload)
134
+ // we flush before shutting down. Only in CI Visibility
135
+ if (config.isCiVisibility) {
136
+ metricsManager.send(config, application, host)
137
+ }
73
138
  }
74
139
 
75
140
  function onBeforeExit () {
76
141
  process.removeListener('beforeExit', onBeforeExit)
77
- sendData(config, application, host, 'app-closing')
142
+ appClosing()
78
143
  }
79
144
 
80
145
  function createAppObject (config) {
@@ -121,14 +186,53 @@ function getTelemetryData () {
121
186
  return { config, application, host, heartbeatInterval }
122
187
  }
123
188
 
189
+ function createBatchPayload (payload) {
190
+ const batchPayload = []
191
+ payload.map(item => {
192
+ batchPayload.push({
193
+ request_type: item.reqType,
194
+ payload: item.payload
195
+ })
196
+ })
197
+
198
+ return batchPayload
199
+ }
200
+
201
+ function createPayload (currReqType, currPayload = {}) {
202
+ if (getRetryData()) {
203
+ const payload = { reqType: currReqType, payload: currPayload }
204
+ const batchPayload = createBatchPayload([payload, retryData])
205
+ return { 'reqType': 'message-batch', 'payload': batchPayload }
206
+ }
207
+
208
+ return { 'reqType': currReqType, 'payload': currPayload }
209
+ }
210
+
124
211
  function heartbeat (config, application, host) {
125
212
  heartbeatTimeout = setTimeout(() => {
126
- sendData(config, application, host, 'app-heartbeat')
213
+ metricsManager.send(config, application, host)
214
+ logs.send(config, application, host)
215
+
216
+ const { reqType, payload } = createPayload('app-heartbeat')
217
+ sendData(config, application, host, reqType, payload, updateRetryData)
127
218
  heartbeat(config, application, host)
128
219
  }, heartbeatInterval).unref()
129
220
  return heartbeatTimeout
130
221
  }
131
222
 
223
+ function extendedHeartbeat (config) {
224
+ extendedInterval = setInterval(() => {
225
+ const appPayload = appStarted(config)
226
+ const payload = {
227
+ ...appPayload,
228
+ ...extendedHeartbeatPayload
229
+ }
230
+ sendData(config, application, host, 'app-extended-heartbeat', payload)
231
+ Object.keys(extendedHeartbeatPayload).forEach(key => delete extendedHeartbeatPayload[key])
232
+ }, 1000 * 60 * 60 * 24).unref()
233
+ return extendedInterval
234
+ }
235
+
132
236
  function start (aConfig, thePluginManager) {
133
237
  if (!aConfig.telemetry.enabled) {
134
238
  return
@@ -138,19 +242,23 @@ function start (aConfig, thePluginManager) {
138
242
  application = createAppObject(config)
139
243
  host = createHostObject()
140
244
  heartbeatInterval = config.telemetry.heartbeatInterval
245
+ integrations = getIntegrations()
141
246
 
142
- dependencies.start(config, application, host)
247
+ dependencies.start(config, application, host, getRetryData, updateRetryData)
143
248
  logs.start(config)
144
249
 
145
- sendData(config, application, host, 'app-started', appStarted())
250
+ sendData(config, application, host, 'app-started', appStarted(config))
251
+
252
+ if (integrations.length > 0) {
253
+ sendData(config, application, host, 'app-integrations-change',
254
+ { integrations }, updateRetryData)
255
+ }
256
+
146
257
  heartbeat(config, application, host)
147
- interval = setInterval(() => {
148
- metricsManager.send(config, application, host)
149
- logs.send(config, application, host)
150
- }, heartbeatInterval)
151
- interval.unref()
152
- process.on('beforeExit', onBeforeExit)
153
258
 
259
+ extendedHeartbeat(config)
260
+
261
+ process.on('beforeExit', onBeforeExit)
154
262
  telemetryStartChannel.publish(getTelemetryData())
155
263
  }
156
264
 
@@ -158,7 +266,7 @@ function stop () {
158
266
  if (!config) {
159
267
  return
160
268
  }
161
- clearInterval(interval)
269
+ clearInterval(extendedInterval)
162
270
  clearTimeout(heartbeatTimeout)
163
271
  process.removeListener('beforeExit', onBeforeExit)
164
272
 
@@ -175,39 +283,60 @@ function updateIntegrations () {
175
283
  if (integrations.length === 0) {
176
284
  return
177
285
  }
178
- sendData(config, application, host, 'app-integrations-change', { integrations })
286
+
287
+ const { reqType, payload } = createPayload('app-integrations-change', { integrations })
288
+
289
+ sendData(config, application, host, reqType, payload, updateRetryData)
290
+ }
291
+
292
+ function formatMapForTelemetry (map) {
293
+ // format from an object to a string map in order for
294
+ // telemetry intake to accept the configuration
295
+ return map
296
+ ? Object.entries(map).map(([key, value]) => `${key}:${value}`).join(',')
297
+ : ''
179
298
  }
180
299
 
181
300
  function updateConfig (changes, config) {
182
301
  if (!config.telemetry.enabled) return
183
302
  if (changes.length === 0) return
184
303
 
185
- // Hack to make system tests happy until we ship telemetry v2
186
- if (process.env.DD_INTERNAL_TELEMETRY_V2_ENABLED !== '1') return
187
-
188
304
  const application = createAppObject(config)
189
305
  const host = createHostObject()
190
306
 
191
307
  const names = {
192
308
  sampleRate: 'DD_TRACE_SAMPLE_RATE',
193
309
  logInjection: 'DD_LOG_INJECTION',
194
- headerTags: 'DD_TRACE_HEADER_TAGS'
310
+ headerTags: 'DD_TRACE_HEADER_TAGS',
311
+ tags: 'DD_TAGS'
195
312
  }
196
313
 
197
- const configuration = changes.map(change => ({
198
- name: names[change.name],
199
- value: Array.isArray(change.value) ? change.value.join(',') : change.value,
200
- origin: change.origin
201
- }))
314
+ const configuration = []
202
315
 
203
- sendData(config, application, host, 'app-client-configuration-change', {
204
- configuration
205
- })
316
+ for (const change of changes) {
317
+ const name = names[change.name] || change.name
318
+ const { origin, value } = change
319
+ const entry = { name, value, origin }
320
+
321
+ if (Array.isArray(value)) entry.value = value.join(',')
322
+ if (entry.name === 'DD_TAGS') entry.value = formatMapForTelemetry(entry.value)
323
+ if (entry.name === 'url' && entry.value) entry.value = entry.value.toString()
324
+ if (entry.name === 'peerServiceMapping' || entry.name === 'tags') entry.value = formatMapForTelemetry(entry.value)
325
+
326
+ configuration.push(entry)
327
+ }
328
+ if (!configWithOrigin.length) {
329
+ configWithOrigin = configuration
330
+ } else {
331
+ const { reqType, payload } = createPayload('app-client-configuration-change', { configuration })
332
+ sendData(config, application, host, reqType, payload, updateRetryData)
333
+ }
206
334
  }
207
335
 
208
336
  module.exports = {
209
337
  start,
210
338
  stop,
211
339
  updateIntegrations,
212
- updateConfig
340
+ updateConfig,
341
+ appClosing
213
342
  }
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const dc = require('../../../../diagnostics_channel')
3
+ const dc = require('dc-polyfill')
4
4
  const logCollector = require('./log-collector')
5
5
  const { sendData } = require('../send-data')
6
6
 
@@ -54,7 +54,7 @@ function send (config, application, host) {
54
54
 
55
55
  const logs = logCollector.drain()
56
56
  if (logs) {
57
- sendData(config, application, host, 'logs', logs)
57
+ sendData(config, application, host, 'logs', { logs })
58
58
  }
59
59
  }
60
60