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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fad2341a7e82e22c18faf4809a1807407660eed238b04886b2687e4dc15989b2
4
- data.tar.gz: 7a46b9c4935290898decf6f835257d5b8551281eb1ddebb1680128fcd5ac8c74
3
+ metadata.gz: 5b80995a3476aa0fd9dd0eb926924081189bd6dc85efbdf9fe5ad0a07f375dae
4
+ data.tar.gz: 409f0f9b9837f142aed5233a6307fb30740f5409955dd0d74af8c9d36e27ebc6
5
5
  SHA512:
6
- metadata.gz: 82ef99ecba72308fc1e865257f260756cab829bae0167404f50f8317dd0d921446b02f4379f5c254e98f8deb3fb68104afa73075c4081cc9d5a7b7345243611f
7
- data.tar.gz: 696fb090a73f51ab3bb9ca07277e89a0529dd3031a8c96bfa581f7c54235ba3205f804df4790e5d83163d635dfa5228f9d0be05e4e37d4c1805fb7271a920732
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.message
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.message)
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
- serialized = {type: value.class.name, message: value.message, backtrace: value.backtrace}
164
- if value.is_a?(DbBranchReplayError)
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.message unless error.is_a?(DbBranchReplayError)
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&.message,
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,
@@ -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.message}"
743
+ entry.error = "#{exception.class}: #{ErrorChain.describe(exception)}"
744
744
  end
745
745
 
746
746
  def clear_rescued_error(event)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bitfab
4
- VERSION = "0.75.1"
4
+ VERSION = "0.75.2"
5
5
  end
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.1
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