bitfab 0.51.9 → 0.51.10

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.
@@ -6,6 +6,8 @@ require "time"
6
6
 
7
7
  module Bitfab
8
8
  # Captures Ruby method calls beneath one traced root via TracePoint.
9
+ class MixedTracingError < StandardError; end
10
+
9
11
  module Subtree
10
12
  DEFAULT_MAX_DEPTH = 30
11
13
  DEFAULT_MAX_SPANS = 500
@@ -46,11 +48,115 @@ module Bitfab
46
48
  :kwargs,
47
49
  :started_at,
48
50
  :error,
51
+ :span_type,
52
+ :test_run_id,
53
+ :span_data,
49
54
  keyword_init: true
50
55
  )
51
56
 
52
57
  module_function
53
58
 
59
+ def assert_surface!(surface)
60
+ enclosing = current_session ? :trace : SpanContext.current&.dig(:surface)
61
+ return unless enclosing && enclosing != surface
62
+
63
+ raise MixedTracingError,
64
+ "opt-in and opt-out tracing cannot be mixed: #{surface} entered inside #{enclosing}. " \
65
+ "Use bitfab_node inside bitfab_trace, or use bitfab_span throughout the workflow."
66
+ rescue MixedTracingError
67
+ raise
68
+ rescue
69
+ nil
70
+ end
71
+
72
+ def nested_root_suppressed?
73
+ current_session && (ReplayContext.current || (defined?(SeedContext) && SeedContext.current))
74
+ end
75
+
76
+ def qualified_name(receiver, method_name)
77
+ owner = receiver.is_a?(Module) ? receiver.name : receiver.class.name
78
+ (owner && !owner.empty? && owner != "Object") ? "#{owner}.#{method_name}" : method_name.to_s
79
+ end
80
+
81
+ class ConfiguredCall
82
+ attr_reader :span_data, :owner
83
+
84
+ def initialize(receiver:, method_name:, name:, type:, args:, kwargs:, test_run_id: nil,
85
+ nested_trace_key: nil)
86
+ @receiver = receiver
87
+ @method_name = method_name
88
+ @nested_trace_key = nested_trace_key
89
+ @span_data = {}
90
+ @completed = false
91
+ @records = Subtree.active_sessions.map do |session|
92
+ omitted = session.excluded?(method_name)
93
+ entry = unless omitted
94
+ session.configured_entry(name:, type:, args:, kwargs:, test_run_id:)
95
+ end
96
+ [session, entry, !omitted]
97
+ end
98
+ @owner = @records.find { |_, entry, _| entry }&.first unless nested_trace_key
99
+ end
100
+
101
+ def start(trace_id:, span_id:)
102
+ @records.each do |session, entry, _|
103
+ next unless entry
104
+
105
+ if session.equal?(@owner)
106
+ entry.span_id = span_id
107
+ session.discard_configured(entry)
108
+ elsif @nested_trace_key
109
+ entry.span_data = {
110
+ "nested_trace_id" => trace_id,
111
+ "nested_trace_function_key" => @nested_trace_key,
112
+ "nested_root_span_id" => span_id
113
+ }
114
+ end
115
+ end
116
+ if @nested_trace_key && (enclosing = @records.find { |_, entry, _| entry })
117
+ session, entry = enclosing
118
+ @span_data.merge!(
119
+ "enclosing_trace_id" => session.trace_id,
120
+ "enclosing_trace_function_key" => session.trace_function_key,
121
+ "enclosing_span_id" => entry.span_id
122
+ )
123
+ end
124
+ end
125
+
126
+ def run
127
+ boundaries = @records.filter_map do |session, entry, counts_toward_depth|
128
+ next if session.equal?(@owner)
129
+
130
+ [session, session.push_boundary(
131
+ receiver: @receiver, method_name: @method_name,
132
+ span_id: entry&.span_id, counts_toward_depth:
133
+ )]
134
+ end
135
+ yield
136
+ ensure
137
+ boundaries&.reverse_each { |session, boundary| session.pop_boundary(boundary) }
138
+ end
139
+
140
+ def with_owner_parent(&body)
141
+ return yield unless @owner
142
+
143
+ SpanContext.with_span(trace_id: @owner.trace_id,
144
+ span_id: @owner.current_parent_span_id, surface: :trace, &body)
145
+ end
146
+
147
+ def complete(result:, error:, ended_at:)
148
+ return if @completed
149
+
150
+ @completed = true
151
+ @records.each do |session, entry, _|
152
+ next if !entry || session.equal?(@owner)
153
+
154
+ entry.error = error
155
+ session.emit_configured(entry, result:)
156
+ end
157
+ end
158
+ end
159
+
54
160
  def prepare(root_source:, exclude:)
