exception-track 1.2.0 → 1.3.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: 032c9ba1ad0589f704bedbff866d3bab2aae870abc44251b2e8b5f504732fe77
4
- data.tar.gz: 72946a5e0284dac9fd318671aa46f2322c10522010a6d2c076024801d7749be9
3
+ metadata.gz: 71a2bc45d225661a466bdd4fe3ae8227253cf0e4e48430a3f3db33605c04aed7
4
+ data.tar.gz: fbf989949bd59b5ac8ea1b44c159cae09ded01832c21c42193729a95b20f3015
5
5
  SHA512:
6
- metadata.gz: 4e2fe6f4660aec15140e9fa70f1876a51d78e1c61fdc98cfadfaaee08a5426fc3ddba66e6b3b858cc119293add6503a95bd3934de779bdcb22cb23ad0bfbfe61
7
- data.tar.gz: 3807967a319b57e6e7a1fe1dc0647e3bbb4496729b47ddd392d9a36973f4d76c7470dfb646c085a841d9921b5295e8e6e6de170534a55d51bcd739295cdebbb6
6
+ metadata.gz: a7b776ccbda802c32ca77f60ec4257e198d2e9e96c611ccc03c14fb00efc1fb5f127a60e9df627b985ddef347d1e1ceb2f04c8919894ab73da3ac796fe8cc0c5
7
+ data.tar.gz: 14a4f70638b02742170ff3138d553624a8c379b2c0480624f178354fd0324dbcb58d5d75627e78f24026338fbeb750478431034ac76eb3b7cb3f8cd42df4dc9e
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ExceptionTrack
4
- VERSION = "1.2.0"
4
+ VERSION = "1.3.0"
5
5
  end
@@ -7,7 +7,6 @@ require "exception-track/engine"
7
7
 
8
8
  require "exception_notification"
9
9
  require "exception_notification/rails"
10
- require "exception_notifier/exception_track_notifier"
11
10
 
12
11
  require "kaminari"
13
12
 
@@ -28,5 +27,5 @@ module ExceptionTrack
28
27
  end
29
28
 
30
29
  ExceptionNotification.configure do |config|
31
- config.add_notifier :exception_track, {}
30
+ config.add_notifier :db, {}
32
31
  end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ExceptionNotification
