dd-trace 4.11.1 → 4.16.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 (78) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/README.md +4 -9
  3. package/ext/tags.d.ts +1 -0
  4. package/ext/tags.js +1 -0
  5. package/index.d.ts +44 -0
  6. package/package.json +9 -6
  7. package/packages/datadog-esbuild/index.js +57 -32
  8. package/packages/datadog-instrumentations/src/body-parser.js +2 -2
  9. package/packages/datadog-instrumentations/src/cookie-parser.js +37 -0
  10. package/packages/datadog-instrumentations/src/cucumber.js +30 -11
  11. package/packages/datadog-instrumentations/src/express.js +1 -1
  12. package/packages/datadog-instrumentations/src/graphql.js +10 -4
  13. package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
  14. package/packages/datadog-instrumentations/src/http/server.js +1 -1
  15. package/packages/datadog-instrumentations/src/jest.js +22 -11
  16. package/packages/datadog-instrumentations/src/kafkajs.js +3 -4
  17. package/packages/datadog-instrumentations/src/mocha.js +33 -8
  18. package/packages/datadog-instrumentations/src/mysql.js +39 -1
  19. package/packages/datadog-instrumentations/src/next.js +47 -19
  20. package/packages/datadog-instrumentations/src/openai.js +1 -1
  21. package/packages/datadog-instrumentations/src/pg.js +60 -15
  22. package/packages/datadog-instrumentations/src/playwright.js +15 -3
  23. package/packages/datadog-plugin-cucumber/src/index.js +14 -2
  24. package/packages/datadog-plugin-cypress/src/plugin.js +49 -13
  25. package/packages/datadog-plugin-graphql/src/index.js +3 -3
  26. package/packages/datadog-plugin-graphql/src/resolve.js +27 -2
  27. package/packages/datadog-plugin-jest/src/index.js +10 -2
  28. package/packages/datadog-plugin-jest/src/util.js +10 -4
  29. package/packages/datadog-plugin-mocha/src/index.js +14 -2
  30. package/packages/datadog-plugin-mongodb-core/src/index.js +6 -2
  31. package/packages/datadog-plugin-mysql/src/index.js +2 -2
  32. package/packages/datadog-plugin-next/src/index.js +22 -5
  33. package/packages/datadog-plugin-pg/src/index.js +2 -2
  34. package/packages/dd-trace/src/appsec/addresses.js +1 -0
  35. package/packages/dd-trace/src/appsec/channels.js +2 -0
  36. package/packages/dd-trace/src/appsec/iast/analyzers/ldap-injection-analyzer.js +7 -0
  37. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +29 -18
  38. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +19 -1
  39. package/packages/dd-trace/src/appsec/iast/path-line.js +1 -0
  40. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +1 -1
  41. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +48 -5
  42. package/packages/dd-trace/src/appsec/iast/telemetry/index.js +14 -5
  43. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +131 -10
  44. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +0 -1
  45. package/packages/dd-trace/src/appsec/index.js +42 -7
  46. package/packages/dd-trace/src/appsec/recommended.json +655 -31
  47. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
  48. package/packages/dd-trace/src/appsec/remote_config/index.js +2 -0
  49. package/packages/dd-trace/src/appsec/reporter.js +26 -0
  50. package/packages/dd-trace/src/appsec/telemetry.js +132 -0
  51. package/packages/dd-trace/src/appsec/waf/index.js +1 -1
  52. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +13 -5
  53. package/packages/dd-trace/src/appsec/waf/waf_manager.js +12 -14
  54. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +1 -14
  55. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +1 -13
  56. package/packages/dd-trace/src/datastreams/processor.js +6 -2
  57. package/packages/dd-trace/src/dogstatsd.js +108 -8
  58. package/packages/dd-trace/src/exporters/agent/writer.js +9 -9
  59. package/packages/dd-trace/src/exporters/common/request.js +13 -4
  60. package/packages/dd-trace/src/format.js +6 -1
  61. package/packages/dd-trace/src/opentracing/propagation/text_map.js +2 -2
  62. package/packages/dd-trace/src/opentracing/span.js +13 -13
  63. package/packages/dd-trace/src/opentracing/tracer.js +3 -5
  64. package/packages/dd-trace/src/plugin_manager.js +1 -2
  65. package/packages/dd-trace/src/plugins/ci_plugin.js +22 -1
  66. package/packages/dd-trace/src/plugins/database.js +14 -4
  67. package/packages/dd-trace/src/plugins/index.js +1 -0
  68. package/packages/dd-trace/src/plugins/outbound.js +4 -3
  69. package/packages/dd-trace/src/plugins/tracing.js +1 -1
  70. package/packages/dd-trace/src/plugins/util/test.js +20 -3
  71. package/packages/dd-trace/src/profiling/config.js +3 -1
  72. package/packages/dd-trace/src/profiling/profilers/wall.js +31 -7
  73. package/packages/dd-trace/src/proxy.js +13 -2
  74. package/packages/dd-trace/src/ritm.js +10 -2
  75. package/packages/dd-trace/src/{metrics.js → runtime_metrics.js} +1 -32
  76. package/packages/dd-trace/src/telemetry/dependencies.js +15 -0
  77. package/packages/dd-trace/src/telemetry/index.js +21 -2
  78. package/packages/dd-trace/src/util.js +1 -1
