datadog 2.35.0 → 2.36.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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -1
  3. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +68 -31
  4. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -1
  5. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +1 -1
  6. data/ext/datadog_profiling_native_extension/collectors_stack.c +37 -18
  7. data/ext/datadog_profiling_native_extension/collectors_stack.h +8 -2
  8. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +434 -300
  9. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +9 -7
  10. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +7 -8
  11. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +0 -12
  12. data/ext/datadog_profiling_native_extension/extconf.rb +2 -2
  13. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +4 -43
  14. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +15 -47
  15. data/ext/datadog_profiling_native_extension/heap_recorder.c +44 -26
  16. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +14 -35
  17. data/ext/datadog_profiling_native_extension/profiling.c +41 -4
  18. data/ext/datadog_profiling_native_extension/ruby_helpers.c +33 -34
  19. data/ext/datadog_profiling_native_extension/stack_recorder.c +24 -3
  20. data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -0
  21. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +4 -2
  22. data/ext/libdatadog_api/datadog_ruby_common.c +7 -8
  23. data/ext/libdatadog_api/datadog_ruby_common.h +0 -12
  24. data/ext/libdatadog_extconf_helpers.rb +1 -1
  25. data/lib/datadog/appsec/api_security/route_extractor.rb +6 -0
  26. data/lib/datadog/appsec/component.rb +1 -1
  27. data/lib/datadog/appsec/configuration.rb +7 -0
  28. data/lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb +37 -4
  29. data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +64 -19
  30. data/lib/datadog/appsec/contrib/graphql/integration.rb +1 -0
  31. data/lib/datadog/appsec/contrib/rack/buffered_input.rb +83 -0
  32. data/lib/datadog/appsec/contrib/rack/gateway/request.rb +41 -3
  33. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +20 -7
  34. data/lib/datadog/appsec/contrib/rack/input_peeker.rb +92 -0
  35. data/lib/datadog/appsec/contrib/rails/gateway/request.rb +33 -0
  36. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +17 -1
  37. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +20 -3
  38. data/lib/datadog/appsec/default_header_tags.rb +10 -6
  39. data/lib/datadog/core/configuration/components.rb +1 -0
  40. data/lib/datadog/core/configuration/settings.rb +1 -2
  41. data/lib/datadog/core/configuration/supported_configurations.rb +2 -0
  42. data/lib/datadog/core/remote/component.rb +1 -1
  43. data/lib/datadog/core/telemetry/event/app_started.rb +0 -21
  44. data/lib/datadog/core/utils/at_fork_monkey_patch.rb +1 -1
  45. data/lib/datadog/core/utils/forking.rb +3 -1
  46. data/lib/datadog/core/utils/spawn_monkey_patch.rb +3 -1
  47. data/lib/datadog/core.rb +3 -0
  48. data/lib/datadog/di/base.rb +4 -1
  49. data/lib/datadog/di/component.rb +1 -1
  50. data/lib/datadog/error_tracking/collector.rb +2 -1
  51. data/lib/datadog/error_tracking/component.rb +2 -2
  52. data/lib/datadog/kit/tracing/method_tracer.rb +4 -1
  53. data/lib/datadog/opentelemetry/sdk/propagator.rb +9 -3
  54. data/lib/datadog/opentelemetry/sdk/span_processor.rb +4 -1
  55. data/lib/datadog/profiling/collectors/thread_context.rb +1 -0
  56. data/lib/datadog/profiling/component.rb +13 -15
  57. data/lib/datadog/profiling/ext/dir_monkey_patches.rb +3 -3
  58. data/lib/datadog/ruby_version.rb +25 -0
  59. data/lib/datadog/symbol_database/component.rb +306 -98
  60. data/lib/datadog/symbol_database/extractor.rb +223 -84
  61. data/lib/datadog/tracing/configuration/ext.rb +13 -0
  62. data/lib/datadog/tracing/configuration/settings.rb +17 -0
  63. data/lib/datadog/tracing/contrib/configuration/resolver.rb +7 -0
  64. data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +2 -0
  65. data/lib/datadog/tracing/contrib/grpc.rb +1 -0
  66. data/lib/datadog/tracing/contrib/http/distributed/propagation.rb +2 -0
  67. data/lib/datadog/tracing/contrib/http.rb +1 -0
  68. data/lib/datadog/tracing/contrib/karafka/distributed/propagation.rb +2 -0
  69. data/lib/datadog/tracing/contrib/karafka.rb +1 -0
  70. data/lib/datadog/tracing/contrib/rack/middlewares.rb +3 -1
  71. data/lib/datadog/tracing/contrib/rack/route_inference.rb +3 -1
  72. data/lib/datadog/tracing/contrib/sidekiq/distributed/propagation.rb +2 -0
  73. data/lib/datadog/tracing/contrib/sidekiq.rb +1 -0
  74. data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +2 -0
  75. data/lib/datadog/tracing/contrib/waterdrop.rb +1 -0
  76. data/lib/datadog/tracing/distributed/propagation.rb +33 -1
  77. data/lib/datadog/tracing/distributed/trace_context.rb +11 -2
  78. data/lib/datadog/tracing/trace_digest.rb +7 -0
  79. data/lib/datadog/tracing/trace_operation.rb +4 -1
  80. data/lib/datadog/tracing/tracer.rb +1 -0
  81. data/lib/datadog/version.rb +1 -1
  82. data/lib/datadog.rb +4 -1
  83. metadata +8 -5