4
+ class Rack
5
+ class CascadePassException < RuntimeError; end
6
+
7
+ def initialize(app, options = {})
8
+ @app = app
9
+
10
+ ExceptionNotifier.tap do |en|
11
+ en.ignored_exceptions = options.delete(:ignore_exceptions) if options.key?(:ignore_exceptions)
12
+ en.error_grouping = options.delete(:error_grouping) if options.key?(:error_grouping)
13
+ en.error_grouping_period = options.delete(:error_grouping_period) if options.key?(:error_grouping_period)
14
+ en.notification_trigger = options.delete(:notification_trigger) if options.key?(:notification_trigger)
15
+
16
+ if options.key?(:error_grouping_cache)
17
+ en.error_grouping_cache = options.delete(:error_grouping_cache)
18
+ elsif defined?(Rails) && Rails.respond_to?(:cache)
19
+ en.error_grouping_cache = Rails.cache
20
+ end
21
+ end
22
+
23
+ if options.key?(:ignore_if)
24
+ rack_ignore = options.delete(:ignore_if)
25
+ ExceptionNotifier.ignore_if do |exception, opts|
26
+ opts.key?(:env) && rack_ignore.call(opts[:env], exception)
27
+ end
28
+ end
29
+
30
+ if options.key?(:ignore_notifier_if)
31
+ rack_ignore_by_notifier = options.delete(:ignore_notifier_if)
32
+ rack_ignore_by_notifier.each do |notifier, proc|
33
+ ExceptionNotifier.ignore_notifier_if(notifier) do |exception, opts|
34
+ opts.key?(:env) && proc.call(opts[:env], exception)
35
+ end
36
+ end
37
+ end
38
+
39
+ ExceptionNotifier.ignore_crawlers(options.delete(:ignore_crawlers)) if options.key?(:ignore_crawlers)
40
+
41
+ @ignore_cascade_pass = options.delete(:ignore_cascade_pass) { true }
42
+
43
+ options.each do |notifier_name, opts|
44
+ ExceptionNotifier.register_exception_notifier(notifier_name, opts)
45
+ end
46
+ end
47
+
48
+ def call(env)
49
+ _, headers, = response = @app.call(env)
50
+
51
+ if !@ignore_cascade_pass && headers["X-Cascade"] == "pass"
52
+ msg = "This exception means that the preceding Rack middleware set the 'X-Cascade' header to 'pass' -- in " \
53
+ "Rails, this often means that the route was not found (404 error)."
54
+ raise CascadePassException, msg
55
+ end
56
+
57
+ response
58
+ rescue Exception => e
59
+ env["exception_notifier.delivered"] = true if ExceptionNotifier.notify_exception(e, env: env)
60
+
61
+ raise e unless e.is_a?(CascadePassException)
62
+
63
+ response
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ExceptionNotification
4
+ class Engine < ::Rails::Engine
5
+ config.exception_notification = ExceptionNotifier
6
+ config.exception_notification.logger = Rails.logger
7
+ config.exception_notification.error_grouping_cache = Rails.cache
8
+
9
+ config.app_middleware.use ExceptionNotification::Rack
10
+ end
11
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "resque/failure/base"
4
+
5
+ module ExceptionNotification
6
+ class Resque < Resque::Failure::Base
7
+ def self.count
8
+ ::Resque::Stat[:failed]
9
+ end
10
+
11
+ def save
12
+ data = {
13
+ error_class: exception.class.name,
14
+ error_message: exception.message,
15
+ failed_at: Time.now.to_s,
16
+ payload: payload,
17
+ queue: queue,
18
+ worker: worker.to_s
19
+ }
20
+
21
+ ExceptionNotifier.notify_exception(exception, data: {resque: data})
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sidekiq"
4
+
5
+ # Note: this class is only needed for Sidekiq version < 3.
6
+ module ExceptionNotification
7
+ class Sidekiq
8
+ def call(_worker, msg, _queue)
9
+ yield
10
+ rescue Exception => e
11
+ ExceptionNotifier.notify_exception(e, data: {sidekiq: msg})
12
+ raise e
13
+ end
14
+ end
15
+ end
16
+
17
+ if ::Sidekiq::VERSION < "3"
18
+ ::Sidekiq.configure_server do |config|
19
+ config.server_middleware do |chain|
20
+ chain.add ::ExceptionNotification::Sidekiq
21
+ end
22
+ end
23
+ else
24
+ ::Sidekiq.configure_server do |config|
25
+ config.error_handlers << proc do |ex, context|
26
+ ExceptionNotifier.notify_exception(ex, data: {sidekiq: context})
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ExceptionNotification
4
+ VERSION = "4.5.0"
5
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "exception_notifier/notifier"
4
+ require "exception_notification/rack"
5
+ require "exception_notification/version"
6
+
7
+ module ExceptionNotification
8
+ # Alternative way to setup ExceptionNotification.
9
+ # Run 'rails generate exception_notification:install' to create
10
+ # a fresh initializer with all configuration values.
11
+ def self.configure
12
+ yield ExceptionNotifier
13
+ end
14
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ExceptionNotifier
4
+ class BaseNotifier
5
+ attr_accessor :base_options
6
+
7
+ def initialize(options = {})
8
+ @base_options = options
9
+ end
10
+
11
+ def send_notice(exception, options, message, message_opts = nil)
12
+ _pre_callback(exception, options, message, message_opts)
13
+ result = yield(message, message_opts)
14
+ _post_callback(exception, options, message, message_opts)
15
+ result
16
+ end
17
+
18
+ def _pre_callback(exception, options, message, message_opts)
19
+ return unless @base_options[:pre_callback].respond_to?(:call)
20
+
21
+ @base_options[:pre_callback].call(options, self, exception.backtrace, message, message_opts)
22
+ end
23
+
24
+ def _post_callback(exception, options, message, message_opts)
25
+ return unless @base_options[:post_callback].respond_to?(:call)
26
+
27
+ @base_options[:post_callback].call(options, self, exception.backtrace, message, message_opts)
28
+ end
29
+ end
30
+ end
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ExceptionNotifier
4
- class ExceptionTrackNotifier < ExceptionNotifier::BaseNotifier
5
- def initialize(_opts); end
4
+ class DbNotifier < ExceptionNotifier::BaseNotifier
5
+ def initialize(opts = {})
6
+ super(opts)
7
+ end
6
8
 
