datadog 2.36.0 → 2.37.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 (88) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -1
  3. data/ext/datadog_profiling_native_extension/clock_id_from_mach.c +5 -12
  4. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +6 -3
  5. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +17 -3
  6. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +219 -98
  7. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +2 -0
  8. data/ext/datadog_profiling_native_extension/extconf.rb +11 -0
  9. data/ext/datadog_profiling_native_extension/ruby_helpers.c +7 -0
  10. data/ext/libdatadog_api/di.c +76 -0
  11. data/ext/libdatadog_api/extconf.rb +6 -0
  12. data/ext/libdatadog_extconf_helpers.rb +1 -1
  13. data/lib/datadog/ai_guard/api_client.rb +6 -4
  14. data/lib/datadog/appsec/api_security/route_extractor.rb +7 -4
  15. data/lib/datadog/appsec/configuration.rb +6 -0
  16. data/lib/datadog/appsec/context.rb +4 -2
  17. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +59 -4
  18. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +59 -5
  19. data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +1 -1
  20. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +4 -0
  21. data/lib/datadog/appsec/contrib/rack/input_peeker.rb +4 -17
  22. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +21 -0
  23. data/lib/datadog/appsec/contrib/rails/gateway/request.rb +2 -1
  24. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +60 -7
  25. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +4 -0
  26. data/lib/datadog/appsec/ext.rb +1 -0
  27. data/lib/datadog/appsec/metrics/collector.rb +18 -1
  28. data/lib/datadog/appsec/metrics/exporter.rb +9 -3
  29. data/lib/datadog/appsec/remote.rb +9 -3
  30. data/lib/datadog/appsec/route_normalizer/rails_route_pattern.rb +176 -0
  31. data/lib/datadog/appsec/route_normalizer/route_pattern.rb +378 -0
  32. data/lib/datadog/appsec/route_normalizer/route_text.rb +57 -0
  33. data/lib/datadog/appsec/route_normalizer.rb +80 -0
  34. data/lib/datadog/appsec/utils/http/body_reader.rb +61 -0
  35. data/lib/datadog/core/configuration/components.rb +82 -2
  36. data/lib/datadog/core/configuration/components_state.rb +6 -1
  37. data/lib/datadog/core/configuration/settings.rb +0 -3
  38. data/lib/datadog/core/configuration/supported_configurations.rb +2 -0
  39. data/lib/datadog/core/evp.rb +11 -0
  40. data/lib/datadog/core/remote/client/capabilities.rb +34 -7
  41. data/lib/datadog/core/telemetry/event/app_started.rb +8 -1
  42. data/lib/datadog/di/base.rb +3 -1
  43. data/lib/datadog/di/code_tracker.rb +5 -2
  44. data/lib/datadog/di/component.rb +110 -36
  45. data/lib/datadog/di/error.rb +10 -0
  46. data/lib/datadog/di/fatal_exceptions.rb +26 -0
  47. data/lib/datadog/di/instrumenter.rb +381 -165
  48. data/lib/datadog/di/probe.rb +14 -0
  49. data/lib/datadog/di/probe_file_loader.rb +8 -3
  50. data/lib/datadog/di/probe_manager.rb +67 -9
  51. data/lib/datadog/di/probe_notification_builder.rb +4 -1
  52. data/lib/datadog/di/probe_notifier_worker.rb +52 -32
  53. data/lib/datadog/di/redactor.rb +16 -1
  54. data/lib/datadog/di/remote.rb +175 -14
  55. data/lib/datadog/di/serializer.rb +12 -5
  56. data/lib/datadog/di/transport/input.rb +8 -4
  57. data/lib/datadog/di.rb +63 -0
  58. data/lib/datadog/kit/appsec/events/v2.rb +60 -2
  59. data/lib/datadog/open_feature/component.rb +29 -6
  60. data/lib/datadog/open_feature/configuration.rb +8 -0
  61. data/lib/datadog/open_feature/ext.rb +2 -0
  62. data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +286 -0
  63. data/lib/datadog/open_feature/flag_evaluation/writer.rb +433 -0
  64. data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +101 -0
  65. data/lib/datadog/open_feature/hooks/{flag_eval_hook.rb → flag_eval_metrics_hook.rb} +1 -1
  66. data/lib/datadog/open_feature/native_evaluator.rb +22 -0
  67. data/lib/datadog/open_feature/provider.rb +59 -27
  68. data/lib/datadog/open_feature/transport.rb +55 -1
  69. data/lib/datadog/opentelemetry/sdk/logs_exporter.rb +5 -12
  70. data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +4 -11
  71. data/lib/datadog/opentelemetry/sdk.rb +19 -0
  72. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +1 -1
  73. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +5 -3
  74. data/lib/datadog/symbol_database/component.rb +164 -47
  75. data/lib/datadog/symbol_database/configuration.rb +12 -4
  76. data/lib/datadog/symbol_database/extensions.rb +19 -0
  77. data/lib/datadog/symbol_database/extractor.rb +124 -87
  78. data/lib/datadog/symbol_database/file_hash.rb +3 -1
  79. data/lib/datadog/symbol_database/remote.rb +8 -3
  80. data/lib/datadog/symbol_database/scope_batcher.rb +7 -3
  81. data/lib/datadog/symbol_database/uploader.rb +9 -2
  82. data/lib/datadog/symbol_database.rb +22 -0
  83. data/lib/datadog/tracing/distributed/baggage.rb +2 -1
  84. data/lib/datadog/tracing/ext.rb +9 -0
  85. data/lib/datadog/tracing/remote.rb +34 -3
  86. data/lib/datadog/version.rb +1 -1
  87. data/lib/datadog.rb +1 -0
  88. metadata +19 -6