@@ -79,7 +79,7 @@ module Datadog
79
79
  # Cached unbound Module#singleton_class? — dispatched explicitly so user classes
80
80
  # that define their own `singleton_class?` (e.g. with required arguments) cannot
81
81
  # intercept the predicate and cause the module to be silently dropped from
82
- # extract_all. Cached at load time because collect_extractable_modules iterates
82
+ # extract_all. Cached at load time because build_per_file_index iterates
83
83
  # ObjectSpace.each_object(Module) over tens of thousands of modules.
84
84
  MODULE_SINGLETON_CLASS_PRED = Module.instance_method(:singleton_class?)
85
85
  private_constant :MODULE_SINGLETON_CLASS_PRED
@@ -134,21 +134,65 @@ module Datadog
134
134
  # Returns an array of FILE scopes with proper FQN-based nesting.
135
135
  #
136
136
  # Two-pass algorithm:
137
- # Pass 1: Iterate ObjectSpace, collect all extractable modules with methods grouped by file
138
- # Pass 2: Build FILE scope trees with nested MODULE/CLASS hierarchy from FQN splitting
137
+ # Pass 1 (`build_per_file_index`): iterate ObjectSpace once, building
138
+ # `{ file_path => [[mod_name, mod, [method_name_symbol, ...]], ...] }`.
139
+ # Stores Symbol method names + Module refs only; no UnboundMethod retention
140
+ # between passes.
141
+ # Pass 2 (`build_file_scope`): for each file in the index, resolve
142
+ # UnboundMethods just-in-time, build the nested MODULE/CLASS scope tree from
143
+ # FQN splitting, and produce one FILE Scope. The per-file working set is
144
+ # released as soon as the FILE scope is yielded (or accumulated into the
145
+ # returned Array, in legacy mode).
139
146
  #
140
147
  # This is the production path used by Component. Methods are split by source file,
141
148
  # so a class reopened across two files produces two FILE scopes, each with only
142
149
  # the methods defined in that file.
143
150
  #
144
- # @return [Array<Scope>] Array of FILE scopes
151
+ # Memory profile (with a block):
152
+ # - Pass 1 builds a per-file index containing only Symbol method names plus
153
+ # Module references. No UnboundMethod objects are retained between passes.
154
+ # - Pass 2 processes one file at a time. The peak per file is bounded by the
155
+ # number of methods that live in that one file across all its modules
156
+ # (typical Rails: tens of methods; pathological case: a single very large
157
+ # source file). Once a FILE scope is yielded and the caller stops referencing
158
+ # it, the entire per-file working set becomes garbage.
159
+ #
160
+ # This is O(largest_file + batch_buffer), not O(total_classes).
161
+ #
162
+ # Without a block, returns the full `Array<Scope>` (legacy form, used by specs).
163
+ # The Array itself still scales with the number of files, so block form is the
164
+ # one to use for production memory bounds.
165
+ #
166
+ # @yieldparam scope [Scope] FILE scope for one source file
167
+ # @return [Array<Scope>, nil] Array of FILE scopes when called without a block; nil when a block is given
145
168
  def extract_all
