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 +4 -4
- data/lib/oopsie_exceptions/exception_normalizer.rb +89 -0
- data/lib/oopsie_exceptions/version.rb +1 -1
- data/lib/oopsie_exceptions.rb +10 -4
- metadata +8 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 49072b73da34cefc5bf2c080d3bad5f4d0d6f0494225ae8a6e4140488aa00744
|
|
4
|
+
data.tar.gz: 85a4877ef4f61c7a81932e1b607d2d0a637ecfaebff111b54eb1a59ded96a496
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
data/lib/oopsie_exceptions.rb
CHANGED
|
@@ -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
|
-
|
|
28
|
-
|
|
28
|
+
normalized = ExceptionNormalizer.normalize(exception, context: context)
|
|
29
|
+
reportable_exception = normalized.exception
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
return if configuration.ignored?(reportable_exception)
|
|
32
|
+
return if reportable_exception.instance_variable_get(REPORTED_MARKER)
|
|
31
33
|
|
|
32
|
-
|
|
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.
|
|
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:
|
|
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.
|
|
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: []
|