Module: Bitfab

Defined in:
lib/bitfab.rb,
lib/bitfab/client.rb,
lib/bitfab/replay.rb,
lib/bitfab/version.rb,
lib/bitfab/constants.rb,
lib/bitfab/serialize.rb,
lib/bitfab/traceable.rb,
lib/bitfab/warn_once.rb,
lib/bitfab/db_snapshot.rb,
lib/bitfab/http_client.rb,
lib/bitfab/span_context.rb,
lib/bitfab/mock_override.rb,
lib/bitfab/replay_branch.rb

Defined Under Namespace

Modules: DbSnapshot, MockOverride, Replay, ReplayContext, Serialize, SpanContext, TraceState, Traceable Classes: BitfabFunction, Client, CurrentSpan, CurrentTrace, HttpClient, NoOpCurrentSpan, NoOpCurrentTrace, ReplayBranch

Constant Summary collapse

NO_OP_SPAN =
NoOpCurrentSpan.new.freeze
NO_OP_TRACE =
NoOpCurrentTrace.new.freeze
MOCK_STRATEGIES =

Replay mock strategies. Mirrors the Python and TypeScript SDKs.

  • "marked" : only spans declared with mock_on_replay: true return historical output; everything else runs real code (default)
  • "none" : every child span runs real code
  • "all" : every child span returns its historical output
%w[none all marked].freeze
VERSION =
"0.33.5"
DEFAULT_SERVICE_URL =
"https://bitfab.ai"
REPLAY_CONTEXT_KEY =
:__bitfab_replay_context
BITFAB_PROGRESS_PREFIX =

Wire prefix the Bitfab plugin scans for on stderr to report live progress while a replay runs. One trailing space, then the progress object as JSON.

"@@bitfab:progress "

Class Method Summary collapse

Class Method Details

._reset_warn_onceObject

Test-only: clear the dedup set so a warning can fire again.



31
32
33
# File 'lib/bitfab/warn_once.rb', line 31

def _reset_warn_once
  @warn_once_mutex.synchronize { @warn_once_seen.clear }
end

._run_in_background(&block) ⇒ Object

Run a block in a background thread with tracking. Returns the thread for callers that need to join on it.



292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/bitfab/http_client.rb', line 292

def _run_in_background(&block)
  thread = Thread.new do
    block.call
  rescue => e
    Bitfab.warn_once(
      "send-request-failed",
      "failed to send a request to the backend (further occurrences " \
      "suppressed): #{e.message}"
    )
  ensure
    @pending_threads_mutex.synchronize { @pending_threads.delete(Thread.current) }
  end

  @pending_threads_mutex.synchronize { @pending_threads << thread }
  thread
end

.clientObject

Returns the global client, raising if not configured.



76
77
78
# File 'lib/bitfab.rb', line 76

def client
  @client or raise "Bitfab not configured. Call Bitfab.configure(api_key: '...') first."
end

.client_or_nilObject

Returns the global client, or nil if not configured. Used on the traced call path so a method invoked before Bitfab.configure runs untraced rather than crashing the host app with a "not configured" error.



83
84
85
# File 'lib/bitfab.rb', line 83

def client_or_nil
  @client
end

.configure(api_key: nil, service_url: nil, enabled: true, strict: false) ⇒ Object

Configure the global Bitfab client.

Examples:

Bitfab.configure(api_key: ENV["BITFAB_API_KEY"])

Parameters:

  • api_key (String) (defaults to: nil)

    API key for authentication

  • service_url (String, nil) (defaults to: nil)

    base URL (default: https://bitfab.ai)



71
72
73
# File 'lib/bitfab.rb', line 71

def configure(api_key: nil, service_url: nil, enabled: true, strict: false)
  @client = Client.new(api_key:, service_url:, enabled:, strict:)
end

.current_replay_branchReplayBranch?

Get the database branch the current replay item is running against.

Call this from inside a method being replayed with client.replay(db_branch: ...) and point your database client at branch.database_url so the replay reads the data as it was at trace time:

branch = Bitfab.current_replay_branch
url = branch ? branch.database_url : ENV["DATABASE_URL"]

Returns:

  • (ReplayBranch, nil)

    nil outside a replay item, and for an item whose source trace carried no DB snapshot reference



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/bitfab.rb', line 117

def current_replay_branch
  ctx = ReplayContext.current
  return nil unless ctx

  lease = ctx[:db_branch_lease]
  return nil unless lease

  # Surface the Bitfab trace ID (what the customer sees in the dashboard),
  # not the external one. Falling back to the external ID keeps replays from
  # external sources working until that path is fully wired.
  trace_id = ctx[:source_bitfab_trace_id] || ctx[:input_source_trace_id]
  return nil unless trace_id

  ReplayBranch.new(lease, trace_id, ctx)
end

.current_spanCurrentSpan, NoOpCurrentSpan

Get a handle to the current active span.

Call this from inside a traced method to get a span handle that allows setting metadata at runtime.

Returns:



98
99
100
101
102
103
# File 'lib/bitfab.rb', line 98

def current_span
  entry = SpanContext.current
  return NO_OP_SPAN unless entry

  CurrentSpan.new(entry)
end

.current_traceCurrentTrace, NoOpCurrentTrace

Get a handle to the current active trace.

Call this from inside a traced method to get a trace handle that allows setting trace-level context at runtime.

Returns:



139
140
141
142
143
144
# File 'lib/bitfab.rb', line 139

def current_trace
  entry = SpanContext.current
  return NO_OP_TRACE unless entry

  CurrentTrace.new(entry[:trace_id])
end

.flush_traces(timeout: 30) ⇒ Object

Wait for all pending background threads to complete.



310
311
312
313
# File 'lib/bitfab/http_client.rb', line 310

def flush_traces(timeout: 30)
  threads = @pending_threads_mutex.synchronize { @pending_threads.dup }
  threads.each { |t| t.join(timeout) }
end

.report_replay_progress(progress) ⇒ Object

A ready-made on_progress callback for replay: writes one @@bitfab:progress line per trace to stderr, which the Bitfab plugin polls to report live progress while the replay runs in the background, so replay scripts never hand-format the protocol. stdout stays the ReplayResult JSON. Never raises (progress must not crash a run). The progress hash's item is forwarded verbatim through to_json, so the plugin sees the source trace id, local replay trace id, outputs, error, and duration of the item that just settled.

Examples:

Bitfab.client.replay(..., on_progress: Bitfab.method(:report_replay_progress))

Parameters:

  • progress (Hash)

    running totals with keys completed, total, succeeded, errored, and item (the object replay already passes to on_progress)



160
161
162
163
164
# File 'lib/bitfab.rb', line 160

def report_replay_progress(progress)
  warn "#{BITFAB_PROGRESS_PREFIX}#{progress.to_json}"
rescue
  nil
end

.reset!Object

Reset the global client (primarily for testing).



88
89
90
# File 'lib/bitfab.rb', line 88

def reset!
  @client = nil
end

.warn_once(key, message) ⇒ Object



17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/bitfab/warn_once.rb', line 17

def warn_once(key, message)
  @warn_once_mutex.synchronize do
    return if @warn_once_seen.include?(key)

    @warn_once_seen.add(key)
  end
  begin
    warn "Bitfab: #{message}"
  rescue Exception # rubocop:disable Lint/RescueException
    # Logging must never crash the host app (e.g. a closed $stderr).
  end
end