146
- entries = collect_extractable_modules
147
- file_trees = build_file_trees(entries)
148
- convert_trees_to_scopes(file_trees)
169
+ index = build_per_file_index
170
+
171
+ if block_given?
172
+ # Drain the index destructively so each per-file entry becomes eligible for
173
+ # collection as soon as its FILE scope is yielded and consumed. Hash#shift
174
+ # returns [key, value] on a non-empty hash and nil when empty, so the
175
+ # `while (pair = ...)` form is the drain. Indexing pair[0]/pair[1] rather
176
+ # 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.
178
+ while (pair = index.shift)
179
+ scope = build_file_scope(pair[0], pair[1])
180
+ yield scope if scope
181
+ end
182
+ nil
183
+ else
184
+ # Legacy non-block form for specs. No memory bound — the full Array is
185
+ # materialized.
186
+ result = []
187
+ index.each do |path, file_entries|
188
+ scope = build_file_scope(path, file_entries)
189
+ result << scope if scope
190
+ end
191
+ result
192
+ end
149
193
  rescue => e
150
194
  @logger.debug { "symdb: error in extract_all: #{e.class}: #{e.message}" }
151
- []
195
+ block_given? ? nil : []
152
196
  end
153
197
 
154
198
  private
@@ -165,6 +209,58 @@ module Datadog
165
209
  nil
166
210
  end
167
211
 
212
+ # Verify that mod_name still resolves to mod through Ruby's constant
213
+ # table. Returns false when a Class/Module has been detached from its
214
+ # constant (via remove_const) but still carries the cached Module#name —
215
+ # see build_per_file_index for the failure mode this protects.
216
+ #
217
+ # Walks the namespace path segment-by-segment. For each segment:
218
+ # 1. Check for a pending autoload directly on the current namespace.
219
+ # If present, const_get would trigger it — loading customer code as
220
+ # a side effect of symbol extraction and raising LoadError if the
221
+ # target file is missing (LoadError is ScriptError, not StandardError,
222
+ # and would propagate past the outer rescue in
223
+ # build_per_file_index). Return false instead.
224
+ # 2. Otherwise, require the constant to be directly defined on this
225
+ # namespace (const_defined?(sym, false)) and descend via
226
+ # const_get(sym, false). The direct-only lookup means an ancestor's
227
+ # pending autoload at the same name does not affect the result: a
228
+ # subclass with its own binding resolves through the binding, an
229
+ # inherited autoload triggers nothing.
230
+ #
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.
240
+ # @param mod_name [String]
241
+ # @param mod [Module]
242
+ # @return [Boolean]
243
+ def resolves_to_same_module?(mod_name, mod)
244
+ current = Object
245
+ mod_name.split('::').each do |seg|
246
+ 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)
255
+ end
256
+ current.equal?(mod)
257
+ rescue NameError, ArgumentError
258
+ # Expected "no" outcome for stale/detached classes — the whole point
259
+ # of this predicate. Per the rescue convention in this file's header
260
+ # comment: inner per-item rescues are expected failures, no logging.
261
+ false
262
+ end
263
+
168
264
  # Check if module is from user code (not gems or stdlib)
169
265
  # @param mod [Module] The module to check
170
266
  # @return [Boolean] true if user code
@@ -614,7 +710,7 @@ module Datadog
614
710
 
615
711
  # ── extract_all helpers ──────────────────────────────────────────────
616
712
 
617
- # Sleep between chunks of modules processed in collect_extractable_modules so
713
+ # Sleep between chunks of modules processed in build_per_file_index so
618
714
  # request-handling threads have guaranteed CPU time while extraction is in
619
715
  # flight. Unlike Thread.pass (which only offers the GVL among runnable
620
716
  # threads and leaves the extractor immediately re-runnable), sleep removes
@@ -629,10 +725,19 @@ module Datadog
629
725
  SLEEP_SECONDS = 0.001
630
726
  private_constant :SLEEP_EVERY_N_MODULES, :SLEEP_SECONDS
631
727
 