7
9
  def call(exception, opts = {})
8
10
  return unless ExceptionTrack.config.enabled_env?(Rails.env)
@@ -26,7 +28,7 @@ module ExceptionNotifier
26
28
  ExceptionTrack::Log.create(title: title[0, 200], body: messages.join("\n"))
27
29
  end
28
30
  end
29
- rescue StandardError => e
31
+ rescue => e
30
32
  errs = []
31
33
  errs << "-- [ExceptionTrack] create error ---------------------------"
32
34
  errs << e.message.indent(2)
@@ -45,14 +47,14 @@ module ExceptionNotifier
45
47
  parameters = filter_parameters(env)
46
48
 
47
49
  headers = []
48
- headers << "Method: #{env['REQUEST_METHOD']}"
49
- headers << "URL: #{env['REQUEST_URI']}"
50
+ headers << "Method: #{env["REQUEST_METHOD"]}"
51
+ headers << "URL: #{env["REQUEST_URI"]}"
50
52
  headers << "Parameters:\n#{pretty_hash(parameters.except(:controller, :action), 13)}" if env["REQUEST_METHOD"].downcase != "get"
51
- headers << "Controller: #{parameters['controller']}##{parameters['action']}"
52
- headers << "RequestId: #{env['action_dispatch.request_id']}"
53
- headers << "User-Agent: #{env['HTTP_USER_AGENT']}"
54
- headers << "Remote IP: #{env['REMOTE_ADDR']}"
55
- headers << "Language: #{env['HTTP_ACCEPT_LANGUAGE']}"
53
+ headers << "Controller: #{parameters["controller"]}##{parameters["action"]}"
54
+ headers << "RequestId: #{env["action_dispatch.request_id"]}"
55
+ headers << "User-Agent: #{env["HTTP_USER_AGENT"]}"
56
+ headers << "Remote IP: #{env["REMOTE_ADDR"]}"
57
+ headers << "Language: #{env["HTTP_ACCEPT_LANGUAGE"]}"
56
58
  headers << "Server: #{Socket.gethostname}"
57
59
  headers << "Process: #{$PROCESS_ID}"
58
60
 
@@ -63,7 +65,7 @@ module ExceptionNotifier
63
65
  parameters = env["action_dispatch.request.parameters"] || {}
64
66
  parameter_filter = ActiveSupport::ParameterFilter.new(env["action_dispatch.parameter_filter"] || [])
65
67
  parameter_filter.filter(parameters)
66
- rescue StandardError => e
68
+ rescue => e
67
69
  Rails.logger.error "filter_parameters error: #{e.inspect}"
68
70
  parameters