@@ -4,6 +4,7 @@ require_relative 'scope'
4
4
  require_relative 'symbol'
5
5
  require_relative 'file_hash'
6
6
  require_relative '../core/utils/enumerable_compat'
7
+ require_relative '../di/fatal_exceptions'
7
8
 
8
9
  module Datadog
9
10
  module SymbolDatabase
@@ -89,6 +90,26 @@ module Datadog
89
90
  # so we bind the original Module#name to get the real module name safely.
90
91
  MODULE_NAME = Module.instance_method(:name)
91
92
 
93
+ # Cached UnboundMethods for the remaining class/module/object introspection
94
+ # used during extraction. Like MODULE_NAME, these bind the original
95
+ # implementations and are dispatched explicitly so that application code
96
+ # which overrides any of them (per-class, on a subclass, or in a singleton
97
+ # class) can neither intercept extraction nor be executed as a side effect
98
+ # of it. KERNEL_CLASS recovers an object's real class without calling a
99
+ # possibly-overridden #class.
100
+ CLASS_SUPERCLASS = Class.instance_method(:superclass)
101
+ MODULE_INCLUDED_MODULES = Module.instance_method(:included_modules)
102
+ MODULE_ANCESTORS = Module.instance_method(:ancestors)
103
+ MODULE_CLASS_VARIABLES = Module.instance_method(:class_variables)
104
+ MODULE_CONSTANTS = Module.instance_method(:constants)
105
+ MODULE_AUTOLOAD_P = Module.instance_method(:autoload?)
106
+ MODULE_CONST_GET = Module.instance_method(:const_get)
107
+ MODULE_CONST_DEFINED = Module.instance_method(:const_defined?)
108
+ KERNEL_CLASS = ::Kernel.instance_method(:class)
109
+ private_constant :CLASS_SUPERCLASS, :MODULE_INCLUDED_MODULES, :MODULE_ANCESTORS,
110
+ :MODULE_CLASS_VARIABLES, :MODULE_CONSTANTS, :MODULE_AUTOLOAD_P, :MODULE_CONST_GET,
111
+ :MODULE_CONST_DEFINED, :KERNEL_CLASS
112
+
92
113
  # @param logger [Logger] Logger instance (SymbolDatabase::Logger facade or compatible)
93
114
  # @param settings [Configuration::Settings] Tracer settings
94
115
  def initialize(logger:, settings:)
@@ -109,7 +130,7 @@ module Datadog
109
130
  # @param mod [Module, Class] The module or class to extract from
110
131
  # @return [Scope, nil] FILE scope wrapping extracted scope, or nil if filtered out
111
132
  def extract(mod)
112
- return nil unless mod.is_a?(Module)
133
+ return nil unless Module === mod
113
134
  mod_name = safe_mod_name(mod)
114
135
  return nil unless mod_name
115
136
 
@@ -118,14 +139,15 @@ module Datadog
118
139
  source_file = find_source_file(mod)
119
140
  return nil unless source_file
120
141
 
121
- inner_scope = if mod.is_a?(Class)
142
+ inner_scope = if Class === mod
122
143
  extract_class_scope(mod)
123
144
  else
124
145
  extract_module_scope(mod)
125
146
  end
126
147
 
127
148
  wrap_in_file_scope(source_file, [inner_scope])
128
- rescue => e
149
+ rescue Exception => e # standard:disable Lint/RescueException
150
+ Datadog::DI.reraise_if_fatal(e)
129
151
  @logger.debug { "symdb: failed to extract #{mod_name || '<unknown>'}: #{e.class}: #{e.message}" }
130
152
  nil
131
153
  end
@@ -174,7 +196,7 @@ module Datadog
174
196
  # returns [key, value] on a non-empty hash and nil when empty, so the
175
197
  # `while (pair = ...)` form is the drain. Indexing pair[0]/pair[1] rather
176
198
  # than destructuring avoids introducing names into method scope that would
177
- # then shadow the else-branch's block parameters on Ruby 2.5/2.6.
199
+ # then shadow the else-branch's block parameters.
178
200
  while (pair = index.shift)
179
201
  scope = build_file_scope(pair[0], pair[1])
180
202
  yield scope if scope
@@ -190,7 +212,8 @@ module Datadog
190
212
  end
191
213
  result
192
214
  end
193
- rescue => e
215
+ rescue Exception => e # standard:disable Lint/RescueException
216
+ Datadog::DI.reraise_if_fatal(e)
194
217
  @logger.debug { "symdb: error in extract_all: #{e.class}: #{e.message}" }
195
218
  block_given? ? nil : []
196
219
  end
