oopsie_exceptions 0.1.0 → 1.0.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 +4 -4
- data/lib/generators/oopsie_exceptions/templates/initializer.rb +12 -1
- data/lib/oopsie_exceptions/configuration.rb +4 -2
- data/lib/oopsie_exceptions/context.rb +17 -11
- data/lib/oopsie_exceptions/middleware.rb +5 -0
- data/lib/oopsie_exceptions/railtie.rb +20 -0
- data/lib/oopsie_exceptions/version.rb +1 -1
- data/lib/oopsie_exceptions/webhook_client.rb +8 -14
- metadata +7 -34
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 064c67e6e15924cf8a33314dfc3b4f174e749a5eef347b3e7620afb3785de58a
|
|
4
|
+
data.tar.gz: 9fe2be17be0fb8c3d171b302c0552b90f73277fea962f324de14ed84b0d5c487
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d75e1e6e3fc2b6b743414c7fb83ee0beb92ee54f41dd9cc7cd16e87d8036c545d5484e66930b3b50a63953d232c79c4043c6281b1c701d133ed4606b5c44c0ac
|
|
7
|
+
data.tar.gz: c29d4e4a013d41504193009688520ae0afdc55f9d3d6a4281c57d0e203f083314f919c7385bd4ae59de2f56055540ad98d5cc45d1f4fd009103836a6b871fe6d
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
OopsieExceptions.configure do |config|
|
|
2
2
|
# Add webhook endpoints — exceptions get POSTed here as JSON
|
|
3
3
|
# config.add_webhook "https://your-endpoint.com/webhooks/exceptions",
|
|
4
|
-
# headers: { "Authorization" => "Bearer
|
|
4
|
+
# headers: { "Authorization" => "Bearer #{ENV['OOPSIE_WEBHOOK_TOKEN']}" },
|
|
5
5
|
# name: "primary"
|
|
6
6
|
|
|
7
7
|
# config.add_webhook "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK"
|
|
@@ -27,4 +27,15 @@ OopsieExceptions.configure do |config|
|
|
|
27
27
|
# payload[:context][:deploy_sha] = ENV["GIT_SHA"]
|
|
28
28
|
# payload # return nil to skip this notification
|
|
29
29
|
# }
|
|
30
|
+
|
|
31
|
+
# Add custom context to every exception from the Rack env.
|
|
32
|
+
# This runs on every request — use it to attach the current user, feature flags, etc.
|
|
33
|
+
# config.context_builder = ->(env) {
|
|
34
|
+
# warden = env["warden"]
|
|
35
|
+
# user = warden&.user
|
|
36
|
+
# {
|
|
37
|
+
# user: user ? { id: user.id, email: user.email } : nil,
|
|
38
|
+
# action: env["action_dispatch.request.path_parameters"]&.slice(:controller, :action)&.values&.join("#")
|
|
39
|
+
# }
|
|
40
|
+
# }
|
|
30
41
|
end
|
|
@@ -7,13 +7,15 @@ module OopsieExceptions
|
|
|
7
7
|
:filter_headers, :capture_request_body,
|
|
8
8
|
:async_delivery, :timeout, :open_timeout,
|
|
9
9
|
:backtrace_cleaner, :before_notify,
|
|
10
|
-
:enabled
|
|
10
|
+
:context_builder, :logger, :enabled
|
|
11
11
|
|
|
12
12
|
def initialize
|
|
13
13
|
@webhook_urls = []
|
|
14
14
|
@app_name = defined?(Rails) ? (Rails.application.class.module_parent_name rescue "App") : "App"
|
|
15
|
-
@environment = defined?(Rails) ? (Rails.env rescue "development") : "development"
|
|
15
|
+
@environment = defined?(Rails) ? (Rails.env rescue "development") : (ENV["RACK_ENV"] || "development")
|
|
16
16
|
@ignored_exceptions = default_ignored_exceptions
|
|
17
|
+
@context_builder = nil
|
|
18
|
+
@logger = defined?(Rails.logger) && Rails.logger ? Rails.logger : nil
|
|
17
19
|
@filter_parameters = %w[password password_confirmation secret token api_key]
|
|
18
20
|
@filter_headers = %w[Authorization Cookie Set-Cookie]
|
|
19
21
|
@capture_request_body = false
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "rack"
|
|
4
|
+
|
|
3
5
|
module OopsieExceptions
|
|
4
6
|
module Context
|
|
5
7
|
THREAD_KEY = :oopsie_exceptions_context
|
|
@@ -22,18 +24,18 @@ module OopsieExceptions
|
|
|
22
24
|
end
|
|
23
25
|
|
|
24
26
|
def from_rack_env(env)
|
|
25
|
-
request =
|
|
27
|
+
request = Rack::Request.new(env)
|
|
26
28
|
config = OopsieExceptions.configuration
|
|
27
29
|
|
|
28
30
|
ctx = {
|
|
29
31
|
request: {
|
|
30
|
-
url: request.
|
|
31
|
-
method: request.
|
|
32
|
-
ip: request.
|
|
33
|
-
user_agent:
|
|
34
|
-
referer:
|
|
35
|
-
request_id:
|
|
36
|
-
params: sanitize_params(request.
|
|
32
|
+
url: request.url,
|
|
33
|
+
method: request.request_method,
|
|
34
|
+
ip: request.ip,
|
|
35
|
+
user_agent: env["HTTP_USER_AGENT"],
|
|
36
|
+
referer: env["HTTP_REFERER"],
|
|
37
|
+
request_id: env["action_dispatch.request_id"] || env["HTTP_X_REQUEST_ID"],
|
|
38
|
+
params: sanitize_params(request.params, config),
|
|
37
39
|
headers: extract_headers(env, config)
|
|
38
40
|
}
|
|
39
41
|
}
|
|
@@ -41,7 +43,7 @@ module OopsieExceptions
|
|
|
41
43
|
if config.capture_request_body && request.content_type&.include?("application/json")
|
|
42
44
|
body = request.body.read
|
|
43
45
|
request.body.rewind
|
|
44
|
-
ctx[:request][:body] = body
|
|
46
|
+
ctx[:request][:body] = body[0, 10_000] if body && !body.empty?
|
|
45
47
|
end
|
|
46
48
|
|
|
47
49
|
ctx
|
|
@@ -49,8 +51,12 @@ module OopsieExceptions
|
|
|
49
51
|
|
|
50
52
|
private
|
|
51
53
|
|
|
52
|
-
def sanitize_params(params)
|
|
53
|
-
params.
|
|
54
|
+
def sanitize_params(params, config)
|
|
55
|
+
filtered = params.reject { |k, _| k == "controller" || k == "action" }
|
|
56
|
+
filter_keys = config.filter_parameters
|
|
57
|
+
filtered.each_with_object({}) do |(k, v), hash|
|
|
58
|
+
hash[k] = filter_keys.any? { |f| k.to_s.include?(f) } ? "[FILTERED]" : v
|
|
59
|
+
end
|
|
54
60
|
rescue
|
|
55
61
|
{}
|
|
56
62
|
end
|
|
@@ -13,6 +13,11 @@ module OopsieExceptions
|
|
|
13
13
|
request_context = Context.from_rack_env(env)
|
|
14
14
|
Context.merge(request_context)
|
|
15
15
|
|
|
16
|
+
if OopsieExceptions.configuration.context_builder
|
|
17
|
+
extra = OopsieExceptions.configuration.context_builder.call(env)
|
|
18
|
+
Context.merge(extra) if extra.is_a?(Hash)
|
|
19
|
+
end
|
|
20
|
+
|
|
16
21
|
response = @app.call(env)
|
|
17
22
|
|
|
18
23
|
if response[0].to_i >= 500
|
|
@@ -6,6 +6,26 @@ module OopsieExceptions
|
|
|
6
6
|
app.middleware.insert_after ActionDispatch::DebugExceptions, OopsieExceptions::Middleware
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
+
initializer "oopsie_exceptions.active_job" do
|
|
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
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
9
29
|
initializer "oopsie_exceptions.error_subscriber", after: :load_config_initializers do
|
|
10
30
|
if Rails.respond_to?(:error) && OopsieExceptions.configuration.enabled
|
|
11
31
|
Rails.error.subscribe(OopsieExceptions::ErrorSubscriber.new)
|
|
@@ -27,30 +27,24 @@ module OopsieExceptions
|
|
|
27
27
|
response = http.request(request)
|
|
28
28
|
|
|
29
29
|
unless response.is_a?(Net::HTTPSuccess)
|
|
30
|
-
|
|
30
|
+
log(:warn, "Webhook #{webhook.name} responded #{response.code}: #{response.body.to_s[0, 500]}")
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
response
|
|
34
34
|
rescue => e
|
|
35
|
-
|
|
35
|
+
log(:error, "Failed to deliver to #{webhook.name}: #{e.message}")
|
|
36
36
|
nil
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
private
|
|
40
40
|
|
|
41
|
-
def
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
def log(level, message)
|
|
42
|
+
logger = OopsieExceptions.configuration.logger
|
|
43
|
+
formatted = "[OopsieExceptions] #{message}"
|
|
44
|
+
if logger
|
|
45
|
+
logger.send(level, formatted)
|
|
44
46
|
else
|
|
45
|
-
warn(
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def log_error(message)
|
|
50
|
-
if defined?(Rails.logger) && Rails.logger
|
|
51
|
-
Rails.logger.error("[OopsieExceptions] #{message}")
|
|
52
|
-
else
|
|
53
|
-
warn("[OopsieExceptions] ERROR: #{message}")
|
|
47
|
+
warn(formatted)
|
|
54
48
|
end
|
|
55
49
|
end
|
|
56
50
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: oopsie_exceptions
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Troy
|
|
@@ -11,50 +11,23 @@ cert_chain: []
|
|
|
11
11
|
date: 2026-04-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
14
|
+
name: rack
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
19
|
+
version: '2.0'
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
24
|
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
27
|
-
- !ruby/object:Gem::Dependency
|
|
28
|
-
name: activesupport
|
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
|
30
|
-
requirements:
|
|
31
|
-
- - ">="
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: '7.0'
|
|
34
|
-
type: :runtime
|
|
35
|
-
prerelease: false
|
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
-
requirements:
|
|
38
|
-
- - ">="
|
|
39
|
-
- !ruby/object:Gem::Version
|
|
40
|
-
version: '7.0'
|
|
41
|
-
- !ruby/object:Gem::Dependency
|
|
42
|
-
name: actionpack
|
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
|
44
|
-
requirements:
|
|
45
|
-
- - ">="
|
|
46
|
-
- !ruby/object:Gem::Version
|
|
47
|
-
version: '7.0'
|
|
48
|
-
type: :runtime
|
|
49
|
-
prerelease: false
|
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
-
requirements:
|
|
52
|
-
- - ">="
|
|
53
|
-
- !ruby/object:Gem::Version
|
|
54
|
-
version: '7.0'
|
|
26
|
+
version: '2.0'
|
|
55
27
|
description: Captures unhandled exceptions from web requests and background jobs,
|
|
56
28
|
enriches them with request/user/server context, and delivers structured JSON payloads
|
|
57
|
-
to configurable webhook endpoints.
|
|
29
|
+
to configurable webhook endpoints. Works with any Rack-based framework; optional
|
|
30
|
+
Rails integration included.
|
|
58
31
|
email:
|
|
59
32
|
executables: []
|
|
60
33
|
extensions: []
|
|
@@ -99,5 +72,5 @@ requirements: []
|
|
|
99
72
|
rubygems_version: 3.5.23
|
|
100
73
|
signing_key:
|
|
101
74
|
specification_version: 4
|
|
102
|
-
summary: Lightweight exception capture and webhook delivery for
|
|
75
|
+
summary: Lightweight exception capture and webhook delivery for Ruby (framework-agnostic)
|
|
103
76
|
test_files: []
|