bitfab 0.75.0 → 0.75.2
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.
- checksums.yaml +4 -4
- data/lib/bitfab/client.rb +11 -7
- data/lib/bitfab/error_chain.rb +83 -0
- data/lib/bitfab/replay.rb +4 -8
- data/lib/bitfab/span_context.rb +45 -3
- data/lib/bitfab/subtree.rb +1 -1
- data/lib/bitfab/version.rb +1 -1
- data/lib/bitfab.rb +9 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5b80995a3476aa0fd9dd0eb926924081189bd6dc85efbdf9fe5ad0a07f375dae
|
|
4
|
+
data.tar.gz: 409f0f9b9837f142aed5233a6307fb30740f5409955dd0d74af8c9d36e27ebc6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fe3ae22d0d2da90db167cf4d968cf2fe71bcaba899fa3393ab822a8cc7682961c0fcb8aa9bf842e0630cca46827fbfed433813e1a1b2f0bf191f3766bee708c8
|
|
7
|
+
data.tar.gz: 4490e80fdac9fbb0288177e158926e46e5c17f16889d11ce1b241579c98616bf144250a59def158ce771831edfaba80c046f9d2d78604b3079d582f495eb86c1
|
data/lib/bitfab/client.rb
CHANGED
|
@@ -722,7 +722,8 @@ module Bitfab
|
|
|
722
722
|
explicit_span_receiver:,
|
|
723
723
|
explicit_span_method_name: explicit_span_method_name&.to_sym,
|
|
724
724
|
surface:, recording_override:, content_withheld:,
|
|
725
|
-
sends_on_completion: sends_on_completion
|
|
725
|
+
sends_on_completion: sends_on_completion,
|
|
726
|
+
trace_function_key:
|
|
726
727
|
) do
|
|
727
728
|
invoke = -> { around_body ? around_body.call { yield } : yield }
|
|
728
729
|
result = if explicit_span_receiver
|
|
@@ -742,7 +743,7 @@ module Bitfab
|
|
|
742
743
|
end
|
|
743
744
|
end
|
|
744
745
|
rescue => e
|
|
745
|
-
error = e
|
|
746
|
+
error = ErrorChain.describe(e)
|
|
746
747
|
complete.call(result, error)
|
|
747
748
|
raise
|
|
748
749
|
end
|
|
@@ -769,7 +770,8 @@ module Bitfab
|
|
|
769
770
|
surface:,
|
|
770
771
|
around_body:,
|
|
771
772
|
explicit_span_receiver:,
|
|
772
|
-
explicit_span_method_name
|
|
773
|
+
explicit_span_method_name:,
|
|
774
|
+
trace_function_key:
|
|
773
775
|
)
|
|
774
776
|
end
|
|
775
777
|
|
|
@@ -812,7 +814,7 @@ module Bitfab
|
|
|
812
814
|
end
|
|
813
815
|
|
|
814
816
|
def root_trace_function_key(trace_id, trace_function_key)
|
|
815
|
-
TraceState.
|
|
817
|
+
TraceState.trace_function_key(trace_id) || trace_function_key
|
|
816
818
|
end
|
|
817
819
|
|
|
818
820
|
def wait_for_simulation_plan
|
|
@@ -902,8 +904,10 @@ module Bitfab
|
|
|
902
904
|
# span output. The span is sent exactly once: when iteration finishes,
|
|
903
905
|
# raises, or the wrapper is `.close`d.
|
|
904
906
|
def wrap_enumerator(source, trace_id:, span_id:, complete:, explicit_span_receiver: nil,
|
|
905
|
-
explicit_span_method_name: nil, surface: nil, around_body: nil)
|
|
907
|
+
explicit_span_method_name: nil, surface: nil, around_body: nil, trace_function_key: nil)
|
|
906
908
|
span_entry = {trace_id:, span_id:, surface:}
|
|
909
|
+
SpanContext.record_trace_function_keys(span_entry, trace_function_key)
|
|
910
|
+
trace_function_keys = span_entry[:trace_function_keys]
|
|
907
911
|
if explicit_span_receiver
|
|
908
912
|
span_entry[:explicit_span_receiver] = explicit_span_receiver
|
|
909
913
|
span_entry[:explicit_span_method_name] = explicit_span_method_name&.to_sym
|
|
@@ -916,7 +920,7 @@ module Bitfab
|
|
|
916
920
|
# the right parent. The fiber bridge carries the same parent into
|
|
917
921
|
# Enumerator.new / enum_for source fibers on this thread.
|
|
918
922
|
ReplayContext.with_span(trace_id:, span_id:) do
|
|
919
|
-
SpanContext.with_fiber_bridge(trace_id:, span_id:, surface:) do
|
|
923
|
+
SpanContext.with_fiber_bridge(trace_id:, span_id:, surface:, trace_function_key:, trace_function_keys:) do
|
|
920
924
|
SpanContext.stack.push(span_entry)
|
|
921
925
|
begin
|
|
922
926
|
iterate = lambda do
|
|
@@ -940,7 +944,7 @@ module Bitfab
|
|
|
940
944
|
end
|
|
941
945
|
complete.call(yielded, nil)
|
|
942
946
|
rescue => e
|
|
943
|
-
complete.call(yielded, e
|
|
947
|
+
complete.call(yielded, ErrorChain.describe(e))
|
|
944
948
|
raise
|
|
945
949
|
ensure
|
|
946
950
|
SpanContext.stack.pop
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bitfab
|
|
4
|
+
# Reads the chain of causes below an exception. A database driver error
|
|
5
|
+
# usually reaches a traced method wrapped one or two levels down, so
|
|
6
|
+
# recording only the top message makes a connection timeout read as a broken
|
|
7
|
+
# query. Spans and replay results carry the deepest cause too.
|
|
8
|
+
module ErrorChain
|
|
9
|
+
# How many causes below an exception are followed.
|
|
10
|
+
MAX_DEPTH = 10
|
|
11
|
+
CODE_METHODS = %i[code sqlstate errno].freeze
|
|
12
|
+
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
# The causes below `error`, nearest first, stopping at the depth limit or
|
|
16
|
+
# at a cause already seen.
|
|
17
|
+
def causes(error)
|
|
18
|
+
found = []
|
|
19
|
+
seen = {error.object_id => true}
|
|
20
|
+
current = error
|
|
21
|
+
while found.length < MAX_DEPTH
|
|
22
|
+
cause = current.cause
|
|
23
|
+
break if cause.nil? || seen[cause.object_id]
|
|
24
|
+
|
|
25
|
+
found << cause
|
|
26
|
+
seen[cause.object_id] = true
|
|
27
|
+
current = cause
|
|
28
|
+
end
|
|
29
|
+
found
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# The exception's message, followed by the deepest cause's message when it
|
|
33
|
+
# wraps another exception, e.g. `Failed query: ... (caused by: connection
|
|
34
|
+
# timeout)`. The cause is left off when the message already contains it.
|
|
35
|
+
def describe(error)
|
|
36
|
+
message = error.message
|
|
37
|
+
deepest = causes(error).last
|
|
38
|
+
return message if deepest.nil?
|
|
39
|
+
|
|
40
|
+
deepest_message = deepest.message.to_s.strip
|
|
41
|
+
return message if deepest_message.empty? || message.include?(deepest_message)
|
|
42
|
+
|
|
43
|
+
"#{message} (caused by: #{deepest_message})"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# An exception as a JSON record: type, message, backtrace, the driver's
|
|
47
|
+
# code when it has one, and its causes nested under `cause` down to the
|
|
48
|
+
# depth limit. The block adds fields a specific exception type carries.
|
|
49
|
+
def serialize(error, &extra)
|
|
50
|
+
serialize_at(error, 1, {}, &extra)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# `seen` is created fresh by `serialize` for each top-level call, so one
|
|
54
|
+
# call never skips a cause another call already recorded.
|
|
55
|
+
def serialize_at(error, depth, seen, &extra)
|
|
56
|
+
seen[error.object_id] = true
|
|
57
|
+
serialized = {type: error.class.name, message: error.message, backtrace: error.backtrace}
|
|
58
|
+
code = error_code(error)
|
|
59
|
+
serialized[:code] = code unless code.nil?
|
|
60
|
+
serialized.merge!(extra.call(error)) if extra
|
|
61
|
+
cause = error.cause
|
|
62
|
+
if cause && !seen[cause.object_id] && depth < MAX_DEPTH
|
|
63
|
+
serialized[:cause] = serialize_at(cause, depth + 1, seen, &extra)
|
|
64
|
+
end
|
|
65
|
+
serialized
|
|
66
|
+
end
|
|
67
|
+
private_class_method :serialize_at
|
|
68
|
+
|
|
69
|
+
def error_code(error)
|
|
70
|
+
CODE_METHODS.each do |name|
|
|
71
|
+
next unless error.respond_to?(name)
|
|
72
|
+
|
|
73
|
+
value = begin
|
|
74
|
+
error.public_send(name)
|
|
75
|
+
rescue
|
|
76
|
+
nil
|
|
77
|
+
end
|
|
78
|
+
return value if value.is_a?(String) || value.is_a?(Integer)
|
|
79
|
+
end
|
|
80
|
+
nil
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
data/lib/bitfab/replay.rb
CHANGED
|
@@ -160,13 +160,9 @@ module Bitfab
|
|
|
160
160
|
def json_safe(value)
|
|
161
161
|
case value
|
|
162
162
|
when Exception
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
serialized[:code] = value.code
|
|
166
|
-
serialized[:original_trace_id] = value.original_trace_id
|
|
167
|
-
serialized[:cause] = json_safe(value.cause) if value.cause
|
|
163
|
+
ErrorChain.serialize(value) do |error|
|
|
164
|
+
error.is_a?(DbBranchReplayError) ? {original_trace_id: error.original_trace_id} : {}
|
|
168
165
|
end
|
|
169
|
-
serialized
|
|
170
166
|
when Hash
|
|
171
167
|
value.to_h { |key, child| [key, json_safe(child)] }
|
|
172
168
|
when Array
|
|
@@ -177,7 +173,7 @@ module Bitfab
|
|
|
177
173
|
end
|
|
178
174
|
|
|
179
175
|
def replay_item_error_message(error)
|
|
180
|
-
return error
|
|
176
|
+
return ErrorChain.describe(error) unless error.is_a?(DbBranchReplayError)
|
|
181
177
|
|
|
182
178
|
"Replay requested a database branch for trace #{error.original_trace_id} but it " \
|
|
183
179
|
"could not be resolved (#{error.code}): #{error.message}. The method was not run, " \
|
|
@@ -1528,7 +1524,7 @@ module Bitfab
|
|
|
1528
1524
|
input: kwargs.empty? ? args : [*args, kwargs],
|
|
1529
1525
|
result: fn_result,
|
|
1530
1526
|
original_output: item["output"],
|
|
1531
|
-
error: item_error
|
|
1527
|
+
error: item_error && ErrorChain.describe(item_error),
|
|
1532
1528
|
trace_error: fn_error,
|
|
1533
1529
|
replay_error:,
|
|
1534
1530
|
duration_ms: replay_duration_ms,
|
data/lib/bitfab/span_context.rb
CHANGED
|
@@ -19,6 +19,16 @@ module Bitfab
|
|
|
19
19
|
@span_state[:trace_id]
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
# The keys of every trace function running at this point, outermost first,
|
|
23
|
+
# each listed once. A trace function called inside another one comes after
|
|
24
|
+
# it, so a cost recorded here can be counted toward each of them. Empty
|
|
25
|
+
# outside a trace.
|
|
26
|
+
def trace_function_keys
|
|
27
|
+
span_keys = @span_state[:trace_function_keys] || []
|
|
28
|
+
root_keys = Subtree.active_sessions.reverse.map(&:trace_function_key).uniq
|
|
29
|
+
(root_keys - span_keys) + span_keys
|
|
30
|
+
end
|
|
31
|
+
|
|
22
32
|
# Add a context entry to this span.
|
|
23
33
|
# The entire hash is pushed as a single entry in the contexts array.
|
|
24
34
|
# Context entries are accumulated - multiple calls add to the list.
|
|
@@ -53,6 +63,15 @@ module Bitfab
|
|
|
53
63
|
@trace_id = trace_id
|
|
54
64
|
end
|
|
55
65
|
|
|
66
|
+
# The key of the trace function the current trace is stored under (the one
|
|
67
|
+
# that started it). A bitfab_span or get_function call inside another trace
|
|
68
|
+
# function joins that trace, so it reports the outer key. A bitfab_trace
|
|
69
|
+
# root inside another root starts its own trace, so it reports its own key.
|
|
70
|
+
# Empty string outside a trace.
|
|
71
|
+
def trace_function_key
|
|
72
|
+
TraceState.trace_function_key(@trace_id) || ""
|
|
73
|
+
end
|
|
74
|
+
|
|
56
75
|
# Set the session ID for this trace.
|
|
57
76
|
# Session ID is used to group traces from the same user session.
|
|
58
77
|
# This is stored as a database column.
|
|
@@ -121,7 +140,8 @@ module Bitfab
|
|
|
121
140
|
end
|
|
122
141
|
|
|
123
142
|
# Thread-local span stack for tracking nested spans.
|
|
124
|
-
# Each entry is a Hash with :trace_id and :span_id keys
|
|
143
|
+
# Each entry is a Hash with :trace_id and :span_id keys, plus the span's own
|
|
144
|
+
# :trace_function_key and the :trace_function_keys running at it.
|
|
125
145
|
module SpanContext
|
|
126
146
|
STACK_KEY = :__bitfab_span_stack
|
|
127
147
|
FIBER_BRIDGE_STACK_KEY = :__bitfab_span_fiber_bridge_stack
|
|
@@ -139,8 +159,9 @@ module Bitfab
|
|
|
139
159
|
# Enumerator.new and enum_for run their source body in another Fiber on
|
|
140
160
|
# the same thread. Bridge only the active Enumerator parent across that
|
|
141
161
|
# boundary; ordinary fiber-local span stacks remain isolated.
|
|
142
|
-
def with_fiber_bridge(trace_id:, span_id:, surface: nil)
|
|
162
|
+
def with_fiber_bridge(trace_id:, span_id:, surface: nil, trace_function_key: nil, trace_function_keys: nil)
|
|
143
163
|
entry = {trace_id:, span_id:}
|
|
164
|
+
record_trace_function_keys(entry, trace_function_key, trace_function_keys)
|
|
144
165
|
entry[:surface] = surface if surface
|
|
145
166
|
fiber_bridge_stack.push(entry)
|
|
146
167
|
yield
|
|
@@ -150,8 +171,9 @@ module Bitfab
|
|
|
150
171
|
|
|
151
172
|
# Execute a block with a new span pushed onto the stack.
|
|
152
173
|
# The span is automatically popped when the block completes.
|
|
153
|
-
def with_span(trace_id:, span_id:, explicit_span_receiver: nil, explicit_span_method_name: nil, surface: nil, recording_override: nil, content_withheld: nil, sends_on_completion: nil)
|
|
174
|
+
def with_span(trace_id:, span_id:, explicit_span_receiver: nil, explicit_span_method_name: nil, surface: nil, recording_override: nil, content_withheld: nil, sends_on_completion: nil, trace_function_key: nil)
|
|
154
175
|
entry = {trace_id:, span_id:}
|
|
176
|
+
record_trace_function_keys(entry, trace_function_key)
|
|
155
177
|
entry[:sends_on_completion] = sends_on_completion if sends_on_completion
|
|
156
178
|
entry[:surface] = surface if surface
|
|
157
179
|
entry[:recording_override] = recording_override if recording_override
|
|
@@ -166,6 +188,22 @@ module Bitfab
|
|
|
166
188
|
stack.pop
|
|
167
189
|
end
|
|
168
190
|
|
|
191
|
+
# The trace function keys running at this point with the given key added
|
|
192
|
+
# at the end, each listed once. A nil key returns the running keys as is.
|
|
193
|
+
def trace_function_keys_with(trace_function_key)
|
|
194
|
+
running = current&.dig(:trace_function_keys) || []
|
|
195
|
+
trace_function_key ? running | [trace_function_key] : running
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Store a span's own trace function key and the keys running at that span
|
|
199
|
+
# on its stack entry, computing the running keys from the current span
|
|
200
|
+
# when they are not given. Nothing is stored when no key is running.
|
|
201
|
+
def record_trace_function_keys(entry, trace_function_key, trace_function_keys = nil)
|
|
202
|
+
entry[:trace_function_key] = trace_function_key if trace_function_key
|
|
203
|
+
keys = trace_function_keys || trace_function_keys_with(trace_function_key)
|
|
204
|
+
entry[:trace_function_keys] = keys unless keys.empty?
|
|
205
|
+
end
|
|
206
|
+
|
|
169
207
|
def fiber_bridge_stack
|
|
170
208
|
current_thread = Thread.current
|
|
171
209
|
current_thread.thread_variable_get(FIBER_BRIDGE_STACK_KEY) || begin
|
|
@@ -208,6 +246,10 @@ module Bitfab
|
|
|
208
246
|
end
|
|
209
247
|
end
|
|
210
248
|
|
|
249
|
+
def trace_function_key(trace_id)
|
|
250
|
+
get(trace_id)&.dig(:trace_function_key)
|
|
251
|
+
end
|
|
252
|
+
|
|
211
253
|
def delete(trace_id)
|
|
212
254
|
@states_mutex.synchronize { @states.delete(trace_id) }
|
|
213
255
|
end
|
data/lib/bitfab/subtree.rb
CHANGED
|
@@ -740,7 +740,7 @@ module Bitfab
|
|
|
740
740
|
return unless entry.is_a?(OpenSpan)
|
|
741
741
|
|
|
742
742
|
exception = event.raised_exception
|
|
743
|
-
entry.error = "#{exception.class}: #{exception
|
|
743
|
+
entry.error = "#{exception.class}: #{ErrorChain.describe(exception)}"
|
|
744
744
|
end
|
|
745
745
|
|
|
746
746
|
def clear_rescued_error(event)
|
data/lib/bitfab/version.rb
CHANGED
data/lib/bitfab.rb
CHANGED
|
@@ -5,6 +5,7 @@ require "json"
|
|
|
5
5
|
require_relative "bitfab/version"
|
|
6
6
|
require_relative "bitfab/constants"
|
|
7
7
|
require_relative "bitfab/warn_once"
|
|
8
|
+
require_relative "bitfab/error_chain"
|
|
8
9
|
require_relative "bitfab/experiment_id"
|
|
9
10
|
require_relative "bitfab/payload_budget"
|
|
10
11
|
require_relative "bitfab/serialize"
|
|
@@ -49,6 +50,10 @@ module Bitfab
|
|
|
49
50
|
""
|
|
50
51
|
end
|
|
51
52
|
|
|
53
|
+
def trace_function_keys
|
|
54
|
+
[]
|
|
55
|
+
end
|
|
56
|
+
|
|
52
57
|
def add_context(_context)
|
|
53
58
|
# No-op
|
|
54
59
|
end
|
|
@@ -61,6 +66,10 @@ module Bitfab
|
|
|
61
66
|
# No-op trace handle returned when outside a span context.
|
|
62
67
|
# All methods do nothing, preventing crashes when called outside traced code.
|
|
63
68
|
class NoOpCurrentTrace
|
|
69
|
+
def trace_function_key
|
|
70
|
+
""
|
|
71
|
+
end
|
|
72
|
+
|
|
64
73
|
def set_session_id(_session_id)
|
|
65
74
|
# No-op
|
|
66
75
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bitfab
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.75.
|
|
4
|
+
version: 0.75.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Harvest Team
|
|
@@ -183,6 +183,7 @@ files:
|
|
|
183
183
|
- lib/bitfab/db_snapshot.rb
|
|
184
184
|
- lib/bitfab/detached_trace.rb
|
|
185
185
|
- lib/bitfab/env.rb
|
|
186
|
+
- lib/bitfab/error_chain.rb
|
|
186
187
|
- lib/bitfab/experiment_id.rb
|
|
187
188
|
- lib/bitfab/experiments.rb
|
|
188
189
|
- lib/bitfab/git_command.rb
|