dd-trace 4.16.0 → 4.18.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 (71) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/index.d.ts +9 -1
  3. package/package.json +5 -4
  4. package/packages/datadog-instrumentations/src/body-parser.js +2 -1
  5. package/packages/datadog-instrumentations/src/cucumber.js +29 -4
  6. package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +45 -0
  7. package/packages/datadog-instrumentations/src/express.js +2 -1
  8. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
  9. package/packages/datadog-instrumentations/src/jest.js +58 -20
  10. package/packages/datadog-instrumentations/src/knex.js +69 -1
  11. package/packages/datadog-instrumentations/src/mocha.js +34 -4
  12. package/packages/datadog-instrumentations/src/mongodb.js +63 -0
  13. package/packages/datadog-instrumentations/src/mongoose.js +140 -1
  14. package/packages/datadog-instrumentations/src/next.js +98 -23
  15. package/packages/datadog-instrumentations/src/playwright.js +22 -8
  16. package/packages/datadog-plugin-cucumber/src/index.js +17 -5
  17. package/packages/datadog-plugin-cypress/src/plugin.js +38 -8
  18. package/packages/datadog-plugin-http/src/client.js +2 -0
  19. package/packages/datadog-plugin-jest/src/index.js +29 -6
  20. package/packages/datadog-plugin-jest/src/util.js +45 -2
  21. package/packages/datadog-plugin-memcached/src/index.js +10 -5
  22. package/packages/datadog-plugin-mocha/src/index.js +25 -6
  23. package/packages/datadog-plugin-next/src/index.js +4 -3
  24. package/packages/datadog-plugin-playwright/src/index.js +4 -1
  25. package/packages/dd-trace/src/appsec/channels.js +3 -1
  26. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +2 -0
  27. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-analyzer.js +60 -0
  28. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js +269 -0
  29. package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +5 -2
  30. package/packages/dd-trace/src/appsec/iast/analyzers/missing-header-analyzer.js +22 -4
  31. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +173 -0
  32. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +21 -1
  33. package/packages/dd-trace/src/appsec/iast/analyzers/unvalidated-redirect-analyzer.js +3 -3
  34. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +1 -2
  35. package/packages/dd-trace/src/appsec/iast/analyzers/xcontenttype-header-missing-analyzer.js +2 -2
  36. package/packages/dd-trace/src/appsec/iast/iast-log.js +9 -4
  37. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +4 -0
  38. package/packages/dd-trace/src/appsec/iast/path-line.js +6 -1
  39. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +25 -12
  40. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +4 -4
  41. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +13 -2
  42. package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks-generator.js +13 -0
  43. package/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +2 -1
  44. package/packages/dd-trace/src/appsec/iast/telemetry/index.js +1 -14
  45. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/json-sensitive-analyzer.js +16 -0
  46. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +22 -4
  47. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-regex.js +9 -0
  48. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +15 -2
  49. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +169 -0
  50. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +2 -0
  51. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +5 -1
  52. package/packages/dd-trace/src/appsec/index.js +31 -13
  53. package/packages/dd-trace/src/appsec/remote_config/manager.js +11 -3
  54. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +14 -1
  55. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +4 -2
  56. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -0
  57. package/packages/dd-trace/src/config.js +37 -13
  58. package/packages/dd-trace/src/format.js +3 -0
  59. package/packages/dd-trace/src/git_properties.js +16 -15
  60. package/packages/dd-trace/src/plugin_manager.js +3 -1
  61. package/packages/dd-trace/src/plugins/util/ci.js +17 -0
  62. package/packages/dd-trace/src/plugins/util/git.js +26 -4
  63. package/packages/dd-trace/src/plugins/util/test.js +45 -2
  64. package/packages/dd-trace/src/profiling/config.js +20 -3
  65. package/packages/dd-trace/src/profiling/profilers/wall.js +51 -40
  66. package/packages/dd-trace/src/service-naming/extra-services.js +24 -0
  67. package/packages/dd-trace/src/telemetry/index.js +4 -0
  68. package/packages/dd-trace/src/telemetry/logs/index.js +65 -0
  69. package/packages/dd-trace/src/{appsec/iast/telemetry/log → telemetry/logs}/log-collector.js +9 -22
  70. package/packages/dd-trace/src/telemetry/metrics.js +0 -5
  71. package/packages/dd-trace/src/appsec/iast/telemetry/log/index.js +0 -87