632
- # Pass 1: Collect all extractable modules with methods grouped by source file.
633
- # @return [Hash] { mod_name => { mod:, methods_by_file: { path => [{name:, method:, type:}] } } }
634
- def collect_extractable_modules
635
- entries = {}
728
+ # Pass 1 (memory-bounded form): build a per-file index of
729
+ # `{ file_path => [[mod_name, mod, [method_name_symbol, ...]], ...] }`.
730
+ #
731
+ # Stores Symbol method names plus Module references only — no UnboundMethod
732
+ # objects retained between passes. UnboundMethods created here (to read
733
+ # `source_location`) become garbage as the inner loop ends.
734
+ #
735
+ # The Module references are pointer-sized and the modules are already kept
736
+ # alive in ObjectSpace, so adding them to the index costs no extra retention.
737
+ #
738
+ # @return [Hash{String=>Array<Array(String, Module, Array<Symbol>)>}]
739
+ def build_per_file_index
740
+ index = {}
636
741
  seen = 0
637
742
 
638
743
  ObjectSpace.each_object(Module) do |mod|
@@ -650,84 +755,119 @@ module Datadog
650
755
 
651
756
  mod_name = safe_mod_name(mod)
652
757
  next unless mod_name
758
+ next unless resolves_to_same_module?(mod_name, mod)
653
759
  next unless user_code_module?(mod)
654
760
 
655
- methods_by_file = group_methods_by_file(mod)
761
+ file_to_names = collect_method_names_by_file(mod)
656
762
 
657
- # For modules/classes with no methods but valid source, use find_source_file as fallback.
658
- # This handles namespace modules and classes with only constants.
659
- if methods_by_file.empty?
763
+ # Namespace-only modules (no own methods) use find_source_file as the
764
+ # canonical file so the FILE scope still gets a MODULE entry.
765
+ if file_to_names.empty?
660
766
  source_file = find_source_file(mod)
661
- methods_by_file[source_file] = [] if source_file
767
+ file_to_names[source_file] = [] if source_file
662
768
  end
663
769
 
664
- next if methods_by_file.empty?
770
+ next if file_to_names.empty?
665
771
 
666
- entries[mod_name] = {mod: mod, methods_by_file: methods_by_file}
772
+ file_to_names.each do |file_path, method_names|
773
+ (index[file_path] ||= []) << [mod_name, mod, method_names]
774
+ end
667
775
  rescue => e
668
- @logger.debug { "symdb: error collecting #{mod_name || '<unknown>'}: #{e.class}: #{e.message}" }
776
+ @logger.debug { "symdb: error indexing #{mod_name || '<unknown>'}: #{e.class}: #{e.message}" }
669
777
  end
670
778
 
671
- entries
779
+ index
672
780
  end
673
781
 
674
- # Group a module's methods by their source file path.
675
- # @param mod [Module] The module
676
- # @return [Hash] { file_path => [{name:, method:, type:}] }
677
- def group_methods_by_file(mod)
782
+ # For a single module, return `{ file_path => [method_name_symbol, ...] }`.
783
+ # Stores only the method-name symbols and their file paths — UnboundMethod
784
+ # objects allocated to read `source_location` are not retained between
785
+ # passes, so they can be GC'd as soon as the inner loop ends.
786
+ def collect_method_names_by_file(mod)
678
787
  result = Hash.new { |h, k| h[k] = [] } # steep:ignore
679
788
 
680
- # Instance methods (public, protected, private)
681
- all_methods = mod.instance_methods(false) +
682
- mod.protected_instance_methods(false) +
683
- mod.private_instance_methods(false)
684
- all_methods.uniq!
685
-
686
- all_methods.each do |method_name|
687
- method = mod.instance_method(method_name)
688
- loc = method.source_location
689
- next unless loc
690
- next unless user_code_path?(loc[0])
691
-
692
- result[loc[0]] << {name: method_name, method: method, type: :instance}
693
- rescue => e
694
- @logger.debug { "symdb: error grouping method #{method_name}: #{e.class}: #{e.message}" }
789
+ # Module#instance_methods(false) already returns both public and protected
790
+ # methods, so iterating it plus private_instance_methods covers all three
791
+ # visibilities without an intermediate merged array.
792
+ [mod.instance_methods(false), mod.private_instance_methods(false)].each do |method_names|
793
+ method_names.each do |method_name|
794
+ method = mod.instance_method(method_name)
795
+ loc = method.source_location
796
+ next unless loc
797
+ next unless user_code_path?(loc[0])
798
+
799
+ result[loc[0]] << method_name
800
+ rescue => e
801
+ @logger.debug { "symdb: error indexing method #{method_name}: #{e.class}: #{e.message}" }
802
+ end
695
803
  end
