-
Notifications
You must be signed in to change notification settings - Fork 390
/
middleware.rb
137 lines (119 loc) · 4.5 KB
/
middleware.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
module Airbrake
module Rack
# Airbrake Rack middleware for Rails and Sinatra applications (or any other
# Rack-compliant app). Any errors raised by the upstream application will be
# delivered to Airbrake and re-raised.
#
# The middleware automatically sends information about the framework that
# uses it (name and version).
#
# For Rails apps the middleware collects route performance statistics.
class Middleware
# @return [Array<Class>] the list of Rack filters that read Rack request
# information and append it to notices
RACK_FILTERS = [
Airbrake::Rack::ContextFilter,
Airbrake::Rack::UserFilter,
Airbrake::Rack::SessionFilter,
Airbrake::Rack::HttpParamsFilter,
Airbrake::Rack::HttpHeadersFilter,
Airbrake::Rack::RouteFilter,
# Optional filters (must be included by users):
# Airbrake::Rack::RequestBodyFilter
].freeze
# An Array that holds notifier names, which are known to be associated
# with particular Airbrake Rack middleware.
# rubocop:disable Style/ClassVars
@@known_notifiers = []
# rubocop:enable Style/ClassVars
def initialize(app, notifier_name = :default)
@app = app
@notice_notifier = Airbrake.notifiers[:notice][notifier_name]
@performance_notifier = Airbrake.notifiers[:performance][notifier_name]
# This object is shared among hooks. ActionControllerRouteSubscriber
# writes it and other hooks read it.
@routes = {}
# Prevent adding same filters to the same notifier.
return if @@known_notifiers.include?(notifier_name)
@@known_notifiers << notifier_name
return unless @notice_notifier
RACK_FILTERS.each do |filter|
@notice_notifier.add_filter(filter.new)
end
install_action_controller_hooks if defined?(Rails)
install_active_record_hooks if defined?(ActiveRecord)
end
# Rescues any exceptions, sends them to Airbrake and re-raises the
# exception.
# @param [Hash] env the Rack environment
def call(env)
# rubocop:disable Lint/RescueException
begin
response = @app.call(env)
rescue Exception => ex
notify_airbrake(ex, env)
raise ex
ensure
# Clear routes for the next request.
@routes.clear
end
# rubocop:enable Lint/RescueException
exception = framework_exception(env)
notify_airbrake(exception, env) if exception
response
end
private
def notify_airbrake(exception, env)
notice = @notice_notifier.build_notice(exception)
return unless notice
# ActionDispatch::Request correctly captures server port when using SSL:
# See: https://github.com/airbrake/airbrake/issues/802
notice.stash[:rack_request] =
if defined?(ActionDispatch::Request)
ActionDispatch::Request.new(env)
elsif defined?(Sinatra::Request)
Sinatra::Request.new(env)
else
::Rack::Request.new(env)
end
@notice_notifier.notify(notice)
end
# Web framework middlewares often store rescued exceptions inside the
# Rack env, but Rack doesn't have a standard key for it:
#
# - Rails uses action_dispatch.exception: https://goo.gl/Kd694n
# - Sinatra uses sinatra.error: https://goo.gl/LLkVL9
# - Goliath uses rack.exception: https://goo.gl/i7e1nA
def framework_exception(env)
env['action_dispatch.exception'] ||
env['sinatra.error'] ||
env['rack.exception']
end
def install_action_controller_hooks
ActiveSupport::Notifications.subscribe(
'start_processing.action_controller',
Airbrake::Rails::ActionControllerRouteSubscriber.new(@routes)
)
ActiveSupport::Notifications.subscribe(
'process_action.action_controller',
Airbrake::Rails::ActionControllerNotifySubscriber.new(
@performance_notifier, @routes
)
)
end
def install_active_record_hooks
@performance_notifier.add_filter(
Airbrake::Filters::SqlFilter.new(
ActiveRecord::Base.connection_config[:adapter]
)
)
ActiveSupport::Notifications.subscribe(
'sql.active_record',
Airbrake::Rails::ActiveRecordSubscriber.new(
@performance_notifier, @routes
)
)
end
end
end
end