oopsie_exceptions 1.0.0 → 1.1.0

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: 064c67e6e15924cf8a33314dfc3b4f174e749a5eef347b3e7620afb3785de58a
4
- data.tar.gz: 9fe2be17be0fb8c3d171b302c0552b90f73277fea962f324de14ed84b0d5c487
3
+ metadata.gz: a3c1bf7176b541c4c19f09ca56724e8c873e1bae149ac9ff9963a4f4fb4f1156
4
+ data.tar.gz: 692fd55d89e3235279b1ba98764f1fbeaeab5e11204c3733674f7a8958956262
5
5
  SHA512:
6
- metadata.gz: d75e1e6e3fc2b6b743414c7fb83ee0beb92ee54f41dd9cc7cd16e87d8036c545d5484e66930b3b50a63953d232c79c4043c6281b1c701d133ed4606b5c44c0ac
7
- data.tar.gz: c29d4e4a013d41504193009688520ae0afdc55f9d3d6a4281c57d0e203f083314f919c7385bd4ae59de2f56055540ad98d5cc45d1f4fd009103836a6b871fe6d
6
+ metadata.gz: 0c8a3bfd9907819f35ecd979d114e9784daf71ff7c5b2264246782458463c15271a71319208fe5798de1c11178c7bf643e8bcda693124ed74e7f2a50cc25ef17
7
+ data.tar.gz: 9d4f5168d31b516127abc0afa2e894ec02c28cdf076e82f96d555f81c1e38fb3884d3fbca46307572ce2f9749798cf1ace9c4e2fa6d98a6263908c4a695a14a1
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Lightweight exception capture and webhook delivery for Rails. Like Sentry/Rollbar, but self-hosted and webhook-driven.
4
4
 
5
- Captures unhandled exceptions from web requests and background jobs, enriches them with request/user/server context, and delivers structured JSON payloads to configurable webhook endpoints.
5
+ Captures unhandled exceptions from web requests and background jobs, enriches them with request/user/server context, and POSTs structured JSON payloads to one or more webhook endpoints.
6
6
 
7
7
  ## Installation
8
8
 
@@ -18,49 +18,99 @@ Then run the install generator:
18
18
  bin/rails generate oopsie_exceptions:install
19
19
  ```
20
20
 
21
- This creates:
22
- - `config/initializers/oopsie_exceptions.rb` — configuration
23
- - `app/jobs/oopsie_exceptions/delivery_job.rb` — async webhook delivery
21
+ That creates a single file: `config/initializers/oopsie_exceptions.rb`. No code lands in `app/` — the Rack middleware, Rails error subscriber, ActiveJob hook, and webhook delivery job all live inside the gem.
24
22
 
25
23
  ## Configuration
26
24
 
27
- Edit `config/initializers/oopsie_exceptions.rb`:
25
+ The most common pattern is to configure a different webhook per environment, with the URL and auth token coming from environment variables. Here's a real-world initializer:
28
26
 
29
27
  ```ruby
28
+ # config/initializers/oopsie_exceptions.rb
30
29
  OopsieExceptions.configure do |config|
31
- config.add_webhook "https://your-endpoint.com/webhooks/exceptions",
32
- headers: { "Authorization" => "Bearer #{ENV['OOPSIE_WEBHOOK_TOKEN']}" }
30
+ if Rails.env.development?
31
+ config.add_webhook(
32
+ "http://localhost:3099/api/v1/exceptions",
33
+ headers: { "Authorization" => "Bearer #{ENV['OOPSIE_DEV_TOKEN']}" },
34
+ name: "oopsie-local"
35
+ )
36
+ config.async_delivery = false
37
+ end
38
+
39
+ if Rails.env.production?
40
+ config.add_webhook(
41
+ "https://oopsie.example.com/api/v1/exceptions",
42
+ headers: { "Authorization" => "Bearer #{ENV['OOPSIE_PROD_TOKEN']}" },
43
+ name: "oopsie-prod"
44
+ )
45
+ config.async_delivery = true
46
+ end
33
47
 
34
48
  config.app_name = "MyApp"
35
49
  config.environment = Rails.env