696
804
 
697
805
  result
698
806
  rescue => e
699
- @logger.debug { "symdb: error grouping methods: #{e.class}: #{e.message}" }
807
+ @logger.debug { "symdb: error indexing methods: #{e.class}: #{e.message}" }
700
808
  {}
701
809
  end
702
810
 
703
- # Pass 2: Build per-file trees from collected entries.
704
- # Uses hash nodes during construction, converted to Scope objects at the end.
811
+ # Pass 2: build the FILE scope for one source file by walking just the modules
812
+ # that contribute methods to it. Resolves UnboundMethods just-in-time per
813
+ # method; the per-method scratch is collected by GC as each module's loop body
814
+ # ends. The returned Scope is the only thing the caller needs to keep alive
815
+ # — once the caller drops it, the entire per-file working set is collectable.
705
816
  #
706
- # Node structure: { name:, type:, children: {name => node}, methods: [], mod:, source_file:, fqn: }
707
- #
708
- # @param entries [Hash] Output from collect_extractable_modules
709
- # @return [Hash] { file_path => root_node }
710
- def build_file_trees(entries)
711
- file_trees = {}
712
-
713
- # Sort by FQN depth so parents are placed before children.
714
- # This ensures intermediate nodes created for parents have correct scope_type.
715
- sorted = entries.sort_by { |name, _| name.count(':') }
716
-
717
- sorted.each do |mod_name, entry|
718
- entry[:methods_by_file].each do |file_path, methods|
719
- root = file_trees[file_path] ||= {
720
- name: file_path, type: 'FILE', children: {},
721
- methods: [], mod: nil, source_file: file_path, fqn: nil
722
- }
723
- parts = mod_name.split('::')
724
- place_in_tree(root, parts, entry[:mod], mod_name, methods, file_path)
817
+ # @param file_path [String]
818
+ # @param entries [Array<Array(String, Module, Array<Symbol>)>] tuples produced by build_per_file_index
819
+ # @return [Scope, nil] FILE scope, or nil if nothing extractable
820
+ def build_file_scope(file_path, entries)
821
+ return nil if entries.empty?
822
+
823
+ root = {
824
+ name: file_path, type: 'FILE', children: {},
825
+ methods: [], mod: nil, source_file: file_path, fqn: nil,
826
+ }
827
+
828
+ # Sort by FQN depth so parent namespaces are placed before children.
829
+ sorted = entries.sort_by { |(mod_name, _, _)| mod_name.count(':') }
830
+
831
+ sorted.each do |mod_name, mod, method_names|
832
+ # Resolve UnboundMethods for this (mod, file) just-in-time. These objects
833
+ # live only as long as the tree node holds them; they are released when
834
+ # convert_tree_to_scope finishes building the file's Scope.
835
+ method_infos = Core::Utils::EnumerableCompat.filter_map(method_names) do |name|
836
+ method = mod.instance_method(name)
837
+ # Pass 1 (build_per_file_index) recorded this method under file_path.
838
+ # If the method has been redefined in another file between the two
839
+ # passes (e.g. a class reopened during a Rails reload while extract_all
840
+ # is iterating), the resolved UnboundMethod's source_location now
841
+ # points elsewhere. Drop the stale entry — the hot-load TracePoint
842
+ # enqueues the redefined class and the next debounce window extracts
843
+ # it under the new file_path.
844
+ loc = method.source_location
845
+ next nil unless loc && loc[0] == file_path
846
+ {name: name, method: method, type: :instance}
847
+ rescue => e
848
+ @logger.debug { "symdb: error resolving #{mod_name}##{name}: #{e.class}: #{e.message}" }
849
+ nil
725
850
  end
851
+
852
+ # If Pass 1 recorded methods for this module but every one of them has
853
+ # moved out of file_path between the passes, drop the entry — otherwise
854
+ # the FILE scope would carry an empty CLASS/MODULE node at a location
855
+ # the module no longer lives in.
856
+ next if method_names.any? && method_infos.empty?
857
+
858
+ parts = mod_name.split('::')
859
+ place_in_tree(root, parts, mod, mod_name, method_infos, file_path)
726
860
  rescue => e
