bitfab 0.75.1 → 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 +2 -2
- data/lib/bitfab/error_chain.rb +83 -0
- data/lib/bitfab/replay.rb +4 -8
- data/lib/bitfab/subtree.rb +1 -1
- data/lib/bitfab/version.rb +1 -1
- data/lib/bitfab.rb +1 -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
|
@@ -743,7 +743,7 @@ module Bitfab
|
|
|
743
743
|
end
|
|
744
744
|
end
|
|
745
745
|
rescue => e
|
|
746
|
-
error = e
|
|
746
|
+
error = ErrorChain.describe(e)
|
|
747
747
|
complete.call(result, error)
|
|
748
748
|
raise
|
|
749
749
|
end
|
|
@@ -944,7 +944,7 @@ module Bitfab
|
|
|
944
944
|
end
|
|
945
945
|
complete.call(yielded, nil)
|
|
946
946
|
rescue => e
|
|
947
|
-
complete.call(yielded, e
|
|
947
|
+
complete.call(yielded, ErrorChain.describe(e))
|
|
948
948
|
raise
|
|
949
949
|
ensure
|
|
950
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/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"
|
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
|