@@ -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
 
@@ -26,8 +26,6 @@ class DatadogTracer {
26
26
  this._version = config.version
27
27
  this._env = config.env
28
28
  this._tags = config.tags
29
- this._computePeerService = config.spanComputePeerService
30
- this._peerServiceMapping = config.peerServiceMapping
31
29
  this._logInjection = config.logInjection
32
30
  this._debug = config.debug
33
31
  this._prioritySampler = new PrioritySampler(config.env, config.sampler)
@@ -82,7 +80,7 @@ class DatadogTracer {
82
80
  this._propagators[format].inject(spanContext, carrier)
83
81
  } catch (e) {
84
82
  log.error(e)
85
- metrics.increment('datadog.tracer.node.inject.errors', true)
83
+ runtimeMetrics.increment('datadog.tracer.node.inject.errors', true)
86
84
  }
87
85
  }
88
86
 
@@ -91,7 +89,7 @@ class DatadogTracer {
91
89
  return this._propagators[format].extract(carrier)
92
90
  } catch (e) {
93
91
  log.error(e)
94
- metrics.increment('datadog.tracer.node.extract.errors', true)
92
+ runtimeMetrics.increment('datadog.tracer.node.extract.errors', true)
95
93
  return null
96
94
  }
97
95
  }
@@ -72,11 +72,10 @@ module.exports = class PluginManager {
72
72
  const Plugin = pluginClasses[name]
73
73
 
74
74
  if (!Plugin) return
75
+ if (!this._tracerConfig) return // TODO: don't wait for tracer to be initialized
75
76
  if (!this._pluginsByName[name]) {
76
77
  this._pluginsByName[name] = new Plugin(this._tracer, this._tracerConfig)
77
78
  }
78
- if (!this._tracerConfig) return // TODO: don't wait for tracer to be initialized
79
-
80
79
  const pluginConfig = this._configsByName[name] || {
81
80
  enabled: this._tracerConfig.plugins !== false
82
81
  }
@@ -12,7 +12,10 @@ const {
12
12
  TEST_MODULE_ID,
13
13
  TEST_SESSION_ID,
14
14
  TEST_COMMAND,
15
- TEST_MODULE
15
+ TEST_MODULE,
16
+ getTestSuiteCommonTags,
17
+ TEST_STATUS,
18
+ TEST_SKIPPED_BY_ITR
16
19
  } = require('./util/test')
17
20
  const Plugin = require('./plugin')
18
21
  const { COMPONENT } = require('../constants')
@@ -77,6 +80,24 @@ module.exports = class CiPlugin extends Plugin {
77
80
  }
78
81
  })
79
82
  })