36
- config.enabled = Rails.env.production?
50
+ config.filter_parameters = %w[password password_confirmation secret token api_key]
51
+ config.enabled = Rails.env.development? || Rails.env.production?
52
+
53
+ # Attach the current user and controller#action to every exception.
54
+ # `env` is the Rack env — runs once per request.
55
+ config.context_builder = ->(env) {
56
+ warden = env["warden"]
57
+ user = warden&.user
58
+ params = env["action_dispatch.request.path_parameters"]
59
+ {
60
+ user: user ? { id: user.id, email: user.email } : nil,
61
+ action: params ? "#{params[:controller]}##{params[:action]}" : nil
62
+ }.compact
63
+ }
37
64
  end
38
65
  ```
39
66
 
40
- ## What happens automatically
67
+ ### Configuration options
41
68
 
42
- - **Rack middleware** inserted after `DebugExceptions` catches unhandled exceptions
43
- - **Rails.error subscriber** captures framework-reported errors
44
- - Request context (URL, IP, params, headers) is collected automatically
45
- - Webhooks are delivered async via ActiveJob
69
+ | Option | Default | Description |
70
+ | --- | --- | --- |
71
+ | `add_webhook(url, headers:, name:)` | | Register a webhook. Call multiple times for fan-out. |
72
+ | `app_name` | Rails app module name | Identifier sent in every payload. |
73
+ | `environment` | `Rails.env` | Environment label sent in every payload. |
74
+ | `enabled` | `true` | Master kill switch. Set false in test/dev. |
75
+ | `async_delivery` | `true` | Deliver via ActiveJob. Set false to POST inline. |
76
+ | `filter_parameters` | `password, secret, token, api_key, ...` | Param names to redact in payloads. |
77
+ | `filter_headers` | `Authorization, Cookie, Set-Cookie` | Headers to strip from payloads. |
78
+ | `capture_request_body` | `false` | Include first 10KB of JSON request bodies. |
79
+ | `ignored_exceptions` | 404s, routing errors, etc. | Exception class names to silently drop. |
80
+ | `ignore_exception(*names)` | — | Append to `ignored_exceptions`. |
81
+ | `context_builder` | `nil` | `->(env) { Hash }` — extra context per request. |
82
+ | `before_notify` | `nil` | `->(payload) { payload }` — mutate or drop payloads. |
83
+ | `timeout` / `open_timeout` | `10` / `5` | HTTP timeouts (seconds). |
46
84
 
47
- ## Adding user context
85
+ ## What gets captured automatically
48
86
 
49
- In your `ApplicationController`:
87
+ - **Web requests** — A Rack middleware inserted after `ActionDispatch::DebugExceptions` catches unhandled exceptions and 5xx responses.
88
+ - **Background jobs** — `ActiveJob::Base.execute` is wrapped at the class level (the same approach Appsignal uses), so every queue adapter (Solid Queue, Sidekiq, Async, etc.) is covered. Catches `DeserializationError` and missing job classes too.
89
+ - **`Rails.error` reports** — Subscribed via `Rails.error.subscribe`, so anything reported through the framework error reporter (`Rails.error.report` / `Rails.error.handle`) flows through.
90
+
91
+ Each captured exception is enriched with request URL/method/IP/params/headers, the current user (via `context_builder` or `set_context`), server hostname/PID/Ruby version, and a UTC timestamp.
92
+
93
+ ## Adding context per-request
94
+
95
+ If you don't want to use `context_builder`, you can set context from a controller:
50
96
 
51
97
  ```ruby
52
- before_action :set_oopsie_context
98
+ class ApplicationController < ActionController::Base
99
+ before_action :set_oopsie_context
53
100
 
54
- private
101
+ private
55
102
 
56
- def set_oopsie_context
57
- OopsieExceptions.set_context(
58
- user: current_user ? { id: current_user.id, email: current_user.email } : nil,
59
- action: "#{self.class.name}##{action_name}"
60
- )
103
+ def set_oopsie_context
104
+ OopsieExceptions.set_context(
105
+ user: current_user ? { id: current_user.id, email: current_user.email } : nil,
106
+ action: "#{self.class.name}##{action_name}"
107
+ )
108
+ end
61
109
  end
62
110
  ```
63
111
 
112
+ `context_builder` is preferred — it runs in the middleware before the request hits any controller, so it captures context even on errors raised before `before_action` runs.
113
+
64
114
  ## Manual reporting
65
115
 
66
116
  ```ruby
