dd-trace 4.46.0 → 4.47.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 (89) hide show
  1. package/LICENSE-3rdparty.csv +2 -0
  2. package/index.d.ts +20 -8
  3. package/package.json +9 -3
  4. package/packages/datadog-instrumentations/src/cucumber.js +290 -53
  5. package/packages/datadog-instrumentations/src/jest.js +3 -1
  6. package/packages/datadog-instrumentations/src/kafkajs.js +67 -31
  7. package/packages/datadog-instrumentations/src/microgateway-core.js +3 -1
  8. package/packages/datadog-instrumentations/src/mocha/main.js +139 -54
  9. package/packages/datadog-instrumentations/src/mocha/utils.js +35 -15
  10. package/packages/datadog-instrumentations/src/mocha/worker.js +29 -1
  11. package/packages/datadog-instrumentations/src/openai.js +4 -2
  12. package/packages/datadog-instrumentations/src/pg.js +59 -4
  13. package/packages/datadog-instrumentations/src/vitest.js +184 -9
  14. package/packages/datadog-plugin-amqplib/src/consumer.js +1 -3
  15. package/packages/datadog-plugin-aws-sdk/src/base.js +33 -0
  16. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -1
  17. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +2 -0
  18. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -1
  19. package/packages/datadog-plugin-cucumber/src/index.js +24 -1
  20. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +36 -6
  21. package/packages/datadog-plugin-cypress/src/support.js +4 -1
  22. package/packages/datadog-plugin-http/src/client.js +1 -42
  23. package/packages/datadog-plugin-http2/src/client.js +1 -26
  24. package/packages/datadog-plugin-jest/src/index.js +17 -1
  25. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +20 -0
  26. package/packages/datadog-plugin-kafkajs/src/consumer.js +1 -2
  27. package/packages/datadog-plugin-kafkajs/src/index.js +3 -1
  28. package/packages/datadog-plugin-mocha/src/index.js +18 -0
  29. package/packages/datadog-plugin-openai/src/index.js +27 -18
  30. package/packages/datadog-plugin-playwright/src/index.js +9 -0
  31. package/packages/datadog-plugin-rhea/src/consumer.js +1 -3
  32. package/packages/datadog-plugin-vitest/src/index.js +68 -3
  33. package/packages/dd-trace/src/appsec/addresses.js +3 -1
  34. package/packages/dd-trace/src/appsec/channels.js +4 -2
  35. package/packages/dd-trace/src/appsec/rasp/index.js +103 -0
  36. package/packages/dd-trace/src/appsec/rasp/sql_injection.js +86 -0
  37. package/packages/dd-trace/src/appsec/rasp/ssrf.js +37 -0
  38. package/packages/dd-trace/src/appsec/rasp/utils.js +63 -0
  39. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -0
  40. package/packages/dd-trace/src/appsec/remote_config/index.js +16 -7
  41. package/packages/dd-trace/src/appsec/remote_config/manager.js +89 -51
  42. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +33 -14
  43. package/packages/dd-trace/src/appsec/waf/waf_manager.js +2 -1
  44. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +4 -0
  45. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +13 -0
  46. package/packages/dd-trace/src/config.js +61 -10
  47. package/packages/dd-trace/src/constants.js +11 -1
  48. package/packages/dd-trace/src/data_streams_context.js +3 -0
  49. package/packages/dd-trace/src/datastreams/fnv.js +23 -0
  50. package/packages/dd-trace/src/datastreams/pathway.js +12 -5
  51. package/packages/dd-trace/src/datastreams/processor.js +35 -0
  52. package/packages/dd-trace/src/datastreams/schemas/schema.js +8 -0
  53. package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +125 -0
  54. package/packages/dd-trace/src/datastreams/schemas/schema_sampler.js +29 -0
  55. package/packages/dd-trace/src/debugger/devtools_client/config.js +24 -0
  56. package/packages/dd-trace/src/debugger/devtools_client/index.js +57 -0
  57. package/packages/dd-trace/src/debugger/devtools_client/inspector_promises_polyfill.js +23 -0
  58. package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +164 -0
  59. package/packages/dd-trace/src/debugger/devtools_client/send.js +28 -0
  60. package/packages/dd-trace/src/debugger/devtools_client/session.js +7 -0
  61. package/packages/dd-trace/src/debugger/devtools_client/state.js +47 -0
  62. package/packages/dd-trace/src/debugger/devtools_client/status.js +109 -0
  63. package/packages/dd-trace/src/debugger/index.js +92 -0
  64. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +29 -2
  65. package/packages/dd-trace/src/exporters/common/request.js +1 -1
  66. package/packages/dd-trace/src/payload-tagging/config/aws.json +30 -0
  67. package/packages/dd-trace/src/payload-tagging/config/index.js +30 -0
  68. package/packages/dd-trace/src/payload-tagging/index.js +93 -0
  69. package/packages/dd-trace/src/payload-tagging/tagging.js +83 -0
  70. package/packages/dd-trace/src/plugin_manager.js +11 -10
  71. package/packages/dd-trace/src/plugins/ci_plugin.js +33 -8
  72. package/packages/dd-trace/src/plugins/util/env.js +5 -2
  73. package/packages/dd-trace/src/plugins/util/test.js +26 -2
  74. package/packages/dd-trace/src/profiling/config.js +5 -0
  75. package/packages/dd-trace/src/profiling/exporters/agent.js +1 -1
  76. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns.js +13 -0
  77. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_lookup.js +16 -0
  78. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_lookupservice.js +16 -0
  79. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_resolve.js +24 -0
  80. package/packages/dd-trace/src/profiling/profilers/event_plugins/dns_reverse.js +16 -0
  81. package/packages/dd-trace/src/profiling/profilers/event_plugins/event.js +48 -0
  82. package/packages/dd-trace/src/profiling/profilers/event_plugins/net.js +24 -0
  83. package/packages/dd-trace/src/profiling/profilers/events.js +108 -32
  84. package/packages/dd-trace/src/profiling/profilers/shared.js +5 -0
  85. package/packages/dd-trace/src/profiling/profilers/wall.js +9 -3
  86. package/packages/dd-trace/src/profiling/ssi-heuristics.js +10 -2
  87. package/packages/dd-trace/src/proxy.js +10 -3
  88. package/packages/dd-trace/src/span_stats.js +4 -2
  89. package/packages/dd-trace/src/appsec/rasp.js +0 -176
