@@ -36,6 +36,7 @@ def initialize(*subscribers, logger: nil)
36
36
@subscribers = subscribers . flatten
37
37
@logger = logger
38
38
@debug_mode = false
39
+ @context_middlewares = ErrorContextMiddlewareStack . new
39
40
end
40
41
41
42
# Evaluates the given block, reporting and swallowing any unhandled error.
@@ -202,6 +203,22 @@ def set_context(...)
202
203
ActiveSupport ::ExecutionContext . set ( ...)
203
204
end
204
205
206
+ # Add a middleware to modify the error context before it is sent to subscribers.
207
+ #
208
+ # Middleware is added to a stack of callables run on an error's execution context
209
+ # before passing to subscribers. Allows creation of entries in error context that
210
+ # are shared by all subscribers.
211
+ #
212
+ # A context middleware receives the error and current state of the context hash.
213
+ # It must return a hash - the middleware stack returns the hash after it has
214
+ # run through all middlewares. A middleware can mutate or replace the hash.
215
+ #
216
+ # Rails.error.add_middleware(-> (error, context) { context.merge({ foo: :bar }) })
217
+ #
218
+ def add_middleware ( middleware )
219
+ @context_middlewares . use ( middleware )
220
+ end
221
+
205
222
# Report an error directly to subscribers. You can use this method when the
206
223
# block-based #handle and #record methods are not suitable.
207
224
#
@@ -223,7 +240,11 @@ def report(error, handled: true, severity: handled ? :warning : :error, context:
223
240
raise ArgumentError , "severity must be one of #{ SEVERITIES . map ( &:inspect ) . join ( ", " ) } , got: #{ severity . inspect } "
224
241
end
225
242
226
- full_context = ActiveSupport ::ExecutionContext . to_h . merge ( context || { } )
243
+ full_context = @context_middlewares . execute (
244
+ error ,
245
+ ActiveSupport ::ExecutionContext . to_h . merge ( context || { } )
246
+ )
247
+
227
248
disabled_subscribers = ActiveSupport ::IsolatedExecutionState [ self ]
228
249
@subscribers . each do |subscriber |
229
250
unless disabled_subscribers &.any? { |s | s === subscriber }
@@ -272,5 +293,25 @@ def ensure_backtrace(error)
272
293
273
294
error . backtrace . shift ( count )
274
295
end
296
+
297
+ class ErrorContextMiddlewareStack # :nodoc:
298
+ def initialize
299
+ @stack = [ ]
300
+ end
301
+
302
+ # Add a middleware to the error context stack.
303
+ def use ( middleware )
304
+ unless middleware . respond_to? ( :call )
305
+ raise ArgumentError , "Error context middleware must respond to #call"
306
+ end
307
+
308
+ @stack << middleware
309
+ end
310
+
311
+ # Run all middlewares in the stack on an error and context.
312
+ def execute ( error , context )
313
+ @stack . inject ( context ) { |c , middleware | middleware . call ( error , c ) }
314
+ end
315
+ end
275
316
end
276
317
end
0 commit comments