@@ -26,12 +26,16 @@ module ActiveSupport
26
26
class ErrorReporter
27
27
SEVERITIES = %i( error warning info )
28
28
DEFAULT_SOURCE = "application"
29
+ DEFAULT_RESCUE = [ StandardError ] . freeze
29
30
30
- attr_accessor :logger
31
+ attr_accessor :logger , :debug_mode
32
+
33
+ UnexpectedError = Class . new ( Exception )
31
34
32
35
def initialize ( *subscribers , logger : nil )
33
36
@subscribers = subscribers . flatten
34
37
@logger = logger
38
+ @debug_mode = false
35
39
end
36
40
37
41
# Evaluates the given block, reporting and swallowing any unhandled error.
@@ -72,7 +76,7 @@ def initialize(*subscribers, logger: nil)
72
76
# source of the error. Subscribers can use this value to ignore certain
73
77
# errors. Defaults to <tt>"application"</tt>.
74
78
def handle ( *error_classes , severity : :warning , context : { } , fallback : nil , source : DEFAULT_SOURCE )
75
- error_classes = [ StandardError ] if error_classes . blank ?
79
+ error_classes = DEFAULT_RESCUE if error_classes . empty ?
76
80
yield
77
81
rescue *error_classes => error
78
82
report ( error , handled : true , severity : severity , context : context , source : source )
@@ -108,13 +112,47 @@ def handle(*error_classes, severity: :warning, context: {}, fallback: nil, sourc
108
112
# source of the error. Subscribers can use this value to ignore certain
109
113
# errors. Defaults to <tt>"application"</tt>.
110
114
def record ( *error_classes , severity : :error , context : { } , source : DEFAULT_SOURCE )
111
- error_classes = [ StandardError ] if error_classes . blank ?
115
+ error_classes = DEFAULT_RESCUE if error_classes . empty ?
112
116
yield
113
117
rescue *error_classes => error
114
118
report ( error , handled : false , severity : severity , context : context , source : source )
115
119
raise
116
120
end
117
121
122
+ # Either report the given error when in production, or raise it when in development or test.
123
+ #
124
+ # When called in production, after the error is reported, this method will return
125
+ # nil and execution will continue.
126
+ #
127
+ # When called in development, the original error is wrapped in a different error class to ensure
128
+ # it's not being rescued higher in the stack and will be surfaced to the developer.
129
+ #
130
+ # This method is intended for reporting violated assertions about preconditions, or similar
131
+ # cases that can and should be gracefully handled in production, but that aren't supposed to happen.
132
+ #
133
+ # The error can be either an exception instance or a String.
134
+ #
135
+ # example:
136
+ #
137
+ # def edit
138
+ # if published?
139
+ # Rails.error.unexpected("[BUG] Attempting to edit a published article, that shouldn't be possible")
140
+ # return false
141
+ # end
142
+ # # ...
143
+ # end
144
+ #
145
+ def unexpected ( error , severity : :warning , context : { } , source : DEFAULT_SOURCE )
146
+ error = RuntimeError . new ( error ) if error . is_a? ( String )
147
+ error . set_backtrace ( caller ( 1 ) ) if error . backtrace . nil?
148
+
149
+ if @debug_mode
150
+ raise UnexpectedError , "#{ error . class . name } : #{ error . message } " , error . backtrace , cause : error
151
+ else
152
+ report ( error , handled : true , severity : severity , context : context , source : source )
153
+ end
154
+ end
155
+
118
156
# Register a new error subscriber. The subscriber must respond to
119
157
#
120
158
# report(Exception, handled: Boolean, severity: (:error OR :warning OR :info), context: Hash, source: String)
0 commit comments