@@ -204,7 +227,8 @@ module Datadog
204
227
  # @return [String, nil] Module name or nil
205
228
  def safe_mod_name(mod)
206
229
  MODULE_NAME.bind(mod).call
207
- rescue => e
230
+ rescue Exception => e # standard:disable Lint/RescueException
231
+ Datadog::DI.reraise_if_fatal(e)
208
232
  @logger.debug { "symdb: safe_mod_name failed: #{e.class}: #{e.message}" }
209
233
  nil
210
234
  end
@@ -228,15 +252,10 @@ module Datadog
228
252
  # subclass with its own binding resolves through the binding, an
229
253
  # inherited autoload triggers nothing.
230
254
  #
231
- # Ruby 2.7+ adds an inherit parameter to Module#autoload? —
232
- # `current.autoload?(sym, false)` cleanly asks "is there an autoload
233
- # registered directly on this namespace?". On Ruby 2.5 and 2.6 that
234
- # parameter doesn't exist (per Ruby 2.7.0 NEWS), so the fallback
235
- # branch uses `current.autoload?(sym)` which also reports inherited
236
- # autoloads. The consequence on 2.5/2.6: a subclass binding whose
237
- # name collides with an ancestor's pending autoload is conservatively
238
- # treated as stale and dropped from extraction. The minimum supported
239
- # Ruby is 2.5 per lib/datadog/version.rb, so this fallback ships.
255
+ # Uses `current.autoload?(sym, false)` (the inherit=false form, added
256
+ # in Ruby 2.7) so the question is strictly "is there an autoload
257
+ # registered directly on this namespace?" and ancestors' pending
258
+ # autoloads at the same name do not affect the result.
240
259
  # @param mod_name [String]
241
260
  # @param mod [Module]
242
261
  # @return [Boolean]
@@ -244,17 +263,12 @@ module Datadog
244
263
  current = Object
245
264
  mod_name.split('::').each do |seg|
246
265
  sym = seg.to_sym
247
- pending_autoload = if RubyVersion.is?('>= 2.7')
248
- current.autoload?(sym, false)
249
- else
250
- current.autoload?(sym)
251
- end
252
- return false if pending_autoload
253
- return false unless current.const_defined?(sym, false)
254
- current = current.const_get(sym, false)
266
+ return false if MODULE_AUTOLOAD_P.bind(current).call(sym, false)
267
+ return false unless MODULE_CONST_DEFINED.bind(current).call(sym, false)
268
+ current = MODULE_CONST_GET.bind(current).call(sym, false)
255
269
  end
256
270
  current.equal?(mod)
257
- rescue NameError, ArgumentError
271
+ rescue NameError, ArgumentError, TypeError
258
272
  # Expected "no" outcome for stale/detached classes — the whole point
259
273
  # of this predicate. Per the rescue convention in this file's header
260
274
  # comment: inner per-item rescues are expected failures, no logging.
@@ -324,15 +338,10 @@ module Datadog
324
338
  # AR models get filtered out as gem code.
325
339
  #
326
340
  # For namespace-only modules (no instance or singleton methods), falls back to
327
- # Module#const_source_location (Ruby 2.7+) to locate the module via its constants.
341
+ # Module#const_source_location to locate the module via its constants.
328
342
  # This handles patterns like `module ApplicationCable; class Channel...; end; end`
329
343
  # where the namespace module itself has no methods but defines user-code classes.
330
344
  #
331
- # On Ruby 2.6 (where const_source_location is unavailable), namespace-only modules
332
- # and classes whose only methods are generated (e.g., AR models with only associations)
333
- # may not be found — the extraction silently omits them. This is a graceful degradation:
334
- # fewer symbols uploaded, no errors.
335
- #
336
345
  # @param mod [Module] The module
337
346
  # @return [String, nil] Source file path or nil
338
347
  def find_source_file(mod)
@@ -362,18 +371,19 @@ module Datadog
362
371
  fallback ||= path # steep:ignore
363
372
  end
364
373
 
365
- # Try const_source_location (Ruby 2.7+) to find where this class/module is declared.
374
+ # Use const_source_location to find where this class/module is declared.
366
375
  # This handles two cases:
367
376
  # 1. Classes with no user-defined methods (e.g. AR models with only associations) whose
368
377
  # generated methods point to gem code — we find the `class Foo` declaration instead.
369
378
  # 2. Namespace-only modules (`module Foo; class Bar; end; end`) with no methods at all.
370
- if Module.method_defined?(:const_source_location) && mod.name
379
+ mod_name = safe_mod_name(mod)
380
+ if mod_name
371
381
  # Look up the class/module by its last name component in its enclosing namespace.
372
- parts = mod.name.split('::')
382
+ parts = mod_name.split('::')
373
383
  const_name = parts.last
374
384
  namespace = if parts.length > 1
375
385
  begin
376
- Object.const_get(parts[0..-2].join('::')) # steep:ignore
386
+ MODULE_CONST_GET.bind(Object).call(parts[0..-2].join('::')) # steep:ignore
377
387
  rescue NameError
378
388
  nil
379
389
  end
@@ -384,7 +394,8 @@ module Datadog
384
394
  if namespace