69
71
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ExceptionNotifier
4
+ module BacktraceCleaner
5
+ def clean_backtrace(exception)
6
+ if defined?(Rails) && Rails.respond_to?(:backtrace_cleaner)
7
+ Rails.backtrace_cleaner.send(:filter, exception.backtrace)
8
+ else
9
+ exception.backtrace
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ if Rails.version > "7.0"
4
+ require "active_support/isolated_execution_state"
5
+ end
6
+ require "active_support/core_ext/numeric/time"
7
+ require "active_support/concern"
8
+
9
+ module ExceptionNotifier
10
+ module ErrorGrouping
11
+ extend ActiveSupport::Concern
12
+
13
+ included do
14
+ mattr_accessor :error_grouping
15
+ self.error_grouping = false
16
+
17
+ mattr_accessor :error_grouping_period
18
+ self.error_grouping_period = 5.minutes
19
+
20
+ mattr_accessor :notification_trigger
21
+
22
+ mattr_accessor :error_grouping_cache
23
+ end
24
+
25
+ module ClassMethods
26
+ # Fallback to the memory store while the specified cache store doesn't work
27
+ #
28
+ def fallback_cache_store
29
+ @fallback_cache_store ||= ActiveSupport::Cache::MemoryStore.new
30
+ end
31
+
32
+ def error_count(error_key)
33
+ count =
34
+ begin
35
+ error_grouping_cache.read(error_key)
36
+ rescue => e
37
+ log_cache_error(error_grouping_cache, e, :read)
38
+ fallback_cache_store.read(error_key)
39
+ end
40
+
41
+ count&.to_i
42
+ end
43
+
44
+ def save_error_count(error_key, count)
45
+ error_grouping_cache.write(error_key, count, expires_in: error_grouping_period)
46
+ rescue => e
47
+ log_cache_error(error_grouping_cache, e, :write)
48
+ fallback_cache_store.write(error_key, count, expires_in: error_grouping_period)
49
+ end
50
+
51
+ def group_error!(exception, options)
52
+ message_based_key = "exception:#{Zlib.crc32("#{exception.class.name}\nmessage:#{exception.message}")}"
53
+ accumulated_errors_count = 1
54
+
55
+ if (count = error_count(message_based_key))
56
+ accumulated_errors_count = count + 1
57
+ save_error_count(message_based_key, accumulated_errors_count)
58
+ else
59
+ backtrace_based_key =
60
+ "exception:#{Zlib.crc32("#{exception.class.name}\npath:#{exception.backtrace.try(:first)}")}"
61
+
62
+ if (count = error_grouping_cache.read(backtrace_based_key))
63
+ accumulated_errors_count = count + 1
64
+ save_error_count(backtrace_based_key, accumulated_errors_count)
65
+ else
66
+ save_error_count(backtrace_based_key, accumulated_errors_count)
67
+ save_error_count(message_based_key, accumulated_errors_count)
68
+ end
69
+ end
70
+
71
+ options[:accumulated_errors_count] = accumulated_errors_count
72
+ end
73
+
74
+ def send_notification?(exception, count)
75
+ if notification_trigger.respond_to?(:call)
76
+ notification_trigger.call(exception, count)
77
+ else
78
+ factor = Math.log2(count)
79
+ factor.to_i == factor
80
+ end
81
+ end
82
+
83
+ private
84
+
85
+ def log_cache_error(cache, exception, action)
86
+ "#{cache.inspect} failed to #{action}, reason: #{exception.message}. Falling back to memory cache store."
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/time"
4
+ require "action_dispatch"
5
+
6
+ module ExceptionNotifier
7
+ class Formatter
8
+ include ExceptionNotifier::BacktraceCleaner
9
+
10
+ attr_reader :app_name
11
+
12
+ def initialize(exception, opts = {})
13
+ @exception = exception
14
+
15
+ @env = opts[:env]
16
+ @errors_count = opts[:accumulated_errors_count].to_i
17
+ @app_name = opts[:app_name] || rails_app_name
18
+ end
19
+
20
+ #
21
+ # :warning: Error occurred in production :warning:
22
+ # :warning: Error occurred :warning:
23
+ #
24
+ def title
25
+ env = Rails.env if defined?(::Rails) && ::Rails.respond_to?(:env)
26
+
27
+ if env
28
+ "⚠️ Error occurred in #{env} ⚠️"
29
+ else
30
+ "⚠️ Error occurred ⚠️"
31
+ end
32
+ end
33
+
34
+ #
35
+ # A *NoMethodError* occurred.
36
+ # 3 *NoMethodError* occurred.
37
+ # A *NoMethodError* occurred in *home#index*.
38
+ #
39
+ def subtitle
40
+ errors_text = if errors_count > 1
41
+ errors_count
42
+ else
43
+ /^[aeiou]/i.match?(exception.class.to_s) ? "An" : "A"
44
+ end
45
+
46
+ in_action = " in *#{controller_and_action}*" if controller
47
+
48
+ "#{errors_text} *#{exception.class}* occurred#{in_action}."
49
+ end
50
+
51
+ #
52
+ #
53
+ # *Request:*
54
+ # ```
55
+ # * url : https://www.example.com/
56
+ # * http_method : GET
57
+ # * ip_address : 127.0.0.1
58
+ # * parameters : {"controller"=>"home", "action"=>"index"}
59
+ # * timestamp : 2019-01-01 00:00:00 UTC
60
+ # ```
61
+ #
62
+ def request_message
63
+ request = ActionDispatch::Request.new(env) if env
64
+ return unless request
65
+
66
+ [
67
+ "```",
68
+ "* url : #{request.original_url}",
69
+ "* http_method : #{request.method}",
70
+ "* ip_address : #{request.remote_ip}",
71
+ "* parameters : #{request.filtered_parameters}",
72
+ "* timestamp : #{Time.current}",
73
+ "```"
74
+ ].join("\n")
75
+ end
76
+
77
+ #
78
+ #
79
+ # *Backtrace:*
80
+ # ```
81
+ # * app/controllers/my_controller.rb:99:in `specific_function'
82
+ # * app/controllers/my_controller.rb:70:in `specific_param'
83
+ # * app/controllers/my_controller.rb:53:in `my_controller_params'
84
+ # ```
85
+ #
86
+ def backtrace_message
87
+ backtrace = exception.backtrace ? clean_backtrace(exception) : nil
88
+
89
+ return unless backtrace
90
+
91
+ text = []
92
+
93
+ text << "```"
94
+ backtrace.first(3).each { |line| text << "* #{line}" }
95
+ text << "```"
96
+
97
+ text.join("\n")
98
+ end
99
+
100
+ #
101
+ # home#index
102
+ #
103
+ def controller_and_action
104
+ "#{controller.controller_name}##{controller.action_name}" if controller
105
+ end
106
+
107
+ private
108
+
109
+ attr_reader :exception, :env, :errors_count
110
+
111
+ def rails_app_name
112
+ return unless defined?(::Rails) && ::Rails.respond_to?(:application)
113
+
114
+ if Rails::VERSION::MAJOR >= 6
115
+ Rails.application.class.module_parent_name.underscore
116
+ else
117
+ Rails.application.class.parent_name.underscore
118
+ end
119
+ end
120
+
121
+ def controller
122
+ env["action_controller.instance"] if env
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,191 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+ require "active_support/core_ext/string/inflections"
5
+ require "active_support/core_ext/module/attribute_accessors"
6
+ require "exception_notifier/base_notifier"
7
+ require "exception_notifier/modules/error_grouping"
8
+
9
+ module ExceptionNotifier
10
+ include ErrorGrouping
11
+
12
+ autoload :BacktraceCleaner, "exception_notifier/modules/backtrace_cleaner"
13
+ autoload :Formatter, "exception_notifier/modules/formatter"
14
+
15
+ autoload :Notifier, "exception_notifier/notifier"
16
+ autoload :DbNotifier, "exception_notifier/db_notifier"
17
+
18
+ class UndefinedNotifierError < StandardError; end
19
+
20
+ # Define logger
21
+ mattr_accessor :logger
22
+ @@logger = Logger.new($stdout)
23
+
24
+ # Define a set of exceptions to be ignored, ie, dont send notifications when any of them are raised.
25
+ mattr_accessor :ignored_exceptions
26
+ @@ignored_exceptions = %w[
27
+ AbstractController::ActionNotFound
28
+ ActionController::BadRequest
29
+ ActionController::InvalidAuthenticityToken
30
+ ActionController::InvalidCrossOriginRequest
31
+ ActionController::ParameterMissing
32
+ ActionController::RoutingError
33
+ ActionController::UnknownFormat
34
+ ActionController::UrlGenerationError
35
+ ActionView::MissingTemplate
36
+ ActionView::TemplateError
37
+ ActiveRecord::RecordNotFound
38
+ Mime::Type::InvalidMimeType
39
+ Mongoid::Errors::DocumentNotFound
40
+ ]
41
+
42
+ mattr_accessor :testing_mode
43
+ @@testing_mode = false
44
+
45
+ class << self
46
+ # Store conditions that decide when exceptions must be ignored or not.
47
+ @@ignores = []
48
+
49
+ # Store by-notifier conditions that decide when exceptions must be ignored or not.
50
+ @@by_notifier_ignores = {}
51
+
52
+ # Store notifiers that send notifications when exceptions are raised.
53
+ @@notifiers = {}
54
+
55
+ def testing_mode!
56
+ self.testing_mode = true
57
+ end
58
+
59
+ def notify_exception(exception, options = {}, &block)
60
+ return false if ignored_exception?(options[:ignore_exceptions], exception)
61
+ return false if ignored?(exception, options)
62
+
63
+ if error_grouping
64
+ errors_count = group_error!(exception, options)
65
+ return false unless send_notification?(exception, errors_count)
66
+ end
67
+
68
+ notification_fired = false
69
+ selected_notifiers = options.delete(:notifiers) || notifiers
70
+ [*selected_notifiers].each do |notifier|
71
+ unless notifier_ignored?(exception, options, notifier: notifier)
72
+ fire_notification(notifier, exception, options.dup, &block)
73
+ notification_fired = true
74
+ end
75
+ end
76
+
77
+ notification_fired
78
+ end
79
+
80
+ def register_exception_notifier(name, notifier_or_options)
81
+ if notifier_or_options.respond_to?(:call)
82
+ @@notifiers[name] = notifier_or_options
83
+ elsif notifier_or_options.is_a?(Hash)
84
+ create_and_register_notifier(name, notifier_or_options)
85
+ else
86
+ raise ArgumentError, "Invalid notifier '#{name}' defined as #{notifier_or_options.inspect}"
87
+ end
88
+ end
89
+ alias_method :add_notifier, :register_exception_notifier
90
+
91
+ def unregister_exception_notifier(name)
92
+ @@notifiers.delete(name)
93
+ end
94
+
95
+ def registered_exception_notifier(name)
96
+ @@notifiers[name]
97
+ end
98
+
99
+ def notifiers
100
+ @@notifiers.keys
101
+ end
102
+
103
+ # Adds a condition to decide when an exception must be ignored or not.
104
+ #
105
+ # ExceptionNotifier.ignore_if do |exception, options|
106
+ # not Rails.env.production?
107
+ # end
108
+ def ignore_if(&block)
109
+ @@ignores << block
110
+ end
111
+
112
+ def ignore_notifier_if(notifier, &block)
113
+ @@by_notifier_ignores[notifier] = block
114
+ end
115
+
116
+ def ignore_crawlers(crawlers)
117
+ ignore_if do |_exception, opts|
118
+ opts.key?(:env) && from_crawler(opts[:env], crawlers)
119
+ end
120
+ end
121
+
122
+ def clear_ignore_conditions!
123
+ @@ignores.clear
124
+ @@by_notifier_ignores.clear
125
+ end
126
+
127
+ private
128
+
129
+ def ignored?(exception, options)
130
+ @@ignores.any? { |condition| condition.call(exception, options) }
131
+ rescue Exception => e
132
+ raise e if @@testing_mode
133
+
134
+ logger.warn(
135
+ "An error occurred when evaluating an ignore condition. #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
136
+ )
137
+ false
138
+ end
139
+
140
+ def notifier_ignored?(exception, options, notifier:)
141
+ return false unless @@by_notifier_ignores.key?(notifier)
142
+
143
+ condition = @@by_notifier_ignores[notifier]
144
+ condition.call(exception, options)
145
+ rescue Exception => e
146
+ raise e if @@testing_mode
147
+
148
+ logger.warn(<<~"MESSAGE")
149
+ An error occurred when evaluating a by-notifier ignore condition. #{e.class}: #{e.message}
150
+ #{e.backtrace.join("\n")}
151
+ MESSAGE
152
+ false
153
+ end
154
+
155
+ def ignored_exception?(ignore_array, exception)
156
+ all_ignored_exceptions = (Array(ignored_exceptions) + Array(ignore_array)).map(&:to_s)
157
+ exception_ancestors = exception.singleton_class.ancestors.map(&:to_s)
158
+ !(all_ignored_exceptions & exception_ancestors).empty?
159
+ end
160
+
161
+ def fire_notification(notifier_name, exception, options, &block)
162
+ notifier = registered_exception_notifier(notifier_name)
163
+ notifier.call(exception, options, &block)
164
+ rescue Exception => e
165
+ raise e if @@testing_mode
166
+
167
+ logger.warn(
168
+ "An error occurred when sending a notification using '#{notifier_name}' notifier." \
169
+ "#{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
170
+ )
171
+ false
172
+ end
173
+
174
+ def create_and_register_notifier(name, options)
175
+ notifier_classname = "#{name}_notifier".camelize
176
+ notifier_class = ExceptionNotifier.const_get(notifier_classname)
177
+ notifier = notifier_class.new(options)
178
+ register_exception_notifier(name, notifier)
179
+ rescue NameError => e
180
+ raise UndefinedNotifierError,
181
+ "No notifier named '#{name}' was found. Please, revise your configuration options. Cause: #{e.message}"
182
+ end
183
+
184
+ def from_crawler(env, ignored_crawlers)
185
+ agent = env["HTTP_USER_AGENT"]
186
+ Array(ignored_crawlers).any? do |crawler|
187
+ agent =~ Regexp.new(crawler)
188
+ end
189
+ end
190
+ end
191
+ end
metadata CHANGED
@@ -1,57 +1,127 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: exception-track
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason Lee
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-16 00:00:00.000000000 Z
11
+ date: 2021-12-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: exception_notification
14
+ name: kaminari
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '4'
19
+ version: '0.15'
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: '4'
26
+ version: '0.15'
27
27
  - !ruby/object:Gem::Dependency
