oopsie_exceptions 1.1.1 → 1.1.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: eeb40f85518ab7f9d4d43e33bb9605206de73f6e3d8fc24979039c39c2c11c02
4
- data.tar.gz: e075b4c269916b53dfecd103d7a10ec9ca0b6ce99de3b82c37b391fc386d56cf
3
+ metadata.gz: 49072b73da34cefc5bf2c080d3bad5f4d0d6f0494225ae8a6e4140488aa00744
4
+ data.tar.gz: 85a4877ef4f61c7a81932e1b607d2d0a637ecfaebff111b54eb1a59ded96a496
5
5
  SHA512:
6
- metadata.gz: 71d6c2ce97bae33d43f3d0ad1fee09ede56ca47576c11303a98e663e2046f6719fbd309d1429f6ce375956cff33fefa404a61ddbacf51c82df377b5296929820
7
- data.tar.gz: 064032a2ec6f0d414bcb24935932a2775a8dbe691ff0b6a1252681bf798b68e347f9e5de8d14fa897d29bca8d22caca262751723343730c62c98eb6c99e15d5d
6
+ metadata.gz: 7b1936f78e392b54e8fb27ab5c12fdfcf9c95ff2ffb9a86bd6a06b4c6eb35df914caa6eca293e8a1a755cfff224ec9527c944dfc413b89e381f64df29ca32f37
7
+ data.tar.gz: 5103181f96b85219c367bf6b2df92ce6492d25a98cba35a12868ca2b1f0cfc5a0a239360563d1d45fdfbb2496c6a1ce1a820e542bf974658f62b8014bd58d70e
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OopsieExceptions
4
+ class ExceptionNormalizer
5
+ RETRY_WRAPPER_CLASS_NAMES = %w[
6
+ Sidekiq::JobRetry::Handled
7
+ ].freeze
8
+
9
+ Result = Struct.new(:exception, :context, :wrappers, keyword_init: true)
10
+
11
+ class << self
12
+ def normalize(exception, context:)
13
+ wrappers = collect_retry_wrappers(exception)
14
+ return Result.new(exception: exception, context: context, wrappers: []) if wrappers.empty?
15
+
16
+ Result.new(
17
+ exception: wrappers.last.cause,
18
+ context: merge_wrapper_context(context, wrappers, wrappers.last.cause),
19
+ wrappers: wrappers
20
+ )
21
+ end
22
+
23
+ private
24
+
25
+ def collect_retry_wrappers(exception)
26
+ wrappers = []
27
+ current = exception
28
+ seen = {}
29
+
30
+ while retry_wrapper?(current) && current.cause && !seen[current.__id__]
31
+ seen[current.__id__] = true
32
+ wrappers << current
33
+ current = current.cause
34
+ end
35
+
36
+ wrappers
37
+ end
38
+
39
+ def retry_wrapper?(exception)
40
+ RETRY_WRAPPER_CLASS_NAMES.include?(exception.class.name)
41
+ end
42
+
43
+ def merge_wrapper_context(context, wrappers, normalized_exception)
44
+ merged_context = context.dup
45
+ metadata = wrappers.map do |wrapper|
46
+ wrapper_metadata(wrapper, normalized_exception, context)
47
+ end
48
+
49
+ if merged_context.key?(:exception_wrapper) || merged_context.key?("exception_wrapper")
50
+ merged_context[:oopsie_exception_wrapper] = metadata.first
51
+ else
52
+ merged_context[:exception_wrapper] = metadata.first
53
+ end
54
+
55
+ merged_context[:exception_wrappers] = metadata if metadata.length > 1
56
+ merged_context
57
+ end
58
+
59
+ def wrapper_metadata(wrapper, normalized_exception, context)
60
+ metadata = {
61
+ class_name: wrapper.class.name,
62
+ message: wrapper.message.to_s[0, 1_000],
63
+ first_line: parse_backtrace_line(wrapper.backtrace&.first),
64
+ normalized_exception: {
65
+ class_name: normalized_exception.class.name,
66
+ message: normalized_exception.message.to_s[0, 1_000]
67
+ }
68
+ }
69
+
70
+ source = context[:source] || context["source"]
71
+ metadata[:source] = source if source
72
+ metadata
73
+ end
74
+
75
+ def parse_backtrace_line(line)
76
+ return nil unless line
77
+
78
+ match = line.match(/\A(.+):(\d+):in [`'](.+)'\z/)
79
+ return { raw: line } unless match
80
+
81
+ {
82
+ file: match[1],
83
+ line: match[2].to_i,
84
+ method: match[3]
85
+ }
86
+ end
87
+ end
88
+ end
89
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OopsieExceptions
4
- VERSION = "1.1.1"
4
+ VERSION = "1.1.2"
5
5
  end
@@ -3,6 +3,7 @@
3
3
  require_relative "oopsie_exceptions/version"
4
4
  require_relative "oopsie_exceptions/configuration"
5
5
  require_relative "oopsie_exceptions/context"
6
+ require_relative "oopsie_exceptions/exception_normalizer"
6
7
  require_relative "oopsie_exceptions/payload"
7
8
  require_relative "oopsie_exceptions/webhook_client"
8
9
  require_relative "oopsie_exceptions/middleware"
@@ -24,12 +25,17 @@ module OopsieExceptions
24
25
 
25
26
  def report(exception, context: {}, handled: true)
26
27
  return unless configuration.enabled
27
- return if configuration.ignored?(exception)
28
- return if exception.instance_variable_get(REPORTED_MARKER)
28
+ normalized = ExceptionNormalizer.normalize(exception, context: context)
29
+ reportable_exception = normalized.exception
29
30
 
30
- exception.instance_variable_set(REPORTED_MARKER, true)
31
+ return if configuration.ignored?(reportable_exception)
32
+ return if reportable_exception.instance_variable_get(REPORTED_MARKER)
31
33
 
32
- payload = Payload.build(exception, context: context, handled: handled)
34
+ ([reportable_exception] + normalized.wrappers).each do |reported_exception|
35
+ reported_exception.instance_variable_set(REPORTED_MARKER, true)
36
+ end
37
+
38
+ payload = Payload.build(reportable_exception, context: normalized.context, handled: handled)
33
39
 
34
40
  if configuration.before_notify
35
41
  payload = configuration.before_notify.call(payload)
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oopsie_exceptions
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Troy
8
+ autorequire:
8
9
  bindir: bin
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2026-07-15 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: rack
@@ -41,6 +42,7 @@ description: Captures unhandled exceptions from web requests and background jobs
41
42
  enriches them with request/user/server context, and delivers structured JSON payloads
42
43
  to configurable webhook endpoints. Works with any Rack-based framework; optional
43
44
  Rails integration included.
45
+ email:
44
46
  executables: []
45
47
  extensions: []
46
48
  extra_rdoc_files: []
@@ -54,6 +56,7 @@ files:
54
56
  - lib/oopsie_exceptions/configuration.rb
55
57
  - lib/oopsie_exceptions/context.rb
56
58
  - lib/oopsie_exceptions/error_subscriber.rb
59
+ - lib/oopsie_exceptions/exception_normalizer.rb
57
60
  - lib/oopsie_exceptions/middleware.rb
58
61
  - lib/oopsie_exceptions/payload.rb
59
62
  - lib/oopsie_exceptions/railtie.rb
@@ -67,6 +70,7 @@ metadata:
67
70
  source_code_uri: https://github.com/theinventor/oopsie_exceptions
68
71
  changelog_uri: https://github.com/theinventor/oopsie_exceptions/releases
69
72
  rubygems_mfa_required: 'true'
73
+ post_install_message:
70
74
  rdoc_options: []
71
75
  require_paths:
72
76
  - lib
@@ -81,7 +85,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
81
85
  - !ruby/object:Gem::Version
82
86
  version: '0'
83
87
  requirements: []
84
- rubygems_version: 3.6.7
88
+ rubygems_version: 3.5.23
89
+ signing_key:
85
90
  specification_version: 4
86
91
  summary: Lightweight exception capture and webhook delivery for Ruby (framework-agnostic)
87
92
  test_files: []