dd-trace 5.14.1 → 5.15.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 (68) hide show
  1. package/LICENSE-3rdparty.csv +0 -3
  2. package/README.md +8 -18
  3. package/ci/init.js +7 -0
  4. package/ext/exporters.d.ts +1 -0
  5. package/ext/exporters.js +2 -1
  6. package/ext/tags.d.ts +1 -0
  7. package/ext/tags.js +1 -0
  8. package/index.d.ts +18 -3
  9. package/initialize.mjs +52 -0
  10. package/package.json +9 -12
  11. package/packages/datadog-instrumentations/src/amqplib.js +5 -2
  12. package/packages/datadog-instrumentations/src/apollo-server-core.js +0 -1
  13. package/packages/datadog-instrumentations/src/apollo-server.js +0 -1
  14. package/packages/datadog-instrumentations/src/body-parser.js +0 -1
  15. package/packages/datadog-instrumentations/src/check_require_cache.js +67 -5
  16. package/packages/datadog-instrumentations/src/cookie-parser.js +0 -1
  17. package/packages/datadog-instrumentations/src/express.js +0 -1
  18. package/packages/datadog-instrumentations/src/graphql.js +0 -2
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  20. package/packages/datadog-instrumentations/src/helpers/register.js +5 -2
  21. package/packages/datadog-instrumentations/src/http/server.js +0 -1
  22. package/packages/datadog-instrumentations/src/jest.js +6 -3
  23. package/packages/datadog-instrumentations/src/mocha/common.js +48 -0
  24. package/packages/datadog-instrumentations/src/mocha/main.js +487 -0
  25. package/packages/datadog-instrumentations/src/mocha/utils.js +306 -0
  26. package/packages/datadog-instrumentations/src/mocha/worker.js +51 -0
  27. package/packages/datadog-instrumentations/src/mocha.js +4 -673
  28. package/packages/datadog-instrumentations/src/openai.js +188 -17
  29. package/packages/datadog-instrumentations/src/playwright.js +4 -3
  30. package/packages/datadog-instrumentations/src/router.js +1 -1
  31. package/packages/datadog-instrumentations/src/selenium.js +13 -6
  32. package/packages/datadog-plugin-mocha/src/index.js +82 -8
  33. package/packages/datadog-plugin-next/src/index.js +1 -2
  34. package/packages/datadog-plugin-openai/src/index.js +219 -73
  35. package/packages/dd-trace/src/appsec/addresses.js +4 -2
  36. package/packages/dd-trace/src/appsec/blocking.js +19 -25
  37. package/packages/dd-trace/src/appsec/channels.js +2 -1
  38. package/packages/dd-trace/src/appsec/graphql.js +10 -3
  39. package/packages/dd-trace/src/appsec/index.js +11 -4
  40. package/packages/dd-trace/src/appsec/rasp.js +35 -0
  41. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
  42. package/packages/dd-trace/src/appsec/remote_config/index.js +1 -0
  43. package/packages/dd-trace/src/appsec/rule_manager.js +15 -25
  44. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +2 -5
  45. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +3 -1
  46. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +5 -1
  47. package/packages/dd-trace/src/config.js +59 -16
  48. package/packages/dd-trace/src/encode/0.4.js +47 -8
  49. package/packages/dd-trace/src/exporter.js +1 -0
  50. package/packages/dd-trace/src/flare/file.js +44 -0
  51. package/packages/dd-trace/src/flare/index.js +98 -0
  52. package/packages/dd-trace/src/log/channels.js +54 -29
  53. package/packages/dd-trace/src/log/writer.js +7 -49
  54. package/packages/dd-trace/src/opentracing/propagation/text_map.js +57 -12
  55. package/packages/dd-trace/src/plugins/index.js +1 -0
  56. package/packages/dd-trace/src/plugins/util/ip_extractor.js +1 -1
  57. package/packages/dd-trace/src/plugins/util/test.js +6 -0
  58. package/packages/dd-trace/src/profiler.js +2 -1
  59. package/packages/dd-trace/src/profiling/config.js +1 -0
  60. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  61. package/packages/dd-trace/src/profiling/{ssi-telemetry.js → ssi-heuristics.js} +64 -36
  62. package/packages/dd-trace/src/profiling/ssi-telemetry-mock-profiler.js +4 -9
  63. package/packages/dd-trace/src/proxy.js +49 -15
  64. package/packages/dd-trace/src/ritm.js +13 -1
  65. package/packages/dd-trace/src/startup-log.js +19 -15
  66. package/packages/dd-trace/src/telemetry/index.js +6 -2
  67. package/packages/dd-trace/src/tracer.js +3 -0
  68. package/packages/dd-trace/src/plugins/util/ip_blocklist.js +0 -51