55
161
  path = root_source[0]
56
162
  root = first_party_root(path)
@@ -64,7 +170,7 @@ module Bitfab
64
170
  end
65
171
 
66
172
  def capture(client:, trace_function_key:, config:, max_depth:, max_spans:, include_wrappers:,
67
- root_receiver:, root_method_name:)
173
+ root_receiver:, root_method_name:, mock_on_replay_default: false)
68
174
  return yield unless config.root
69
175
 
70
176
  span = SpanContext.current || ReplayContext.current_span
@@ -83,6 +189,7 @@ module Bitfab
83
189
  max_depth:,
84
190
  max_spans:,
85
191
  include_wrappers:,
192
+ mock_on_replay_default:,
86
193
  root_receiver:,
87
194
  root_method_name:
88
195
  )
@@ -294,7 +401,7 @@ module Bitfab
294
401
  end
295
402
 
296
403
  class Session
297
- attr_reader :client, :parent_session, :trace_function_key, :trace_id, :deferred_frames
404
+ attr_reader :client, :parent_session, :trace_function_key, :trace_id, :deferred_frames, :mock_on_replay_default
298
405
 
299
406
  def active? = @active
300
407
  def deferred? = @deferred
@@ -305,7 +412,8 @@ module Bitfab
305
412
 
306
413
  def initialize(client:, trace_function_key:, trace_id:, root_span_id:, test_run_id:,
307
414
  input_source_span_id:, config:, max_depth:, max_spans:, include_wrappers:,
308
- root_receiver:, root_method_name:)
415
+ root_receiver:, root_method_name:, mock_on_replay_default: false)
416
+ @mock_on_replay_default = mock_on_replay_default
309
417
  @client = client
310
418
  @trace_function_key = trace_function_key
311
419
  @trace_id = trace_id
@@ -322,6 +430,7 @@ module Bitfab
322
430
  @exclude = config.exclude
323
431
  @include_wrappers = include_wrappers
324
432
  @stacks = {}
433
+ @configured_entries = {}
325
434
  @fiber_parents = {}
326
435
  @suppressed_depths = {}
327
436
  @owned_paths = {}
@@ -419,6 +528,29 @@ module Bitfab
419
528
  true
420
529
  end
421
530
 
531
+ def configured_entry(name:, type:, args:, kwargs:, test_run_id:)
532
+ return unless reserve_node
533
+
534
+ entry = OpenSpan.new(
535
+ span_id: SecureRandom.uuid,
536
+ parent_span_id: current_parent_span_id,
537
+ name:, span_type: type, args:, kwargs:, test_run_id:, started_at: now,
538
+ span_data: {}
539
+ )
540
+ @configured_entries[entry.object_id] = entry
541
+ entry
542
+ end
543
+
544
+ def discard_configured(entry)
545
+ @configured_entries.delete(entry.object_id)
546
+ end
547
+
548
+ def emit_configured(entry, result:)
549
+ return unless discard_configured(entry)
550
+
551
+ emit(entry, result:)
552
+ end
553
+
422
554
  def current_parent_span_id
423
555
  stack = @stacks[Fiber.current.object_id] || []
424
556
  stack.reverse_each.find { |entry| entry.span_id }&.span_id ||
@@ -465,6 +597,11 @@ module Bitfab
465
597
  private
466
598
 
467
599
  def finish_open_spans
600
+ @configured_entries.each_value do |entry|
601
+ entry.error ||= "Subtree capture ended before the call returned"
602
+ emit(entry, result: nil)
603
+ end
604
+ @configured_entries.clear
468
605
  @stacks.each_value do |stack|
469
606
  stack.reverse_each do |entry|
470
607
  next unless entry.is_a?(OpenSpan)
@@ -590,7 +727,9 @@ module Bitfab
590
727
  span_id: entry.span_id,
591
728
  parent_span_id: entry.parent_span_id,
592
729
  span_name: entry.name,
