From 696d67ea7c0ec00f905e22ab512b7f10e7ec8347 Mon Sep 17 00:00:00 2001 From: Giovanni Cangiani Date: Wed, 9 Sep 2026 17:57:53 +0200 Subject: [PATCH 1/4] fix: context field compatibility between model and db schema --- app/models/solid_errors/occurrence.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/models/solid_errors/occurrence.rb b/app/models/solid_errors/occurrence.rb index 4f034e0..fb6b694 100644 --- a/app/models/solid_errors/occurrence.rb +++ b/app/models/solid_errors/occurrence.rb @@ -5,6 +5,8 @@ class Occurrence < Record after_create_commit :send_email, if: -> { SolidErrors.send_emails? && SolidErrors.email_to.present? } after_create_commit :clear_resolved_errors, if: :should_clear_resolved_errors? + serialize :context, coder: JSON unless connection.adapter_name.downcase.include?("postgres") + # The parsed exception backtrace. Lines in this backtrace that are from installed gems # have the base path for gem installs replaced by "[GEM_ROOT]", while those in the project # have "[PROJECT_ROOT]". From a32c94cf7915b11cf6dec782c1d2bbce8d5e451b Mon Sep 17 00:00:00 2001 From: Giovanni Cangiani Date: Wed, 9 Sep 2026 17:57:53 +0200 Subject: [PATCH 2/4] fix: context field compatibility between model and db schema --- app/models/solid_errors/occurrence.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/models/solid_errors/occurrence.rb b/app/models/solid_errors/occurrence.rb index 4f034e0..295ff94 100644 --- a/app/models/solid_errors/occurrence.rb +++ b/app/models/solid_errors/occurrence.rb @@ -5,6 +5,10 @@ class Occurrence < Record after_create_commit :send_email, if: -> { SolidErrors.send_emails? && SolidErrors.email_to.present? } after_create_commit :clear_resolved_errors, if: :should_clear_resolved_errors? + unless type_for_attribute("context").is_a?(ActiveRecord::Type::Json) + serialize :context, coder: JSON + end + # The parsed exception backtrace. Lines in this backtrace that are from installed gems # have the base path for gem installs replaced by "[GEM_ROOT]", while those in the project # have "[PROJECT_ROOT]". From 666a1938b166d820261c28cd011a01af01c4e74d Mon Sep 17 00:00:00 2001 From: Giovanni Cangiani Date: Thu, 10 Sep 2026 17:17:28 +0200 Subject: [PATCH 3/4] feature: user defined ignored_errors this includes a slight refactoring of the way the IGNORED_ERRORS array is built. The original code were allowing for class objects to be passed. The feature is not used because it can actually lead to exception when the class is not defined. Instead of a loop lookup, I think it is faster to rely on the keys of an hash. --- README.md | 2 +- lib/solid_errors.rb | 5 +++++ lib/solid_errors/subscriber.rb | 9 ++------ test/models/solid_errors/occurrence_test.rb | 23 +++++++++++++++++++++ 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 874a177..52a36ce 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,7 @@ You can configure Solid Errors via the Rails configuration object, under the `so * `email_subject_prefix` - Prefix added to the subject line for email notifications. See [Email notifications](#email-notifications) for more information. * `base_controller_class` - Specify a different controller as the base class for the Solid Errors controller. See [Authentication](#authentication) for more information. * `destroy_after` - If set, Solid Errors will periodically destroy resolved records that are older than the value specified. See [Automatically destroying old records](#automatically-destroying-old-records) for more information. +* `ignored_errors` - An array of error class names (strings) that will not be saved in addition to the default list hardcoded in `lib/solid_errors/subscriber.rb` ### Database Configuration @@ -376,4 +377,3 @@ The gem is available as open source under the terms of the [MIT License](https:/ ## Code of Conduct Everyone interacting in the SolidErrors project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/fractaledmind/solid_errors/blob/main/CODE_OF_CONDUCT.md). - diff --git a/lib/solid_errors.rb b/lib/solid_errors.rb index c2a3788..dee56aa 100644 --- a/lib/solid_errors.rb +++ b/lib/solid_errors.rb @@ -15,6 +15,7 @@ module SolidErrors mattr_accessor :email_to mattr_accessor :email_subject_prefix mattr_accessor :destroy_after + mattr_writer :ignored_errors class << self # use method instead of attr_accessor to ensure @@ -29,6 +30,10 @@ def password @password ||= ENV["SOLIDERRORS_PASSWORD"] || @@password end + def ignored_errors + @ignored_errors ||= (@@ignored_errors || []).to_h{|k| [k,true]} + end + def send_emails? send_emails && email_to.present? end diff --git a/lib/solid_errors/subscriber.rb b/lib/solid_errors/subscriber.rb index 2063250..c8e8b0c 100644 --- a/lib/solid_errors/subscriber.rb +++ b/lib/solid_errors/subscriber.rb @@ -19,11 +19,10 @@ class Subscriber "CGI::Session::CookieStore::TamperedWithCookie", "Mongoid::Errors::DocumentNotFound", "Sinatra::NotFound", - "Sidekiq::JobRetry::Skip"].map(&:freeze).freeze + "Sidekiq::JobRetry::Skip"].to_h{|v| [v.freeze,true]}.freeze def report(error, handled:, severity:, context:, source: nil) return if ignore_by_class?(error.class.name) - error_attributes = { exception_class: error.class.name, message: s(error.message), @@ -49,11 +48,7 @@ def s(data) end def ignore_by_class?(error_class_name) - IGNORED_ERRORS.any? do |ignored_class| - ignored_class_name = ignored_class.respond_to?(:name) ? ignored_class.name : ignored_class - - ignored_class_name == error_class_name - end + SolidErrors.ignored_errors.key?(error_class_name) || IGNORED_ERRORS.key?(error_class_name) end end end diff --git a/test/models/solid_errors/occurrence_test.rb b/test/models/solid_errors/occurrence_test.rb index c994e7d..6cab4b5 100644 --- a/test/models/solid_errors/occurrence_test.rb +++ b/test/models/solid_errors/occurrence_test.rb @@ -39,6 +39,28 @@ def teardown end end + test "an ignored error should not be saved while an acceptable one is saved" do + SolidErrors.ignored_errors = ["RuntimeError"] + # An hardcoded ignored error should be discarded + assert_difference -> { SolidErrors::Error.count }, 0 do + assert_difference -> { SolidErrors::Occurrence.count }, 0 do + Rails.error.report( ActionController::RoutingError.new('argh')) + end + end + # A user ignored error should be discarded + assert_difference -> { SolidErrors::Error.count }, 0 do + assert_difference -> { SolidErrors::Occurrence.count }, 0 do + Rails.error.report( RuntimeError.new("argh") ) + end + end + # A valid error should be recorded + assert_difference -> { SolidErrors::Error.count }, +1 do + assert_difference -> { SolidErrors::Occurrence.count }, +1 do + Rails.error.report(StandardError.new("argh")) + end + end + end + private def simulate_99_old_exceptions(status) @@ -46,4 +68,5 @@ def simulate_99_old_exceptions(status) SolidErrors::Error.update_all(resolved_at: Time.current) if status == :resolved SolidErrors::Occurrence.last.update!(id: 99, created_at: 1.day.ago) end + end From d99b2716450a152a2794ac57669cba0fc9231ee6 Mon Sep 17 00:00:00 2001 From: Stephen Margheim Date: Tue, 13 Oct 2026 14:54:25 +0200 Subject: [PATCH 4/4] Check ignored_errors alongside the built-in list, without caching it The memoized lookup ignored any value set after the first error was reported, and the Occurrence serialize change duplicated #101 while querying the schema at class load. Classes and names both work. Co-Authored-By: Claude Opus 5.5 (1M context) --- README.md | 2 +- lib/solid_errors.rb | 1 + lib/solid_errors/subscriber.rb | 2 +- test/solid_errors/subscriber_test.rb | 34 ++++++++++++++++++++++++++++ 4 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 test/solid_errors/subscriber_test.rb diff --git a/README.md b/README.md index 03c1909..ef1635c 100644 --- a/README.md +++ b/README.md @@ -165,7 +165,7 @@ You can configure Solid Errors via the Rails configuration object, under the `so * `email_subject_prefix` - Prefix added to the subject line for email notifications. See [Email notifications](#email-notifications) for more information. * `base_controller_class` - Specify a different controller as the base class for the Solid Errors controller. See [Authentication](#authentication) for more information. * `destroy_after` - If set, Solid Errors will periodically destroy resolved records that are older than the value specified. See [Automatically destroying old records](#automatically-destroying-old-records) for more information. -* `ignored_errors` - An array of error class names (strings) that will not be saved in addition to the default list hardcoded in `lib/solid_errors/subscriber.rb` +* `ignored_errors` - Error classes, or their names, to never record, in addition to the built-in list in `SolidErrors::Subscriber::IGNORED_ERRORS`. For example, `config.solid_errors.ignored_errors = ["ActiveStorage::FileNotFoundError"]`. ### Database Configuration diff --git a/lib/solid_errors.rb b/lib/solid_errors.rb index f89f07b..6d70119 100644 --- a/lib/solid_errors.rb +++ b/lib/solid_errors.rb @@ -16,6 +16,7 @@ module SolidErrors mattr_accessor :email_to mattr_accessor :email_subject_prefix mattr_accessor :destroy_after + mattr_accessor :ignored_errors, default: [] class << self # use method instead of attr_accessor to ensure diff --git a/lib/solid_errors/subscriber.rb b/lib/solid_errors/subscriber.rb index 2063250..21ac5a3 100644 --- a/lib/solid_errors/subscriber.rb +++ b/lib/solid_errors/subscriber.rb @@ -49,7 +49,7 @@ def s(data) end def ignore_by_class?(error_class_name) - IGNORED_ERRORS.any? do |ignored_class| + [*IGNORED_ERRORS, *SolidErrors.ignored_errors].any? do |ignored_class| ignored_class_name = ignored_class.respond_to?(:name) ? ignored_class.name : ignored_class ignored_class_name == error_class_name diff --git a/test/solid_errors/subscriber_test.rb b/test/solid_errors/subscriber_test.rb new file mode 100644 index 0000000..68e73e0 --- /dev/null +++ b/test/solid_errors/subscriber_test.rb @@ -0,0 +1,34 @@ +require "test_helper" + +class SolidErrors::SubscriberTest < ActiveSupport::TestCase + CustomError = Class.new(StandardError) + + teardown do + SolidErrors.ignored_errors = [] + end + + test "errors named in ignored_errors are not recorded while others are" do + SolidErrors.ignored_errors = [CustomError.name] + + Rails.error.report(CustomError.new("ignored")) + Rails.error.report(StandardError.new("recorded")) + + assert_equal ["StandardError"], SolidErrors::Error.pluck(:exception_class) + end + + test "ignored_errors accepts classes as well as names" do + SolidErrors.ignored_errors = [CustomError] + + Rails.error.report(CustomError.new("ignored")) + + assert_empty SolidErrors::Error.all + end + + test "the built-in ignored errors still apply alongside ignored_errors" do + SolidErrors.ignored_errors = [CustomError.name] + + Rails.error.report(ActiveRecord::RecordNotFound.new("ignored")) + + assert_empty SolidErrors::Error.all + end +end