727
- @logger.debug { "symdb: error building tree for #{mod_name}: #{e.class}: #{e.message}" }
861
+ @logger.debug { "symdb: error placing #{mod_name} in tree: #{e.class}: #{e.message}" }
728
862
  end
729
863
 
730
- file_trees
864
+ # steep:ignore:start
865
+ # Steep widens root[:children] to the union of all value types declared in
866
+ # the literal (String | Hash | Array | nil), losing the Hash narrowing.
867
+ return nil if root[:children].empty?
868
+ # steep:ignore:end
869
+
870
+ convert_tree_to_scope(file_path, root)
731
871
  end
732
872
 
733
873
  # Place a module/class in the file tree at the correct nesting depth.
@@ -784,25 +924,24 @@ module Datadog
784
924
  'MODULE'
785
925
  end
786
926
 
787
- # Convert hash-based file trees to Scope objects.
788
- # @param file_trees [Hash] { file_path => root_node }
789
- # @return [Array<Scope>] Array of FILE scopes
790
- def convert_trees_to_scopes(file_trees)
791
- file_trees.map do |file_path, root|
792
- file_hash = FileHash.compute(file_path, logger: @logger)
793
- lang = {}
794
- lang[:file_hash] = file_hash if file_hash
795
-
796
- Scope.new(
797
- scope_type: 'FILE',
798
- name: file_path,
799
- source_file: file_path,
800
- start_line: UNKNOWN_MIN_LINE,
801
- end_line: UNKNOWN_MAX_LINE,
802
- language_specifics: lang,
803
- scopes: root[:children].values.map { |child| convert_node_to_scope(child) }
804
- )
805
- end
927
+ # Convert a single file tree (built by build_file_scope) to a FILE Scope.
928
+ # @param file_path [String] Source file path
929
+ # @param root [Hash] Tree node from build_file_scope
930
+ # @return [Scope] FILE scope
931
+ def convert_tree_to_scope(file_path, root)
932
+ file_hash = FileHash.compute(file_path, logger: @logger)
933
+ lang = {}
934
+ lang[:file_hash] = file_hash if file_hash
935
+
936
+ Scope.new(
937
+ scope_type: 'FILE',
938
+ name: file_path,
939
+ source_file: file_path,
940
+ start_line: UNKNOWN_MIN_LINE,
941
+ end_line: UNKNOWN_MAX_LINE,
942
+ language_specifics: lang,
943
+ scopes: root[:children].values.map { |child| convert_node_to_scope(child) },
944
+ )
806
945
  end
807
946
 
808
947
  # Convert a single hash node to a Scope object (recursive).
@@ -62,6 +62,19 @@ module Datadog
62
62
 
63
63
  ENV_PROPAGATION_STYLE_EXTRACT = 'DD_TRACE_PROPAGATION_STYLE_EXTRACT'
64
64
 
65
+ PROPAGATION_BEHAVIOR_EXTRACT_CONTINUE = 'continue'
66
+ PROPAGATION_BEHAVIOR_EXTRACT_RESTART = 'restart'
67
+ PROPAGATION_BEHAVIOR_EXTRACT_IGNORE = 'ignore'
68
+
69
+ PROPAGATION_BEHAVIOR_EXTRACT_SUPPORTED = [
70
+ PROPAGATION_BEHAVIOR_EXTRACT_CONTINUE,
71
+ PROPAGATION_BEHAVIOR_EXTRACT_RESTART,
72
+ PROPAGATION_BEHAVIOR_EXTRACT_IGNORE,
73
+ ].freeze
74
+
75
+ # Behavior applied to a distributed-trace context extracted from incoming requests.
76
+ ENV_PROPAGATION_BEHAVIOR_EXTRACT = 'DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT'
77
+
65
78
  # A no-op propagator. Compatible with OpenTelemetry's `none` propagator.
66
79
  # @see https://opentelemetry.io/docs/concepts/sdk-configuration/general-sdk-configuration/#get_otel__propagators
67
80
  PROPAGATION_STYLE_NONE = 'none'
@@ -117,6 +117,23 @@ module Datadog
117
117
  end
118
118
  end
119
119
 