385
395
  location = begin
386
396
  namespace.const_source_location(const_name)
387
- rescue => e
397
+ rescue Exception => e # standard:disable Lint/RescueException
398
+ Datadog::DI.reraise_if_fatal(e)
388
399
  @logger.debug { "symdb: const_source_location(#{const_name}) failed: #{e.class}: #{e.message}" }
389
400
  nil
390
401
  end
@@ -397,10 +408,11 @@ module Datadog
397
408
  end
398
409
 
399
410
  # Also scan constants defined by mod itself (namespace-only modules).
400
- mod.constants(false).each do |child_const_name|
411
+ MODULE_CONSTANTS.bind(mod).call(false).each do |child_const_name|
401
412
  location = begin
402
413
  mod.const_source_location(child_const_name)
403
- rescue => e
414
+ rescue Exception => e # standard:disable Lint/RescueException
415
+ Datadog::DI.reraise_if_fatal(e)
404
416
  @logger.debug { "symdb: const_source_location(#{child_const_name}) failed: #{e.class}: #{e.message}" }
405
417
  nil
406
418
  end
@@ -416,7 +428,8 @@ module Datadog
416
428
  end
417
429
 
418
430
  fallback
419
- rescue => e
431
+ rescue Exception => e # standard:disable Lint/RescueException
432
+ Datadog::DI.reraise_if_fatal(e)
420
433
  @logger.debug { "symdb: error finding source file for #{safe_mod_name(mod) || '<unknown>'}: #{e.class}: #{e.message}" }
421
434
  nil
422
435
  end
@@ -453,7 +466,7 @@ module Datadog
453
466
 
454
467
  Scope.new(
455
468
  scope_type: 'MODULE',
456
- name: mod.name,
469
+ name: safe_mod_name(mod),
457
470
  source_file: source_file,
458
471
  start_line: UNKNOWN_MIN_LINE,
459
472
  end_line: UNKNOWN_MAX_LINE,
@@ -471,7 +484,7 @@ module Datadog
471
484
 
472
485
  Scope.new(
473
486
  scope_type: 'CLASS',
474
- name: klass.name,
487
+ name: safe_mod_name(klass),
475
488
  source_file: source_file,
476
489
  start_line: start_line,
477
490
  end_line: end_line,
@@ -502,8 +515,9 @@ module Datadog
502
515
  return [UNKNOWN_MIN_LINE, UNKNOWN_MAX_LINE] if starts.empty?
503
516
 
504
517
  [starts.min, ends.max]
505
- rescue => e
506
- @logger.debug { "symdb: error calculating line range for #{klass.name}: #{e.class}: #{e.message}" }
518
+ rescue Exception => e # standard:disable Lint/RescueException
519
+ Datadog::DI.reraise_if_fatal(e)
520
+ @logger.debug { "symdb: error calculating line range for #{safe_mod_name(klass)}: #{e.class}: #{e.message}" }
507
521
  [UNKNOWN_MIN_LINE, UNKNOWN_MAX_LINE]
508
522
  end
509
523
 
@@ -517,8 +531,9 @@ module Datadog
517
531
  # Emitted as an array named super_classes — consistent with Java, .NET, and Python.
518
532
  # Array allows for multiple entries if future Ruby versions or mixins expand the chain.
519
533
  # Anonymous superclasses (class Foo < Class.new { ... }) have nil name; compact to skip.
520
- if klass.superclass && klass.superclass != Object && klass.superclass != BasicObject
521
- super_name = klass.superclass.name # steep:ignore
534
+ superclass = CLASS_SUPERCLASS.bind(klass).call
535
+ if superclass && !superclass.equal?(Object) && !superclass.equal?(BasicObject)
536
+ super_name = safe_mod_name(superclass)
522
537
  specifics[:super_classes] = [super_name] if super_name
523
538
  end
524
539
 
@@ -526,7 +541,7 @@ module Datadog
526
541
  # included_modules returns the entire ancestor chain's mixins, not only directly
527
542
  # included ones. This is intentional: the field reports "modules this class
528
543
  # responds to," which is what the consumer (UI navigation, probe context) needs.
529
- included = klass.included_modules.map(&:name).reject do |name|
544
+ included = MODULE_INCLUDED_MODULES.bind(klass).call.map { |m| safe_mod_name(m) }.reject do |name|
530
545
  name.nil? || EXCLUDED_COMMON_MODULES.any? { |prefix| name.start_with?(prefix) }
531
546
  end
532
547
  specifics[:included_modules] = included unless included.empty?
@@ -537,16 +552,17 @@ module Datadog
537
552
  # Single-pass collection avoids the intermediate arrays from take_while.map.compact.
538
553
  # Test coverage: spec/datadog/symbol_database/extractor_spec.rb tests prepend behavior.
539
554
  prepended = []
540
- klass.ancestors.each do |a|
541
- break if a == klass
542
- name = a.name
555
+ MODULE_ANCESTORS.bind(klass).call.each do |a|
556
+ break if a.equal?(klass)
557
+ name = safe_mod_name(a)
543
558
  prepended << name if name
544
559
  end
545
560
  specifics[:prepended_modules] = prepended unless prepended.empty?
546
561
 
547
562
  specifics
548
- rescue => e
549
- @logger.debug { "symdb: error building language specifics for #{klass.name}: #{e.class}: #{e.message}" }
563
+ rescue Exception => e # standard:disable Lint/RescueException
564
+ Datadog::DI.reraise_if_fatal(e)
565
+ @logger.debug { "symdb: error building language specifics for #{safe_mod_name(klass)}: #{e.class}: #{e.message}" }
550
566
  {}
551
567
  end
552
568
 
@@ -568,8 +584,9 @@ module Datadog
568
584
  end
569
585
 
570
586
  scopes
571
- rescue => e
572
- @logger.debug { "symdb: failed to extract methods from #{klass.name}: #{e.class}: #{e.message}" }
587
+ rescue Exception => e # standard:disable Lint/RescueException
588
+ Datadog::DI.reraise_if_fatal(e)
589
+ @logger.debug { "symdb: failed to extract methods from #{safe_mod_name(klass)}: #{e.class}: #{e.message}" }
573
590
  []
574
591
  end
575
592
 
@@ -603,8 +620,9 @@ module Datadog
603
620
  },
604
621
  symbols: extract_method_parameters(method)
605
622
  )