@@ -71,38 +121,89 @@ rescue => e
71
121
  end
72
122
  ```
73
123
 
124
+ Or scope context to a block:
125
+
126
+ ```ruby
127
+ OopsieExceptions.with_context(tenant_id: 42) do
128
+ do_work
129
+ end
130
+ ```
131
+
74
132
  ## Multiple webhooks
75
133
 
76
134
  ```ruby
77
- config.add_webhook "https://your-api.com/exceptions"
135
+ config.add_webhook "https://your-api.com/exceptions",
136
+ headers: { "Authorization" => "Bearer #{ENV['PRIMARY_TOKEN']}" }
137
+
78
138
  config.add_webhook "https://hooks.slack.com/services/..."
79
139
  config.add_webhook "https://discord.com/api/webhooks/..."
80
140
  ```
81
141
 
82
- Each endpoint receives every exception.
142
+ Each endpoint receives every exception. Failures on one webhook don't affect the others.
83
143
 
84
144
  ## Payload format
85
145
 
86
- Each webhook receives a JSON payload with:
146
+ Each webhook receives a JSON POST with:
87
147
 
88
148
  - **exception** — class, message, backtrace, cause chain
89
149
  - **request** — URL, method, IP, params, headers, user agent
90
- - **context** — user info and custom data you set
150
+ - **context** — `user`, `action`, `job`, plus anything you set via `context_builder` / `set_context`
91
151
  - **server** — hostname, PID, Ruby/Rails versions
92
- - **app** — name, environment
152
+ - **app** — `app_name`, `environment`
93
153
  - **timestamp** — UTC ISO8601
154
+ - **handled** — `true` for `OopsieExceptions.report(..., handled: true)`, `false` for unhandled
155
+
156
+ ## Filtering noise
157
+
158
+ By default these are dropped:
159
+
160
+ ```
161
+ ActionController::RoutingError
162
+ ActionController::UnknownFormat
163
+ ActionController::BadRequest
164
+ ActionDispatch::Http::MimeNegotiation::InvalidType
165
+ AbstractController::ActionNotFound
166
+ ActiveRecord::RecordNotFound
167
+ ActionController::UnknownHttpMethod
168
+ ```
94
169
 
95
- ## Filtering
170
+ Add your own:
96
171
 
97
- By default, common noise exceptions are ignored (404s, routing errors, `ActionController::BadRequest`, etc.). Sensitive parameters (`password`, `token`, `secret`, `api_key`) and headers (`Authorization`, `Cookie`) are automatically filtered from payloads.
172
+ ```ruby
173
+ config.ignore_exception "MyApp::IgnorableError", "ThirdParty::Timeout"
174
+ ```
98
175
 
99
- Customize in your initializer:
176
+ Sensitive params (`password`, `token`, `secret`, `api_key`) and headers (`Authorization`, `Cookie`, `Set-Cookie`) are redacted from payloads automatically.
177
+
178
+ ## Mutating or dropping payloads
100
179
 
101
180
  ```ruby