593
- span_type: "function",
730
+ span_type: entry.span_type || "function",
731
+ instrumentation: "trace",
732
+ span_data: entry.span_data || {},
594
733
  function_name: entry.name,
595
734
  contexts: nil,
596
735
  prompt: nil,
@@ -600,7 +739,7 @@ module Bitfab
600
739
  error: entry.error,
601
740
  started_at: entry.started_at,
602
741
  ended_at: now,
603
- test_run_id: @test_run_id,
742
+ test_run_id: entry.test_run_id || @test_run_id,
604
743
  input_source_span_id: @input_source_span_id
605
744
  )
606
745
  rescue Exception # rubocop:disable Lint/RescueException
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bitfab
4
+ module ThreadPropagation
5
+ ORIGIN_KEY = :__bitfab_submit_origin
6
+ SDK_PATH = File.expand_path(__dir__) + File::SEPARATOR
7
+ @installed = false
8
+ @mutex = Mutex.new
9
+
10
+ module_function
11
+
12
+ def install
13
+ @mutex.synchronize do
14
+ Thread.singleton_class.prepend(ThreadMethods) unless Thread.singleton_class.ancestors.include?(ThreadMethods)
15
+ @installed = true
16
+ end
17
+ begin
18
+ require "concurrent-ruby"
19
+ Concurrent::ThreadPoolExecutor.prepend(PoolMethods) unless Concurrent::ThreadPoolExecutor.ancestors.include?(PoolMethods)
20
+ rescue LoadError
21
+ nil
22
+ end
23
+ end
24
+
25
+ def installed?
26
+ @installed
27
+ end
28
+
29
+ def uninstall
30
+ @installed = false
31
+ end
32
+
33
+ def runtime
34
+ current = Thread.current
35
+ data = {"thread_id" => current.object_id, "thread_name" => current.name || ((current == Thread.main) ? "main" : "thread-#{current.object_id}")}
36
+ if (origin = current.thread_variable_get(ORIGIN_KEY))
37
+ data["parent_thread_id"] = origin["thread_id"]
38
+ data["submit_thread_id"] = origin["thread_id"]
39
+ data["submit_thread_name"] = origin["thread_name"]
40
+ end
41
+ data
42
+ end
43
+
44
+ def bind(block)
45
+ stack = SpanContext.stack.dup
46
+ stack << SpanContext.current if stack.empty? && SpanContext.current
47
+ replay = ReplayContext.current
48
+ seed = SeedContext.current
49
+ return block if stack.empty? && replay.nil? && seed.nil?
50
+
51
+ origin = runtime.slice("thread_id", "thread_name")
52
+ lambda do |*args, **kwargs|
53
+ current = Thread.current
54
+ previous_stack = current[SpanContext::STACK_KEY]
55
+ previous_bridge = current.thread_variable_get(SpanContext::FIBER_BRIDGE_STACK_KEY)
56
+ previous_replay = current.thread_variable_get(REPLAY_CONTEXT_KEY)
57
+ previous_seed = current.thread_variable_get(SeedContext::KEY)
58
+ previous_origin = current.thread_variable_get(ORIGIN_KEY)
59
+ local_replay = replay&.merge(span_stack: replay.fetch(:span_stack, []).dup)
60
+ current[SpanContext::STACK_KEY] = stack.dup
61
+ current.thread_variable_set(SpanContext::FIBER_BRIDGE_STACK_KEY, [])
62
+ current.thread_variable_set(REPLAY_CONTEXT_KEY, local_replay)
63
+ current.thread_variable_set(SeedContext::KEY, seed)
64
+ current.thread_variable_set(ORIGIN_KEY, origin)
65
+ begin
66
+ block.call(*args, **kwargs)
67
+ ensure
68
+ replay[:db_snapshot_accessed] = true if replay && local_replay[:db_snapshot_accessed]
69
+ current[SpanContext::STACK_KEY] = previous_stack
70
+ current.thread_variable_set(SpanContext::FIBER_BRIDGE_STACK_KEY, previous_bridge)
71
+ current.thread_variable_set(REPLAY_CONTEXT_KEY, previous_replay)
72
+ current.thread_variable_set(SeedContext::KEY, previous_seed)
73
+ current.thread_variable_set(ORIGIN_KEY, previous_origin)
74
+ end
75
+ end
76
+ end
77
+
78
+ def internal?(block)
79
+ path = block.source_location&.first
80
+ path && (path.start_with?(SDK_PATH) || path.include?("/opentelemetry") || path.include?("/concurrent-ruby"))
81
+ end
82
+
83
+ module ThreadMethods
84
+ %i[new start fork].each do |method_name|
85
+ define_method(method_name) do |*args, &block|
86
+ return super(*args, &block) if !ThreadPropagation.installed? || block.nil? || ThreadPropagation.internal?(block)
87
+
88
+ bound = begin
89
+ ThreadPropagation.bind(block)
90
+ rescue
91
+ block
92
+ end
93
+ super(*args, &bound)
94
+ end
95
+ end
96
+ end
97
+
98
+ module PoolMethods
99
+ def post(*args, &block)
100
+ return super unless ThreadPropagation.installed? && block
101
+
102
+ bound = begin
103
+ ThreadPropagation.bind(block)
104
+ rescue
105
+ block
106
+ end
107
+ super(*args, &bound)
108
+ end
109
+ end
110
+ end
111
+ end
@@ -53,32 +53,28 @@ module Bitfab
53
53
  # preserve the bound client through the fluent wrapper, matching Python's
