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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 38724bd4e7e3113063179a4306f5207544e2c8933491ab429433d5294c00f04a
4
- data.tar.gz: c42960e5299dfce450d2cd7992cc3fe5acf1c92ab667c41c555a2c8b90a258a5
3
+ metadata.gz: 064c67e6e15924cf8a33314dfc3b4f174e749a5eef347b3e7620afb3785de58a
4
+ data.tar.gz: 9fe2be17be0fb8c3d171b302c0552b90f73277fea962f324de14ed84b0d5c487
5
5
  SHA512:
6
- metadata.gz: aa0c4ed76d142a98fc0b1097ad6956a637296eaa0da494762d839c2b94aecd4eabd061cc5d18e6c22113652ab401e2d74d6767ac86bccaed9c77ea620b990e7e
7
- data.tar.gz: 39de699f9980f8151a55098257c75b50b000300718031baea8b9b8322cd96d8cb49f766a41dfddec615338d3835ece207b0192438f2730146df69d67a8427c48
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 <%= "#{ENV['OOPSIE_WEBHOOK_TOKEN']}" %>" },
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 = ActionDispatch::Request.new(env)
27
+ request = Rack::Request.new(env)
26
28
  config = OopsieExceptions.configuration
27
29
 
28
30
  ctx = {
29
31
  request: {
30
- url: request.original_url,
31
- method: request.method,
32
- ip: request.remote_ip,
33
- user_agent: request.user_agent,
34
- referer: request.referer,
35
- request_id: request.request_id,
36
- params: sanitize_params(request.filtered_parameters),
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.truncate(10_000) if body.present?
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.except("controller", "action").to_h
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)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OopsieExceptions
4
- VERSION = "0.1.0"
4
+ VERSION = "1.0.0"
5
5
  end
@@ -27,30 +27,24 @@ module OopsieExceptions
27
27
  response = http.request(request)
28
28
 
29
29
  unless response.is_a?(Net::HTTPSuccess)
30
- log_warn("Webhook #{webhook.name} responded #{response.code}: #{response.body.to_s[0, 500]}")
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
- log_error("Failed to deliver to #{webhook.name}: #{e.message}")
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 log_warn(message)
42
- if defined?(Rails.logger) && Rails.logger
43
- Rails.logger.warn("[OopsieExceptions] #{message}")
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("[OopsieExceptions] #{message}")
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: 0.1.0
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: railties
14
+ name: rack
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '7.0'
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: '7.0'
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 Rails
75
+ summary: Lightweight exception capture and webhook delivery for Ruby (framework-agnostic)
103
76
  test_files: []