@@ -602,6 +602,23 @@ module.exports = {
602
602
  tags[refKey] = ref
603
603
  }
604
604
 
605
+ if (env.CODEBUILD_INITIATOR?.startsWith('codepipeline/')) {
606
+ const {
607
+ CODEBUILD_BUILD_ARN,
608
+ DD_ACTION_EXECUTION_ID,
609
+ DD_PIPELINE_EXECUTION_ID
610
+ } = env
611
+ tags = {
612
+ [CI_PROVIDER_NAME]: 'awscodepipeline',
613
+ [CI_PIPELINE_ID]: DD_PIPELINE_EXECUTION_ID,
614
+ [CI_ENV_VARS]: JSON.stringify({
615
+ CODEBUILD_BUILD_ARN,
616
+ DD_PIPELINE_EXECUTION_ID,
617
+ DD_ACTION_EXECUTION_ID
618
+ })
619
+ }
620
+ }
621
+
605
622
  normalizeTag(tags, CI_WORKSPACE_PATH, resolveTilde)
606
623
  normalizeTag(tags, GIT_REPOSITORY_URL, filterSensitiveInfoFromRepository)
607
624
  normalizeTag(tags, GIT_BRANCH, normalizeRef)
@@ -61,15 +61,37 @@ function unshallowRepository () {
61
61
  }
62
62
  const defaultRemoteName = sanitizedExec('git', ['config', '--default', 'origin', '--get', 'clone.defaultRemoteName'])
63
63
  const revParseHead = sanitizedExec('git', ['rev-parse', 'HEAD'])
64
- sanitizedExec('git', [
64
+
65
+ const baseGitOptions = [
65
66
  'fetch',
66
67
  '--shallow-since="1 month ago"',
67
68
  '--update-shallow',
68
69
  '--filter=blob:none',
69
70
  '--recurse-submodules=no',
70
- defaultRemoteName,
71
- revParseHead
72
- ])
71
+ defaultRemoteName
72
+ ]
73
+
74
+ try {
75
+ execFileSync('git', [
76
+ ...baseGitOptions,
77
+ revParseHead
78
+ ], { stdio: 'pipe' })
79
+ } catch (e) {
80
+ // If the local HEAD is a commit that has not been pushed to the remote, the above command will fail.
81
+ log.error(e)
82
+ const upstreamRemote = sanitizedExec('git', ['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{upstream}'])
83
+ try {
84
+ execFileSync('git', [
85
+ ...baseGitOptions,
86
+ upstreamRemote
87
+ ], { stdio: 'pipe' })
88
+ } catch (e) {
89
+ // If the CI is working on a detached HEAD or branch tracking hasn’t been set up, the above command will fail.
90
+ log.error(e)
91
+ // We use sanitizedExec here because if this last option fails, we'll give up.
92
+ sanitizedExec('git', baseGitOptions)
93
+ }
94
+ }
73
95
  }
74
96
 