54
54
  # `BitfabFunction.span()` and TypeScript's `BitfabFunction.withSpan()`.
55
55
  def self.wrap(klass, method_name, trace_function_key:, name: nil, type: "custom",
56
- capture_when: "always", mock_on_replay: false, client: nil)
57
- span_name = name || method_name.to_s
56
+ capture_when: "always", mock_on_replay: false, finalize: nil, test_run_id: nil, client: nil)
58
57
  method_name_str = method_name.to_s
59
58
  bound_client = client
60
-
61
59
  wrapper = Module.new do
62
60
  define_method(method_name) do |*args, **kwargs, &block|
63
- target_client = bound_client || Bitfab.client
61
+ target_client = bound_client || Bitfab.client_or_nil
62
+ next super(*args, **kwargs, &block) unless target_client
63
+
64
+ Subtree.assert_surface!(:span) if target_client.send(:should_record?)
64
65
  target_client.send(:execute_span,
65
66
  trace_function_key:,
66
- span_name:,
67
- span_type: type,
68
- function_name: method_name_str,
69
- args:,
70
- kwargs:,
71
- capture_when:,
72
- mock_on_replay:,
73
- explicit_span_receiver: self,
74
- explicit_span_method_name: method_name) do
67
+ span_name: name || Subtree.qualified_name(self, method_name),
68
+ span_type: type, function_name: method_name_str, args:, kwargs:,
69
+ capture_when:, mock_on_replay:, finalize:, test_run_id:, surface: :span,
70
+ explicit_span_receiver: self, explicit_span_method_name: method_name) do
75
71
  super(*args, **kwargs, &block)
76
72
  end
77
73
  end
78
74
  end
79
-
80
75
  wrapper.instance_variable_set(:@bitfab_span_method, method_name_str)
81
76
  wrapper.instance_variable_set(:@bitfab_span_key, trace_function_key)
77
+ preserve_visibility(klass, wrapper, method_name)
82
78
  klass.prepend(wrapper)
83
79
  end
84
80
 
@@ -87,44 +83,45 @@ module Bitfab
87
83
  # beneath it record automatically as function spans.
88
84
  def self.trace(klass, method_name, trace_function_key:, name: nil, type: "custom",
89
85
  max_depth: Subtree::DEFAULT_MAX_DEPTH, max_spans: Subtree::DEFAULT_MAX_SPANS,
90
- exclude: [], include_wrappers: false, client: nil)
91
- source = source_of(klass, method_name)
92
- capture_config = Subtree.prepare(root_source: source, exclude:)
93
- span_name = name || method_name.to_s
86
+ exclude: [], include_wrappers: false, mock_on_replay_default: false, client: nil)
87
+ capture_config = Subtree.prepare(root_source: source_of(klass, method_name), exclude:)
94
88
  method_name_str = method_name.to_s
95
89
  bound_client = client
96
-
97
90
  wrapper = Module.new do
98
91
  define_method(method_name) do |*args, **kwargs, &block|