@@ -15,5 +15,6 @@ module.exports = {
15
15
  APM_TRACING_LOGS_INJECTION: 1n << 13n,
16
16
  APM_TRACING_HTTP_HEADER_TAGS: 1n << 14n,
17
17
  APM_TRACING_CUSTOM_TAGS: 1n << 15n,
18
- APM_TRACING_ENABLED: 1n << 19n
18
+ APM_TRACING_ENABLED: 1n << 19n,
19
+ APM_TRACING_SAMPLE_RULES: 1n << 29n
19
20
  }
@@ -15,6 +15,7 @@ function enable (config, appsec) {
15
15
  rc.updateCapabilities(RemoteConfigCapabilities.APM_TRACING_LOGS_INJECTION, true)
16
16
  rc.updateCapabilities(RemoteConfigCapabilities.APM_TRACING_SAMPLE_RATE, true)
17
17
  rc.updateCapabilities(RemoteConfigCapabilities.APM_TRACING_ENABLED, true)
18
+ rc.updateCapabilities(RemoteConfigCapabilities.APM_TRACING_SAMPLE_RULES, true)
18
19
 
19
20
  const activation = Activation.fromConfig(config)
20
21
 
@@ -3,7 +3,6 @@
3
3
  const fs = require('fs')
4
4
  const waf = require('./waf')
5
5
  const { ACKNOWLEDGED, ERROR } = require('./remote_config/apply_states')
6
- const blocking = require('./blocking')
7
6
 
8
7
  let defaultRules
9
8
 
@@ -20,10 +19,6 @@ function loadRules (config) {
20
19
  : require('./recommended.json')
21
20
 
22
21
  waf.init(defaultRules, config)
23
-
24
- if (defaultRules.actions) {
25
- blocking.updateBlockingConfiguration(defaultRules.actions.find(action => action.id === 'block'))
26
- }
27
22
  }
28
23
 
29
24
  function updateWafFromRC ({ toUnapply, toApply, toModify }) {
@@ -68,7 +63,7 @@ function updateWafFromRC ({ toUnapply, toApply, toModify }) {
68
63
  item.apply_state = ERROR
69
64
  item.apply_error = 'Multiple ruleset received in ASM_DD'
70
65
  } else {
71
- if (file && file.rules && file.rules.length) {
66
+ if (file?.rules?.length) {
72
67
  const { version, metadata, rules, processors, scanners } = file
73
68
 
74
69
  newRuleset = { version, metadata, rules, processors, scanners }
@@ -78,30 +73,23 @@ function updateWafFromRC ({ toUnapply, toApply, toModify }) {
78
73
  batch.add(item)
79
74
  }
80
75
  } else if (product === 'ASM') {
81
- let batchConfiguration = false
82
- if (file && file.rules_override && file.rules_override.length) {
83
- batchConfiguration = true
76
+ if (file?.rules_override?.length) {
84
77
  newRulesOverride.set(id, file.rules_override)
85
78
  }
86
79
 
87
- if (file && file.exclusions && file.exclusions.length) {
88
- batchConfiguration = true
80
+ if (file?.exclusions?.length) {
89
81
  newExclusions.set(id, file.exclusions)
90
82
  }
91
83
 
92
- if (file && file.custom_rules && file.custom_rules.length) {
93
- batchConfiguration = true
84
+ if (file?.custom_rules?.length) {
94
85
  newCustomRules.set(id, file.custom_rules)
95
86
  }
96
87
 
97
- if (file && file.actions && file.actions.length) {
88
+ if (file?.actions?.length) {
98
89
  newActions.set(id, file.actions)
99
90
  }
100
91
 
101
- // "actions" data is managed by tracer and not by waf
102
- if (batchConfiguration) {
103
- batch.add(item)
104
- }
92
+ batch.add(item)
105
93
  }
106
94
  }
107
95
 
@@ -112,7 +100,9 @@ function updateWafFromRC ({ toUnapply, toApply, toModify }) {
112
100
  newRuleset ||
113
101
  newRulesOverride.modified ||
114
102
  newExclusions.modified ||
115
- newCustomRules.modified) {
103
+ newCustomRules.modified ||
104
+ newActions.modified
105
+ ) {
116
106
  const payload = newRuleset || {}
117
107
 
118
108
  if (newRulesData.modified) {
@@ -127,6 +117,9 @@ function updateWafFromRC ({ toUnapply, toApply, toModify }) {
127
117
  if (newCustomRules.modified) {
128
118
  payload.custom_rules = concatArrays(newCustomRules)
129
119
  }
120
+ if (newActions.modified) {
121
+ payload.actions = concatArrays(newActions)
122
+ }
130
123
 
131
124
  try {
132
125
  waf.update(payload)
@@ -146,6 +139,9 @@ function updateWafFromRC ({ toUnapply, toApply, toModify }) {
146
139
  if (newCustomRules.modified) {
147
140
  appliedCustomRules = newCustomRules
148
141
  }
142
+ if (newActions.modified) {
143
+ appliedActions = newActions
144
+ }
149
145
  } catch (err) {
150
146
  newApplyState = ERROR
151
147
  newApplyError = err.toString()
@@ -156,11 +152,6 @@ function updateWafFromRC ({ toUnapply, toApply, toModify }) {
156
152
  config.apply_state = newApplyState
157
153
  if (newApplyError) config.apply_error = newApplyError
158
154
  }
159
-
160
- if (newActions.modified) {
161
- blocking.updateBlockingConfiguration(concatArrays(newActions).find(action => action.id === 'block'))
162
- appliedActions = newActions
163
- }
164
155
  }
165
156
 
166
157
  // A Map with a new prop `modified`, a bool that indicates if the Map was modified
@@ -242,7 +233,6 @@ function copyRulesData (rulesData) {
242
233
 
243
234
  function clearAllRules () {
244
235
  waf.destroy()
245
- blocking.updateBlockingConfiguration(undefined)
246
236
 
247
237
  defaultRules = undefined
248
238
 
@@ -3,17 +3,14 @@
3
3
  const { USER_ID } = require('../addresses')
4
4
  const waf = require('../waf')
5
5
  const { getRootSpan } = require('./utils')
6
- const { block } = require('../blocking')
6
+ const { block, getBlockingAction } = require('../blocking')
7
7
  const { storage } = require('../../../../datadog-core')
8
8
  const { setUserTags } = require('./set_user')
9
9
  const log = require('../../log')
10
10
 
11
11
  function isUserBlocked (user) {
12
12
  const actions = waf.run({ persistent: { [USER_ID]: user.id } })
13
-
14
- if (!actions) return false
15
-
16
- return actions.includes('block')
13
+ return !!getBlockingAction(actions)
17
14
  }
18
15
 
19
16
  function checkUserAndSetUser (tracer, user) {
@@ -3,6 +3,7 @@
3
3
  const log = require('../../log')
4
4
  const Reporter = require('../reporter')
5
5
  const addresses = require('../addresses')
6
+ const { getBlockingAction } = require('../blocking')
6
7
 
7
8
  // TODO: remove once ephemeral addresses are implemented
8
9
  const preventDuplicateAddresses = new Set([
@@ -60,7 +61,8 @@ class WAFContextWrapper {
60
61
  this.addressesToSkip = newAddressesToSkip
61
62
 
62
63
  const ruleTriggered = !!result.events?.length
63
- const blockTriggered = result.actions?.includes('block')
64
+
65
+ const blockTriggered = !!getBlockingAction(result.actions)
64
66
 
65
67
  Reporter.reportMetrics({
66
68
  duration: result.totalRuntime / 1e3,
@@ -4,7 +4,8 @@ const Writer = require('./writer')
4
4
  const {
5
5
  JEST_WORKER_COVERAGE_PAYLOAD_CODE,
6
6
  JEST_WORKER_TRACE_PAYLOAD_CODE,
7
- CUCUMBER_WORKER_TRACE_PAYLOAD_CODE
7
+ CUCUMBER_WORKER_TRACE_PAYLOAD_CODE,
8
+ MOCHA_WORKER_TRACE_PAYLOAD_CODE
8
9
  } = require('../../../plugins/util/test')
9
10
 
10
11
  function getInterprocessTraceCode () {
@@ -14,6 +15,9 @@ function getInterprocessTraceCode () {
14
15
  if (process.env.CUCUMBER_WORKER_ID) {
15
16
  return CUCUMBER_WORKER_TRACE_PAYLOAD_CODE
16
17
  }
18
+ if (process.env.MOCHA_WORKER_ID) {
19
+ return MOCHA_WORKER_TRACE_PAYLOAD_CODE
20
+ }
17
21
  return null
18
22
  }
19
23
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  const fs = require('fs')
4
4
  const os = require('os')
5
- const uuid = require('crypto-randomuuid')
5
+ const uuid = require('crypto-randomuuid') // we need to keep the old uuid dep because of cypress
6
6
  const URL = require('url').URL
7
7
  const log = require('./log')
8
8
  const pkg = require('./pkg')
@@ -170,6 +170,7 @@ class Config {
170
170
  // Configure the logger first so it can be used to warn about other configs
171
171
  this.debug = isTrue(coalesce(
172
172
  process.env.DD_TRACE_DEBUG,
173
+ process.env.OTEL_LOG_LEVEL && process.env.OTEL_LOG_LEVEL === 'debug',
173
174
  false
174
175
  ))
175
176
  this.logger = options.logger
@@ -288,15 +289,6 @@ class Config {
288
289
  )
289
290
 
290
291
  const sampler = {
291
- rules: coalesce(
292
- options.samplingRules,
293
- safeJsonParse(process.env.DD_TRACE_SAMPLING_RULES),
294
- []
295
- ).map(rule => {
296
- return remapify(rule, {
297
- sample_rate: 'sampleRate'
298
- })
299
- }),
300
292
  spanSamplingRules: coalesce(
301
293
  options.spanSamplingRules,
302
294
  safeJsonParse(maybeFile(process.env.DD_SPAN_SAMPLING_RULES_FILE)),
@@ -444,6 +436,7 @@ class Config {
444
436
  this._setValue(defaults, 'appsec.enabled', undefined)
445
437
  this._setValue(defaults, 'appsec.obfuscatorKeyRegex', defaultWafObfuscatorKeyRegex)
446
438
  this._setValue(defaults, 'appsec.obfuscatorValueRegex', defaultWafObfuscatorValueRegex)
439
+ this._setValue(defaults, 'appsec.rasp.enabled', false)
447
440
  this._setValue(defaults, 'appsec.rateLimit', 100)
448
441
  this._setValue(defaults, 'appsec.rules', undefined)
449
442
  this._setValue(defaults, 'appsec.sca.enabled', null)
@@ -485,9 +478,12 @@ class Config {
485
478
  this._setValue(defaults, 'peerServiceMapping', {})
486
479
  this._setValue(defaults, 'plugins', true)
487
480
  this._setValue(defaults, 'port', '8126')
488
- this._setValue(defaults, 'profiling.enabled', false)
481
+ this._setValue(defaults, 'profiling.enabled', undefined)
489
482
  this._setValue(defaults, 'profiling.exporters', 'agent')
490
483
  this._setValue(defaults, 'profiling.sourceMap', true)
484
+ this._setValue(defaults, 'profiling.ssi', false)
485
+ this._setValue(defaults, 'profiling.heuristicsEnabled', false)
486
+ this._setValue(defaults, 'profiling.longLivedThreshold', undefined)
491
487
  this._setValue(defaults, 'protocolVersion', '0.4')
492
488
  this._setValue(defaults, 'queryStringObfuscation', qsRegex)
493
489
  this._setValue(defaults, 'remoteConfig.enabled', true)
@@ -496,6 +492,7 @@ class Config {
496
492
  this._setValue(defaults, 'runtimeMetrics', false)
497
493
  this._setValue(defaults, 'sampleRate', undefined)
498
494
  this._setValue(defaults, 'sampler.rateLimit', undefined)
495
+ this._setValue(defaults, 'sampler.rules', [])
499
496
  this._setValue(defaults, 'scope', undefined)
500
497
  this._setValue(defaults, 'service', service)
501
498
  this._setValue(defaults, 'site', 'datadoghq.com')
@@ -531,6 +528,7 @@ class Config {
531
528
  DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP,
532
529
  DD_APPSEC_RULES,
533
530
  DD_APPSEC_SCA_ENABLED,
531
+ DD_APPSEC_RASP_ENABLED,
534
532
  DD_APPSEC_TRACE_RATE_LIMIT,
535
533
  DD_APPSEC_WAF_TIMEOUT,
536
534
  DD_DATA_STREAMS_ENABLED,
@@ -558,6 +556,7 @@ class Config {
558
556
  DD_PROFILING_ENABLED,
559
557
  DD_PROFILING_EXPORTERS,
560
558
  DD_PROFILING_SOURCE_MAP,
559
+ DD_INTERNAL_PROFILING_LONG_LIVED_THRESHOLD,
561
560
  DD_REMOTE_CONFIGURATION_ENABLED,
562
561
  DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS,
563
562
  DD_RUNTIME_METRICS_ENABLED,
@@ -590,6 +589,7 @@ class Config {
590
589
  DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED,
591
590
  DD_TRACE_REPORT_HOSTNAME,
592
591
  DD_TRACE_SAMPLE_RATE,
592
+ DD_TRACE_SAMPLING_RULES,
593
593
  DD_TRACE_SCOPE,
594
594
  DD_TRACE_SPAN_ATTRIBUTE_SCHEMA,
595
595
  DD_TRACE_STARTUP_LOGS,
@@ -618,6 +618,7 @@ class Config {
618
618
  this._setBoolean(env, 'appsec.enabled', DD_APPSEC_ENABLED)
619
619
  this._setString(env, 'appsec.obfuscatorKeyRegex', DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP)
620
620
  this._setString(env, 'appsec.obfuscatorValueRegex', DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP)
621
+ this._setBoolean(env, 'appsec.rasp.enabled', DD_APPSEC_RASP_ENABLED)
621
622
  this._setValue(env, 'appsec.rateLimit', maybeInt(DD_APPSEC_TRACE_RATE_LIMIT))
622
623
  this._setString(env, 'appsec.rules', DD_APPSEC_RULES)
623
624
  // DD_APPSEC_SCA_ENABLED is never used locally, but only sent to the backend
@@ -663,6 +664,17 @@ class Config {
663
664
  this._setBoolean(env, 'profiling.enabled', coalesce(DD_EXPERIMENTAL_PROFILING_ENABLED, DD_PROFILING_ENABLED))
664
665
  this._setString(env, 'profiling.exporters', DD_PROFILING_EXPORTERS)
665
666
  this._setBoolean(env, 'profiling.sourceMap', DD_PROFILING_SOURCE_MAP && !isFalse(DD_PROFILING_SOURCE_MAP))
667
+ if (DD_PROFILING_ENABLED === 'auto' || DD_INJECTION_ENABLED) {
668
+ this._setBoolean(env, 'profiling.ssi', true)
669
+ if (DD_PROFILING_ENABLED === 'auto' || DD_INJECTION_ENABLED.split(',').includes('profiler')) {
670
+ this._setBoolean(env, 'profiling.heuristicsEnabled', true)
671
+ }
672
+ if (DD_INTERNAL_PROFILING_LONG_LIVED_THRESHOLD) {
673
+ // This is only used in testing to not have to wait 30s
674
+ this._setValue(env, 'profiling.longLivedThreshold', Number(DD_INTERNAL_PROFILING_LONG_LIVED_THRESHOLD))
675
+ }
676
+ }
677
+
666
678
  this._setString(env, 'protocolVersion', DD_TRACE_AGENT_PROTOCOL_VERSION)
667
679
  this._setString(env, 'queryStringObfuscation', DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP)
668
680
  this._setBoolean(env, 'remoteConfig.enabled', coalesce(
@@ -687,6 +699,7 @@ class Config {
687
699
  }
688
700
  this._setUnit(env, 'sampleRate', DD_TRACE_SAMPLE_RATE || OTEL_TRACES_SAMPLER_MAPPING[OTEL_TRACES_SAMPLER])
689
701
  this._setValue(env, 'sampler.rateLimit', DD_TRACE_RATE_LIMIT)
702
+ this._setSamplingRule(env, 'sampler.rules', safeJsonParse(DD_TRACE_SAMPLING_RULES)) // example
690
703
  this._setString(env, 'scope', DD_TRACE_SCOPE)
691
704
  this._setString(env, 'service', DD_SERVICE || DD_SERVICE_NAME || tags.service || OTEL_SERVICE_NAME)
692
705
  this._setString(env, 'site', DD_SITE)
@@ -707,9 +720,7 @@ class Config {
707
720
  this._setBoolean(env, 'telemetry.dependencyCollection', DD_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED)
708
721
  this._setValue(env, 'telemetry.heartbeatInterval', maybeInt(Math.floor(DD_TELEMETRY_HEARTBEAT_INTERVAL * 1000)))
709
722
  const hasTelemetryLogsUsingFeatures =
710
- isTrue(DD_IAST_ENABLED) ||
711
- isTrue(DD_PROFILING_ENABLED) ||
712
- (typeof DD_INJECTION_ENABLED === 'string' && DD_INJECTION_ENABLED.split(',').includes('profiling'))
723
+ env['iast.enabled'] || env['profiling.enabled'] || env['profiling.heuristicsEnabled']
713
724
  ? true
714
725
  : undefined
715
726
  this._setBoolean(env, 'telemetry.logCollection', coalesce(DD_TELEMETRY_LOG_COLLECTION_ENABLED,
@@ -734,6 +745,7 @@ class Config {
734
745
  this._setBoolean(opts, 'appsec.enabled', options.appsec.enabled)
735
746
  this._setString(opts, 'appsec.obfuscatorKeyRegex', options.appsec.obfuscatorKeyRegex)
736
747
  this._setString(opts, 'appsec.obfuscatorValueRegex', options.appsec.obfuscatorValueRegex)
748
+ this._setBoolean(opts, 'appsec.rasp.enabled', options.appsec.rasp?.enabled)
737
749
  this._setValue(opts, 'appsec.rateLimit', maybeInt(options.appsec.rateLimit))
738
750
  this._setString(opts, 'appsec.rules', options.appsec.rules)
739
751
  this._setValue(opts, 'appsec.wafTimeout', maybeInt(options.appsec.wafTimeout))
@@ -786,6 +798,7 @@ class Config {
786
798
  this._setUnit(opts, 'sampleRate', coalesce(options.sampleRate, options.ingestion.sampleRate))
787
799
  const ingestion = options.ingestion || {}
788
800
  this._setValue(opts, 'sampler.rateLimit', coalesce(options.rateLimit, ingestion.rateLimit))
801
+ this._setSamplingRule(opts, 'sampler.rules', options.samplingRules)
789
802
  this._setString(opts, 'service', options.service || tags.service)
790
803
  this._setString(opts, 'site', options.site)
791
804
  if (options.spanAttributeSchema) {
@@ -933,6 +946,17 @@ class Config {
933
946
  this._setArray(opts, 'headerTags', headerTags)
934
947
  this._setTags(opts, 'tags', tags)
935
948
  this._setBoolean(opts, 'tracing', options.tracing_enabled)
949
+ // ignore tags for now since rc sampling rule tags format is not supported
950
+ this._setSamplingRule(opts, 'sampler.rules', this._ignoreTags(options.trace_sample_rules))
951
+ }
952
+
953
+ _ignoreTags (samplingRules) {
954
+ if (samplingRules) {
955
+ for (const rule of samplingRules) {
956
+ delete rule.tags
957
+ }
958
+ }
959
+ return samplingRules
936
960
  }
937
961
 
938
962
  _setBoolean (obj, name, value) {
@@ -959,12 +983,12 @@ class Config {
959
983
  }
960
984
 
961
985
  _setArray (obj, name, value) {
962
- if (value === null || value === undefined) {
986
+ if (value == null) {
963
987
  return this._setValue(obj, name, null)
964
988
  }
965
989
 
966
990
  if (typeof value === 'string') {
967
- value = value && value.split(',')
991
+ value = value.split(',')
968
992
  }
969
993
 
970
994
  if (Array.isArray(value)) {
@@ -972,6 +996,25 @@ class Config {
972
996
  }
973
997
  }
974
998
 
999
+ _setSamplingRule (obj, name, value) {
1000
+ if (value == null) {
1001
+ return this._setValue(obj, name, null)
1002
+ }
1003
+
1004
+ if (typeof value === 'string') {
1005
+ value = value.split(',')
1006
+ }
1007
+
1008
+ if (Array.isArray(value)) {
1009
+ value = value.map(rule => {
1010
+ return remapify(rule, {
1011
+ sample_rate: 'sampleRate'
1012
+ })
1013
+ })
1014
+ this._setValue(obj, name, value)
1015
+ }
1016
+ }
1017
+
975
1018
  _setString (obj, name, value) {
976
1019
  obj[name] = value ? String(value) : undefined // unset for empty strings
977
1020
  }
@@ -120,7 +120,7 @@ class AgentEncoder {
120
120
  this._encodeMap(bytes, span.metrics)
121
121
  if (span.meta_struct) {
122
122
  this._encodeString(bytes, 'meta_struct')
123
- this._encodeObject(bytes, span.meta_struct)
123
+ this._encodeMetaStruct(bytes, span.meta_struct)
124
124
  }
125
125
  }
126
126
  }
@@ -271,12 +271,48 @@ class AgentEncoder {
271
271
  }
272
272
  }
273
273
 
274
+ _encodeMetaStruct (bytes, value) {
275
+ const keys = Array.isArray(value) ? [] : Object.keys(value)
276
+ const validKeys = keys.filter(key => {
277
+ const v = value[key]
278
+ return typeof v === 'string' ||
279
+ typeof v === 'number' ||
280
+ (v !== null && typeof v === 'object')
281
+ })
282
+
283
+ this._encodeMapPrefix(bytes, validKeys.length)
284
+
285
+ for (const key of validKeys) {
286
+ const v = value[key]
287
+ this._encodeString(bytes, key)
288
+ this._encodeObjectAsByteArray(bytes, v)
289
+ }
290
+ }
291
+
292
+ _encodeObjectAsByteArray (bytes, value) {
293
+ const prefixLength = 5
294
+ const offset = bytes.length
295
+
296
+ bytes.reserve(prefixLength)
297
+ bytes.length += prefixLength
298
+
299
+ this._encodeObject(bytes, value)
300
+
301
+ // we should do it after encoding the object to know the real length
302
+ const length = bytes.length - offset - prefixLength
303
+ bytes.buffer[offset] = 0xc6
304
+ bytes.buffer[offset + 1] = length >> 24
305
+ bytes.buffer[offset + 2] = length >> 16
306
+ bytes.buffer[offset + 3] = length >> 8
307
+ bytes.buffer[offset + 4] = length
308
+ }
309
+
274
310
  _encodeObject (bytes, value, circularReferencesDetector = new Set()) {
275
311
  circularReferencesDetector.add(value)
276
312
  if (Array.isArray(value)) {
277
- return this._encodeObjectAsArray(bytes, value, circularReferencesDetector)
313
+ this._encodeObjectAsArray(bytes, value, circularReferencesDetector)
278
314
  } else if (value !== null && typeof value === 'object') {
279
- return this._encodeObjectAsMap(bytes, value, circularReferencesDetector)
315
+ this._encodeObjectAsMap(bytes, value, circularReferencesDetector)
280
316
  } else if (typeof value === 'string' || typeof value === 'number') {
281
317
  this._encodeValue(bytes, value)
282
318
  }
@@ -284,16 +320,19 @@ class AgentEncoder {
284
320
 
285
321
  _encodeObjectAsMap (bytes, value, circularReferencesDetector) {
286
322
  const keys = Object.keys(value)
287
- const validKeys = keys.filter(key =>
288
- typeof value[key] === 'string' ||
289
- typeof value[key] === 'number' ||
290
- (value[key] !== null && typeof value[key] === 'object' && !circularReferencesDetector.has(value[key])))
323
+ const validKeys = keys.filter(key => {
324
+ const v = value[key]
325
+ return typeof v === 'string' ||
326
+ typeof v === 'number' ||
327
+ (v !== null && typeof v === 'object' && !circularReferencesDetector.has(v))
328
+ })
291
329
 
292
330
  this._encodeMapPrefix(bytes, validKeys.length)
293
331
 
294
332
  for (const key of validKeys) {
333
+ const v = value[key]
295
334
  this._encodeString(bytes, key)
296
- this._encodeObject(bytes, value[key], circularReferencesDetector)
335
+ this._encodeObject(bytes, v, circularReferencesDetector)
297
336
  }
298
337
  }
299
338
 
@@ -19,6 +19,7 @@ module.exports = name => {
19
19
  return require('./ci-visibility/exporters/agent-proxy')
20
20
  case exporters.JEST_WORKER:
21
21
  case exporters.CUCUMBER_WORKER:
22
+ case exporters.MOCHA_WORKER:
22
23
  return require('./ci-visibility/exporters/test-worker')
23
24
  default:
24
25
  return inAWSLambda && !usingLambdaExtension ? require('./exporters/log') : require('./exporters/agent')
@@ -0,0 +1,44 @@
1
+ 'use strict'
2
+
3
+ const { Writable } = require('stream')
4
+
5
+ const INITIAL_SIZE = 64 * 1024
6
+
7
+ class FlareFile extends Writable {
8
+ constructor () {
9
+ super()
10
+
11
+ this.length = 0
12
+
13
+ this._buffer = Buffer.alloc(INITIAL_SIZE)
14
+ }
15
+
16
+ get data () {
17
+ return this._buffer.subarray(0, this.length)
18
+ }
19
+
20
+ _write (chunk, encoding, callback) {
21
+ const length = Buffer.byteLength(chunk)
22
+
23
+ this._reserve(length)
24
+
25
+ if (Buffer.isBuffer(chunk)) {
26
+ this.length += chunk.copy(this._buffer, this.length)
27
+ } else {
28
+ this.length += this._buffer.write(chunk, encoding)
29
+ }
30
+
31
+ callback()
32
+ }
33
+
34
+ _reserve (length) {
35
+ while (this.length + length > this._buffer.length) {
36
+ const buffer = Buffer.alloc(this.length * 2)
37
+
38
+ this._buffer.copy(buffer)
39
+ this._buffer = buffer
40
+ }
41
+ }
42
+ }
43
+
44
+ module.exports = FlareFile
@@ -0,0 +1,98 @@
1
+ 'use strict'
2
+
3
+ const log = require('../log')
4
+ const startupLog = require('../startup-log')
5
+ const FlareFile = require('./file')
6
+ const { LogChannel } = require('../log/channels')
7
+ const request = require('../exporters/common/request')
8
+ const FormData = require('../exporters/common/form-data')
9
+
10
+ const MAX_LOG_SIZE = 12 * 1024 * 1024 // 12MB soft limit
11
+ const TIMEOUT = 20 * 1000 * 60
12
+
13
+ let logChannel = null
14
+ let tracerLogs = null
15
+ let timer
16
+ let tracerConfig = null
17
+
18
+ const logger = {
19
+ debug: (msg) => recordLog(msg),
20
+ info: (msg) => recordLog(msg),
21
+ warn: (msg) => recordLog(msg),
22
+ error: (err) => recordLog(err.stack)
23
+ }
24
+
25
+ const flare = {
26
+ enable (tracerConfig_) {
27
+ tracerConfig = tracerConfig_
28
+ },
29
+
30
+ disable () {
31
+ tracerConfig = null
32
+
33
+ flare.cleanup()
34
+ },
35
+
36
+ prepare (logLevel) {
37
+ if (!tracerConfig) return
38
+
39
+ logChannel?.unsubscribe(logger)
40
+ logChannel = new LogChannel(logLevel)
41
+ logChannel.subscribe(logger)
42
+ tracerLogs = tracerLogs || new FlareFile()
43
+ timer = timer || setTimeout(flare.cleanup, TIMEOUT)
44
+ },
45
+
46
+ send (task) {
47
+ if (!tracerConfig) return
48
+
49
+ const tracerInfo = new FlareFile()
50
+
51
+ tracerInfo.write(JSON.stringify(startupLog.tracerInfo(), null, 2))
52
+
53
+ flare._sendFile(task, tracerInfo, 'tracer_info.txt')
54
+ flare._sendFile(task, tracerLogs, 'tracer_logs.txt')
55
+
56
+ flare.cleanup()
57
+ },
58
+
59
+ cleanup () {
60
+ logChannel?.unsubscribe(logger)
61
+ timer = clearTimeout(timer)
62
+ logChannel = null
63
+ tracerLogs = null
64
+ },
65
+
66
+ _sendFile (task, file, filename) {
67
+ if (!file) return
68
+
69
+ const form = new FormData()
70
+
71
+ form.append('case_id', task.case_id)
72
+ form.append('hostname', task.hostname)
73
+ form.append('email', task.user_handle)
74
+ form.append('source', 'tracer_nodejs')
75
+ form.append('flare_file', file.data, { filename })
76
+
77
+ request(form, {
78
+ url: tracerConfig.url,
79
+ hostname: tracerConfig.hostname,
80
+ port: tracerConfig.port,
81
+ method: 'POST',
82
+ path: '/tracer_flare/v1',
83
+ headers: form.getHeaders()
84
+ }, (err) => {
85
+ if (err) {
86
+ log.error(err)
87
+ }
88
+ })
89
+ }
90
+ }
91
+
92
+ function recordLog (msg) {
93
+ if (tracerLogs.length > MAX_LOG_SIZE) return
94
+
95
+ tracerLogs.write(`${msg}\n`) // TODO: gzip
96
+ }
97
+
98
+ module.exports = flare