606
- rescue => e
607
- @logger.debug { "symdb: failed to extract method #{klass.name}##{method_name}: #{e.class}: #{e.message}" }
623
+ rescue Exception => e # standard:disable Lint/RescueException
624
+ Datadog::DI.reraise_if_fatal(e)
625
+ @logger.debug { "symdb: failed to extract method #{safe_mod_name(klass)}##{method_name}: #{e.class}: #{e.message}" }
608
626
  nil
609
627
  end
610
628
 
@@ -681,7 +699,8 @@ module Datadog
681
699
  def extract_method_parameters(method)
682
700
  method_name = begin
683
701
  method.name.to_s
684
- rescue => e
702
+ rescue Exception => e # standard:disable Lint/RescueException
703
+ Datadog::DI.reraise_if_fatal(e)
685
704
  @logger.debug { "symdb: method.name failed: #{e.class}: #{e.message}" }
686
705
  'unknown'
687
706
  end
@@ -703,7 +722,8 @@ module Datadog
703
722
  line: UNKNOWN_MIN_LINE, # Parameters available in entire method
704
723
  )
705
724
  end
706
- rescue => e
725
+ rescue Exception => e # standard:disable Lint/RescueException
726
+ Datadog::DI.reraise_if_fatal(e)
707
727
  @logger.debug { "symdb: failed to extract parameters from #{method_name}: #{e.class}: #{e.message}" }
708
728
  []
709
729
  end
@@ -743,11 +763,7 @@ module Datadog
743
763
  ObjectSpace.each_object(Module) do |mod|
744
764
  # Singleton classes (per-object metaclasses) are never user-code classes.
745
765
  # They're not const-referenced, DI cannot instrument methods on a singular
746
- # object instance, and on Ruby 2.6 specifically, Module#name on unnamed
747
- # singleton classes with long ancestor chains (e.g. through monkey-patches
748
- # prepended into Kernel, common in dd-trace-rb test processes) is O(ancestors)
749
- # — measured ~20ms per call, which dominates extract_all on heavily-loaded
750
- # processes. Ruby 2.7+ optimized this path; the skip is a no-op there.
766
+ # object instance, so skipping them is both correct and cheap.
751
767
  next if MODULE_SINGLETON_CLASS_PRED.bind(mod).call
752
768
 
753
769
  seen += 1
@@ -772,7 +788,8 @@ module Datadog
772
788
  file_to_names.each do |file_path, method_names|
773
789
  (index[file_path] ||= []) << [mod_name, mod, method_names]
774
790
  end
775
- rescue => e
791
+ rescue Exception => e # standard:disable Lint/RescueException
792
+ Datadog::DI.reraise_if_fatal(e)
776
793
  @logger.debug { "symdb: error indexing #{mod_name || '<unknown>'}: #{e.class}: #{e.message}" }
777
794
  end
778
795
 
@@ -797,13 +814,15 @@ module Datadog
797
814
  next unless user_code_path?(loc[0])
798
815
 
799
816
  result[loc[0]] << method_name
800
- rescue => e
817
+ rescue Exception => e # standard:disable Lint/RescueException
818
+ Datadog::DI.reraise_if_fatal(e)
801
819
  @logger.debug { "symdb: error indexing method #{method_name}: #{e.class}: #{e.message}" }
802
820
  end
803
821
  end
804
822
 
805
823
  result
806
- rescue => e
824
+ rescue Exception => e # standard:disable Lint/RescueException
825
+ Datadog::DI.reraise_if_fatal(e)
807
826
  @logger.debug { "symdb: error indexing methods: #{e.class}: #{e.message}" }
808
827
  {}
809
828
  end
@@ -844,7 +863,8 @@ module Datadog
844
863
  loc = method.source_location
845
864
  next nil unless loc && loc[0] == file_path
846
865
  {name: name, method: method, type: :instance}
