dd-trace 5.0.0-pre-0a30f33 → 5.0.0-pre-cd5fbdb

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 (26) hide show
  1. package/README.md +4 -9
  2. package/index.d.ts +43 -0
  3. package/package.json +2 -2
  4. package/packages/datadog-esbuild/index.js +20 -6
  5. package/packages/datadog-instrumentations/src/jest.js +1 -1
  6. package/packages/datadog-instrumentations/src/kafkajs.js +3 -4
  7. package/packages/datadog-instrumentations/src/mocha.js +3 -0
  8. package/packages/datadog-instrumentations/src/next.js +86 -2
  9. package/packages/datadog-instrumentations/src/openai.js +1 -1
  10. package/packages/datadog-plugin-cypress/src/plugin.js +32 -5
  11. package/packages/datadog-plugin-graphql/src/index.js +3 -3
  12. package/packages/datadog-plugin-mongodb-core/src/index.js +6 -2
  13. package/packages/datadog-plugin-next/src/index.js +4 -0
  14. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +12 -1
  15. package/packages/dd-trace/src/dogstatsd.js +65 -5
  16. package/packages/dd-trace/src/exporters/agent/writer.js +9 -9
  17. package/packages/dd-trace/src/exporters/common/request.js +13 -4
  18. package/packages/dd-trace/src/opentracing/span.js +13 -13
  19. package/packages/dd-trace/src/opentracing/tracer.js +3 -3
  20. package/packages/dd-trace/src/plugins/util/test.js +2 -2
  21. package/packages/dd-trace/src/profiling/config.js +3 -1
  22. package/packages/dd-trace/src/profiling/profilers/wall.js +23 -7
  23. package/packages/dd-trace/src/proxy.js +23 -2
  24. package/packages/dd-trace/src/telemetry/dependencies.js +15 -0
  25. package/packages/dd-trace/src/telemetry/index.js +11 -1
  26. /package/packages/dd-trace/src/{metrics.js → runtime_metrics.js} +0 -0