@@ -3,7 +3,7 @@
3
3
  const fs = require('fs')
4
4
  const os = require('os')
5
5
  const uuid = require('crypto-randomuuid') // we need to keep the old uuid dep because of cypress
6
- const URL = require('url').URL
6
+ const { URL } = require('url')
7
7
  const log = require('./log')
8
8
  const pkg = require('./pkg')
9
9
  const coalesce = require('koalas')
@@ -18,6 +18,7 @@ const { updateConfig } = require('./telemetry')
18
18
  const telemetryMetrics = require('./telemetry/metrics')
19
19
  const { getIsGCPFunction, getIsAzureFunction } = require('./serverless')
20
20
  const { ORIGIN_KEY } = require('./constants')
21
+ const { appendRules } = require('./payload-tagging/config')
21
22
 
22
23
  const tracerMetrics = telemetryMetrics.manager.namespace('tracers')
23
24
 
@@ -173,6 +174,21 @@ function validateNamingVersion (versionString) {
173
174
  return versionString
174
175
  }
175
176
 
177
+ /**
178
+ * Given a string of comma-separated paths, return the array of paths.
179
+ * If a blank path is provided a null is returned to signal that the feature is disabled.
180
+ * An empty array means the feature is enabled but that no rules need to be applied.
181
+ *
182
+ * @param {string} input
183
+ * @returns {[string]|null}
184
+ */
185
+ function splitJSONPathRules (input) {
186
+ if (!input) return null
187
+ if (Array.isArray(input)) return input
188
+ if (input === 'all') return []
189
+ return input.split(',')
190
+ }
191
+
176
192
  // Shallow clone with property name remapping