83
+
84
+ this.addSub(`ci:${this.constructor.id}:itr:skipped-suites`, ({ skippedSuites, frameworkVersion }) => {
85
+ const testCommand = this.testSessionSpan.context()._tags[TEST_COMMAND]
86
+ skippedSuites.forEach((testSuite) => {
87
+ const testSuiteMetadata = getTestSuiteCommonTags(testCommand, frameworkVersion, testSuite, this.constructor.id)
88
+
89
+ this.tracer.startSpan(`${this.constructor.id}.test_suite`, {
90
+ childOf: this.testModuleSpan,
91
+ tags: {
92
+ [COMPONENT]: this.constructor.id,
93
+ ...this.testEnvironmentMetadata,
94
+ ...testSuiteMetadata,
95
+ [TEST_STATUS]: 'skip',
96
+ [TEST_SKIPPED_BY_ITR]: 'true'
97
+ }
98
+ }).finish()
99
+ })
100
+ })
80
101
  }
81
102
 
82
103
  configure (config) {
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const StoragePlugin = require('./storage')
4
+ const { PEER_SERVICE_KEY } = require('../constants')
4
5
 
5
6
  class DatabasePlugin extends StoragePlugin {
6
7
  static get operation () { return 'query' }
@@ -38,20 +39,29 @@ class DatabasePlugin extends StoragePlugin {
38
39
  `ddps='${encodedDdps}',ddpv='${encodedDdpv}'`
39
40
  }
40
41
 
41
- injectDbmQuery (query, serviceName, isPreparedStatement = false) {
42
+ getDbmServiceName (span, tracerService) {
43
+ if (this._tracerConfig.spanComputePeerService) {
44
+ const peerData = this.getPeerService(span.context()._tags)
45
+ return this.getPeerServiceRemap(peerData)[PEER_SERVICE_KEY] || tracerService
46
+ }
47
+ return tracerService
48
+ }
49
+
50
+ injectDbmQuery (span, query, serviceName, isPreparedStatement = false) {
42
51
  const mode = this.config.dbmPropagationMode
52
+ const dbmService = this.getDbmServiceName(span, serviceName)
43
53
 
44
54
  if (mode === 'disabled') {
45
55
  return query
46
56
  }
47
57
 
48
- const servicePropagation = this.createDBMPropagationCommentService(serviceName)
58
+ const servicePropagation = this.createDBMPropagationCommentService(dbmService)
49
59
 
50
60
  if (isPreparedStatement || mode === 'service') {
51
61
  return `/*${servicePropagation}*/ ${query}`
52
62
  } else if (mode === 'full') {
53
- this.activeSpan.setTag('_dd.dbm_trace_injected', 'true')
54
- const traceparent = this.activeSpan._spanContext.toTraceparent()
63
+ span.setTag('_dd.dbm_trace_injected', 'true')
64
+ const traceparent = span._spanContext.toTraceparent()
55
65
  return `/*${servicePropagation},traceparent='${traceparent}'*/ ${query}`
56
66
  }
57
67
  }
@@ -64,6 +64,7 @@ module.exports = {
64
64
  get 'pg' () { return require('../../../datadog-plugin-pg/src') },
65
65
  get 'pino' () { return require('../../../datadog-plugin-pino/src') },
66
66
  get 'pino-pretty' () { return require('../../../datadog-plugin-pino/src') },
67
+ get 'playwright' () { return require('../../../datadog-plugin-playwright/src') },
67
68
  get 'redis' () { return require('../../../datadog-plugin-redis/src') },
68
69
  get 'restify' () { return require('../../../datadog-plugin-restify/src') },
69
70
  get 'rhea' () { return require('../../../datadog-plugin-rhea/src') },
@@ -64,10 +64,11 @@ class OutboundPlugin extends TracingPlugin {
64
64
  * peer service and add the value we overrode.
65
65
  */
66
66
  const peerService = peerData[PEER_SERVICE_KEY]
67
- if (peerService && this.tracer._peerServiceMapping[peerService]) {
67
+ const mappedService = this._tracerConfig.peerServiceMapping?.[peerService]
68
+ if (peerService && mappedService) {
68
69
  return {
69
70
  ...peerData,
70
- [PEER_SERVICE_KEY]: this.tracer._peerServiceMapping[peerService],
71
+ [PEER_SERVICE_KEY]: mappedService,
71
72
  [PEER_SERVICE_REMAP_KEY]: peerService
72
73
  }
73
74
  }
@@ -80,7 +81,7 @@ class OutboundPlugin extends TracingPlugin {
80
81
  }
81
82
 
82
83
  tagPeerService (span) {
83
- if (this.tracer._computePeerService) {
84
+ if (this._tracerConfig.spanComputePeerService) {
84
85
  const peerData = this.getPeerService(span.context()._tags)
85
86
  if (peerData !== undefined) {
86
87
  span.addTags(this.getPeerServiceRemap(peerData))
@@ -55,7 +55,7 @@ class TracingPlugin extends Plugin {
55
55
  start () {} // implemented by individual plugins
56
56
 
57
57
  finish () {
58
- this.activeSpan.finish()
58
+ this.activeSpan?.finish()
59
59
  }
60
60
 
61
61
  error (error) {
@@ -47,6 +47,7 @@ const TEST_SESSION_ID = 'test_session_id'
47
47
  const TEST_MODULE_ID = 'test_module_id'
48
48
  const TEST_SUITE_ID = 'test_suite_id'
49
49
  const TEST_TOOLCHAIN = 'test.toolchain'
50
+ const TEST_SKIPPED_BY_ITR = 'test.skipped_by_itr'
50
51
 
51
52
  const CI_APP_ORIGIN = 'ciapp-test'
52
53
 
@@ -54,6 +55,8 @@ const JEST_TEST_RUNNER = 'test.jest.test_runner'
54
55
 
55
56
  const TEST_ITR_TESTS_SKIPPED = '_dd.ci.itr.tests_skipped'
56
57
  const TEST_ITR_SKIPPING_ENABLED = 'test.itr.tests_skipping.enabled'
58
+ const TEST_ITR_SKIPPING_TYPE = 'test.itr.tests_skipping.type'
59
+ const TEST_ITR_SKIPPING_COUNT = 'test.itr.tests_skipping.count'
57
60
  const TEST_CODE_COVERAGE_ENABLED = 'test.code_coverage.enabled'
58
61
 
59
62
  const TEST_CODE_COVERAGE_LINES_PCT = 'test.code_coverage.lines_pct'
@@ -80,6 +83,7 @@ module.exports = {
80
83
  JEST_WORKER_TRACE_PAYLOAD_CODE,
81
84
  JEST_WORKER_COVERAGE_PAYLOAD_CODE,
82
85
  TEST_SOURCE_START,
86
+ TEST_SKIPPED_BY_ITR,
83
87
  getTestEnvironmentMetadata,
84
88
  getTestParametersString,
85
89
  finishAllTraceSpans,
@@ -99,6 +103,8 @@ module.exports = {
99
103
  TEST_ITR_TESTS_SKIPPED,
100
104
  TEST_MODULE,
101
105
  TEST_ITR_SKIPPING_ENABLED,
106
+ TEST_ITR_SKIPPING_TYPE,
107
+ TEST_ITR_SKIPPING_COUNT,
102
108
  TEST_CODE_COVERAGE_ENABLED,
103
109
  TEST_CODE_COVERAGE_LINES_PCT,
104
110
  addIntelligentTestRunnerSpanTags,
@@ -133,13 +139,13 @@ function removeInvalidMetadata (metadata) {
133
139
  return Object.keys(metadata).reduce((filteredTags, tag) => {
134
140
  if (tag === GIT_REPOSITORY_URL) {
135
141
  if (!validateGitRepositoryUrl(metadata[GIT_REPOSITORY_URL])) {
136
- 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]}.`)
137
143
  return filteredTags
138
144
  }
139
145
  }
140
146
  if (tag === GIT_COMMIT_SHA) {
141
147
  if (!validateGitCommitSha(metadata[GIT_COMMIT_SHA])) {
142
- 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]}.`)
143
149
  return filteredTags
144
150
  }
145
151
  }
@@ -354,14 +360,25 @@ function getTestSuiteCommonTags (command, testFrameworkVersion, testSuite, testF
354
360
  function addIntelligentTestRunnerSpanTags (
355
361
  testSessionSpan,
356
362
  testModuleSpan,
357
- { isSuitesSkipped, isSuitesSkippingEnabled, isCodeCoverageEnabled, testCodeCoverageLinesTotal }
363
+ {
364
+ isSuitesSkipped,
365
+ isSuitesSkippingEnabled,
366
+ isCodeCoverageEnabled,
367
+ testCodeCoverageLinesTotal,
368
+ skippingCount,
369
+ skippingType = 'suite'
370
+ }
358
371
  ) {
359
372
  testSessionSpan.setTag(TEST_ITR_TESTS_SKIPPED, isSuitesSkipped ? 'true' : 'false')
360
373
  testSessionSpan.setTag(TEST_ITR_SKIPPING_ENABLED, isSuitesSkippingEnabled ? 'true' : 'false')
374
+ testSessionSpan.setTag(TEST_ITR_SKIPPING_TYPE, skippingType)
375
+ testSessionSpan.setTag(TEST_ITR_SKIPPING_COUNT, skippingCount)
361
376
  testSessionSpan.setTag(TEST_CODE_COVERAGE_ENABLED, isCodeCoverageEnabled ? 'true' : 'false')
362
377
 
363
378
  testModuleSpan.setTag(TEST_ITR_TESTS_SKIPPED, isSuitesSkipped ? 'true' : 'false')
364
379
  testModuleSpan.setTag(TEST_ITR_SKIPPING_ENABLED, isSuitesSkippingEnabled ? 'true' : 'false')
380
+ testModuleSpan.setTag(TEST_ITR_SKIPPING_TYPE, skippingType)
381
+ testModuleSpan.setTag(TEST_ITR_SKIPPING_COUNT, skippingCount)
365
382
  testModuleSpan.setTag(TEST_CODE_COVERAGE_ENABLED, isCodeCoverageEnabled ? 'true' : 'false')
366
383
 
367
384
  // If suites have been skipped we don't want to report the total coverage, as it will be wrong
@@ -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,9 +3,14 @@
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')
9
+ const telemetryMetrics = require('../../telemetry/metrics')
6
10
 
7
11
  const beforeCh = dc.channel('dd-trace:storage:before')
8
12
  const enterCh = dc.channel('dd-trace:storage:enter')
13
+ const profilerTelemetryMetrics = telemetryMetrics.manager.namespace('profilers')
9
14
 
10
15
  let kSampleCount
11
16
 
@@ -41,13 +46,13 @@ function getSpanContextTags (span) {
41
46
  }
42
47
 
43
48
  function isWebServerSpan (tags) {
44
- return tags['span.type'] === 'web'
49
+ return tags[SPAN_TYPE] === WEB
45
50
  }
46
51
 
47
52
  function endpointNameFromTags (tags) {
48
- return tags['resource.name'] || [
49
- tags['http.method'],
50
- tags['http.route']
53
+ return tags[RESOURCE_NAME] || [
54
+ tags[HTTP_METHOD],
55
+ tags[HTTP_ROUTE]
51
56
  ].filter(v => v).join(' ')
52
57
  }
53
58
 
@@ -81,6 +86,7 @@ class NativeWallProfiler {
81
86
  this._flushIntervalMillis = options.flushInterval || 60 * 1e3 // 60 seconds
82
87
  this._codeHotspotsEnabled = !!options.codeHotspotsEnabled
83
88
  this._endpointCollectionEnabled = !!options.endpointCollectionEnabled
89
+ this._v8ProfilerBugWorkaroundEnabled = !!options.v8ProfilerBugWorkaroundEnabled
84
90
  this._mapper = undefined
85
91
  this._pprof = undefined
86
92
 
@@ -99,7 +105,7 @@ class NativeWallProfiler {
99
105
 
100
106
  if (this._codeHotspotsEnabled && !this._emittedFFMessage && this._logger) {
101
107
  this._logger.debug(
102
- `Wall profiler: Enable config_trace_show_breakdown_profiling_for_node feature flag to see code hotspots.`)
108
+ `Wall profiler: Enable trace_show_breakdown_profiling_for_node feature flag to see code hotspots.`)
103
109
  this._emittedFFMessage = true
104
110
  }
105
111
 
@@ -120,7 +126,8 @@ class NativeWallProfiler {
120
126
  durationMillis: this._flushIntervalMillis,
121
127
  sourceMapper: this._mapper,
122
128
  withContexts: this._codeHotspotsEnabled,
123
- lineNumbers: false
129
+ lineNumbers: false,
130
+ workaroundV8Bug: this._v8ProfilerBugWorkaroundEnabled
124
131
  })
125
132
 
126
133
  if (this._codeHotspotsEnabled) {
@@ -163,6 +170,16 @@ class NativeWallProfiler {
163
170
  }
164
171
  }
165
172
 
173
+ _reportV8bug (maybeBug) {
174
+ const tag = `v8_profiler_bug_workaround_enabled:${this._v8ProfilerBugWorkaroundEnabled}`
175
+ const metric = `v8_cpu_profiler${maybeBug ? '_maybe' : ''}_stuck_event_loop`
176
+ this._logger?.warn(`Wall profiler: ${maybeBug ? 'possible ' : ''}v8 profiler stuck event loop detected.`)
177
+ // report as runtime metric (can be removed in the future when telemetry is mature)
178
+ runtimeMetrics.increment(`runtime.node.profiler.${metric}`, tag, true)
179
+ // report as telemetry metric
180
+ profilerTelemetryMetrics.count(metric, [tag]).inc()
181
+ }
182
+
166
183
  _stop (restart) {
167
184
  if (!this._started) return
168
185
  if (this._codeHotspotsEnabled) {
@@ -170,7 +187,14 @@ class NativeWallProfiler {
170
187
  this._enter()
171
188
  this._lastSampleCount = 0
172
189
  }
173
- return this._pprof.time.stop(restart, this._codeHotspotsEnabled ? generateLabels : undefined)
190
+ const profile = this._pprof.time.stop(restart, this._codeHotspotsEnabled ? generateLabels : undefined)
191
+ if (restart) {
192
+ const v8BugDetected = this._pprof.time.v8ProfilerStuckEventLoopDetected()
193
+ if (v8BugDetected !== 0) {
194
+ this._reportV8bug(v8BugDetected === 1)
195
+ }
196
+ }
197
+ return profile
174
198
  }
175
199
 
176
200
  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,15 @@ 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(config)
34
+
35
+ setInterval(() => {
36
+ this.dogstatsd.flush()
37
+ }, 10 * 1000).unref()
38
+ }
39
+
29
40
  if (config.remoteConfig.enabled && !config.isCiVisibility) {
30
41
  const rc = remoteConfig.enable(config)
31
42
 
@@ -58,7 +69,7 @@ class Tracer extends NoopProxy {
58
69
  }
59
70
 
60
71
  if (config.runtimeMetrics) {
61
- metrics.start(config)
72
+ runtimeMetrics.start(config)
62
73
  }
63
74
 
64
75
  if (config.tracing) {
@@ -117,8 +117,16 @@ function Hook (modules, options, onrequire) {
117
117
  // abort if _resolveLookupPaths return null
118
118
  return exports
119
119
  }
120
- const res = Module._findPath(name, [basedir, ...paths])
121
- if (res !== filename) {
120
+
121
+ let res
122
+ try {
123
+ res = Module._findPath(name, [basedir, ...paths])
124
+ } catch (e) {
125
+ // case where the file specified in package.json "main" doesn't exist
126
+ // in this case, the file is treated as module-internal
127
+ }
128
+
129
+ if (!res || res !== filename) {
122
130
  // this is a module-internal file
123
131
  // use the module-relative path to the file, prefixed by original module name
124
132
  name = name + path.sep + path.relative(basedir, filename)
@@ -2,7 +2,6 @@
2
2
 
3
3
  // TODO: capture every second and flush every 10 seconds
4
4
 
5
- const { URL, format } = require('url')
6
5
  const v8 = require('v8')
7
6
  const os = require('os')
8
7
  const { DogStatsDClient } = require('./dogstatsd')
@@ -27,21 +26,7 @@ reset()
27
26
 
28
27
  module.exports = {
29
28
  start (config) {
30
- const tags = []
31
-
32
- Object.keys(config.tags)
33
- .filter(key => typeof config.tags[key] === 'string')
34
- .filter(key => {
35
- // Skip runtime-id unless enabled as cardinality may be too high
36
- if (key !== 'runtime-id') return true
37
- return (config.experimental && config.experimental.runtimeId)
38
- })
39
- .forEach(key => {
40
- // https://docs.datadoghq.com/tagging/#defining-tags
41
- const value = config.tags[key].replace(/[^a-z0-9_:./-]/ig, '_')
42
-
43
- tags.push(`${key}:${value}`)
44
- })
29
+ const clientConfig = DogStatsDClient.generateClientConfig(config)
45
30
 
46
31
  try {
47
32
  nativeMetrics = require('@datadog/native-metrics')
@@ -51,22 +36,6 @@ module.exports = {
51
36
  nativeMetrics = null
52
37
  }
53
38
 
54
- const clientConfig = {
55
- host: config.dogstatsd.hostname,
56
- port: config.dogstatsd.port,
57
- tags
58
- }
59
-
60
- if (config.url) {
61
- clientConfig.metricsProxyUrl = config.url
62
- } else if (config.port) {
63
- clientConfig.metricsProxyUrl = new URL(format({
64
- protocol: 'http:',
65
- hostname: config.hostname || 'localhost',
66
- port: config.port
67
- }))
68
- }
69
-
70
39
  client = new DogStatsDClient(clientConfig)
71
40
 
72
41
  time = process.hrtime()
@@ -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
 
@@ -56,11 +57,20 @@ function appStarted () {
56
57
  return {
57
58
  integrations: getIntegrations(),
58
59
  dependencies: [],
59
- configuration: flatten(config),
60
+ configuration: flatten(formatConfig(config)),
60
61
  additional_payload: []
61
62
  }
62
63
  }
63
64
 
65
+ function formatConfig (config) {
66
+ // format peerServiceMapping from an object to a string map in order for
67
+ // telemetry intake to accept the configuration
68
+ config.peerServiceMapping = config.peerServiceMapping
69
+ ? Object.entries(config.peerServiceMapping).map(([key, value]) => `${key}:${value}`).join(',')
70
+ : ''
71
+ return config
72
+ }
73
+
64
74
  function onBeforeExit () {
65
75
  process.removeListener('beforeExit', onBeforeExit)
66
76
  sendData(config, application, host, 'app-closing')
@@ -110,6 +120,14 @@ function getTelemetryData () {
110
120
  return { config, application, host, heartbeatInterval }
111
121
  }
112
122
 
123
+ function heartbeat (config, application, host) {
124
+ heartbeatTimeout = setTimeout(() => {
125
+ sendData(config, application, host, 'app-heartbeat')
126
+ heartbeat(config, application, host)
127
+ }, heartbeatInterval).unref()
128
+ return heartbeatTimeout
129
+ }
130
+
113
131
  function start (aConfig, thePluginManager) {
114
132
  if (!aConfig.telemetry.enabled) {
115
133
  return
@@ -122,9 +140,9 @@ function start (aConfig, thePluginManager) {
122
140
 
123
141
  dependencies.start(config, application, host)
124
142
  sendData(config, application, host, 'app-started', appStarted())
143
+ heartbeat(config, application, host)
125
144
  interval = setInterval(() => {
126
145
  metricsManager.send(config, application, host)
127
- sendData(config, application, host, 'app-heartbeat')
128
146
  }, heartbeatInterval)
129
147
  interval.unref()
130
148
  process.on('beforeExit', onBeforeExit)
@@ -137,6 +155,7 @@ function stop () {
137
155
  return
138
156
  }
139
157
  clearInterval(interval)
158
+ clearTimeout(heartbeatTimeout)
140
159
  process.removeListener('beforeExit', onBeforeExit)
141
160
 
142
161
  telemetryStopChannel.publish(getTelemetryData())
@@ -66,7 +66,7 @@ function globMatch (pattern, subject) {
66
66
  function calculateDDBasePath (dirname) {
67
67
  const dirSteps = dirname.split(path.sep)
68
68
  const packagesIndex = dirSteps.lastIndexOf('packages')
69
- return dirSteps.slice(0, packagesIndex).join(path.sep) + path.sep
69
+ return dirSteps.slice(0, packagesIndex + 1).join(path.sep) + path.sep
70
70
  }
71
71
 
72
72
  module.exports = {