847
- rescue => e
866
+ rescue Exception => e # standard:disable Lint/RescueException
867
+ Datadog::DI.reraise_if_fatal(e)
848
868
  @logger.debug { "symdb: error resolving #{mod_name}##{name}: #{e.class}: #{e.message}" }
849
869
  nil
850
870
  end
@@ -857,7 +877,8 @@ module Datadog
857
877
 
858
878
  parts = mod_name.split('::')
859
879
  place_in_tree(root, parts, mod, mod_name, method_infos, file_path)
860
- rescue => e
880
+ rescue Exception => e # standard:disable Lint/RescueException
881
+ Datadog::DI.reraise_if_fatal(e)
861
882
  @logger.debug { "symdb: error placing #{mod_name} in tree: #{e.class}: #{e.message}" }
862
883
  end
863
884
 
@@ -895,12 +916,12 @@ module Datadog
895
916
  if leaf
896
917
  # Node exists (was created as intermediate or from another entry).
897
918
  # Update type and mod — the actual module object is authoritative.
898
- leaf[:type] = mod.is_a?(Class) ? 'CLASS' : 'MODULE'
919
+ leaf[:type] = (Class === mod) ? 'CLASS' : 'MODULE'
899
920
  leaf[:mod] = mod
900
921
  else
901
922
  leaf = {
902
923
  name: mod_name,
903
- type: mod.is_a?(Class) ? 'CLASS' : 'MODULE',
924
+ type: (Class === mod) ? 'CLASS' : 'MODULE',
904
925
  children: {}, methods: [],
905
926
  mod: mod, source_file: file_path,
906
927
  fqn: mod_name
@@ -917,9 +938,21 @@ module Datadog
917
938
  # @param fqn [String] Fully-qualified name (e.g. "Authentication::Strategies")
918
939
  # @return [String] 'CLASS' or 'MODULE'
919
940
  def resolve_scope_type(fqn)
920
- const = Object.const_get(fqn)
921
- const.is_a?(Class) ? 'CLASS' : 'MODULE'
922
- rescue => e
941
+ current = Object
942
+ fqn.split('::').each do |seg|
943
+ sym = seg.to_sym
944
+ pending_autoload = if RubyVersion.is?('>= 2.7')
945
+ MODULE_AUTOLOAD_P.bind(current).call(sym, false)
946
+ else
947
+ MODULE_AUTOLOAD_P.bind(current).call(sym)
948
+ end
949
+ return 'MODULE' if pending_autoload
950
+ return 'MODULE' unless MODULE_CONST_DEFINED.bind(current).call(sym, false)
951
+ current = MODULE_CONST_GET.bind(current).call(sym, false)
952
+ end
953
+ (Class === current) ? 'CLASS' : 'MODULE'
954
+ rescue Exception => e # standard:disable Lint/RescueException
955
+ Datadog::DI.reraise_if_fatal(e)
923
956
  @logger.debug { "symdb: resolve_scope_type(#{fqn}) failed: #{e.class}: #{e.message}, defaulting to MODULE" }
924
957
  'MODULE'
925
958
  end
@@ -1014,7 +1047,8 @@ module Datadog
1014
1047
  },
1015
1048
  symbols: extract_method_parameters(method)
1016
1049
  )
1017
- rescue => e
1050
+ rescue Exception => e # standard:disable Lint/RescueException
1051
+ Datadog::DI.reraise_if_fatal(e)
1018
1052
  klass_name = klass ? (safe_mod_name(klass) || '<unknown>') : '<unknown>'
1019
1053
  @logger.debug { "symdb: failed to build method scope #{klass_name}##{method_name}: #{e.class}: #{e.message}" }
1020
1054
  nil
@@ -1028,8 +1062,8 @@ module Datadog
1028
1062
  symbols = []
1029
1063
 
1030
1064
  # Class variables (only for classes)