99
- if Subtree.active_trace?(trace_function_key)
100
- next super(*args, **kwargs, &block)
92
+ target_client = bound_client || Bitfab.client_or_nil
93
+ next super(*args, **kwargs, &block) unless target_client&.send(:should_record?)
94
+
95
+ Subtree.assert_surface!(:trace)
96
+ next super(*args, **kwargs, &block) if Subtree.nested_root_suppressed?
97
+
98
+ span_name = name || Subtree.qualified_name(self, method_name)
99
+ managed = SpanContext.current&.dig(:recording_override)
100
+ if managed && managed.empty? && (ReplayContext.current || SeedContext.current)
101
+ managed.merge!(name: span_name, type:, function_name: method_name_str, instrumentation: "trace")
102
+ SpanContext.current[:surface] = :trace
103
+ next Subtree.capture(
104
+ client: target_client, trace_function_key:, config: capture_config,
105
+ max_depth:, max_spans:, include_wrappers:, mock_on_replay_default:,
106
+ root_receiver: self, root_method_name: method_name
107
+ ) { super(*args, **kwargs, &block) }
101
108
  end
102
-
103
- target_client = bound_client || Bitfab.client
104
- target_client.send(
105
- :execute_span,
106
- trace_function_key:,
107
- span_name:,
108
- span_type: type,
109
- function_name: method_name_str,
110
- args:,
111
- kwargs:,
112
- independent_root: true
113
- ) do
109
+ call = Subtree::ConfiguredCall.new(receiver: self, method_name:, name: span_name,
110
+ type:, args:, kwargs:, nested_trace_key: trace_function_key)
111
+ target_client.send(:execute_span,
112
+ trace_function_key:, span_name:, span_type: type,
113
+ function_name: method_name_str, args:, kwargs:, independent_root: true,
114
+ surface: :trace, span_data: call.span_data,
115
+ on_start: call.method(:start), on_complete: call.method(:complete),
116
+ around_body: call.method(:run)) do
114
117
  Subtree.capture(
115
- client: target_client,
116
- trace_function_key:,
117
- config: capture_config,
118
- max_depth:,
119
- max_spans:,
120
- include_wrappers:,
121
- root_receiver: self,
122
- root_method_name: method_name
118
+ client: target_client, trace_function_key:, config: capture_config,
119
+ max_depth:, max_spans:, include_wrappers:, mock_on_replay_default:,
120
+ root_receiver: self, root_method_name: method_name
123
121
  ) { super(*args, **kwargs, &block) }
124
122
  end
125
123
  end
126
124
  end
127
-
128
125
  wrapper.instance_variable_set(:@bitfab_span_method, method_name_str)
129
126
  wrapper.instance_variable_set(:@bitfab_span_key, trace_function_key)
130
127
  preserve_visibility(klass, wrapper, method_name)
@@ -134,55 +131,42 @@ module Bitfab
134
131
  # Configure an existing method only when it is discovered beneath a
135
132
  # bitfab_trace root. Outside subtree capture the method runs unchanged.
136
133
  def self.node(klass, method_name, name: nil, type: "custom", capture: true,
137
- test_run_id: nil, mock_on_replay: false, finalize: nil, client: nil)
134
+ test_run_id: nil, mock_on_replay: nil, finalize: nil, client: nil)
138
135
  validate_node_options!(capture:, mock_on_replay:)
139
- span_name = name || method_name.to_s
140
136
  method_name_str = method_name.to_s
141
137
  bound_client = client
142
-
143
138
  wrapper = Module.new do
144
139
  define_method(method_name) do |*args, **kwargs, &block|
140
+ Subtree.assert_surface!(:trace)
145
141
  session = Subtree.current_session
146
142
  if !session || (bound_client && !session.client.equal?(bound_client))
147
143
  next super(*args, **kwargs, &block)
148
144
  end
149
-
150
145
  unless capture
151
146
  next Subtree.with_suppressed_node(receiver: self, method_name:) do
152
147
  super(*args, **kwargs, &block)
153
148
  end
154
149
  end
155
150
 
156
- if session.excluded?(method_name)
157
- next Subtree.with_suppressed_node(receiver: self, method_name:, sessions: [session]) do
158
- super(*args, **kwargs, &block)
159
- end
151
+ span_name = name || Subtree.qualified_name(self, method_name)
152
+ call = Subtree::ConfiguredCall.new(receiver: self, method_name:, name: span_name,
153
+ type:, args:, kwargs:, test_run_id:)
154
+ unless call.owner
155
+ next call.run { super(*args, **kwargs, &block) }
160
156
  end