102
- config.ignored_exceptions += ["MyApp::IgnorableError"]
103
- config.filter_parameters += [:credit_card]
181
+ config.before_notify = ->(payload) {
182
+ payload[:context][:deploy_sha] = ENV["GIT_SHA"]
183
+ return nil if payload[:exception][:message].include?("known noise")
184
+ payload
185
+ }
104
186
  ```
105
187
 
188
+ Return `nil` to drop the notification entirely.
189
+
190
+ ## Upgrading from earlier versions
191
+
192
+ If you're coming from an older version of the gem that generated `app/jobs/oopsie_exceptions/delivery_job.rb` in your app, **delete that file**. The gem now ships its own `OopsieExceptions::WebhookJob` and the host-app file is obsolete.
193
+
194
+ ```bash
195
+ rm app/jobs/oopsie_exceptions/delivery_job.rb
196
+ rmdir app/jobs/oopsie_exceptions # if empty
197
+ ```
198
+
199
+ Or just re-run the install generator and it will detect and remove the legacy file for you:
200
+
201
+ ```bash
202
+ bin/rails generate oopsie_exceptions:install
203
+ ```
204
+
205
+ No initializer changes are required.
206
+
106
207
  ## License
107
208
 
108
209
  MIT — see [LICENSE.txt](LICENSE.txt).
@@ -5,32 +5,27 @@ module OopsieExceptions
5
5
  class InstallGenerator < Rails::Generators::Base
6
6
  source_root File.expand_path("templates", __dir__)
7
7
 
8
- desc "Creates an OopsieExceptions initializer and delivery job"
8
+ desc "Creates an OopsieExceptions initializer"
9
9
 
10
10
  def create_initializer
11
11
  template "initializer.rb", "config/initializers/oopsie_exceptions.rb"
12
12
  end
13
13
 
14
- def create_delivery_job
15
- template "delivery_job.rb", "app/jobs/oopsie_exceptions/delivery_job.rb"
14
+ def remove_legacy_delivery_job
15
+ legacy_path = "app/jobs/oopsie_exceptions/delivery_job.rb"
16
+ return unless File.exist?(File.join(destination_root, legacy_path))
17
+
18
+ say ""
19
+ say "Found legacy #{legacy_path} from a previous version.", :yellow
20
+ say "The gem now ships its own webhook job — this file is no longer used."
21
+ remove_file legacy_path
16
22
  end
17
23
 
18
24
  def show_post_install
19
25
  say ""
20
26
  say "OopsieExceptions installed!", :green
21
27
  say ""
22
- say "Next steps:"
23
- say " 1. Edit config/initializers/oopsie_exceptions.rb to add your webhook URLs"
24
- say " 2. Optionally add user context in ApplicationController:"
25
- say ""
26
- say " before_action :set_oopsie_context"
27
- say ""
28
- say " def set_oopsie_context"
29
- say " OopsieExceptions.set_context("
30
- say " user: current_user ? { id: current_user.id, email: current_user.email } : nil,"
31
- say " action: \"\#{self.class.name}#\#{action_name}\""
32
- say " )"
33
- say " end"
28
+ say "Next step: edit config/initializers/oopsie_exceptions.rb and add your webhook URLs."
34
29
  say ""
35
30
  end
36
31
  end
@@ -1,8 +1,23 @@
1
1
  OopsieExceptions.configure do |config|
2
- # Add webhook endpoints — exceptions get POSTed here as JSON
3
- # config.add_webhook "https://your-endpoint.com/webhooks/exceptions",
4
- # headers: { "Authorization" => "Bearer #{ENV['OOPSIE_WEBHOOK_TOKEN']}" },
5
- # name: "primary"
2
+ # Webhook endpoints — exceptions get POSTed as JSON.
3
+ # Configure per-environment so dev errors don't hit your prod tracker.
4
+ if Rails.env.development?
5
+ config.add_webhook(
6
+ "http://localhost:3099/api/v1/exceptions",
7
+ headers: { "Authorization" => "Bearer #{ENV['OOPSIE_DEV_TOKEN']}" },
8
+ name: "oopsie-local"
9
+ )
10
+ config.async_delivery = false
11
+ end
12
+
13
+ if Rails.env.production?
14
+ config.add_webhook(
15
+ "https://oopsie.example.com/api/v1/exceptions",
16
+ headers: { "Authorization" => "Bearer #{ENV['OOPSIE_PROD_TOKEN']}" },
17
+ name: "oopsie-prod"
18
+ )
19
+ config.async_delivery = true
20
+ end
6
21
 
7
22
  # config.add_webhook "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK"
8
23
  # config.add_webhook "https://discord.com/api/webhooks/YOUR/DISCORD/WEBHOOK"
@@ -10,32 +25,30 @@ OopsieExceptions.configure do |config|
10
25
  config.app_name = Rails.application.class.module_parent_name
11
26
  config.environment = Rails.env
12
27
 
13
- # Deliver webhooks async via ActiveJob (set false for sync delivery)
14
- config.async_delivery = true
15
-
16
- # Exceptions that won't be reported (404s, bot garbage, etc.)
17
- # config.ignore_exception "ActionController::RoutingError"
18
-
19
28
  # Filter sensitive params from payloads (inherits from Rails by default)
20
29
  config.filter_parameters = Rails.application.config.filter_parameters.map(&:to_s)
21
30
 
22
- # Set false to disable in dev/test
23
- config.enabled = Rails.env.production? || Rails.env.staging?
31
+ # Master kill switch leave off in test
32
+ config.enabled = Rails.env.development? || Rails.env.production?
24
33
 
25
- # Optional: modify or drop payloads before sending
26
- # config.before_notify = ->(payload) {
27
- # payload[:context][:deploy_sha] = ENV["GIT_SHA"]
28
- # payload # return nil to skip this notification
29
- # }
34
+ # Exceptions that won't be reported (404s, bot garbage, etc.)
35
+ # config.ignore_exception "MyApp::IgnorableError"
30
36
 
31
- # Add custom context to every exception from the Rack env.
32
- # This runs on every requestuse it to attach the current user, feature flags, etc.
37
+ # Attach the current user and controller#action to every exception.
38
+ # `env` is the Rack envruns once per request, before any controller code.
33
39
  # config.context_builder = ->(env) {
34
40
  # warden = env["warden"]
35
41
  # user = warden&.user
42
+ # params = env["action_dispatch.request.path_parameters"]
36
43
  # {
37
44
  # user: user ? { id: user.id, email: user.email } : nil,
38
- # action: env["action_dispatch.request.path_parameters"]&.slice(:controller, :action)&.values&.join("#")
39
- # }
45
+ # action: params ? "#{params[:controller]}##{params[:action]}" : nil
46
+ # }.compact
47
+ # }
48
+
49
+ # Optional: modify or drop payloads before sending
50
+ # config.before_notify = ->(payload) {
51
+ # payload[:context][:deploy_sha] = ENV["GIT_SHA"]
52
+ # payload # return nil to skip this notification
40
53
  # }
41
54
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OopsieExceptions
4
+ # Class-level extension for ActiveJob::Base. Wraps `execute(job_data)`, the
5
+ # entry point queue adapters call, so we capture every unhandled exception
6
+ # — including ones that fire before `perform` runs (e.g. DeserializationError,
7
+ # or a missing job class).
8
+ #
9
+ # This mirrors how Appsignal hooks ActiveJob (lib/appsignal/hooks/active_job.rb).
10
+ module ActiveJobExtension
11
+ def execute(job_data)
12
+ job_context = {
13
+ job: {
14
+ class: job_data["job_class"],
15
+ job_id: job_data["job_id"],
16
+ queue: job_data["queue_name"],
17
+ arguments: job_data["arguments"],
18
+ executions: job_data["executions"],
19
+ provider_job_id: job_data["provider_job_id"]
20
+ }.compact
21
+ }
22
+
23
+ OopsieExceptions.with_context(job_context) do
24
+ super
25
+ rescue Exception => exception
26
+ OopsieExceptions.report(
27
+ exception,
28
+ context: { namespace: "background_job" },
29
+ handled: false
30
+ )
31
+ raise
32
+ end
33
+ end
34
+ end
35
+ end
@@ -8,21 +8,8 @@ module OopsieExceptions
8
8
 
9
9
  initializer "oopsie_exceptions.active_job" do
10
10
  ActiveSupport.on_load(:active_job) do
11
- around_perform do |job, block|
12
- OopsieExceptions.with_context(
13
- job: {
14
- class: job.class.name,
15
- job_id: job.job_id,
16
- queue: job.queue_name,
17
- arguments: job.arguments.map(&:to_s)
18
- }
19
- ) do
20
- block.call
21
- end
22
- rescue Exception => e
23
- OopsieExceptions.report(e, context: { namespace: "background_job" }, handled: false)
24
- raise
25
- end
11
+ ::ActiveJob::Base.extend(OopsieExceptions::ActiveJobExtension)
12
+ require "oopsie_exceptions/webhook_job"
26
13
  end
27
14
  end
28
15
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OopsieExceptions
4
- VERSION = "1.0.0"
4
+ VERSION = "1.1.0"
5
5
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Webhook delivery job. Loaded from the Railtie inside on_load(:active_job),
4
+ # so it's only defined when the host app has ActiveJob available.
5
+ #
6
+ # Inherits from ActiveJob::Base directly (not ApplicationJob) so the gem has
7
+ # zero hard dependencies on host-app code.
8
+ module OopsieExceptions
9
+ class WebhookJob < ActiveJob::Base
10
+ queue_as :default
11
+
12
+ discard_on StandardError do |_job, error|
13
+ logger = OopsieExceptions.configuration.logger
14
+ logger&.error("[OopsieExceptions] WebhookJob discarded permanently: #{error.message}")
15
+ end
16
+
17
+ retry_on Net::OpenTimeout, Net::ReadTimeout, wait: 5.seconds, attempts: 3
18
+
19
+ def perform(payload_json, webhook_url, headers_json)
20
+ webhook = OopsieExceptions::Configuration::Webhook.new(
21
+ url: webhook_url,
22
+ headers: JSON.parse(headers_json),
23
+ name: webhook_url
24
+ )
25
+
26
+ OopsieExceptions::WebhookClient.post(webhook, payload_json)
27
+ end
28
+ end
29
+ end
@@ -7,6 +7,7 @@ require_relative "oopsie_exceptions/payload"
7
7
  require_relative "oopsie_exceptions/webhook_client"
8
8
  require_relative "oopsie_exceptions/middleware"
9
9
  require_relative "oopsie_exceptions/error_subscriber"
10
+ require_relative "oopsie_exceptions/active_job_extension"
10
11
  require_relative "oopsie_exceptions/railtie" if defined?(Rails::Railtie)
11
12
 
12
13
  module OopsieExceptions
@@ -58,8 +59,8 @@ module OopsieExceptions
58
59
 
59
60
  def deliver(payload)
60
61
  configuration.webhook_urls.each do |webhook|
61
- if configuration.async_delivery && defined?(OopsieExceptions::DeliveryJob)
62
- DeliveryJob.perform_later(
62
+ if configuration.async_delivery && defined?(OopsieExceptions::WebhookJob)
63
+ WebhookJob.perform_later(
63
64
  payload.to_json,
64
65
  webhook.url,
65
66
  webhook.headers.to_json
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oopsie_exceptions
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Troy
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-04-04 00:00:00.000000000 Z
11
+ date: 2026-04-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -36,9 +36,9 @@ files:
36
36
  - LICENSE.txt
37
37
  - README.md
38
38
  - lib/generators/oopsie_exceptions/install_generator.rb
39
- - lib/generators/oopsie_exceptions/templates/delivery_job.rb
40
39
  - lib/generators/oopsie_exceptions/templates/initializer.rb
41
40
  - lib/oopsie_exceptions.rb
41
+ - lib/oopsie_exceptions/active_job_extension.rb
42
42
  - lib/oopsie_exceptions/configuration.rb
43
43
  - lib/oopsie_exceptions/context.rb
44
44
  - lib/oopsie_exceptions/error_subscriber.rb
@@ -47,6 +47,7 @@ files:
47
47
  - lib/oopsie_exceptions/railtie.rb
48
48
  - lib/oopsie_exceptions/version.rb
49
49
  - lib/oopsie_exceptions/webhook_client.rb
50
+ - lib/oopsie_exceptions/webhook_job.rb
50
51
  homepage: https://github.com/theinventor/oopsie_exceptions
51
52
  licenses:
52
53
  - MIT
@@ -69,7 +70,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
69
70
  - !ruby/object:Gem::Version
70
71
  version: '0'
71
72
  requirements: []
72
- rubygems_version: 3.5.23
73
+ rubygems_version: 3.5.3
73
74
  signing_key:
74
75
  specification_version: 4
75
76
  summary: Lightweight exception capture and webhook delivery for Ruby (framework-agnostic)
@@ -1,21 +0,0 @@
1
- module OopsieExceptions
2
- class DeliveryJob < ApplicationJob
3
- queue_as :default
4
-
5
- discard_on StandardError do |job, error|
6
- Rails.logger.error("[OopsieExceptions] DeliveryJob failed permanently: #{error.message}")
7
- end
8
-
9
- retry_on Net::OpenTimeout, Net::ReadTimeout, wait: 5.seconds, attempts: 3
10
-
11
- def perform(payload_json, webhook_url, headers_json)
12
- webhook = OopsieExceptions::Configuration::Webhook.new(
13
- url: webhook_url,
14
- headers: JSON.parse(headers_json),
15
- name: webhook_url
16
- )
17
-
18
- OopsieExceptions::WebhookClient.post(webhook, payload_json)
19
- end
20
- end
21
- end