package/README.md CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  [![npm v4](https://img.shields.io/npm/v/dd-trace/latest?color=blue&label=dd-trace%40v4&logo=npm)](https://www.npmjs.com/package/dd-trace)
4
4
  [![npm v3](https://img.shields.io/npm/v/dd-trace/latest-node14?color=blue&label=dd-trace%40v3&logo=npm)](https://www.npmjs.com/package/dd-trace/v/latest-node12)
5
- [![npm v2](https://img.shields.io/npm/v/dd-trace/latest-node12?color=blue&label=dd-trace%40v2&logo=npm)](https://www.npmjs.com/package/dd-trace/v/latest-node12)
6
- [![npm dev](https://img.shields.io/npm/v/dd-trace/dev?color=orange&label=dd-trace%40dev&logo=npm)](https://www.npmjs.com/package/dd-trace/v/dev)
7
5
  [![codecov](https://codecov.io/gh/DataDog/dd-trace-js/branch/master/graph/badge.svg)](https://codecov.io/gh/DataDog/dd-trace-js)
8
6
 
9
7
  <img align="right" src="https://user-images.githubusercontent.com/551402/208212084-1d0c07e2-4135-4c61-b2da-8f2fddbc66ed.png" alt="Bits the dog JavaScript" width="200px"/>
@@ -28,12 +26,12 @@ Most of the documentation for `dd-trace` is available on these webpages:
28
26
  | Release Line | Latest Version | Node.js | Status |Initial Release | End of Life |
29
27
  | :---: | :---: | :---: | :---: | :---: | :---: |
30
28
  | [`v1`](https://github.com/DataDog/dd-trace-js/tree/v1.x) | ![npm v1](https://img.shields.io/npm/v/dd-trace/legacy-v1?color=white&label=%20&style=flat-square) | `>= v12` | **End of Life** | 2021-07-13 | 2022-02-25 |
31
- | [`v2`](https://github.com/DataDog/dd-trace-js/tree/v2.x) | ![npm v2](https://img.shields.io/npm/v/dd-trace/latest-node12?color=white&label=%20&style=flat-square) | `>= v12` | **Maintenance** | 2022-01-28 | 2023-08-15 |
29
+ | [`v2`](https://github.com/DataDog/dd-trace-js/tree/v2.x) | ![npm v2](https://img.shields.io/npm/v/dd-trace/latest-node12?color=white&label=%20&style=flat-square) | `>= v12` | **End of Life** | 2022-01-28 | 2023-08-15 |
32
30
  | [`v3`](https://github.com/DataDog/dd-trace-js/tree/v3.x) | ![npm v3](https://img.shields.io/npm/v/dd-trace/latest-node14?color=white&label=%20&style=flat-square) | `>= v14` | **Maintenance** | 2022-08-15 | 2024-05-15 |
33
31
  | [`v4`](https://github.com/DataDog/dd-trace-js/tree/v4.x) | ![npm v4](https://img.shields.io/npm/v/dd-trace/latest?color=white&label=%20&style=flat-square) | `>= v16` | **Current** | 2023-05-12 | Unknown |
34
32
 
35
- We currently maintain three release lines, namely `v2`, `v3` and `v4`.
36
- Features and bug fixes that are merged are released to the `v4` line and, if appropriate, also the `v2` and `v3` line.
33
+ We currently maintain two release lines, namely `v3` and `v4`.
34
+ Features and bug fixes that are merged are released to the `v4` line and, if appropriate, also the `v3` line.
37
35
 
38
36
  For any new projects it is recommended to use the `v4` release line:
39
37
 
@@ -42,15 +40,12 @@ $ npm install dd-trace
42
40
  $ yarn add dd-trace
43
41
  ```
44
42
 
45
- However, existing projects that already use the `v2` or `v3` release lines, or projects that need to support EOL versions of Node.js, may continue to use these release lines.
43
+ However, existing projects that already use the `v3` release line, or projects that need to support EOL versions of Node.js, may continue to use these release lines.
46
44
  This is done by specifying the version when installing the package.
47
- Note that we also publish to npm using a `latest-node12` and `latest-node14` tag that can also be used for install:
48
45
 
49
46
  ```sh
50
47
  $ npm install dd-trace@3
51
48
  $ yarn add dd-trace@3
52
- $ npm install dd-trace@latest-node14
53
- $ yarn add dd-trace@latest-node14
54
49
  ```
55
50
 
56
51
  Any backwards-breaking functionality that is introduced into the library will result in an increase of the major version of the library and therefore a new release line.
package/index.d.ts CHANGED
@@ -121,6 +121,8 @@ export declare interface Tracer extends opentracing.Tracer {
121
121
  appsec: Appsec;
122
122
 
123
123
  TracerProvider: opentelemetry.TracerProvider;
124
+
125
+ dogstatsd: DogStatsD;
124
126
  }
125
127
 
126
128
  export declare interface TraceOptions extends Analyzable {
@@ -642,6 +644,47 @@ export declare interface User {
642
644
  [key: string]: string | undefined
643
645
  }
644
646
 
647
+ export declare interface DogStatsD {
648
+ /**
649
+ * Increments a metric by the specified value, optionally specifying tags.
650
+ * @param {string} stat The dot-separated metric name.
651
+ * @param {number} value The amount to increment the stat by.
652
+ * @param {[tag:string]:string|number} tags Tags to pass along, such as `[ 'foo:bar' ]`. Values are combined with config.tags.
653
+ */
654
+ increment(stat: string, value?: number, tags?: { [tag: string]: string|number }): void
655
+
656
+ /**
657
+ * Decrements a metric by the specified value, optionally specifying tags.
658
+ * @param {string} stat The dot-separated metric name.
659
+ * @param {number} value The amount to decrement the stat by.
660
+ * @param {[tag:string]:string|number} tags Tags to pass along, such as `[ 'foo:bar' ]`. Values are combined with config.tags.
661
+ */
662
+ decrement(stat: string, value?: number, tags?: { [tag: string]: string|number }): void
663
+
664
+ /**
665
+ * Sets a distribution value, optionally specifying tags.
666
+ * @param {string} stat The dot-separated metric name.
667
+ * @param {number} value The amount to increment the stat by.
668
+ * @param {[tag:string]:string|number} tags Tags to pass along, such as `[ 'foo:bar' ]`. Values are combined with config.tags.
669
+ */
670
+ distribution(stat: string, value?: number, tags?: { [tag: string]: string|number }): void
671
+
672
+ /**
673
+ * Sets a gauge value, optionally specifying tags.
674
+ * @param {string} stat The dot-separated metric name.
675
+ * @param {number} value The amount to increment the stat by.
676
+ * @param {[tag:string]:string|number} tags Tags to pass along, such as `[ 'foo:bar' ]`. Values are combined with config.tags.
677
+ */
678
+ gauge(stat: string, value?: number, tags?: { [tag: string]: string|number }): void
679
+
680
+ /**
681
+ * Forces any unsent metrics to be sent
682
+ *
683
+ * @beta This method is experimental and could be removed in future versions.
684
+ */
685
+ flush(): void
686
+ }
687
+
645
688
  export declare interface Appsec {
646
689
  /**
647
690
  * Links a successful login event to the current trace. Will link the passed user to the current trace with Appsec.setUser() internally.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "5.0.0-pre-0a30f33",
3
+ "version": "5.0.0-pre-cd5fbdb",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -71,7 +71,7 @@
71
71
  "@datadog/native-iast-rewriter": "2.0.1",
72
72
  "@datadog/native-iast-taint-tracking": "1.5.0",
73
73
  "@datadog/native-metrics": "^2.0.0",
74
- "@datadog/pprof": "3.1.0",
74
+ "@datadog/pprof": "3.2.0",
75
75
  "@datadog/sketches-js": "^2.1.0",
76
76
  "@opentelemetry/api": "^1.0.0",
77
77
  "@opentelemetry/core": "^1.14.0",
@@ -53,10 +53,25 @@ module.exports.setup = function (build) {
53
53
  const externalModules = new Set(build.initialOptions.external || [])
54
54
  build.onResolve({ filter: /.*/ }, args => {
55
55
  if (externalModules.has(args.path)) {
56
+ // Internal Node.js packages will still be instrumented via require()
56
57
  if (DEBUG) console.log(`EXTERNAL: ${args.path}`)
57
58
  return
58
59
  }
59
60
 
61
+ // TODO: Should this also check for namespace === 'file'?
62
+ if (args.path.startsWith('.') && !args.importer.includes('node_modules/')) {
63
+ // This is local application code, not an instrumented package
64
+ if (DEBUG) console.log(`LOCAL: ${args.path}`)
65
+ return
66
+ }
67
+
68
+ // TODO: Should this also check for namespace === 'file'?
69
+ if (args.path.startsWith('@') && !args.importer.includes('node_modules/')) {
70
+ // This is the Next.js convention for loading local files
71
+ if (DEBUG) console.log(`@LOCAL: ${args.path}`)
72
+ return
73
+ }
74
+
60
75
  let fullPathToModule
61
76
  try {
62
77
  fullPathToModule = dotFriendlyResolve(args.path, args.resolveDir)
@@ -65,12 +80,11 @@ module.exports.setup = function (build) {
65
80
  return
66
81
  }
67
82
  const extracted = extractPackageAndModulePath(fullPathToModule)
68
- const packageName = args.path
69
83
 
70
84
  const internal = builtins.has(args.path)
71
85
 
72
86
  if (args.namespace === 'file' && (
73
- modulesOfInterest.has(packageName) || modulesOfInterest.has(`${extracted.pkg}/${extracted.path}`))
87
+ modulesOfInterest.has(args.path) || modulesOfInterest.has(`${extracted.pkg}/${extracted.path}`))
74
88
  ) {
75
89
  // The file namespace is used when requiring files from disk in userland
76
90
 
@@ -90,7 +104,7 @@ module.exports.setup = function (build) {
90
104
 
91
105
  const packageJson = require(pathToPackageJson)
92
106
 
93
- if (DEBUG) console.log(`RESOLVE: ${packageName}@${packageJson.version}`)
107
+ if (DEBUG) console.log(`RESOLVE: ${args.path}@${packageJson.version}`)
94
108
 
95
109
  // https://esbuild.github.io/plugins/#on-resolve-arguments
96
110
  return {
@@ -101,17 +115,17 @@ module.exports.setup = function (build) {
101
115
  pkg: extracted.pkg,
102
116
  path: extracted.path,
103
117
  full: fullPathToModule,
104
- raw: packageName,
118
+ raw: args.path,
105
119
  internal
106
120
  }
107
121
  }
108
122
  } else if (args.namespace === NAMESPACE) {
109
123
  // The datadog namespace is used when requiring files that are injected during the onLoad stage
110
124
 
111
- if (builtins.has(packageName)) return
125
+ if (builtins.has(args.path)) return
112
126
 
113
127
  return {
114
- path: require.resolve(packageName, { paths: [ args.resolveDir ] }),
128
+ path: require.resolve(args.path, { paths: [ args.resolveDir ] }),
115
129
  namespace: 'file'
116
130
  }
117
131
  }
@@ -105,7 +105,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
105
105
  await super.handleTestEvent(event, state)
106
106
  }
107
107
 
108
- const setNameToParams = (name, params) => { this.nameToParams[name] = params }
108
+ const setNameToParams = (name, params) => { this.nameToParams[name] = [...params] }
109
109
 
110
110
  if (event.name === 'setup') {
111
111
  if (this.global.test) {
@@ -15,15 +15,14 @@ const consumerStartCh = channel('apm:kafkajs:consume:start')
15
15
  const consumerFinishCh = channel('apm:kafkajs:consume:finish')
16
16
  const consumerErrorCh = channel('apm:kafkajs:consume:error')
17
17
 
18
- addHook({ name: 'kafkajs', versions: ['>=1.4'] }, (obj) => {
19
- class Kafka extends obj.Kafka {
18
+ addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKafka) => {
19
+ class Kafka extends BaseKafka {
20
20
  constructor (options) {
21
21
  super(options)
22
22
  this._brokers = (options.brokers && typeof options.brokers !== 'function')
23
23
  ? options.brokers.join(',') : undefined
24
24
  }
25
25
  }
26
- obj.Kafka = Kafka
27
26
 
28
27
  shimmer.wrap(Kafka.prototype, 'producer', createProducer => function () {
29
28
  const producer = createProducer.apply(this, arguments)
@@ -117,5 +116,5 @@ addHook({ name: 'kafkajs', versions: ['>=1.4'] }, (obj) => {
117
116
  }
118
117
  return consumer
119
118
  })
120
- return obj
119
+ return Kafka
121
120
  })
@@ -441,6 +441,9 @@ addHook({
441
441
  file: 'lib/cli/run-helpers.js'
442
442
  }, (run) => {
443
443
  shimmer.wrap(run, 'runMocha', runMocha => async function () {
444
+ if (!testStartCh.hasSubscribers) {
445
+ return runMocha.apply(this, arguments)
446
+ }
444
447
  const mocha = arguments[0]
445
448
  /**
446
449
  * This attaches `run` to the global context, which we'll call after
@@ -12,6 +12,7 @@ const errorChannel = channel('apm:next:request:error')
12
12
  const pageLoadChannel = channel('apm:next:page:load')
13
13
 
14
14
  const requests = new WeakSet()
15
+ const requestToNextjsPagePath = new WeakMap()
15
16
 
16
17
  function wrapHandleRequest (handleRequest) {
17
18
  return function (req, res, pathname, query) {
@@ -132,6 +133,57 @@ function instrument (req, res, handler) {
132
133
  })
133
134
  }
134
135
 
136
+ function wrapSetupServerWorker (setupServerWorker) {
137
+ return function (requestHandler) {
138
+ arguments[0] = shimmer.wrap(requestHandler, wrapRequestHandler(requestHandler))
139
+ return setupServerWorker.apply(this, arguments)
140
+ }
141
+ }
142
+
143
+ function wrapInitialize (initialize) {
144
+ return async function () {
145
+ const result = await initialize.apply(this, arguments)
146
+ if (Array.isArray(result)) {
147
+ const requestHandler = result[0]
148
+ result[0] = shimmer.wrap(requestHandler, wrapRequestHandler(requestHandler))
149
+ }
150
+ return result
151
+ }
152
+ }
153
+
154
+ function wrapRequestHandler (requestHandler) {
155
+ return function (req, res) {
156
+ return instrument(req, res, async () => {
157
+ const result = await requestHandler.apply(this, arguments) // apply here first to get page path association
158
+
159
+ const page = requestToNextjsPagePath.get(req)
160
+ if (page && pageLoadChannel.hasSubscribers) pageLoadChannel.publish({ page })
161
+
162
+ return result
163
+ })
164
+ }
165
+ }
166
+
167
+ // these two functions make sure we get path groups for routes in standalone,
168
+ // as it doesn't route through `next-server`/`base-server`
169
+ function wrapGetResolveRoutes (getResolveRoutes) {
170
+ return function () {
171
+ const result = getResolveRoutes.apply(this, arguments)
172
+ return shimmer.wrap(result, wrapResolveRoutes(result))
173
+ }
174
+ }
175
+
176
+ function wrapResolveRoutes (resolveRoutes) {
177
+ return async function (req) {
178
+ const result = await resolveRoutes.apply(this, arguments)
179
+ if (result && result.matchedOutput) {
180
+ const path = result.matchedOutput.itemPath
181
+ requestToNextjsPagePath.set(req, path)
182
+ }
183
+ return result
184
+ }
185
+ }
186
+
135
187
  function finish (ctx, result, err) {
136
188
  if (err) {
137
189
  ctx.error = err
@@ -147,11 +199,27 @@ function finish (ctx, result, err) {
147
199
  return result
148
200
  }
149
201
 
202
+ addHook({
203
+ name: 'next',
204
+ versions: ['>=13.4.13'],
205
+ file: 'dist/server/lib/router-utils/resolve-routes.js'
206
+ }, resolveRoutesModule => shimmer.wrap(resolveRoutesModule, 'getResolveRoutes', wrapGetResolveRoutes))
207
+
208
+ addHook({
209
+ name: 'next',
210
+ versions: ['13.4.13'],
211
+ file: 'dist/server/lib/setup-server-worker.js'
212
+ }, setupServerWorker => shimmer.wrap(setupServerWorker, 'initializeServerWorker', wrapSetupServerWorker))
213
+
214
+ addHook({
215
+ name: 'next',
216
+ versions: ['>=13.4.15'],
217
+ file: 'dist/server/lib/router-server.js'
218
+ }, routerServer => shimmer.wrap(routerServer, 'initialize', wrapInitialize))
219
+
150
220
  addHook({ name: 'next', versions: ['>=13.2'], file: 'dist/server/next-server.js' }, nextServer => {
151
221
  const Server = nextServer.default
152
222
 
153
- shimmer.wrap(Server.prototype, 'handleRequest', wrapHandleRequest)
154
- shimmer.wrap(Server.prototype, 'handleApiRequest', wrapHandleApiRequestWithMatch)
155
223
  shimmer.wrap(Server.prototype, 'renderToResponse', wrapRenderToResponse)
156
224
  shimmer.wrap(Server.prototype, 'renderErrorToResponse', wrapRenderErrorToResponse)
157
225
  shimmer.wrap(Server.prototype, 'findPageComponents', wrapFindPageComponents)
@@ -159,6 +227,22 @@ addHook({ name: 'next', versions: ['>=13.2'], file: 'dist/server/next-server.js'
159
227
  return nextServer
160
228
  })
161
229
 
230
+ // these functions wrapped in all versions above 13.2 except:
231
+ // 13.4.13 due to tests failing when these functions are wrapped
232
+ // 13.4.14 due to it not being in the NPM registry/officially released
233
+ addHook({
234
+ name: 'next',
235
+ versions: ['>=13.2 <13.4.13', '>=13.4.15'],
236
+ file: 'dist/server/next-server.js'
237
+ }, nextServer => {
238
+ const Server = nextServer.default
239
+
240
+ shimmer.wrap(Server.prototype, 'handleRequest', wrapHandleRequest)
241
+ shimmer.wrap(Server.prototype, 'handleApiRequest', wrapHandleApiRequestWithMatch)
242
+
243
+ return nextServer
244
+ })
245
+
162
246
  addHook({ name: 'next', versions: ['>=11.1 <13.2'], file: 'dist/server/next-server.js' }, nextServer => {
163
247
  const Server = nextServer.default
164
248
 
@@ -10,7 +10,7 @@ const startCh = channel('apm:openai:request:start')
10
10
  const finishCh = channel('apm:openai:request:finish')
11
11
  const errorCh = channel('apm:openai:request:error')
12
12
 
13
- addHook({ name: 'openai', file: 'dist/api.js', versions: ['>=3.0.0'] }, exports => {
13
+ addHook({ name: 'openai', file: 'dist/api.js', versions: ['>=3.0.0 <4'] }, exports => {
14
14
  const methodNames = Object.getOwnPropertyNames(exports.OpenAIApi.prototype)
15
15
  methodNames.shift() // remove leading 'constructor' method
16
16
 
@@ -25,6 +25,7 @@ const {
25
25
  } = require('../../dd-trace/src/plugins/util/test')
26
26
  const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
27
27
  const log = require('../../dd-trace/src/log')
28
+ const NoopTracer = require('../../dd-trace/src/noop/tracer')
28
29
 
29
30
  const TEST_FRAMEWORK_NAME = 'cypress'
30
31
 
@@ -119,10 +120,32 @@ function getSkippableTests (isSuitesSkippingEnabled, tracer, testConfiguration)
119
120
  })
120
121
  }
121
122
 
123
+ const noopTask = {
124
+ 'dd:testSuiteStart': () => {
125
+ return null
126
+ },
127
+ 'dd:beforeEach': () => {
128
+ return {}
129
+ },
130
+ 'dd:afterEach': () => {
131
+ return null
132
+ },
133
+ 'dd:addTags': () => {
134
+ return null
135
+ }
136
+ }
137
+
122
138
  module.exports = (on, config) => {
123
139
  let isTestsSkipped = false
124
140
  const skippedTests = []
125
141
  const tracer = require('../../dd-trace')
142
+
143
+ // The tracer was not init correctly for whatever reason (such as invalid DD_SITE)
144
+ if (tracer._tracer instanceof NoopTracer) {
145
+ // We still need to register these tasks or the support file will fail
146
+ return on('task', noopTask)
147
+ }
148
+
126
149
  const testEnvironmentMetadata = getTestEnvironmentMetadata(TEST_FRAMEWORK_NAME)
127
150
 
128
151
  const {
@@ -328,12 +351,16 @@ module.exports = (on, config) => {
328
351
  }
329
352
 
330
353
  return new Promise(resolve => {
331
- if (tracer._tracer._exporter.flush) {
332
- tracer._tracer._exporter.flush(() => {
354
+ const exporter = tracer._tracer._exporter
355
+ if (!exporter) {
356
+ return resolve(null)
357
+ }
358
+ if (exporter.flush) {
359
+ exporter.flush(() => {
333
360
  resolve(null)
334
361
  })
335
- } else {
336
- tracer._tracer._exporter._writer.flush(() => {
362
+ } else if (exporter._writer) {
363
+ exporter._writer.flush(() => {
337
364
  resolve(null)
338
365
  })
339
366
  }
@@ -375,7 +402,7 @@ module.exports = (on, config) => {
375
402
  'dd:afterEach': ({ test, coverage }) => {
376
403
  const { state, error, isRUMActive, testSourceLine, testSuite, testName } = test
377
404
  if (activeSpan) {
378
- if (coverage && tracer._tracer._exporter.exportCoverage && isCodeCoverageEnabled) {
405
+ if (coverage && isCodeCoverageEnabled && tracer._tracer._exporter && tracer._tracer._exporter.exportCoverage) {
379
406
  const coverageFiles = getCoveredFilenamesFromCoverage(coverage)
380
407
  const relativeCoverageFiles = coverageFiles.map(file => getTestSuitePath(file, rootDir))
381
408
  const { _traceId, _spanId } = testSuiteSpan.context()
@@ -56,9 +56,9 @@ function getVariablesFilter (config) {
56
56
 
57
57
  function getHooks (config) {
58
58
  const noop = () => { }
59
- const execute = (config.hooks && config.hooks.execute) || noop
60
- const parse = (config.hooks && config.hooks.parse) || noop
61
- const validate = (config.hooks && config.hooks.validate) || noop
59
+ const execute = config.hooks?.execute || noop
60
+ const parse = config.hooks?.parse || noop
61
+ const validate = config.hooks?.validate || noop
62
62
 
63
63
  return { execute, parse, validate }
64
64
  }
@@ -36,10 +36,14 @@ class MongodbCorePlugin extends DatabasePlugin {
36
36
  }
37
37
  }
38
38
 
39
+ function sanitizeBigInt (data) {
40
+ return JSON.stringify(data, (_key, value) => typeof value === 'bigint' ? value.toString() : value)
41
+ }
42
+
39
43
  function getQuery (cmd) {
40
44
  if (!cmd || typeof cmd !== 'object' || Array.isArray(cmd)) return
41
- if (cmd.query) return JSON.stringify(limitDepth(cmd.query))
42
- if (cmd.filter) return JSON.stringify(limitDepth(cmd.filter))
45
+ if (cmd.query) return sanitizeBigInt(limitDepth(cmd.query))
46
+ if (cmd.filter) return sanitizeBigInt(limitDepth(cmd.filter))
43
47
  }
44
48
 
45
49
  function getResource (plugin, ns, query, operationName) {
@@ -4,6 +4,7 @@ const ServerPlugin = require('../../dd-trace/src/plugins/server')
4
4
  const { storage } = require('../../datadog-core')
5
5
  const analyticsSampler = require('../../dd-trace/src/analytics_sampler')
6
6
  const { COMPONENT } = require('../../dd-trace/src/constants')
7
+ const web = require('../../dd-trace/src/plugins/util/web')
7
8
 
8
9
  class NextPlugin extends ServerPlugin {
9
10
  static get id () {
@@ -49,6 +50,7 @@ class NextPlugin extends ServerPlugin {
49
50
 
50
51
  const span = store.span
51
52
  const error = span.context()._tags['error']
53
+ const page = span.context()._tags['next.page']
52
54
 
53
55
  if (!this.config.validateStatus(res.statusCode) && !error) {
54
56
  span.setTag('error', true)
@@ -58,6 +60,8 @@ class NextPlugin extends ServerPlugin {
58
60
  'http.status_code': res.statusCode
59
61
  })
60
62
 
63
+ if (page) web.setRoute(req, page)
64
+
61
65
  this.config.hooks.request(span, req, res)
62
66
 
63
67
  span.finish()
@@ -76,7 +76,18 @@ class SensitiveHandler {
76
76
  while (nextSensitive != null && contains(nextTainted, nextSensitive)) {
77
77
  const redactionStart = nextSensitive.start - nextTainted.start
78
78
  const redactionEnd = nextSensitive.end - nextTainted.start
79
- this.redactSource(sources, redactedSources, redactedSourcesContext, sourceIndex, redactionStart, redactionEnd)
79
+ if (redactionStart === redactionEnd) {
80
+ this.writeRedactedValuePart(valueParts, 0)
81
+ } else {
82
+ this.redactSource(
83
+ sources,
84
+ redactedSources,
85
+ redactedSourcesContext,
86
+ sourceIndex,
87
+ redactionStart,
88
+ redactionEnd
89
+ )
90
+ }
80
91
  nextSensitive = sensitive.shift()
81
92
  }
82
93
 
@@ -35,14 +35,14 @@ class DogStatsDClient {
35
35
  this._udp6 = this._socket('udp6')
36
36
  }
37
37
 
38
- gauge (stat, value, tags) {
39
- this._add(stat, value, TYPE_GAUGE, tags)
40
- }
41
-
42
38
  increment (stat, value, tags) {
43
39
  this._add(stat, value, TYPE_COUNTER, tags)
44
40
  }
45
41
 
42
+ gauge (stat, value, tags) {
43
+ this._add(stat, value, TYPE_GAUGE, tags)
44
+ }
45
+
46
46
  distribution (stat, value, tags) {
47
47
  this._add(stat, value, TYPE_DISTRIBUTION, tags)
48
48
  }
@@ -153,7 +153,67 @@ class NoopDogStatsDClient {
153
153
  flush () { }
154
154
  }
155
155
 
156
+ // This is a simplified user-facing proxy to the underlying DogStatsDClient instance
157
+ class CustomMetrics {
158
+ constructor (options) {
159
+ this.dogstatsd = new DogStatsDClient(options)
160
+ }
161
+
162
+ increment (stat, value = 1, tags) {
163
+ return this.dogstatsd.increment(
164
+ stat,
165
+ value,
166
+ CustomMetrics.tagTranslator(tags)
167
+ )
168
+ }
169
+
170
+ decrement (stat, value = 1, tags) {
171
+ return this.dogstatsd.increment(
172
+ stat,
173
+ value * -1,
174
+ CustomMetrics.tagTranslator(tags)
175
+ )
176
+ }
177
+
178
+ gauge (stat, value, tags) {
179
+ return this.dogstatsd.gauge(
180
+ stat,
181
+ value,
182
+ CustomMetrics.tagTranslator(tags)
183
+ )
184
+ }
185
+
186
+ distribution (stat, value, tags) {
187
+ return this.dogstatsd.distribution(
188
+ stat,
189
+ value,
190
+ CustomMetrics.tagTranslator(tags)
191
+ )
192
+ }
193
+
194
+ flush () {
195
+ return this.dogstatsd.flush()
196
+ }
197
+
198
+ /**
199
+ * Exposing { tagName: 'tagValue' } to the end user
200
+ * These are translated into [ 'tagName:tagValue' ] for internal use
201
+ */
202
+ static tagTranslator (objTags) {
203
+ const arrTags = []
204
+
205
+ if (!objTags) return arrTags
206
+
207
+ for (const [key, value] of Object.entries(objTags)) {
208
+ arrTags.push(`${key}:${value}`)
209
+ }
210
+
211
+ return arrTags
212
+ }
213
+ }
214
+
156
215
  module.exports = {
157
216
  DogStatsDClient,
158
- NoopDogStatsDClient
217
+ NoopDogStatsDClient,
218
+ CustomMetrics
159
219
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  const request = require('../common/request')
4
4
  const { startupLog } = require('../../startup-log')
5
- const metrics = require('../../metrics')
5
+ const runtimeMetrics = require('../../runtime_metrics')
6
6
  const log = require('../../log')
7
7
  const tracerVersion = require('../../../../../package.json').version
8
8
  const BaseWriter = require('../common/writer')
@@ -22,19 +22,19 @@ class Writer extends BaseWriter {
22
22
  }
23
23
 
24
24
  _sendPayload (data, count, done) {
25
- metrics.increment(`${METRIC_PREFIX}.requests`, true)
25
+ runtimeMetrics.increment(`${METRIC_PREFIX}.requests`, true)
26
26
 
27
27
  const { _headers, _lookup, _protocolVersion, _url } = this
28
28
  makeRequest(_protocolVersion, data, count, _url, _headers, _lookup, true, (err, res, status) => {
29
29
  if (status) {
30
- metrics.increment(`${METRIC_PREFIX}.responses`, true)
31
- metrics.increment(`${METRIC_PREFIX}.responses.by.status`, `status:${status}`, true)
30
+ runtimeMetrics.increment(`${METRIC_PREFIX}.responses`, true)
31
+ runtimeMetrics.increment(`${METRIC_PREFIX}.responses.by.status`, `status:${status}`, true)
32
32
  } else if (err) {
33
- metrics.increment(`${METRIC_PREFIX}.errors`, true)
34
- metrics.increment(`${METRIC_PREFIX}.errors.by.name`, `name:${err.name}`, true)
33
+ runtimeMetrics.increment(`${METRIC_PREFIX}.errors`, true)
34
+ runtimeMetrics.increment(`${METRIC_PREFIX}.errors.by.name`, `name:${err.name}`, true)
35
35
 
36
36
  if (err.code) {
37
- metrics.increment(`${METRIC_PREFIX}.errors.by.code`, `code:${err.code}`, true)
37
+ runtimeMetrics.increment(`${METRIC_PREFIX}.errors.by.code`, `code:${err.code}`, true)
38
38
  }
39
39
  }
40
40
 
@@ -53,8 +53,8 @@ class Writer extends BaseWriter {
53
53
  } catch (e) {
54
54
  log.error(e)
55
55
 
56
- metrics.increment(`${METRIC_PREFIX}.errors`, true)
57
- metrics.increment(`${METRIC_PREFIX}.errors.by.name`, `name:${e.name}`, true)
56
+ runtimeMetrics.increment(`${METRIC_PREFIX}.errors`, true)
57
+ runtimeMetrics.increment(`${METRIC_PREFIX}.errors.by.name`, `name:${e.name}`, true)
58
58
  }
59
59
  done()
60
60
  })
@@ -42,10 +42,19 @@ function urlToOptions (url) {
42
42
  return options
43
43
  }
44
44
 
45
- function fromUrlString (url) {
46
- return typeof urlToHttpOptions === 'function'
47
- ? urlToOptions(new URL(url))
48
- : urlParse(url)
45
+ function fromUrlString (urlString) {
46
+ const url = typeof urlToHttpOptions === 'function'
47
+ ? urlToOptions(new URL(urlString))
48
+ : urlParse(urlString)
49
+
50
+ // Add the 'hostname' back if we're using named pipes
51
+ if (url.protocol === 'unix:' && url.host === '.') {
52
+ const udsPath = urlString.replace(/^unix:/, '')
53
+ url.path = udsPath
54
+ url.pathname = udsPath
55
+ }
56
+
57
+ return url
49
58
  }
50
59
 
51
60
  function request (data, options, callback) {
@@ -8,7 +8,7 @@ const semver = require('semver')
8
8
  const SpanContext = require('./span_context')
9
9
  const id = require('../id')
10
10
  const tagger = require('../tagger')
11
- const metrics = require('../metrics')
11
+ const runtimeMetrics = require('../runtime_metrics')
12
12
  const log = require('../log')
13
13
  const { storage } = require('../../../datadog-core')
14
14
  const telemetryMetrics = require('../telemetry/metrics')
@@ -79,11 +79,11 @@ class DatadogSpan {
79
79
  this._startTime = fields.startTime || this._getTime()
80
80
 
81
81
  if (DD_TRACE_EXPERIMENTAL_SPAN_COUNTS && finishedRegistry) {
82
- metrics.increment('runtime.node.spans.unfinished')
83
- metrics.increment('runtime.node.spans.unfinished.by.name', `span_name:${operationName}`)
82
+ runtimeMetrics.increment('runtime.node.spans.unfinished')
83
+ runtimeMetrics.increment('runtime.node.spans.unfinished.by.name', `span_name:${operationName}`)
84
84
 
85
- metrics.increment('runtime.node.spans.open') // unfinished for real
86
- metrics.increment('runtime.node.spans.open.by.name', `span_name:${operationName}`)
85
+ runtimeMetrics.increment('runtime.node.spans.open') // unfinished for real
86
+ runtimeMetrics.increment('runtime.node.spans.open.by.name', `span_name:${operationName}`)
87
87
 
88
88
  unfinishedRegistry.register(this, operationName, this)
89
89
  }
@@ -159,13 +159,13 @@ class DatadogSpan {
159
159
  getIntegrationCounter('span_finished', this._integrationName).inc()
160
160
 
161
161
  if (DD_TRACE_EXPERIMENTAL_SPAN_COUNTS && finishedRegistry) {
162
- metrics.decrement('runtime.node.spans.unfinished')
163
- metrics.decrement('runtime.node.spans.unfinished.by.name', `span_name:${this._name}`)
164
- metrics.increment('runtime.node.spans.finished')
165
- metrics.increment('runtime.node.spans.finished.by.name', `span_name:${this._name}`)
162
+ runtimeMetrics.decrement('runtime.node.spans.unfinished')
163
+ runtimeMetrics.decrement('runtime.node.spans.unfinished.by.name', `span_name:${this._name}`)
164
+ runtimeMetrics.increment('runtime.node.spans.finished')
165
+ runtimeMetrics.increment('runtime.node.spans.finished.by.name', `span_name:${this._name}`)
166
166
 
167
- metrics.decrement('runtime.node.spans.open') // unfinished for real
168
- metrics.decrement('runtime.node.spans.open.by.name', `span_name:${this._name}`)
167
+ runtimeMetrics.decrement('runtime.node.spans.open') // unfinished for real
168
+ runtimeMetrics.decrement('runtime.node.spans.open.by.name', `span_name:${this._name}`)
169
169
 
170
170
  unfinishedRegistry.unregister(this)
171
171
  finishedRegistry.register(this, this._name)
@@ -243,8 +243,8 @@ function createRegistry (type) {
243
243
  if (!semver.satisfies(process.version, '>=14.6')) return
244
244
 
245
245
  return new global.FinalizationRegistry(name => {
246
- metrics.decrement(`runtime.node.spans.${type}`)
247
- metrics.decrement(`runtime.node.spans.${type}.by.name`, [`span_name:${name}`])
246
+ runtimeMetrics.decrement(`runtime.node.spans.${type}`)
247
+ runtimeMetrics.decrement(`runtime.node.spans.${type}.by.name`, [`span_name:${name}`])
248
248
  })
249
249
  }
250
250
 
@@ -11,7 +11,7 @@ const LogPropagator = require('./propagation/log')
11
11
  const formats = require('../../../../ext/formats')
12
12
 
13
13
  const log = require('../log')
14
- const metrics = require('../metrics')
14
+ const runtimeMetrics = require('../runtime_metrics')
15
15
  const getExporter = require('../exporter')
16
16
  const SpanContext = require('./span_context')
17
17
 
@@ -82,7 +82,7 @@ class DatadogTracer {
82
82
  this._propagators[format].inject(spanContext, carrier)
83
83
  } catch (e) {
84
84
  log.error(e)
85
- metrics.increment('datadog.tracer.node.inject.errors', true)
85
+ runtimeMetrics.increment('datadog.tracer.node.inject.errors', true)
86
86
  }
87
87
  }
88
88
 
@@ -91,7 +91,7 @@ class DatadogTracer {
91
91
  return this._propagators[format].extract(carrier)
92
92
  } catch (e) {
93
93
  log.error(e)
94
- metrics.increment('datadog.tracer.node.extract.errors', true)
94
+ runtimeMetrics.increment('datadog.tracer.node.extract.errors', true)
95
95
  return null
96
96
  }
97
97
  }
@@ -139,13 +139,13 @@ function removeInvalidMetadata (metadata) {
139
139
  return Object.keys(metadata).reduce((filteredTags, tag) => {
140
140
  if (tag === GIT_REPOSITORY_URL) {
141
141
  if (!validateGitRepositoryUrl(metadata[GIT_REPOSITORY_URL])) {
142
- log.error('DD_GIT_REPOSITORY_URL must be a valid URL')
142
+ log.error(`Repository URL is not a valid repository URL: ${metadata[GIT_REPOSITORY_URL]}.`)
143
143
  return filteredTags
144
144
  }
145
145
  }
146
146
  if (tag === GIT_COMMIT_SHA) {
147
147
  if (!validateGitCommitSha(metadata[GIT_COMMIT_SHA])) {
148
- log.error('DD_GIT_COMMIT_SHA must be a full-length git SHA')
148
+ log.error(`Git commit SHA must be a full-length git SHA: ${metadata[GIT_COMMIT_SHA]}.`)
149
149
  return filteredTags
150
150
  }
151
151
  }
@@ -31,6 +31,7 @@ class Config {
31
31
  DD_PROFILING_UPLOAD_PERIOD,
32
32
  DD_PROFILING_PPROF_PREFIX,
33
33
  DD_PROFILING_HEAP_ENABLED,
34
+ DD_PROFILING_V8_PROFILER_BUG_WORKAROUND,
34
35
  DD_PROFILING_WALLTIME_ENABLED,
35
36
  DD_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED,
36
37
  DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE,
@@ -76,7 +77,8 @@ class Config {
76
77
  this.debugSourceMaps = isTrue(coalesce(options.debugSourceMaps, DD_PROFILING_DEBUG_SOURCE_MAPS, false))
77
78
  this.endpointCollectionEnabled = endpointCollectionEnabled
78
79
  this.pprofPrefix = pprofPrefix
79
-
80
+ this.v8ProfilerBugWorkaroundEnabled = isTrue(coalesce(options.v8ProfilerBugWorkaround,
81
+ DD_PROFILING_V8_PROFILER_BUG_WORKAROUND, true))
80
82
  const hostname = coalesce(options.hostname, DD_AGENT_HOST) || 'localhost'
81
83
  const port = coalesce(options.port, DD_TRACE_AGENT_PORT) || 8126
82
84
  this.url = new URL(coalesce(options.url, DD_TRACE_AGENT_URL, format({
@@ -3,6 +3,9 @@
3
3
  const { storage } = require('../../../../datadog-core')
4
4
 
5
5
  const dc = require('../../../../diagnostics_channel')
6
+ const { HTTP_METHOD, HTTP_ROUTE, RESOURCE_NAME, SPAN_TYPE } = require('../../../../../ext/tags')
7
+ const { WEB } = require('../../../../../ext/types')
8
+ const runtimeMetrics = require('../../runtime_metrics')
6
9
 
7
10
  const beforeCh = dc.channel('dd-trace:storage:before')
8
11
  const enterCh = dc.channel('dd-trace:storage:enter')
@@ -41,13 +44,13 @@ function getSpanContextTags (span) {
41
44
  }
42
45
 
43
46
  function isWebServerSpan (tags) {
44
- return tags['span.type'] === 'web'
47
+ return tags[SPAN_TYPE] === WEB
45
48
  }
46
49
 
47
50
  function endpointNameFromTags (tags) {
48
- return tags['resource.name'] || [
49
- tags['http.method'],
50
- tags['http.route']
51
+ return tags[RESOURCE_NAME] || [
52
+ tags[HTTP_METHOD],
53
+ tags[HTTP_ROUTE]
51
54
  ].filter(v => v).join(' ')
52
55
  }
53
56
 
@@ -81,6 +84,7 @@ class NativeWallProfiler {
81
84
  this._flushIntervalMillis = options.flushInterval || 60 * 1e3 // 60 seconds
82
85
  this._codeHotspotsEnabled = !!options.codeHotspotsEnabled
83
86
  this._endpointCollectionEnabled = !!options.endpointCollectionEnabled
87
+ this._v8ProfilerBugWorkaroundEnabled = !!options.v8ProfilerBugWorkaroundEnabled
84
88
  this._mapper = undefined
85
89
  this._pprof = undefined
86
90
 
@@ -99,7 +103,7 @@ class NativeWallProfiler {
99
103
 
100
104
  if (this._codeHotspotsEnabled && !this._emittedFFMessage && this._logger) {
101
105
  this._logger.debug(
102
- `Wall profiler: Enable config_trace_show_breakdown_profiling_for_node feature flag to see code hotspots.`)
106
+ `Wall profiler: Enable trace_show_breakdown_profiling_for_node feature flag to see code hotspots.`)
103
107
  this._emittedFFMessage = true
104
108
  }
105
109
 
@@ -120,7 +124,8 @@ class NativeWallProfiler {
120
124
  durationMillis: this._flushIntervalMillis,
121
125
  sourceMapper: this._mapper,
122
126
  withContexts: this._codeHotspotsEnabled,
123
- lineNumbers: false
127
+ lineNumbers: false,
128
+ workaroundV8Bug: this._v8ProfilerBugWorkaroundEnabled
124
129
  })
125
130
 
126
131
  if (this._codeHotspotsEnabled) {
@@ -170,7 +175,18 @@ class NativeWallProfiler {
170
175
  this._enter()
171
176
  this._lastSampleCount = 0
172
177
  }
173
- return this._pprof.time.stop(restart, this._codeHotspotsEnabled ? generateLabels : undefined)
178
+ const profile = this._pprof.time.stop(restart, this._codeHotspotsEnabled ? generateLabels : undefined)
179
+ if (restart) {
180
+ const v8BugDetected = this._pprof.time.v8ProfilerStuckEventLoopDetected()
181
+ if (v8BugDetected === 1) {
182
+ this._logger?.warn('Wall profiler: possible v8 profiler stuck event loop detected.')
183
+ runtimeMetrics.increment('runtime.node.profiler.v8_cpu_profiler_maybe_stuck_event_loop', undefined, true)
184
+ } else if (v8BugDetected === 2) {
185
+ this._logger?.warn('Wall profiler: v8 profiler stuck event loop detected.')
186
+ runtimeMetrics.increment('runtime.node.profiler.v8_cpu_profiler_stuck_event_loop', undefined, true)
187
+ }
188
+ }
189
+ return profile
174
190
  }
175
191
 
176
192
  profile () {
@@ -2,13 +2,14 @@
2
2
  const NoopProxy = require('./noop/proxy')
3
3
  const DatadogTracer = require('./tracer')
4
4
  const Config = require('./config')
5
- const metrics = require('./metrics')
5
+ const runtimeMetrics = require('./runtime_metrics')
6
6
  const log = require('./log')
7
7
  const { setStartupLogPluginManager } = require('./startup-log')
8
8
  const telemetry = require('./telemetry')
9
9
  const PluginManager = require('./plugin_manager')
10
10
  const remoteConfig = require('./appsec/remote_config')
11
11
  const AppsecSdk = require('./appsec/sdk')
12
+ const dogstatsd = require('./dogstatsd')
12
13
 
13
14
  class Tracer extends NoopProxy {
14
15
  constructor () {
@@ -16,6 +17,7 @@ class Tracer extends NoopProxy {
16
17
 
17
18
  this._initialized = false
18
19
  this._pluginManager = new PluginManager(this)
20
+ this.dogstatsd = new dogstatsd.NoopDogStatsDClient()
19
21
  }
20
22
 
21
23
  init (options) {
@@ -26,6 +28,25 @@ class Tracer extends NoopProxy {
26
28
  try {
27
29
  const config = new Config(options) // TODO: support dynamic code config
28
30
 
31
+ if (config.dogstatsd) {
32
+ // Custom Metrics
33
+ this.dogstatsd = new dogstatsd.CustomMetrics({
34
+ host: config.dogstatsd.hostname,
35
+ port: config.dogstatsd.port,
36
+ tags: [
37
+ // these are the Runtime Metrics default tags
38
+ // Python also uses these as default Custom Metrics tags
39
+ `service:${config.tags.service}`,
40
+ `env:${config.tags.env}`,
41
+ `version:${config.tags.version}`
42
+ ]
43
+ })
44
+
45
+ setInterval(() => {
46
+ this.dogstatsd.flush()
47
+ }, 10 * 1000).unref()
48
+ }
49
+
29
50
  if (config.remoteConfig.enabled && !config.isCiVisibility) {
30
51
  const rc = remoteConfig.enable(config)
31
52
 
@@ -58,7 +79,7 @@ class Tracer extends NoopProxy {
58
79
  }
59
80
 
60
81
  if (config.runtimeMetrics) {
61
- metrics.start(config)
82
+ runtimeMetrics.start(config)
62
83
  }
63
84
 
64
85
  if (config.tracing) {
@@ -15,6 +15,7 @@ const FILE_URI_START = `file://`
15
15
  const moduleLoadStartChannel = dc.channel('dd-trace:moduleLoadStart')
16
16
 
17
17
  let immediate, config, application, host
18
+ let isFirstModule = true
18
19
 
19
20
  function waitAndSend (config, application, host) {
20
21
  if (!immediate) {
@@ -36,7 +37,21 @@ function waitAndSend (config, application, host) {
36
37
  }
37
38
  }
38
39
 
40
+ function loadAllTheLoadedModules () {
41
+ if (require.cache) {
42
+ const filenames = Object.keys(require.cache)
43
+ filenames.forEach(filename => {
44
+ onModuleLoad({ filename })
45
+ })
46
+ }
47
+ }
48
+
39
49
  function onModuleLoad (data) {
50
+ if (isFirstModule) {
51
+ isFirstModule = false
52
+ loadAllTheLoadedModules()
53
+ }
54
+
40
55
  if (data) {
41
56
  let filename = data.filename
42
57
  if (filename && filename.startsWith(FILE_URI_START)) {
@@ -17,6 +17,7 @@ let pluginManager
17
17
  let application
18
18
  let host
19
19
  let interval
20
+ let heartbeatTimeout
20
21
  let heartbeatInterval
21
22
  const sentIntegrations = new Set()
22
23
 
@@ -110,6 +111,14 @@ function getTelemetryData () {
110
111
  return { config, application, host, heartbeatInterval }
111
112
  }
112
113
 
114
+ function heartbeat (config, application, host) {
115
+ heartbeatTimeout = setTimeout(() => {
116
+ sendData(config, application, host, 'app-heartbeat')
117
+ heartbeat(config, application, host)
118
+ }, heartbeatInterval).unref()
119
+ return heartbeatTimeout
120
+ }
121
+
113
122
  function start (aConfig, thePluginManager) {
114
123
  if (!aConfig.telemetry.enabled) {
115
124
  return
@@ -122,9 +131,9 @@ function start (aConfig, thePluginManager) {
122
131
 
123
132
  dependencies.start(config, application, host)
124
133
  sendData(config, application, host, 'app-started', appStarted())
134
+ heartbeat(config, application, host)
125
135
  interval = setInterval(() => {
126
136
  metricsManager.send(config, application, host)
127
- sendData(config, application, host, 'app-heartbeat')
128
137
  }, heartbeatInterval)
129
138
  interval.unref()
130
139
  process.on('beforeExit', onBeforeExit)
@@ -137,6 +146,7 @@ function stop () {
137
146
  return
138
147
  }
139
148
  clearInterval(interval)
149
+ clearTimeout(heartbeatTimeout)
140
150
  process.removeListener('beforeExit', onBeforeExit)
141
151
 
142
152
  telemetryStopChannel.publish(getTelemetryData())