exception-track 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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.