177
193
  function remapify (input, mappings) {
178
194
  if (!input) return
@@ -281,6 +297,26 @@ class Config {
281
297
  null
282
298
  )
283
299
 
300
+ const DD_TRACE_CLOUD_REQUEST_PAYLOAD_TAGGING = splitJSONPathRules(
301
+ coalesce(
302
+ process.env.DD_TRACE_CLOUD_REQUEST_PAYLOAD_TAGGING,
303
+ options.cloudPayloadTagging?.request,
304
+ ''
305
+ ))
306
+
307
+ const DD_TRACE_CLOUD_RESPONSE_PAYLOAD_TAGGING = splitJSONPathRules(
308
+ coalesce(
309
+ process.env.DD_TRACE_CLOUD_RESPONSE_PAYLOAD_TAGGING,
310
+ options.cloudPayloadTagging?.response,
311
+ ''
312
+ ))
313
+
314
+ const DD_TRACE_CLOUD_PAYLOAD_TAGGING_MAX_DEPTH = coalesce(
315
+ process.env.DD_TRACE_CLOUD_PAYLOAD_TAGGING_MAX_DEPTH,
316
+ options.cloudPayloadTagging?.maxDepth,
317
+ 10
318
+ )
319
+
284
320
  // TODO: refactor
285
321
  this.apiKey = DD_API_KEY
286
322
 
@@ -291,6 +327,15 @@ class Config {
291
327
  type: DD_INSTRUMENTATION_INSTALL_TYPE
292
328
  }
293
329
 
330
+ this.cloudPayloadTagging = {
331
+ requestsEnabled: !!DD_TRACE_CLOUD_REQUEST_PAYLOAD_TAGGING,
332
+ responsesEnabled: !!DD_TRACE_CLOUD_RESPONSE_PAYLOAD_TAGGING,
333
+ maxDepth: DD_TRACE_CLOUD_PAYLOAD_TAGGING_MAX_DEPTH,
334
+ rules: appendRules(
335
+ DD_TRACE_CLOUD_REQUEST_PAYLOAD_TAGGING, DD_TRACE_CLOUD_RESPONSE_PAYLOAD_TAGGING
336
+ )
337
+ }
338
+
294
339
  this._applyDefaults()
295
340
  this._applyEnvironment()
296
341
  this._applyOptions(options)
@@ -423,6 +468,7 @@ class Config {
423
468
  this._setValue(defaults, 'dogstatsd.hostname', '127.0.0.1')
424
469
  this._setValue(defaults, 'dogstatsd.port', '8125')
425
470
  this._setValue(defaults, 'dsmEnabled', false)
471
+ this._setValue(defaults, 'dynamicInstrumentationEnabled', false)
426
472
  this._setValue(defaults, 'env', undefined)
427
473
  this._setValue(defaults, 'experimental.enableGetRumData', false)
428
474
  this._setValue(defaults, 'experimental.exporter', undefined)
@@ -451,6 +497,7 @@ class Config {
451
497
  this._setValue(defaults, 'isGitUploadEnabled', false)
452
498
  this._setValue(defaults, 'isIntelligentTestRunnerEnabled', false)
453
499
  this._setValue(defaults, 'isManualApiEnabled', false)
500
+ this._setValue(defaults, 'ciVisibilityTestSessionName', '')
454
501
  this._setValue(defaults, 'logInjection', false)
455
502
  this._setValue(defaults, 'lookup', undefined)
456
503
  this._setValue(defaults, 'memcachedCommandEnabled', false)
@@ -528,6 +575,7 @@ class Config {
528
575
  DD_DBM_PROPAGATION_MODE,
529
576
  DD_DOGSTATSD_HOSTNAME,
530
577
  DD_DOGSTATSD_PORT,
578
+ DD_DYNAMIC_INSTRUMENTATION_ENABLED,
531
579
  DD_ENV,
532
580
  DD_EXPERIMENTAL_API_SECURITY_ENABLED,
533
581
  DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED,
@@ -657,6 +705,7 @@ class Config {
657
705
  this._setString(env, 'dogstatsd.hostname', DD_DOGSTATSD_HOSTNAME)
658
706
  this._setString(env, 'dogstatsd.port', DD_DOGSTATSD_PORT)
659
707
  this._setBoolean(env, 'dsmEnabled', DD_DATA_STREAMS_ENABLED)
708
+ this._setBoolean(env, 'dynamicInstrumentationEnabled', DD_DYNAMIC_INSTRUMENTATION_ENABLED)
660
709
  this._setString(env, 'env', DD_ENV || tags.env)
661
710
  this._setBoolean(env, 'experimental.enableGetRumData', DD_TRACE_EXPERIMENTAL_GET_RUM_DATA_ENABLED)
662
711
  this._setString(env, 'experimental.exporter', DD_TRACE_EXPERIMENTAL_EXPORTER)
@@ -824,11 +873,11 @@ class Config {
824
873
  this._setString(opts, 'dogstatsd.port', options.dogstatsd.port)
825
874
  }
826
875
  this._setBoolean(opts, 'dsmEnabled', options.dsmEnabled)
876
+ this._setBoolean(opts, 'dynamicInstrumentationEnabled', options.experimental?.dynamicInstrumentationEnabled)
827
877
  this._setString(opts, 'env', options.env || tags.env)
828
- this._setBoolean(opts, 'experimental.enableGetRumData',
829
- options.experimental && options.experimental.enableGetRumData)
830
- this._setString(opts, 'experimental.exporter', options.experimental && options.experimental.exporter)
831
- this._setBoolean(opts, 'experimental.runtimeId', options.experimental && options.experimental.runtimeId)
878
+ this._setBoolean(opts, 'experimental.enableGetRumData', options.experimental?.enableGetRumData)
879
+ this._setString(opts, 'experimental.exporter', options.experimental?.exporter)
880
+ this._setBoolean(opts, 'experimental.runtimeId', options.experimental?.runtimeId)
832
881
  this._setValue(opts, 'flushInterval', maybeInt(options.flushInterval))
833
882
  this._optsUnprocessed.flushInterval = options.flushInterval
834
883
  this._setValue(opts, 'flushMinSpans', maybeInt(options.flushMinSpans))
@@ -954,10 +1003,10 @@ class Config {
954
1003
  }
955
1004
 
956
1005
  _isCiVisibilityManualApiEnabled () {
957
- return isTrue(coalesce(
1006
+ return coalesce(
958
1007
  process.env.DD_CIVISIBILITY_MANUAL_API_ENABLED,
959
- false
960
- ))
1008
+ true
1009
+ )
961
1010
  }
962
1011
 
963
1012
  _isTraceStatsComputationEnabled () {
@@ -985,7 +1034,8 @@ class Config {
985
1034
  DD_CIVISIBILITY_AGENTLESS_URL,
986
1035
  DD_CIVISIBILITY_EARLY_FLAKE_DETECTION_ENABLED,
987
1036
  DD_CIVISIBILITY_FLAKY_RETRY_ENABLED,
988
- DD_CIVISIBILITY_FLAKY_RETRY_COUNT
1037
+ DD_CIVISIBILITY_FLAKY_RETRY_COUNT,
1038
+ DD_TEST_SESSION_NAME
989
1039
  } = process.env
990
1040
 
991
1041
  if (DD_CIVISIBILITY_AGENTLESS_URL) {
@@ -1000,7 +1050,8 @@ class Config {
1000
1050
  coalesce(DD_CIVISIBILITY_FLAKY_RETRY_ENABLED, true))
1001
1051
  this._setValue(calc, 'flakyTestRetriesCount', coalesce(maybeInt(DD_CIVISIBILITY_FLAKY_RETRY_COUNT), 5))
1002
1052
  this._setBoolean(calc, 'isIntelligentTestRunnerEnabled', isTrue(this._isCiVisibilityItrEnabled()))
1003
- this._setBoolean(calc, 'isManualApiEnabled', this._isCiVisibilityManualApiEnabled())
1053
+ this._setBoolean(calc, 'isManualApiEnabled', !isFalse(this._isCiVisibilityManualApiEnabled()))
1054
+ this._setString(calc, 'ciVisibilityTestSessionName', DD_TEST_SESSION_NAME)
1004
1055
  }
1005
1056
  this._setString(calc, 'dogstatsd.hostname', this._getHostname())
1006
1057
  this._setBoolean(calc, 'isGitUploadEnabled',
@@ -34,5 +34,15 @@ module.exports = {
34
34
  SCI_REPOSITORY_URL: '_dd.git.repository_url',
35
35
  SCI_COMMIT_SHA: '_dd.git.commit.sha',
36
36
  APM_TRACING_ENABLED_KEY: '_dd.apm.enabled',
37
- APPSEC_PROPAGATION_KEY: '_dd.p.appsec'
37
+ APPSEC_PROPAGATION_KEY: '_dd.p.appsec',
38
+ PAYLOAD_TAG_REQUEST_PREFIX: 'aws.request.body',
39
+ PAYLOAD_TAG_RESPONSE_PREFIX: 'aws.response.body',
40
+ PAYLOAD_TAGGING_MAX_TAGS: 758,
41
+ SCHEMA_DEFINITION: 'schema.definition',
42
+ SCHEMA_WEIGHT: 'schema.weight',
43
+ SCHEMA_TYPE: 'schema.type',
44
+ SCHEMA_ID: 'schema.id',
45
+ SCHEMA_TOPIC: 'schema.topic',
46
+ SCHEMA_OPERATION: 'schema.operation',
47
+ SCHEMA_NAME: 'schema.name'
38
48
  }
@@ -1,4 +1,5 @@
1
1
  const { storage } = require('../../datadog-core')
2
+ const log = require('./log')
2
3
 
3
4
  function getDataStreamsContext () {
4
5
  const store = storage.getStore()
@@ -6,6 +7,8 @@ function getDataStreamsContext () {
6
7
  }
7
8
 
8
9
  function setDataStreamsContext (dataStreamsContext) {
10
+ log.debug(() => `Setting new DSM Context: ${JSON.stringify(dataStreamsContext)}.`)
11
+
9
12
  if (dataStreamsContext) storage.enterWith({ ...(storage.getStore()), dataStreamsContext })
10
13
  }
11
14
 
@@ -0,0 +1,23 @@
1
+ const FNV_64_PRIME = BigInt('0x100000001B3')
2
+ const FNV1_64_INIT = BigInt('0xCBF29CE484222325')
3
+
4
+ function fnv (data, hvalInit, fnvPrime, fnvSize) {
5
+ let hval = hvalInit
6
+ for (const byte of data) {
7
+ hval = (hval * fnvPrime) % fnvSize
8
+ hval = hval ^ BigInt(byte)
9
+ }
10
+ return hval
11
+ }
12
+
13
+ function fnv64 (data) {
14
+ if (!Buffer.isBuffer(data)) {
15
+ data = Buffer.from(data, 'utf-8')
16
+ }
17
+ const byteArray = new Uint8Array(data)
18
+ return fnv(byteArray, FNV1_64_INIT, FNV_64_PRIME, BigInt(2) ** BigInt(64))
19
+ }
20
+
21
+ module.exports = {
22
+ fnv64
23
+ }
@@ -4,6 +4,8 @@
4
4
  const crypto = require('crypto')
5
5
  const { encodeVarint, decodeVarint } = require('./encoding')
6
6
  const LRUCache = require('lru-cache')
7
+ const log = require('../log')
8
+ const pick = require('../../../datadog-core/src/utils/src/pick')
7
9
 
8
10
  const options = { max: 500 }
9
11
  const cache = new LRUCache(options)
@@ -11,6 +13,8 @@ const cache = new LRUCache(options)
11
13
  const CONTEXT_PROPAGATION_KEY = 'dd-pathway-ctx'
12
14
  const CONTEXT_PROPAGATION_KEY_BASE64 = 'dd-pathway-ctx-base64'
13
15
 
16
+ const logKeys = [CONTEXT_PROPAGATION_KEY, CONTEXT_PROPAGATION_KEY_BASE64]
17
+
14
18
  function shaHash (checkpointString) {
15
19
  const hash = crypto.createHash('md5').update(checkpointString).digest('hex').slice(0, 16)
16
20
  return Buffer.from(hash, 'hex')
@@ -80,9 +84,13 @@ class DsmPathwayCodec {
80
84
  return
81
85
  }
82
86
  carrier[CONTEXT_PROPAGATION_KEY_BASE64] = encodePathwayContextBase64(dataStreamsContext)
87
+
88
+ log.debug(() => `Injected into DSM carrier: ${JSON.stringify(pick(carrier, logKeys))}.`)
83
89
  }
84
90
 
85
91
  static decode (carrier) {
92
+ log.debug(() => `Attempting extract from DSM carrier: ${JSON.stringify(pick(carrier, logKeys))}.`)
93
+
86
94
  if (carrier == null) return
87
95
 
88
96
  let ctx
@@ -97,13 +105,12 @@ class DsmPathwayCodec {
97
105
  // pass
98
106
  }
99
107
  // cover case where base64 context was received under wrong key
100
- if (!ctx) ctx = decodePathwayContextBase64(carrier[CONTEXT_PROPAGATION_KEY])
108
+ if (!ctx && CONTEXT_PROPAGATION_KEY in carrier) {
109
+ ctx = decodePathwayContextBase64(carrier[CONTEXT_PROPAGATION_KEY])
110
+ }
101
111
  }
102
- return ctx
103
- }
104
112
 
105
- static contextExists (carrier) {
106
- return CONTEXT_PROPAGATION_KEY_BASE64 in carrier || CONTEXT_PROPAGATION_KEY in carrier
113
+ return ctx
107
114
  }
108
115
  }
109
116
 
@@ -9,6 +9,9 @@ const { DataStreamsWriter } = require('./writer')
9
9
  const { computePathwayHash } = require('./pathway')
10
10
  const { types } = require('util')
11
11
  const { PATHWAY_HASH } = require('../../../../ext/tags')
12
+ const { SchemaBuilder } = require('./schemas/schema_builder')
13
+ const { SchemaSampler } = require('./schemas/schema_sampler')
14
+ const log = require('../log')
12
15
 
13
16
  const ENTRY_PARENT_HASH = Buffer.from('0000000000000000', 'hex')
14
17
 
@@ -194,6 +197,7 @@ class DataStreamsProcessor {
194
197
  this.version = version || ''
195
198
  this.sequence = 0
196
199
  this.flushInterval = flushInterval
200
+ this._schemaSamplers = {}
197
201
 
198
202
  if (this.enabled) {
199
203
  this.timer = setInterval(this.onInterval.bind(this), flushInterval)
@@ -269,6 +273,11 @@ class DataStreamsProcessor {
269
273
  closestOppositeDirectionHash = parentHash
270
274
  closestOppositeDirectionEdgeStart = edgeStartNs
271
275
  }
276
+ log.debug(
277
+ () => `Setting DSM Checkpoint from extracted parent context with hash: ${parentHash} and edge tags: ${edgeTags}`
278
+ )
279
+ } else {
280
+ log.debug(() => 'Setting DSM Checkpoint with empty parent context.')
272
281
  }
273
282
  const hash = computePathwayHash(this.service, this.env, edgeTags, parentHash)
274
283
  const edgeLatencyNs = nowNs - edgeStartNs
@@ -352,6 +361,32 @@ class DataStreamsProcessor {
352
361
  setUrl (url) {
353
362
  this.writer.setUrl(url)
354
363
  }
364
+
365
+ trySampleSchema (topic) {
366
+ const nowMs = Date.now()
367
+
368
+ if (!this._schemaSamplers[topic]) {
369
+ this._schemaSamplers[topic] = new SchemaSampler()
370
+ }
371
+
372
+ const sampler = this._schemaSamplers[topic]
373
+ return sampler.trySample(nowMs)
374
+ }
375
+
376
+ canSampleSchema (topic) {
377
+ const nowMs = Date.now()
378
+
379
+ if (!this._schemaSamplers[topic]) {
380
+ this._schemaSamplers[topic] = new SchemaSampler()
381
+ }
382
+
383
+ const sampler = this._schemaSamplers[topic]
384
+ return sampler.canSample(nowMs)
385
+ }
386
+
387
+ getSchema (schemaName, iterator) {
388
+ return SchemaBuilder.getSchema(schemaName, iterator)
389
+ }
355
390
  }
356
391
 
357
392
  module.exports = {
@@ -0,0 +1,8 @@
1
+ class Schema {
2
+ constructor (definition, id) {
3
+ this.definition = definition
4
+ this.id = id
5
+ }
6
+ }
7
+
8
+ module.exports = { Schema }
@@ -0,0 +1,125 @@
1
+ const LRUCache = require('lru-cache')
2
+ const { fnv64 } = require('../fnv')
3
+ const { Schema } = require('./schema')
4
+
5
+ const maxDepth = 10
6
+ const maxProperties = 1000
7
+ const CACHE = new LRUCache({ max: 32 })
8
+
9
+ class SchemaBuilder {
10
+ constructor (iterator) {
11
+ this.schema = new OpenApiSchema()
12
+ this.iterator = iterator
13
+ this.proerties = 0
14
+ }
15
+
16
+ addProperty (schemaName, fieldName, isArray, type, description, ref, format, enumValues) {
17
+ if (this.properties >= maxProperties) {
18
+ return false
19
+ }
20
+ this.properties += 1
21
+ let property = new OpenApiSchema.PROPERTY(type, description, ref, format, enumValues, null)
22
+ if (isArray) {
23
+ property = new OpenApiSchema.PROPERTY('array', null, null, null, null, property)
24
+ }
25
+ this.schema.components.schemas[schemaName].properties[fieldName] = property
26
+ return true
27
+ }
28
+
29
+ build () {
30
+ this.iterator.iterateOverSchema(this)
31
+ const noNones = convertToJsonCompatible(this.schema)
32
+ const definition = jsonStringify(noNones)
33
+ const id = fnv64(Buffer.from(definition, 'utf-8')).toString()
34
+ return new Schema(definition, id)
35
+ }
36
+
37
+ shouldExtractSchema (schemaName, depth) {
38
+ if (depth > maxDepth) {
39
+ return false
40
+ }
41
+ if (schemaName in this.schema.components.schemas) {
42
+ return false
43
+ }
44
+ this.schema.components.schemas[schemaName] = new OpenApiSchema.SCHEMA()
45
+ return true
46
+ }
47
+
48
+ static getSchema (schemaName, iterator) {
49
+ if (!CACHE.has(schemaName)) {
50
+ CACHE.set(schemaName, new SchemaBuilder(iterator).build())
51
+ }
52
+ return CACHE.get(schemaName)
53
+ }
54
+ }
55
+
56
+ class OpenApiSchema {
57
+ constructor () {
58
+ this.openapi = '3.0.0'
59
+ this.components = new OpenApiComponents()
60
+ }
61
+ }
62
+
63
+ OpenApiSchema.SCHEMA = class {
64
+ constructor () {
65
+ this.type = 'object'
66
+ this.properties = {}
67
+ }
68
+ }
69
+
70
+ OpenApiSchema.PROPERTY = class {
71
+ constructor (type, description = null, ref = null, format = null, enumValues = null, items = null) {
72
+ this.type = type
73
+ this.description = description
74
+ this.$ref = ref
75
+ this.format = format
76
+ this.enum = enumValues
77
+ this.items = items
78
+ }
79
+ }
80
+
81
+ class OpenApiComponents {
82
+ constructor () {
83
+ this.schemas = {}
84
+ }
85
+ }
86
+
87
+ function convertToJsonCompatible (obj) {
88
+ if (Array.isArray(obj)) {
89
+ return obj.filter(item => item !== null).map(item => convertToJsonCompatible(item))
90
+ } else if (obj && typeof obj === 'object') {
91
+ const jsonObj = {}
92
+ for (const [key, value] of Object.entries(obj)) {
93
+ if (value !== null) {
94
+ jsonObj[key] = convertToJsonCompatible(value)
95
+ }
96
+ }
97
+ return jsonObj
98
+ }
99
+ return obj
100
+ }
101
+
102
+ function convertKey (key) {
103
+ if (key === 'enumValues') {
104
+ return 'enum'
105
+ }
106
+ return key
107
+ }
108
+
109
+ function jsonStringify (obj, indent = 2) {
110
+ // made to stringify json exactly similar to python / java in order for hashing to be the same
111
+ const jsonString = JSON.stringify(obj, (_, value) => value, indent)
112
+ return jsonString.replace(/^ +/gm, ' ') // Replace leading spaces with single space
113
+ .replace(/\n/g, '') // Remove newlines
114
+ .replace(/{ /g, '{') // Remove space after '{'
115
+ .replace(/ }/g, '}') // Remove space before '}'
116
+ .replace(/\[ /g, '[') // Remove space after '['
117
+ .replace(/ \]/g, ']') // Remove space before ']'
118
+ }
119
+
120
+ module.exports = {
121
+ SchemaBuilder,
122
+ OpenApiSchema,
123
+ convertToJsonCompatible,
124
+ convertKey
125
+ }
@@ -0,0 +1,29 @@
1
+ const SAMPLE_INTERVAL_MILLIS = 30 * 1000
2
+
3
+ class SchemaSampler {
4
+ constructor () {
5
+ this.weight = 0
6
+ this.lastSampleMs = 0
7
+ }
8
+
9
+ trySample (currentTimeMs) {
10
+ if (currentTimeMs >= this.lastSampleMs + SAMPLE_INTERVAL_MILLIS) {
11
+ if (currentTimeMs >= this.lastSampleMs + SAMPLE_INTERVAL_MILLIS) {
12
+ this.lastSampleMs = currentTimeMs
13
+ const weight = this.weight
14
+ this.weight = 0
15
+ return weight
16
+ }
17
+ }
18
+ return 0
19
+ }
20
+
21
+ canSample (currentTimeMs) {
22
+ this.weight += 1
23
+ return currentTimeMs >= this.lastSampleMs + SAMPLE_INTERVAL_MILLIS
24
+ }
25
+ }
26
+
27
+ module.exports = {
28
+ SchemaSampler
29
+ }
@@ -0,0 +1,24 @@
1
+ 'use strict'
2
+
3
+ const { workerData: { config: parentConfig, parentThreadId, configPort } } = require('node:worker_threads')
4
+ const { format } = require('node:url')
5
+ const log = require('../../log')
6
+
7
+ const config = module.exports = {
8
+ runtimeId: parentConfig.tags['runtime-id'],
9
+ service: parentConfig.service,
10
+ parentThreadId
11
+ }
12
+
13
+ updateUrl(parentConfig)
14
+
15
+ configPort.on('message', updateUrl)
16
+ configPort.on('messageerror', (err) => log.error(err))
17
+
18
+ function updateUrl (updates) {
19
+ config.url = updates.url || format({
20
+ protocol: 'http:',
21
+ hostname: updates.hostname || 'localhost',
22
+ port: updates.port
23
+ })
24
+ }
@@ -0,0 +1,57 @@
1
+ 'use strict'
2
+
3
+ const { randomUUID } = require('crypto')
4
+ const { breakpoints } = require('./state')
5
+ const session = require('./session')
6
+ const send = require('./send')
7
+ const { ackEmitting } = require('./status')
8
+ const { parentThreadId } = require('./config')
9
+ const log = require('../../log')
10
+ const { version } = require('../../../../../package.json')
11
+
12
+ require('./remote_config')
13
+
14
+ // There doesn't seem to be an official standard for the content of these fields, so we're just populating them with
15
+ // something that should be useful to a Node.js developer.
16
+ const threadId = parentThreadId === 0 ? `pid:${process.pid}` : `pid:${process.pid};tid:${parentThreadId}`
17
+ const threadName = parentThreadId === 0 ? 'MainThread' : `WorkerThread:${parentThreadId}`
18
+
19
+ session.on('Debugger.paused', async ({ params }) => {
20
+ const start = process.hrtime.bigint()
21
+ const timestamp = Date.now()
22
+ const probes = params.hitBreakpoints.map((id) => breakpoints.get(id))
23
+ await session.post('Debugger.resume')
24
+ const diff = process.hrtime.bigint() - start // TODO: Should this be recored as telemetry?
25
+
26
+ log.debug(`Finished processing breakpoints - main thread paused for: ${Number(diff) / 1000000} ms`)
27
+
28
+ const logger = {
29
+ // We can safely use `location.file` from the first probe in the array, since all probes hit by `hitBreakpoints`
30
+ // must exist in the same file since the debugger can only pause the main thread in one location.
31
+ name: probes[0].location.file, // name of the class/type/file emitting the snapshot
32
+ method: params.callFrames[0].functionName, // name of the method/function emitting the snapshot
33
+ version,
34
+ thread_id: threadId,
35
+ thread_name: threadName
36
+ }
37
+
38
+ // TODO: Send multiple probes in one HTTP request as an array
39
+ for (const probe of probes) {
40
+ const snapshot = {
41
+ id: randomUUID(),
42
+ timestamp,
43
+ probe: {
44
+ id: probe.id,
45
+ version: probe.version,
46
+ location: probe.location
47
+ },
48
+ language: 'javascript'
49
+ }
50
+
51
+ // TODO: Process template
52
+ send(probe.template, logger, snapshot, (err) => {
53
+ if (err) log.error(err)
54
+ else ackEmitting(probe)
55
+ })
56
+ }
57
+ })
@@ -0,0 +1,23 @@
1
+ 'use strict'
2
+
3
+ const { builtinModules } = require('node:module')
4
+
5
+ if (builtinModules.includes('inspector/promises')) {
6
+ module.exports = require('node:inspector/promises')
7
+ } else {
8
+ const inspector = require('node:inspector')
9
+ const { promisify } = require('node:util')
10
+
11
+ // The rest of the code in this file is lifted from:
12
+ // https://github.com/nodejs/node/blob/1d4d76ff3fb08f9a0c55a1d5530b46c4d5d550c7/lib/inspector/promises.js
13
+ class Session extends inspector.Session {
14
+ constructor () { super() } // eslint-disable-line no-useless-constructor
15
+ }
16
+
17
+ Session.prototype.post = promisify(inspector.Session.prototype.post)
18
+
19
+ module.exports = {
20
+ ...inspector,
21
+ Session
22
+ }
23
+ }