28
- name: kaminari
28
+ name: rails
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '0.15'
33
+ version: '5.2'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '0.15'
40
+ version: '5.2'
41
41
  - !ruby/object:Gem::Dependency
42
- name: rails
42
+ name: pg
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '5.2'
48
- type: :runtime
47
+ version: '1'
48
+ type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '5.2'
54
+ version: '1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: mocha
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 0.13.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 0.13.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: mock_redis
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.19.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.19.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: resque
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 1.8.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 1.8.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: sidekiq
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: 5.0.4
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: 5.0.4
111
+ - !ruby/object:Gem::Dependency
112
+ name: timecop
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 0.9.0
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 0.9.0
55
125
  description: Tracking exceptions for Rails application store them in database by exception_notification
56
126
  gem.
57
127
  email:
@@ -77,7 +147,18 @@ files:
77
147
  - lib/exception-track/engine.rb
78
148
  - lib/exception-track/log_subscriber.rb
79
149
  - lib/exception-track/version.rb
80
- - lib/exception_notifier/exception_track_notifier.rb
150
+ - lib/exception_notification.rb
151
+ - lib/exception_notification/rack.rb
152
+ - lib/exception_notification/rails.rb
153
+ - lib/exception_notification/resque.rb
154
+ - lib/exception_notification/sidekiq.rb
155
+ - lib/exception_notification/version.rb
156
+ - lib/exception_notifier/base_notifier.rb
157
+ - lib/exception_notifier/db_notifier.rb
158
+ - lib/exception_notifier/modules/backtrace_cleaner.rb
159
+ - lib/exception_notifier/modules/error_grouping.rb
160
+ - lib/exception_notifier/modules/formatter.rb
161
+ - lib/exception_notifier/notifier.rb
81
162
  - lib/generators/exception_track/install_generator.rb
82
163
  homepage: https://github.com/rails-engine/exception-track
83
164
  licenses:
@@ -98,7 +179,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
98
179
  - !ruby/object:Gem::Version
99
180
  version: '0'
100
181
  requirements: []
101
- rubygems_version: 3.1.4
182
+ rubygems_version: 3.2.3
102
183
  signing_key:
103
184
  specification_version: 4
104
185
  summary: Tracking exceptions for Rails application store them in database.