1031
- if mod.is_a?(Class)
1032
- mod.class_variables(false).each do |var_name|
1065
+ if Class === mod
1066
+ MODULE_CLASS_VARIABLES.bind(mod).call(false).each do |var_name|
1033
1067
  symbols << Symbol.new(
1034
1068
  symbol_type: 'STATIC_FIELD',
1035
1069
  name: var_name.to_s,
@@ -1040,30 +1074,33 @@ module Datadog
1040
1074
 
1041
1075
  # Constants (excluding nested modules/classes).
1042
1076
  # Skip autoloaded constants to avoid triggering loading as a side effect.
1043
- mod.constants(false).each do |const_name|
1044
- next if mod.autoload?(const_name)
1045
- const_value = mod.const_get(const_name)
1046
- next if const_value.is_a?(Module)
1077
+ MODULE_CONSTANTS.bind(mod).call(false).each do |const_name|
1078
+ next if MODULE_AUTOLOAD_P.bind(mod).call(const_name)
1079
+ const_value = MODULE_CONST_GET.bind(mod).call(const_name)
1080
+ next if Module === const_value
1047
1081
 
1048
1082
  symbols << Symbol.new(
1049
1083
  symbol_type: 'STATIC_FIELD',
1050
1084
  name: const_name.to_s,
1051
1085
  line: UNKNOWN_MIN_LINE,
1052
- type: const_value.class.name
1086
+ type: safe_mod_name(KERNEL_CLASS.bind(const_value).call)
1053
1087
  )
1054
- rescue NameError, LoadError, NoMethodError => e # standard:disable Lint/ShadowedException
1055
- # Expected: constant removed/undefined, autoload failure, or const value missing
1056
- # #class. Logged separately from unexpected errors so the latter stand out in triage.
1057
- # Lint/ShadowedException disabled: NameError/NoMethodError do descend from
1058
- # StandardError, but Ruby's rescue-clause-order semantics ensure the bare rescue
1059
- # below only catches exceptions not matched here.
1088
+ rescue NameError, LoadError, NoMethodError, TypeError => e # standard:disable Lint/ShadowedException
1089
+ # Expected: constant removed/undefined (NameError), autoload failure (LoadError),
1090
+ # or a value whose class cannot be read (NoMethodError/TypeError). Skipping one
1091
+ # constant here keeps the rest of the module's symbols. Logged separately from
1092
+ # unexpected errors so the latter stand out in triage. Lint/ShadowedException
1093
+ # disabled: these descend from StandardError, but Ruby's rescue-clause-order
1094
+ # semantics ensure the catch-all rescue below only catches exceptions not matched here.
1060
1095
  @logger.debug { "symdb: skipping module constant #{const_name}: #{e.class}: #{e.message}" }
1061
- rescue => e
1096
+ rescue Exception => e # standard:disable Lint/RescueException
1097
+ Datadog::DI.reraise_if_fatal(e)
1062
1098
  @logger.debug { "symdb: unexpected error reading module constant #{const_name}: #{e.class}: #{e.message}" }
1063
1099
  end
1064
1100
 
1065
1101
  symbols
1066
- rescue => e
1102
+ rescue Exception => e # standard:disable Lint/RescueException
1103
+ Datadog::DI.reraise_if_fatal(e)
1067
1104
  mod_name = safe_mod_name(mod) || '<unknown>'
1068
1105
  @logger.debug { "symdb: failed to extract symbols from #{mod_name}: #{e.class}: #{e.message}" }
1069
1106
  []
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'digest/sha1'
4
+ require_relative '../di/fatal_exceptions'
4
5
 
5
6
  module Datadog
6
7
  module SymbolDatabase
@@ -37,7 +38,8 @@ module Datadog
37
38
  # This is not a security vulnerability - we're computing file content hashes
38
39
  # to match against Git objects, not using SHA-1 for authentication/integrity.
39
40
  Digest::SHA1.hexdigest(git_blob) # nosemgrep: ruby.lang.security.weak-hashes-sha1.weak-hashes-sha1
40
- rescue => e
41
+ rescue Exception => e # standard:disable Lint/RescueException
42
+ Datadog::DI.reraise_if_fatal(e)
41
43
  logger.debug { "symdb: file hash failed for #{file_path}: #{e.class}: #{e.message}" }
42
44
  nil
43
45
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../di/fatal_exceptions'
4
+
3
5
  module Datadog
4
6
  module SymbolDatabase
5
7
  # Provides remote configuration integration for symbol database.
@@ -44,7 +46,8 @@ module Datadog
44
46
  telemetry = lookup_telemetry
45
47
  component = begin
46
48
  Datadog.send(:components, allow_initialization: false)&.symbol_database
47
- rescue => e
49
+ rescue Exception => e # standard:disable Lint/RescueException
50
+ Datadog::DI.reraise_if_fatal(e)
48
51
  Datadog.logger.debug { "symdb: failed to look up component in RC receiver: #{e.class}: #{e.message}" }
49
52
  telemetry&.report(e, description: 'symdb: failed to look up component in RC receiver')
50
53
  nil
@@ -76,7 +79,8 @@ module Datadog
76
79
  # @api private
77
80
  def lookup_telemetry
78
81
  Datadog.send(:components, allow_initialization: false)&.telemetry
79
- rescue
82
+ rescue Exception => e # standard:disable Lint/RescueException
83
+ Datadog::DI.reraise_if_fatal(e)
80
84
  nil
81
85
  end
82
86
 
@@ -107,7 +111,8 @@ module Datadog
107
111
  # the Repository::Change union type where `Deleted` lacks `content`.
108
112
  change.content.errored("Unrecognized change type: #{change.type}") if change.respond_to?(:content) # steep:ignore NoMethod
109
113
  end
110
- rescue => e
114
+ rescue Exception => e # standard:disable Lint/RescueException
115
+ Datadog::DI.reraise_if_fatal(e)
111
116
  component.logger.debug { "symdb: error processing remote config change: #{e.class}: #{e.message}" }
112
117
  telemetry&.report(e, description: 'symdb: error processing remote config change')
113
118
  # Rescue runs regardless of which branch raised — Steep cannot narrow the
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'set'
4
+ require_relative '../di/fatal_exceptions'
4
5
 
5
6
  module Datadog
6
7
  module SymbolDatabase
@@ -122,7 +123,8 @@ module Datadog
122
123
 
123
124
  # Upload outside mutex (if batch was full)
124
125
  perform_upload(scopes_to_upload) if scopes_to_upload
125
- rescue => e
126
+ rescue Exception => e # standard:disable Lint/RescueException
127
+ Datadog::DI.reraise_if_fatal(e)
126
128
  @logger.debug { "symdb: failed to add scope: #{e.class}: #{e.message}" }
127
129
  # Don't propagate, continue operation
128
130
  end
@@ -267,7 +269,8 @@ module Datadog
267
269
  flush
268
270
  end
269
271
  end
270
- rescue => e
272
+ rescue Exception => e # standard:disable Lint/RescueException
273
+ Datadog::DI.reraise_if_fatal(e)
271
274
  @logger.debug { "symdb: timer thread error: #{e.class}: #{e.message}" }
272
275
  end
273
276
 
@@ -279,7 +282,8 @@ module Datadog
279
282
 
280
283
  @uploader.upload_scopes(scopes)
281
284
  @on_upload&.call(scopes) # Notify tests after upload
282
- rescue => e
285
+ rescue Exception => e # standard:disable Lint/RescueException
286
+ Datadog::DI.reraise_if_fatal(e)
283
287
  @logger.debug { "symdb: upload failed: #{e.class}: #{e.message}" }
284
288
  # Don't propagate, uploader handles retries
285
289
  end
@@ -6,8 +6,10 @@ require 'zlib'
6
6
  require 'stringio'
7
7
  require_relative '../core/environment/identity'
8
8
  require_relative '../core/vendor/multipart-post/multipart/post/composite_read_io'
9
+ require_relative '../tracing/ext'
9
10
  require_relative 'service_version'
10
11
  require_relative 'transport/http'
12
+ require_relative '../di/fatal_exceptions'
11
13
 
12
14
  module Datadog
13
15
  module SymbolDatabase
@@ -68,7 +70,11 @@ module Datadog
68
70
  compressed_data = Zlib.gzip(json_data)
69
71
 
70
72
  # Emitted unconditionally so the rare oversized case is observable.
71
- @telemetry&.distribution('tracers', 'symbol_database.payload_size', compressed_data.bytesize)
73
+ @telemetry&.distribution(
74
+ Tracing::Ext::TELEMETRY_METRICS_NAMESPACE,
75
+ 'symbol_database.payload_size',
76
+ compressed_data.bytesize
77
+ )
72
78
 
73
79
  # Symbols for very large applications (>50MB after gzip) are dropped:
74
80
  # the upload is skipped and the customer sees no autocomplete /
@@ -81,7 +87,8 @@ module Datadog
81
87
  end
82
88
 
83
89
  perform_http_upload(compressed_data, scopes.size, upload_id: upload_id, batch_num: batch_num)
84
- rescue => e
90
+ rescue Exception => e # standard:disable Lint/RescueException
91
+ Datadog::DI.reraise_if_fatal(e)
85
92
  @logger.debug { "symdb: upload failed: #{e.class}: #{e.message}" }
86
93
  @telemetry&.report(e, description: 'symdb: upload failed')
87
94
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'ruby_version'
4
+
3
5
  module Datadog
4
6
  # Namespace for Datadog symbol database upload.
5
7
  #
@@ -45,5 +47,25 @@ module Datadog
45
47
  # Reference: Symbol Database Backend RFC, section "Scope" and "Edge Cases"
46
48
  # @see https://www.postgresql.org/docs/current/datatype-numeric.html
47
49
  UNKNOWN_MAX_LINE = 2147483647
50
+
51
+ # Collapses the symbol_database.enabled tri-state setting to a boolean.
52
+ # An explicit true/false wins; nil (unconfigured) yields the caller-supplied
53
+ # fallback.
54
+ # @param setting_value [Boolean, nil] the symbol_database.enabled setting
55
+ # @param di_fallback [Boolean] value used when the setting is unconfigured
56
+ # @return [Boolean]
57
+ def self.resolve_enabled(setting_value, di_fallback)
58
+ setting_value.nil? ? di_fallback : setting_value
59
+ end
60
+
61
+ # Whether the current Ruby runtime can run symbol database extraction:
62
+ # MRI (CRuby) on Ruby 2.7 or later. Used by the remote-config layer to avoid
63
+ # advertising the product on runtimes where Component.build would return nil
64
+ # (e.g. Ruby 2.6, which Dynamic Instrumentation supports but Symbol Database
65
+ # does not).
66
+ # @return [Boolean]
67
+ def self.supported_runtime?
68
+ RUBY_ENGINE == 'ruby' && RubyVersion.is?('>= 2.7')
69
+ end
48
70
  end
49
71
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../ext'
3
4
  require_relative '../metadata/ext'
4
5
  require_relative '../trace_digest'
5
6
  require_relative 'datadog_tags_codec'
@@ -243,7 +244,7 @@ module Datadog
243
244
  # Record telemetry metrics for baggage operations
244
245
  def record_telemetry_metric(metric_name, value, tags)
245
246
  telemetry = ::Datadog.send(:components).telemetry
246
- telemetry.inc('instrumentation_telemetry_data.tracers', metric_name, value, tags: tags)
247
+ telemetry.inc(Tracing::Ext::TELEMETRY_METRICS_NAMESPACE, metric_name, value, tags: tags)
247
248
  end
248
249
  end
249
250
  end