75
97
  function getRepositoryUrl () {
@@ -58,6 +58,8 @@ const TEST_ITR_SKIPPING_ENABLED = 'test.itr.tests_skipping.enabled'
58
58
  const TEST_ITR_SKIPPING_TYPE = 'test.itr.tests_skipping.type'
59
59
  const TEST_ITR_SKIPPING_COUNT = 'test.itr.tests_skipping.count'
60
60
  const TEST_CODE_COVERAGE_ENABLED = 'test.code_coverage.enabled'
61
+ const TEST_ITR_UNSKIPPABLE = 'test.itr.unskippable'
62
+ const TEST_ITR_FORCED_RUN = 'test.itr.forced_run'
61
63
 
62
64
  const TEST_CODE_COVERAGE_LINES_PCT = 'test.code_coverage.lines_pct'
63
65
 
@@ -107,6 +109,8 @@ module.exports = {
107
109
  TEST_ITR_SKIPPING_COUNT,
108
110
  TEST_CODE_COVERAGE_ENABLED,
109
111
  TEST_CODE_COVERAGE_LINES_PCT,
112
+ TEST_ITR_UNSKIPPABLE,
113
+ TEST_ITR_FORCED_RUN,
110
114
  addIntelligentTestRunnerSpanTags,
111
115
  getCoveredFilenamesFromCoverage,
112
116
  resetCoverage,
@@ -114,7 +118,8 @@ module.exports = {
114
118
  fromCoverageMapToCoverage,
115
119
  getTestLineStart,
116
120
  getCallSites,
117
- removeInvalidMetadata
121
+ removeInvalidMetadata,
122
+ parseAnnotations
118
123
  }
119
124
 
120
125
  // Returns pkg manager and its version, separated by '-', e.g. npm-8.15.0 or yarn-1.22.19
@@ -366,7 +371,9 @@ function addIntelligentTestRunnerSpanTags (
366
371
  isCodeCoverageEnabled,
367
372
  testCodeCoverageLinesTotal,
368
373
  skippingCount,
369
- skippingType = 'suite'
374
+ skippingType = 'suite',
375
+ hasUnskippableSuites,
376
+ hasForcedToRunSuites
370
377
  }
371
378
  ) {
372
379
  testSessionSpan.setTag(TEST_ITR_TESTS_SKIPPED, isSuitesSkipped ? 'true' : 'false')
@@ -381,6 +388,15 @@ function addIntelligentTestRunnerSpanTags (
381
388
  testModuleSpan.setTag(TEST_ITR_SKIPPING_COUNT, skippingCount)
382
389
  testModuleSpan.setTag(TEST_CODE_COVERAGE_ENABLED, isCodeCoverageEnabled ? 'true' : 'false')
383
390
 
391
+ if (hasUnskippableSuites) {
392
+ testSessionSpan.setTag(TEST_ITR_UNSKIPPABLE, 'true')
393
+ testModuleSpan.setTag(TEST_ITR_UNSKIPPABLE, 'true')
394
+ }
395
+ if (hasForcedToRunSuites) {
396
+ testSessionSpan.setTag(TEST_ITR_FORCED_RUN, 'true')
397
+ testModuleSpan.setTag(TEST_ITR_FORCED_RUN, 'true')
398
+ }
399
+
384
400
  // If suites have been skipped we don't want to report the total coverage, as it will be wrong
385
401
  if (testCodeCoverageLinesTotal !== undefined && !isSuitesSkipped) {
386
402
  testSessionSpan.setTag(TEST_CODE_COVERAGE_LINES_PCT, testCodeCoverageLinesTotal)
@@ -477,3 +493,30 @@ function getCallSites () {
477
493
 
478
494
  return v8StackTrace
479
495
  }
496
+
497
+ /**
498
+ * Gets an object of test tags from an Playwright annotations array.
499
+ * @param {Object[]} annotations - Annotations from a Playwright test.
500
+ * @param {string} annotations[].type - Type of annotation. A string of the shape DD_TAGS[$tag_name].
501
+ * @param {string} annotations[].description - Value of the tag.
502
+ */
503
+ function parseAnnotations (annotations) {
504
+ return annotations.reduce((tags, annotation) => {
505
+ if (!annotation?.type) {
506
+ return tags
507
+ }
508
+ const { type, description } = annotation
509
+ if (type.startsWith('DD_TAGS')) {
510
+ const regex = /\[(.*?)\]/
511
+ const match = regex.exec(type)
512
+ let tagValue = ''
513
+ if (match) {
514
+ tagValue = match[1]
515
+ }
516
+ if (tagValue) {
517
+ tags[tagValue] = description
518
+ }
519
+ }
520
+ return tags
521
+ }, {})
522
+ }
@@ -37,6 +37,8 @@ class Config {
37
37
  DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE,
38
38
  DD_PROFILING_EXPERIMENTAL_OOM_MAX_HEAP_EXTENSION_COUNT,
39
39
  DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES,
40
+ DD_PROFILING_CODEHOTSPOTS_ENABLED,
41
+ DD_PROFILING_ENDPOINT_COLLECTION_ENABLED,
40
42
  DD_PROFILING_EXPERIMENTAL_CODEHOTSPOTS_ENABLED,
41
43
  DD_PROFILING_EXPERIMENTAL_ENDPOINT_COLLECTION_ENABLED
42
44
  } = process.env
@@ -53,8 +55,6 @@ class Config {
53
55
  Number(DD_PROFILING_UPLOAD_TIMEOUT), 60 * 1000)
54
56
  const sourceMap = coalesce(options.sourceMap,
55
57
  DD_PROFILING_SOURCE_MAP, true)
56
- const endpointCollectionEnabled = coalesce(options.endpointCollection,
57
- DD_PROFILING_EXPERIMENTAL_ENDPOINT_COLLECTION_ENABLED, false)
58
58
  const pprofPrefix = coalesce(options.pprofPrefix,
59
59
  DD_PROFILING_PPROF_PREFIX, '')
60
60
 
@@ -71,11 +71,25 @@ class Config {
71
71
  tagger.parse({ env, host, service, version, functionname })
72
72
  )
73
73
  this.logger = ensureLogger(options.logger)
74
+ const logger = this.logger
75
+ function logExperimentalVarDeprecation (shortVarName) {
76
+ const deprecatedEnvVarName = `DD_PROFILING_EXPERIMENTAL_${shortVarName}`
77
+ const v = process.env[deprecatedEnvVarName]
78
+ // not null, undefined, or NaN -- same logic as koalas.hasValue
79
+ // eslint-disable-next-line no-self-compare
80
+ if (v != null && v === v) {
81
+ logger.warn(`${deprecatedEnvVarName} is deprecated. Use DD_PROFILING_${shortVarName} instead.`)
82
+ }
83
+ }
74
84
  this.flushInterval = flushInterval
75
85
  this.uploadTimeout = uploadTimeout
76
86
  this.sourceMap = sourceMap
77
87
  this.debugSourceMaps = isTrue(coalesce(options.debugSourceMaps, DD_PROFILING_DEBUG_SOURCE_MAPS, false))
78
- this.endpointCollectionEnabled = endpointCollectionEnabled
88
+ this.endpointCollectionEnabled = isTrue(coalesce(options.endpointCollection,
89
+ DD_PROFILING_ENDPOINT_COLLECTION_ENABLED,
90
+ DD_PROFILING_EXPERIMENTAL_ENDPOINT_COLLECTION_ENABLED, false))
91
+ logExperimentalVarDeprecation('ENDPOINT_COLLECTION_ENABLED')
92
+
79
93
  this.pprofPrefix = pprofPrefix
80
94
  this.v8ProfilerBugWorkaroundEnabled = isTrue(coalesce(options.v8ProfilerBugWorkaround,
81
95
  DD_PROFILING_V8_PROFILER_BUG_WORKAROUND, true))
@@ -113,8 +127,11 @@ class Config {
113
127
  const profilers = options.profilers
114
128
  ? options.profilers
115
129
  : getProfilers({ DD_PROFILING_HEAP_ENABLED, DD_PROFILING_WALLTIME_ENABLED, DD_PROFILING_PROFILERS })
130
+
116
131
  this.codeHotspotsEnabled = isTrue(coalesce(options.codeHotspotsEnabled,
132
+ DD_PROFILING_CODEHOTSPOTS_ENABLED,
117
133
  DD_PROFILING_EXPERIMENTAL_CODEHOTSPOTS_ENABLED, false))
134
+ logExperimentalVarDeprecation('CODEHOTSPOTS_ENABLED')
118
135
 
119
136
  this.profilers = ensureProfilers(profilers, this)
120
137
  }
@@ -12,6 +12,12 @@ const beforeCh = dc.channel('dd-trace:storage:before')
12
12
  const enterCh = dc.channel('dd-trace:storage:enter')
13
13
  const profilerTelemetryMetrics = telemetryMetrics.manager.namespace('profilers')
14
14
 
15
+ const threadName = (function () {
16
+ const { isMainThread, threadId } = require('node:worker_threads')
17
+ const name = isMainThread ? 'Main' : `Worker #${threadId}`
18
+ return `${name} Event Loop`
19
+ })()
20
+
15
21
  let kSampleCount
16
22
 
17
23
  function getActiveSpan () {
@@ -23,8 +29,8 @@ function getStartedSpans (context) {
23
29
  return context._trace.started
24
30
  }
25
31
 
26
- function generateLabels ({ spanId, rootSpanId, webTags, endpoint }) {
27
- const labels = {}
32
+ function generateLabels ({ context: { spanId, rootSpanId, webTags, endpoint }, timestamp }) {
33
+ const labels = { 'thread name': threadName }
28
34
  if (spanId) {
29
35
  labels['span id'] = spanId
30
36
  }
@@ -37,6 +43,8 @@ function generateLabels ({ spanId, rootSpanId, webTags, endpoint }) {
37
43
  // fallback to endpoint computed when sample was taken
38
44
  labels['trace endpoint'] = endpoint
39
45
  }
46
+ // Incoming timestamps are in microseconds, we emit nanos.
47
+ labels['end_timestamp_ns'] = timestamp * 1000n
40
48
 
41
49
  return labels
42
50
  }
@@ -56,29 +64,6 @@ function endpointNameFromTags (tags) {
56
64
  ].filter(v => v).join(' ')
57
65
  }
58
66
 
59
- function updateContext (context, span, startedSpans, endpointCollectionEnabled) {
60
- context.spanId = span.context().toSpanId()
61
- const rootSpan = startedSpans[0]
62
- if (rootSpan) {
63
- context.rootSpanId = rootSpan.context().toSpanId()
64
- if (endpointCollectionEnabled) {
65
- // Find the first webspan starting from the end:
66
- // There might be several webspans, for example with next.js, http plugin creates a first span
67
- // and then next.js plugin creates a child span, and this child span haves the correct endpoint information.
68
- for (let i = startedSpans.length - 1; i >= 0; i--) {
69
- const tags = getSpanContextTags(startedSpans[i])
70
- if (isWebServerSpan(tags)) {
71
- context.webTags = tags
72
- // endpoint may not be determined yet, but keep it as fallback
73
- // if tags are not available anymore during serialization
74
- context.endpoint = endpointNameFromTags(tags)
75
- break
76
- }
77
- }
78
- }
79
- }
80
- }
81
-
82
67
  class NativeWallProfiler {
83
68
  constructor (options = {}) {
84
69
  this.type = 'wall'
@@ -86,6 +71,7 @@ class NativeWallProfiler {
86
71
  this._flushIntervalMillis = options.flushInterval || 60 * 1e3 // 60 seconds
87
72
  this._codeHotspotsEnabled = !!options.codeHotspotsEnabled
88
73
  this._endpointCollectionEnabled = !!options.endpointCollectionEnabled
74
+ this._withContexts = this._codeHotspotsEnabled || this._endpointCollectionEnabled
89
75
  this._v8ProfilerBugWorkaroundEnabled = !!options.v8ProfilerBugWorkaroundEnabled
90
76
  this._mapper = undefined
91
77
  this._pprof = undefined
@@ -100,15 +86,13 @@ class NativeWallProfiler {
100
86
  return this._codeHotspotsEnabled
101
87
  }
102
88
 
89
+ endpointCollectionEnabled () {
90
+ return this._endpointCollectionEnabled
91
+ }
92
+
103
93
  start ({ mapper } = {}) {
104
94
  if (this._started) return
105
95
 
106
- if (this._codeHotspotsEnabled && !this._emittedFFMessage && this._logger) {
107
- this._logger.debug(
108
- `Wall profiler: Enable trace_show_breakdown_profiling_for_node feature flag to see code hotspots.`)
109
- this._emittedFFMessage = true
110
- }
111
-
112
96
  this._mapper = mapper
113
97
  this._pprof = require('@datadog/pprof')
114
98
  kSampleCount = this._pprof.time.constants.kSampleCount
@@ -125,12 +109,12 @@ class NativeWallProfiler {
125
109
  intervalMicros: this._samplingIntervalMicros,
126
110
  durationMillis: this._flushIntervalMillis,
127
111
  sourceMapper: this._mapper,
128
- withContexts: this._codeHotspotsEnabled,
112
+ withContexts: this._withContexts,
129
113
  lineNumbers: false,
130
114
  workaroundV8Bug: this._v8ProfilerBugWorkaroundEnabled
131
115
  })
132
116
 
133
- if (this._codeHotspotsEnabled) {
117
+ if (this._withContexts) {
134
118
  this._profilerState = this._pprof.time.getState()
135
119
  this._currentContext = {}
136
120
  this._pprof.time.setContext(this._currentContext)
@@ -155,9 +139,7 @@ class NativeWallProfiler {
155
139
  this._currentContext = {}
156
140
  this._pprof.time.setContext(this._currentContext)
157
141
 
158
- if (this._lastSpan) {
159
- updateContext(context, this._lastSpan, this._lastStartedSpans, this._endpointCollectionEnabled)
160
- }
142
+ this._updateContext(context)
161
143
  }
162
144
 
163
145
  const span = getActiveSpan()
@@ -170,6 +152,35 @@ class NativeWallProfiler {
170
152
  }
171
153
  }
172
154
 
155
+ _updateContext (context) {
156
+ if (!this._lastSpan) {
157
+ return
158
+ }
159
+ if (this._codeHotspotsEnabled) {
160
+ context.spanId = this._lastSpan.context().toSpanId()
161
+ const rootSpan = this._lastStartedSpans[0]
162
+ if (rootSpan) {
163
+ context.rootSpanId = rootSpan.context().toSpanId()
164
+ }
165
+ }
166
+ if (this._endpointCollectionEnabled) {
167
+ const startedSpans = this._lastStartedSpans
168
+ // Find the first webspan starting from the end:
169
+ // There might be several webspans, for example with next.js, http plugin creates a first span
170
+ // and then next.js plugin creates a child span, and this child span haves the correct endpoint information.
171
+ for (let i = startedSpans.length - 1; i >= 0; i--) {
172
+ const tags = getSpanContextTags(startedSpans[i])
173
+ if (isWebServerSpan(tags)) {
174
+ context.webTags = tags
175
+ // endpoint may not be determined yet, but keep it as fallback
176
+ // if tags are not available anymore during serialization
177
+ context.endpoint = endpointNameFromTags(tags)
178
+ break
179
+ }
180
+ }
181
+ }
182
+ }
183
+
173
184
  _reportV8bug (maybeBug) {
174
185
  const tag = `v8_profiler_bug_workaround_enabled:${this._v8ProfilerBugWorkaroundEnabled}`
175
186
  const metric = `v8_cpu_profiler${maybeBug ? '_maybe' : ''}_stuck_event_loop`
@@ -182,12 +193,12 @@ class NativeWallProfiler {
182
193
 
183
194
  _stop (restart) {
184
195
  if (!this._started) return
185
- if (this._codeHotspotsEnabled) {
196
+ if (this._withContexts) {
186
197
  // update last sample context if needed
187
198
  this._enter()
188
199
  this._lastSampleCount = 0
189
200
  }
190
- const profile = this._pprof.time.stop(restart, this._codeHotspotsEnabled ? generateLabels : undefined)
201
+ const profile = this._pprof.time.stop(restart, this._withContexts ? generateLabels : undefined)
191
202
  if (restart) {
192
203
  const v8BugDetected = this._pprof.time.v8ProfilerStuckEventLoopDetected()
193
204
  if (v8BugDetected !== 0) {
@@ -209,9 +220,9 @@ class NativeWallProfiler {
209
220
  if (!this._started) return
210
221
 
211
222
  const profile = this._stop(false)
212
- if (this._codeHotspotsEnabled) {
223
+ if (this._withContexts) {
213
224
  beforeCh.unsubscribe(this._enter)
214
- enterCh.subscribe(this._enter)
225
+ enterCh.unsubscribe(this._enter)
215
226
  this._profilerState = undefined
216
227
  }
217
228
 
@@ -0,0 +1,24 @@
1
+ 'use strict'
2
+
3
+ const maxExtraServices = 64
4
+ const extraServices = new Set()
5
+
6
+ function getExtraServices () {
7
+ return [...extraServices]
8
+ }
9
+
10
+ function registerExtraService (serviceName) {
11
+ if (serviceName && extraServices.size < maxExtraServices) {
12
+ extraServices.add(serviceName)
13
+ }
14
+ }
15
+
16
+ function clear () {
17
+ extraServices.clear()
18
+ }
19
+
20
+ module.exports = {
21
+ registerExtraService,
22
+ getExtraServices,
23
+ clear
24
+ }
@@ -7,6 +7,7 @@ const dependencies = require('./dependencies')
7
7
  const { sendData } = require('./send-data')
8
8
 
9
9
  const { manager: metricsManager } = require('./metrics')
10
+ const logs = require('./logs')
10
11
 
11
12
  const telemetryStartChannel = dc.channel('datadog:telemetry:start')
12
13
  const telemetryStopChannel = dc.channel('datadog:telemetry:stop')
@@ -139,10 +140,13 @@ function start (aConfig, thePluginManager) {
139
140
  heartbeatInterval = config.telemetry.heartbeatInterval
140
141
 
141
142
  dependencies.start(config, application, host)
143
+ logs.start(config)
144
+
142
145
  sendData(config, application, host, 'app-started', appStarted())
143
146
  heartbeat(config, application, host)
144
147
  interval = setInterval(() => {
145
148
  metricsManager.send(config, application, host)
149
+ logs.send(config, application, host)
146
150
  }, heartbeatInterval)
147
151
  interval.unref()
148
152
  process.on('beforeExit', onBeforeExit)
@@ -0,0 +1,65 @@
1
+ 'use strict'
2
+
3
+ const dc = require('../../../../diagnostics_channel')
4
+ const logCollector = require('./log-collector')
5
+ const { sendData } = require('../send-data')
6
+
7
+ const telemetryLog = dc.channel('datadog:telemetry:log')
8
+
9
+ let enabled = false
10
+
11
+ /**
12
+ * Telemetry logs api defines only ERROR, WARN and DEBUG levels:
13
+ * - WARN level is enabled by default
14
+ * - DEBUG level will be possible to activate with an env var or telemetry config property
15
+ */
16
+ function isLevelEnabled (level) {
17
+ return isValidLevel(level)
18
+ }
19
+
20
+ function isValidLevel (level) {
21
+ switch (level) {
22
+ case 'ERROR':
23
+ case 'WARN':
24
+ return true
25
+ default:
26
+ return false
27
+ }
28
+ }
29
+
30
+ function onLog (log) {
31
+ if (isLevelEnabled(log?.level?.toUpperCase())) {
32
+ logCollector.add(log)
33
+ }
34
+ }
35
+
36
+ function start (config) {
37
+ if (!config.telemetry.logCollection || enabled) return
38
+
39
+ enabled = true
40
+
41
+ telemetryLog.subscribe(onLog)
42
+ }
43
+
44
+ function stop () {
45
+ enabled = false
46
+
47
+ if (telemetryLog.hasSubscribers) {
48
+ telemetryLog.unsubscribe(onLog)
49
+ }
50
+ }
51
+
52
+ function send (config, application, host) {
53
+ if (!enabled) return
54
+
55
+ const logs = logCollector.drain()
56
+ if (logs) {
57
+ sendData(config, application, host, 'logs', logs)
58
+ }
59
+ }
60
+
61
+ module.exports = {
62
+ start,
63
+ stop,
64
+ send
65
+ }
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const log = require('../../../../log')
3
+ const log = require('../../log')
4
4
 
5
5
  const logs = new Map()
6
6
 
@@ -18,37 +18,21 @@ function hashCode (hashSource) {
18
18
  }
19
19
 
20
20
  function createHash (logEntry) {
21
- if (!logEntry) return 0
22
-
23
21
  const prime = 31
24
22
  let result = ((!logEntry.level) ? 0 : hashCode(logEntry.level))
25
23
  result = (((prime * result) | 0) + ((!logEntry.message) ? 0 : hashCode(logEntry.message))) | 0
26
-
27
- // NOTE: tags are not used at the moment
28
- // result = (((prime * result) | 0) + ((!logEntry.tags) ? 0 : hashCode(logEntry.tags))) | 0
29
24
  result = (((prime * result) | 0) + ((!logEntry.stack_trace) ? 0 : hashCode(logEntry.stack_trace))) | 0
30
25
  return result
31
26
  }
32
27
 
33
- function newLogEntry (message, level, tags) {
34
- return {
35
- message,
36
- level,
37
- tags
38
- }
39
- }
40
-
41
28
  function isValid (logEntry) {
42
- return logEntry && logEntry.level && logEntry.message
29
+ return logEntry?.level && logEntry.message
43
30
  }
44
31
 
45
32
  const logCollector = {
46
33
  add (logEntry) {
47
34
  try {
48
- if (!isValid(logEntry)) {
49
- log.info('IAST log collector discarding invalid log')
50
- return
51
- }
35
+ if (!isValid(logEntry)) return false
52
36
 
53
37
  // NOTE: should errors have higher priority? and discard log entries with lower priority?
54
38
  if (logs.size >= maxEntries) {
@@ -70,11 +54,13 @@ const logCollector = {
70
54
  drain () {
71
55
  if (logs.size === 0) return
72
56
 
73
- const drained = []
74
- drained.push(...logs.values())
57
+ const drained = [...logs.values()]
75
58
 
76
59
  if (overflowedCount > 0) {
77
- drained.push(newLogEntry(`Omitted ${overflowedCount} entries due to overflowing`, 'ERROR'))
60
+ drained.push({
61
+ message: `Omitted ${overflowedCount} entries due to overflowing`,
62
+ level: 'ERROR'
63
+ })
78
64
  }
79
65
 
80
66
  this.reset()
@@ -85,6 +71,7 @@ const logCollector = {
85
71
  reset (max) {
86
72
  logs.clear()
87
73
  overflowedCount = 0
74
+
88
75
  if (max) {
89
76
  maxEntries = max
90
77
  }
@@ -1,7 +1,5 @@
1
1
  'use strict'
2
2
 
3
- const { version } = require('../../../../package.json')
4
-
5
3
  const { sendData } = require('./send-data')
6
4
 
7
5
  function getId (type, namespace, name, tags) {
@@ -35,10 +33,7 @@ class Metric {
35
33
  this.metric = common ? metric : `nodejs.${metric}`
36
34
  this.tags = tagArray(tags)
37
35
  if (common) {
38
- this.tags.push('lib_language:nodejs')
39
36
  this.tags.push(`version:${process.version}`)
40
- } else {
41
- this.tags.push(`lib_version:${version}`)
42
37
  }
43
38
  this.common = common
44
39
 
@@ -1,87 +0,0 @@
1
- 'use strict'
2
-
3
- const dc = require('../../../../../../diagnostics_channel')
4
- const logCollector = require('./log-collector')
5
- const { sendData } = require('../../../../telemetry/send-data')
6
- const log = require('../../../../log')
7
-
8
- const telemetryStartChannel = dc.channel('datadog:telemetry:start')
9
- const telemetryStopChannel = dc.channel('datadog:telemetry:stop')
10
-
11
- let config, application, host, interval
12
-
13
- function publish (log) {
14
- if (log && isLevelEnabled(log.level)) {
15
- logCollector.add(log)
16
- }
17
- }
18
-
19
- function sendLogs () {
20
- try {
21
- const logs = logCollector.drain()
22
- if (logs) {
23
- sendData(config, application, host, 'logs', logs)
24
- }
25
- } catch (e) {
26
- log.error(e)
27
- }
28
- }
29
-
30
- function isLevelEnabled (level) {
31
- return isLogCollectionEnabled(config) && level !== 'DEBUG'
32
- }
33
-
34
- function isLogCollectionEnabled (config) {
35
- return config && config.telemetry && config.telemetry.logCollection
36
- }
37
-
38
- function onTelemetryStart (msg) {
39
- if (!msg || !isLogCollectionEnabled(msg.config)) {
40
- log.info('IAST telemetry logs start event received but log collection is not enabled or configuration is incorrect')
41
- return false
42
- }
43
-
44
- log.info('IAST telemetry logs starting')
45
-
46
- config = msg.config
47
- application = msg.application
48
- host = msg.host
49
-
50
- if (msg.heartbeatInterval) {
51
- interval = setInterval(sendLogs, msg.heartbeatInterval)
52
- interval.unref()
53
- }
54
-
55
- return true
56
- }
57
-
58
- function onTelemetryStop () {
59
- stop()
60
- }
61
-
62
- function start () {
63
- telemetryStartChannel.subscribe(onTelemetryStart)
64
- telemetryStopChannel.subscribe(onTelemetryStop)
65
- }
66
-
67
- function stop () {
68
- if (!isLogCollectionEnabled(config)) return
69
-
70
- log.info('IAST telemetry logs stopping')
71
-
72
- config = null
73
- application = null
74
- host = null
75
-
76
- if (telemetryStartChannel.hasSubscribers) {
77
- telemetryStartChannel.unsubscribe(onTelemetryStart)
78
- }
79
-
80
- if (telemetryStopChannel.hasSubscribers) {
81
- telemetryStopChannel.unsubscribe(onTelemetryStop)
82
- }
83
-
84
- clearInterval(interval)
85
- }
86
-
87
- module.exports = { start, stop, publish, isLevelEnabled }