161
-
162
- unless session.reserve_node
163
- next Subtree.with_suppressed_node(receiver: self, method_name:, sessions: [session]) do
164
- super(*args, **kwargs, &block)
165
- end
157
+ policy = mock_on_replay.nil? ? call.owner.mock_on_replay_default : mock_on_replay
158
+ call.with_owner_parent do
159
+ call.owner.client.send(:execute_span,
160
+ trace_function_key: call.owner.trace_function_key,
161
+ span_name:, span_type: type, function_name: method_name_str,
162
+ args:, kwargs:, capture_when: "nested", mock_on_replay: policy,
163
+ explicit_span_receiver: self, explicit_span_method_name: method_name,
164
+ test_run_id:, finalize:, surface: :trace,
165
+ on_start: call.method(:start), on_complete: call.method(:complete),
166
+ around_body: call.method(:run)) { super(*args, **kwargs, &block) }
166
167
  end
167
-
168
- session.client.send(
169
- :execute_span,
170
- trace_function_key: session.trace_function_key,
171
- span_name:,
172
- span_type: type,
173
- function_name: method_name_str,
174
- args:,
175
- kwargs:,
176
- capture_when: "nested",
177
- mock_on_replay:,
178
- explicit_span_receiver: self,
179
- explicit_span_method_name: method_name,
180
- test_run_id:,
181
- finalize:
182
- ) { super(*args, **kwargs, &block) }
183
168
  end
184
169
  end
185
-
186
170
  preserve_visibility(klass, wrapper, method_name)
187
171
  klass.prepend(wrapper)
188
172
  end
@@ -278,7 +262,7 @@ module Bitfab
278
262
  # When true, `client.replay(...)` returns this span's
279
263
  # historical output instead of executing the wrapped method.
280
264
  def bitfab_span(method_name, trace_function_key: nil, name: nil, type: "custom",
281
- capture_when: "always", mock_on_replay: false)
265
+ capture_when: "always", mock_on_replay: false, finalize: nil, test_run_id: nil)
282
266
  trace_function_key ||= @bitfab_function_key
283
267
  unless trace_function_key
284
268
  raise "No trace function key provided. Pass `trace_function_key:` to `bitfab_span` " \
@@ -288,7 +272,7 @@ module Bitfab
288
272
  # If the method already exists (inline or after-method style), wrap it immediately
289
273
  if method_defined?(method_name) || private_method_defined?(method_name)
290
274
  _bitfab_wrap_method(
291
- method_name, trace_function_key:, name:, type:, capture_when:, mock_on_replay:
275
+ method_name, trace_function_key:, name:, type:, capture_when:, mock_on_replay:, finalize:, test_run_id:
292
276
  )
293
277
  else
294
278
  # Method doesn't exist yet (before-method style) - register for method_added hook
@@ -298,7 +282,9 @@ module Bitfab
298
282
  name:,
299
283
  type:,
300
284
  capture_when:,
301
- mock_on_replay:
285
+ mock_on_replay:,
286
+ finalize:,
287
+ test_run_id:
302
288
  }
303
289
  end
304
290
  end
@@ -307,7 +293,7 @@ module Bitfab
307
293
  # Descendants require no Bitfab declarations and record as function spans.
308
294
  def bitfab_trace(method_name, trace_function_key: nil, name: nil, type: "custom",
309
295
  max_depth: Subtree::DEFAULT_MAX_DEPTH, max_spans: Subtree::DEFAULT_MAX_SPANS,
310
- exclude: [], include_wrappers: false)
296
+ exclude: [], include_wrappers: false, mock_on_replay_default: false)
311
297
  trace_function_key ||= @bitfab_function_key
312
298
  unless trace_function_key
313
299
  raise "No trace function key provided. Pass `trace_function_key:` to `bitfab_trace` " \
@@ -321,7 +307,8 @@ module Bitfab
321
307
  max_depth:,
322
308
  max_spans:,
323
309
  exclude:,
324
- include_wrappers:
310
+ include_wrappers:,
311
+ mock_on_replay_default:
325
312
  }
326
313
  if method_defined?(method_name) || private_method_defined?(method_name)
327
314
  _bitfab_trace_method(method_name, **config)
@@ -334,7 +321,7 @@ module Bitfab
334
321
  # EXPERIMENTAL. Configure one method discovered beneath bitfab_trace.