120
+ # Behavior applied to a distributed-trace context extracted from incoming requests.
121
+ option :propagation_behavior_extract do |o|
122
+ o.env Tracing::Configuration::Ext::Distributed::ENV_PROPAGATION_BEHAVIOR_EXTRACT
123
+ o.default Tracing::Configuration::Ext::Distributed::PROPAGATION_BEHAVIOR_EXTRACT_CONTINUE
124
+ o.type :string
125
+ o.env_parser do |value|
126
+ value = value&.downcase
127
+ if Tracing::Configuration::Ext::Distributed::PROPAGATION_BEHAVIOR_EXTRACT_SUPPORTED.include?(value)
128
+ value
129
+ else
130
+ Datadog.logger.warn("Unsupported propagation behavior extract: #{value.inspect}. " \
131
+ "Set to default value (#{Tracing::Configuration::Ext::Distributed::PROPAGATION_BEHAVIOR_EXTRACT_CONTINUE}).")
132
+ nil
133
+ end
134
+ end
135
+ end
136
+
120
137
  # Strictly stop at the first successfully serialized style.
121
138
  # This prevents the tracer from enriching the extracted context with information from
122
139
  # other valid propagations styles present in the request.
@@ -101,6 +101,13 @@ module Datadog
101
101
  # shrink a Hash using an "st_table" back to an "ar_table")
102
102
  @cache = Hash[*1..20]
103
103
  @cache.clear
104
+ # Workaround for the segfault from https://github.com/DataDog/dd-trace-rb/issues/5718#issuecomment-4421844775.
105
+ # It crashes on a simple {Hash} lookup, {Hash#key?}, called from `@cache.fetch(value)`.
106
+ # Using an identity-based {Hash} avoids {Hash#key?} calls.
107
+ # We should attempt to remove this workaround when we only support Ruby 4+,
108
+ # as large change around the crash site was done in that version (https://github.com/ruby/ruby/pull/14039/changes#diff-884a5a8a369ef1b4c7597e00aa65974cec8c5f54f25f03ad5d24848f64892869R1743),
109
+ # where `RClass.cc_table` (the NULL dereferenced pointer) became a GC-managed object,
110
+ @cache.compare_by_identity
104
111
  end
105
112
 
106
113
  # (see Resolver#resolve)
@@ -20,6 +20,7 @@ module Datadog
20
20
  def initialize(
21
21
  propagation_style_inject:,
22
22
  propagation_style_extract:,
23
+ propagation_behavior_extract:,
23
24
  propagation_extract_first:
24
25
  )
25
26
  super(
@@ -38,6 +39,7 @@ module Datadog
38
39
  },
39
40
  propagation_style_inject: propagation_style_inject,
40
41
  propagation_style_extract: propagation_style_extract,
42
+ propagation_behavior_extract: propagation_behavior_extract,
41
43
  propagation_extract_first: propagation_extract_first
42
44
  )
43
45
  end
@@ -36,6 +36,7 @@ module Datadog
36
36
  @propagation = GRPC::Distributed::Propagation.new(
37
37
  propagation_style_inject: tracing.propagation_style_inject,
38
38
  propagation_style_extract: tracing.propagation_style_extract,
39
+ propagation_behavior_extract: tracing.propagation_behavior_extract,
39
40
  propagation_extract_first: tracing.propagation_extract_first
40
41
  )
41
42
  end
@@ -19,6 +19,7 @@ module Datadog
19
19
  def initialize(
20
20
  propagation_style_inject:,
21
21
  propagation_style_extract:,
22
+ propagation_behavior_extract:,
22
23
  propagation_extract_first:
23
24
  )
24
25
  super(
@@ -37,6 +38,7 @@ module Datadog
37
38
  },
38
39
  propagation_style_inject: propagation_style_inject,
39
40
  propagation_style_extract: propagation_style_extract,
41
+ propagation_behavior_extract: propagation_behavior_extract,
40
42
  propagation_extract_first: propagation_extract_first
41
43
  )
42
44
  end
@@ -36,6 +36,7 @@ module Datadog
36
36
  @propagation = HTTP::Distributed::Propagation.new(
37
37
  propagation_style_inject: tracing.propagation_style_inject,
38
38
  propagation_style_extract: tracing.propagation_style_extract,
39
+ propagation_behavior_extract: tracing.propagation_behavior_extract,
39
40
  propagation_extract_first: tracing.propagation_extract_first
40
41
  )
41
42
  end