335
322
  # The node inherits the active trace key and never traces by itself.
336
323
  def bitfab_node(method_name, name: nil, type: "custom", capture: true,
337
- test_run_id: nil, mock_on_replay: false, finalize: nil)
324
+ test_run_id: nil, mock_on_replay: nil, finalize: nil)
338
325
  Traceable.send(:validate_node_options!, capture:, mock_on_replay:)
339
326
  config = {name:, type:, capture:, test_run_id:, mock_on_replay:, finalize:}
340
327
  if method_defined?(method_name) || private_method_defined?(method_name)
@@ -361,88 +348,16 @@ module Bitfab
361
348
  end
362
349
  end
363
350
 
364
- def _bitfab_wrap_method(method_name, trace_function_key:, name: nil, type: "custom",
365
- capture_when: "always", mock_on_replay: false)
366
- span_name = name || method_name.to_s
367
- method_name_str = method_name.to_s
368
-
369
- wrapper = Module.new do
370
- define_method(method_name) do |*args, **kwargs, &block|
371
- # If Bitfab was never configured, run the real method untraced
372
- # rather than raising a "not configured" error into the host app.
373
- client = Bitfab.client_or_nil
374
- return super(*args, **kwargs, &block) unless client
375
-
376
- client.send(:execute_span,
377
- trace_function_key:,
378
- span_name:,
379
- span_type: type,
380
- function_name: method_name_str,
381
- args:,
382
- kwargs:,
383
- capture_when:,
384
- mock_on_replay:,
385
- explicit_span_receiver: self,
386
- explicit_span_method_name: method_name) do
387
- super(*args, **kwargs, &block)
388
- end
389
- end
390
- end
391
-
392
- wrapper.instance_variable_set(:@bitfab_span_method, method_name_str)
393
- wrapper.instance_variable_set(:@bitfab_span_key, trace_function_key)
394
- prepend(wrapper)
351
+ def _bitfab_wrap_method(method_name, **options)
352
+ Traceable.wrap(self, method_name, **options)
395
353
  end
396
354
 
397
- def _bitfab_trace_method(method_name, trace_function_key:, name: nil, type: "custom",
398
- max_depth: Subtree::DEFAULT_MAX_DEPTH, max_spans: Subtree::DEFAULT_MAX_SPANS,
399
- exclude: [], include_wrappers: false)
400
- source = Traceable.send(:source_of, self, method_name)
401
- capture_config = Subtree.prepare(root_source: source, exclude:)
402
- span_name = name || method_name.to_s
403
- method_name_str = method_name.to_s
404
-
405
- wrapper = Module.new do
406
- define_method(method_name) do |*args, **kwargs, &block|
407
- if Subtree.active_trace?(trace_function_key)
408
- next super(*args, **kwargs, &block)
409
- end
410
-
411
- client = Bitfab.client_or_nil
412
- return super(*args, **kwargs, &block) unless client
413
-
414
- client.send(
415
- :execute_span,
416
- trace_function_key:,
417
- span_name:,
418
- span_type: type,
419
- function_name: method_name_str,
420
- args:,
421
- kwargs:,
422
- independent_root: true
423
- ) do
424
- Subtree.capture(
425
- client:,
426
- trace_function_key:,
427
- config: capture_config,
428
- max_depth:,
429
- max_spans:,
430
- include_wrappers:,
431
- root_receiver: self,
432
- root_method_name: method_name
433
- ) { super(*args, **kwargs, &block) }
434
- end
435
- end
436
- end
437
-
438
- wrapper.instance_variable_set(:@bitfab_span_method, method_name_str)
439
- wrapper.instance_variable_set(:@bitfab_span_key, trace_function_key)
440
- Traceable.send(:preserve_visibility, self, wrapper, method_name)
441
- prepend(wrapper)
355
+ def _bitfab_trace_method(method_name, **options)
356
+ Traceable.trace(self, method_name, **options)
442
357
  end
443
358
 
444
359
  def _bitfab_node_method(method_name, name: nil, type: "custom", capture: true,
445
- test_run_id: nil, mock_on_replay: false, finalize: nil)
360
+ test_run_id: nil, mock_on_replay: nil, finalize: nil)
446
361
  Traceable.node(
447
362
  self,
448
363
  method_name,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bitfab
4
- VERSION = "0.51.9"
4
+ VERSION = "0.51.10"
5
5
  end