@@ -19,6 +19,7 @@ module Datadog
19
19
  def initialize(
20
20
  propagation_style_inject:,
21
21
  propagation_style_extract:,
22
+ propagation_behavior_extract:,
22
23
  propagation_extract_first:
23
24
  )
24
25
  super(
@@ -37,6 +38,7 @@ module Datadog
37
38
  },
38
39
  propagation_style_inject: propagation_style_inject,
39
40
  propagation_style_extract: propagation_style_extract,
41
+ propagation_behavior_extract: propagation_behavior_extract,
40
42
  propagation_extract_first: propagation_extract_first
41
43
  )
42
44
  end
@@ -28,6 +28,7 @@ module Datadog
28
28
  @propagation = Karafka::Distributed::Propagation.new(
29
29
  propagation_style_inject: tracing.propagation_style_inject,
30
30
  propagation_style_extract: tracing.propagation_style_extract,
31
+ propagation_behavior_extract: tracing.propagation_behavior_extract,
31
32
  propagation_extract_first: tracing.propagation_extract_first
32
33
  )
33
34
  end
@@ -268,7 +268,9 @@ module Datadog
268
268
  # 1. resource renaming is enabled
269
269
  # 2. AppSec is enabled and resource renaming is disabled (by default, not explicitly)
270
270
  if Datadog.configuration.tracing.resource_renaming.enabled ||
271
- Datadog.configuration.appsec.enabled && Datadog.configuration.tracing.resource_renaming.options[:enabled].default_precedence?
271
+ Datadog.configuration.respond_to?(:appsec) &&
272
+ Datadog.configuration.appsec&.enabled &&
273
+ Datadog.configuration.tracing.resource_renaming.options[:enabled].default_precedence?
272
274
  request_span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_ENDPOINT, value)
273
275
  end
274
276
  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
  module Tracing
5
7
  module Contrib
@@ -48,7 +50,7 @@ module Datadog
48
50
  nil
49
51
  end
50
52
 
51
- if RUBY_VERSION >= '2.6.'
53
+ if RubyVersion.is?('>= 2.6')
52
54
  def split(path, pattern = nil, &block)
53
55
  path.split(pattern, &block)
54
56
  end
@@ -20,6 +20,7 @@ module Datadog
20
20
  def initialize(
21
21
  propagation_style_inject:,
22
22
  propagation_style_extract:,
23
+ propagation_behavior_extract:,
23
24
  propagation_extract_first:
24
25
  )
25
26
  super(
@@ -38,6 +39,7 @@ module Datadog
38
39
  },
39
40
  propagation_style_inject: propagation_style_inject,
40
41
  propagation_style_extract: propagation_style_extract,
42
+ propagation_behavior_extract: propagation_behavior_extract,
41
43
  propagation_extract_first: propagation_extract_first
42
44
  )
43
45
  end
@@ -28,6 +28,7 @@ module Datadog
28
28
  @propagation = Sidekiq::Distributed::Propagation.new(
29
29
  propagation_style_inject: tracing.propagation_style_inject,
30
30
  propagation_style_extract: tracing.propagation_style_extract,
31
+ propagation_behavior_extract: tracing.propagation_behavior_extract,
31
32
  propagation_extract_first: tracing.propagation_extract_first
32
33
  )
33
34
  end
@@ -19,6 +19,7 @@ module Datadog
19
19
  def initialize(
20
20
  propagation_style_inject:,
21
21
  propagation_style_extract:,
22
+ propagation_behavior_extract:,
22
23
  propagation_extract_first:
23
24
  )
24
25
  super(
@@ -37,6 +38,7 @@ module Datadog
37
38
  },
38
39
  propagation_style_inject: propagation_style_inject,
39
40
  propagation_style_extract: propagation_style_extract,
41
+ propagation_behavior_extract: propagation_behavior_extract,
40
42
  propagation_extract_first: propagation_extract_first
41
43
  )
42
44
  end
@@ -32,6 +32,7 @@ module Datadog
32
32
  @propagation = WaterDrop::Distributed::Propagation.new(
33
33
  propagation_style_inject: tracing.propagation_style_inject,
34
34
  propagation_style_extract: tracing.propagation_style_extract,
35
+ propagation_behavior_extract: tracing.propagation_behavior_extract,
35
36
  propagation_extract_first: tracing.propagation_extract_first
36
37
  )
37
38
  end