hydraulic_brake 0.0.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.
- data/.rbenv-version +1 -0
- data/.rvmrc +1 -0
- data/.yardopts +2 -0
- data/Gemfile +16 -0
- data/INSTALL +20 -0
- data/MIT-LICENSE +22 -0
- data/README.md +103 -0
- data/Rakefile +39 -0
- data/VERSION +1 -0
- data/features/rake.feature +27 -0
- data/features/step_definitions/rake_steps.rb +17 -0
- data/features/support/matchers.rb +35 -0
- data/features/support/rake/Rakefile +61 -0
- data/features/support/terminal.rb +107 -0
- data/hydraulic_brake.gemspec +102 -0
- data/lib/hydraulic_brake/backtrace.rb +108 -0
- data/lib/hydraulic_brake/configuration.rb +242 -0
- data/lib/hydraulic_brake/notice.rb +321 -0
- data/lib/hydraulic_brake/sender.rb +128 -0
- data/lib/hydraulic_brake/version.rb +5 -0
- data/lib/hydraulic_brake.rb +148 -0
- data/lib/hydraulic_brake_tasks.rb +63 -0
- data/resources/README.md +34 -0
- data/resources/ca-bundle.crt +3376 -0
- data/script/integration_test.rb +36 -0
- data/test/airbrake_2_3.xsd +88 -0
- data/test/backtrace_test.rb +162 -0
- data/test/configuration_test.rb +178 -0
- data/test/helper.rb +239 -0
- data/test/logger_test.rb +79 -0
- data/test/notice_test.rb +324 -0
- data/test/notifier_test.rb +220 -0
- data/test/recursion_test.rb +10 -0
- data/test/sender_test.rb +288 -0
- metadata +259 -0
@@ -0,0 +1,242 @@
|
|
1
|
+
module HydraulicBrake
|
2
|
+
# Used to set up and modify settings for the notifier.
|
3
|
+
class Configuration
|
4
|
+
|
5
|
+
OPTIONS = [
|
6
|
+
:api_key,
|
7
|
+
:backtrace_filters,
|
8
|
+
:development_environments,
|
9
|
+
:development_lookup,
|
10
|
+
:environment_name,
|
11
|
+
:framework,
|
12
|
+
:host,
|
13
|
+
:http_open_timeout,
|
14
|
+
:http_read_timeout,
|
15
|
+
:notifier_name,
|
16
|
+
:notifier_url,
|
17
|
+
:notifier_version,
|
18
|
+
:params_filters,
|
19
|
+
:port,
|
20
|
+
:project_root,
|
21
|
+
:protocol,
|
22
|
+
:proxy_host,
|
23
|
+
:proxy_pass,
|
24
|
+
:proxy_port,
|
25
|
+
:proxy_user,
|
26
|
+
:rake_environment_filters,
|
27
|
+
:rescue_rake_exceptions,
|
28
|
+
:secure,
|
29
|
+
:use_system_ssl_cert_chain,
|
30
|
+
].freeze
|
31
|
+
|
32
|
+
# The API key for your project, found on the project edit form.
|
33
|
+
attr_accessor :api_key
|
34
|
+
|
35
|
+
# The host to connect to
|
36
|
+
attr_accessor :host
|
37
|
+
|
38
|
+
# The port on which your Airbrake server runs (defaults to 443 for secure
|
39
|
+
# connections, 80 for insecure connections).
|
40
|
+
attr_accessor :port
|
41
|
+
|
42
|
+
# +true+ for https connections, +false+ for http connections.
|
43
|
+
attr_accessor :secure
|
44
|
+
|
45
|
+
# +true+ to use whatever CAs OpenSSL has installed on your system. +false+
|
46
|
+
# to use the ca-bundle.crt file included in HydraulicBrake itself
|
47
|
+
# (reccomended and default)
|
48
|
+
attr_accessor :use_system_ssl_cert_chain
|
49
|
+
|
50
|
+
# The HTTP open timeout in seconds (defaults to 2).
|
51
|
+
attr_accessor :http_open_timeout
|
52
|
+
|
53
|
+
# The HTTP read timeout in seconds (defaults to 5).
|
54
|
+
attr_accessor :http_read_timeout
|
55
|
+
|
56
|
+
# The hostname of your proxy server (if using a proxy)
|
57
|
+
attr_accessor :proxy_host
|
58
|
+
|
59
|
+
# The port of your proxy server (if using a proxy)
|
60
|
+
attr_accessor :proxy_port
|
61
|
+
|
62
|
+
# The username to use when logging into your proxy server (if using a proxy)
|
63
|
+
attr_accessor :proxy_user
|
64
|
+
|
65
|
+
# The password to use when logging into your proxy server (if using a proxy)
|
66
|
+
attr_accessor :proxy_pass
|
67
|
+
|
68
|
+
# A list of parameters that should be filtered out of what is sent to Airbrake.
|
69
|
+
# By default, all "password" attributes will have their contents replaced.
|
70
|
+
attr_reader :params_filters
|
71
|
+
|
72
|
+
# A list of filters for cleaning and pruning the backtrace. See #filter_backtrace.
|
73
|
+
attr_reader :backtrace_filters
|
74
|
+
|
75
|
+
# A list of environment keys that will be ignored from what is sent to Airbrake server
|
76
|
+
# Empty by default and used only in rake handler
|
77
|
+
attr_reader :rake_environment_filters
|
78
|
+
|
79
|
+
# A list of environments in which notifications should not be sent.
|
80
|
+
attr_accessor :development_environments
|
81
|
+
|
82
|
+
# +true+ if you want to check for production errors matching development errors, +false+ otherwise.
|
83
|
+
attr_accessor :development_lookup
|
84
|
+
|
85
|
+
# The name of the environment the application is running in
|
86
|
+
attr_accessor :environment_name
|
87
|
+
|
88
|
+
# The path to the project in which the error occurred, such as the Rails.root
|
89
|
+
attr_accessor :project_root
|
90
|
+
|
91
|
+
# The name of the notifier library being used to send notifications (such
|
92
|
+
# as "HydraulicBrake Notifier")
|
93
|
+
attr_accessor :notifier_name
|
94
|
+
|
95
|
+
# The version of the notifier library being used to send notifications (such as "1.0.2")
|
96
|
+
attr_accessor :notifier_version
|
97
|
+
|
98
|
+
# The url of the notifier library being used to send notifications
|
99
|
+
attr_accessor :notifier_url
|
100
|
+
|
101
|
+
# The logger used by HydraulicBrake
|
102
|
+
attr_accessor :logger
|
103
|
+
|
104
|
+
# The framework HydraulicBrake is configured to use
|
105
|
+
attr_accessor :framework
|
106
|
+
|
107
|
+
# Should HydraulicBrake catch exceptions from Rake tasks?
|
108
|
+
# (boolean or nil; set to nil to catch exceptions when rake isn't running from a terminal; default is nil)
|
109
|
+
attr_accessor :rescue_rake_exceptions
|
110
|
+
|
111
|
+
# User attributes that are being captured
|
112
|
+
attr_accessor :user_attributes
|
113
|
+
|
114
|
+
|
115
|
+
DEFAULT_PARAMS_FILTERS = %w(password password_confirmation).freeze
|
116
|
+
|
117
|
+
DEFAULT_USER_ATTRIBUTES = %w(id name username email).freeze
|
118
|
+
|
119
|
+
DEFAULT_BACKTRACE_FILTERS = [
|
120
|
+
lambda { |line|
|
121
|
+
if defined?(HydraulicBrake.configuration.project_root) && HydraulicBrake.configuration.project_root.to_s != ''
|
122
|
+
line.sub(/#{HydraulicBrake.configuration.project_root}/, "[PROJECT_ROOT]")
|
123
|
+
else
|
124
|
+
line
|
125
|
+
end
|
126
|
+
},
|
127
|
+
lambda { |line| line.gsub(/^\.\//, "") },
|
128
|
+
lambda { |line|
|
129
|
+
if defined?(Gem)
|
130
|
+
Gem.path.inject(line) do |line, path|
|
131
|
+
line.gsub(/#{path}/, "[GEM_ROOT]")
|
132
|
+
end
|
133
|
+
end
|
134
|
+
},
|
135
|
+
lambda { |line| line if line !~ %r{lib/hydraulic_brake} }
|
136
|
+
].freeze
|
137
|
+
|
138
|
+
alias_method :secure?, :secure
|
139
|
+
alias_method :use_system_ssl_cert_chain?, :use_system_ssl_cert_chain
|
140
|
+
|
141
|
+
def initialize
|
142
|
+
@secure = false
|
143
|
+
@use_system_ssl_cert_chain= false
|
144
|
+
@host = 'api.airbrake.io'
|
145
|
+
@http_open_timeout = 2
|
146
|
+
@http_read_timeout = 5
|
147
|
+
@params_filters = DEFAULT_PARAMS_FILTERS.dup
|
148
|
+
@backtrace_filters = DEFAULT_BACKTRACE_FILTERS.dup
|
149
|
+
@development_environments = %w(development test cucumber)
|
150
|
+
@development_lookup = true
|
151
|
+
@notifier_name = 'HydraulicBrake Notifier'
|
152
|
+
@notifier_version = VERSION
|
153
|
+
@notifier_url = 'https://github.com/stevecrozz/hydraulic_brake'
|
154
|
+
@framework = 'Standalone'
|
155
|
+
@rescue_rake_exceptions = nil
|
156
|
+
@user_attributes = DEFAULT_USER_ATTRIBUTES.dup
|
157
|
+
@rake_environment_filters = []
|
158
|
+
end
|
159
|
+
|
160
|
+
# Takes a block and adds it to the list of backtrace filters. When the filters
|
161
|
+
# run, the block will be handed each line of the backtrace and can modify
|
162
|
+
# it as necessary.
|
163
|
+
#
|
164
|
+
# @example
|
165
|
+
# config.filter_bracktrace do |line|
|
166
|
+
# line.gsub(/^#{Rails.root}/, "[Rails.root]")
|
167
|
+
# end
|
168
|
+
#
|
169
|
+
# @param [Proc] block The new backtrace filter.
|
170
|
+
# @yieldparam [String] line A line in the backtrace.
|
171
|
+
def filter_backtrace(&block)
|
172
|
+
self.backtrace_filters << block
|
173
|
+
end
|
174
|
+
|
175
|
+
# Allows config options to be read like a hash
|
176
|
+
#
|
177
|
+
# @param [Symbol] option Key for a given attribute
|
178
|
+
def [](option)
|
179
|
+
send(option)
|
180
|
+
end
|
181
|
+
|
182
|
+
# Returns a hash of all configurable options
|
183
|
+
def to_hash
|
184
|
+
OPTIONS.inject({}) do |hash, option|
|
185
|
+
hash[option.to_sym] = self.send(option)
|
186
|
+
hash
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# Returns a hash of all configurable options merged with +hash+
|
191
|
+
#
|
192
|
+
# @param [Hash] hash A set of configuration options that will take precedence over the defaults
|
193
|
+
def merge(hash)
|
194
|
+
to_hash.merge(hash)
|
195
|
+
end
|
196
|
+
|
197
|
+
# Determines if the notifier will send notices.
|
198
|
+
# @return [Boolean] Returns +false+ if in a development environment, +true+ otherwise.
|
199
|
+
def public?
|
200
|
+
!development_environments.include?(environment_name)
|
201
|
+
end
|
202
|
+
|
203
|
+
def port
|
204
|
+
@port || default_port
|
205
|
+
end
|
206
|
+
|
207
|
+
# Determines whether protocol should be "http" or "https".
|
208
|
+
# @return [String] Returns +"http"+ if you've set secure to +false+ in
|
209
|
+
# configuration, and +"https"+ otherwise.
|
210
|
+
def protocol
|
211
|
+
if secure?
|
212
|
+
'https'
|
213
|
+
else
|
214
|
+
'http'
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def ca_bundle_path
|
219
|
+
if use_system_ssl_cert_chain? && File.exist?(OpenSSL::X509::DEFAULT_CERT_FILE)
|
220
|
+
OpenSSL::X509::DEFAULT_CERT_FILE
|
221
|
+
else
|
222
|
+
local_cert_path # ca-bundle.crt built from source, see resources/README.md
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def local_cert_path
|
227
|
+
File.expand_path(File.join("..", "..", "..", "resources", "ca-bundle.crt"), __FILE__)
|
228
|
+
end
|
229
|
+
|
230
|
+
private
|
231
|
+
# Determines what port should we use for sending notices.
|
232
|
+
# @return [Fixnum] Returns 443 if you've set secure to true in your
|
233
|
+
# configuration, and 80 otherwise.
|
234
|
+
def default_port
|
235
|
+
if secure?
|
236
|
+
443
|
237
|
+
else
|
238
|
+
80
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
@@ -0,0 +1,321 @@
|
|
1
|
+
require 'builder'
|
2
|
+
require 'socket'
|
3
|
+
|
4
|
+
module HydraulicBrake
|
5
|
+
class Notice
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def attr_reader_with_tracking(*names)
|
9
|
+
attr_readers.concat(names)
|
10
|
+
attr_reader_without_tracking(*names)
|
11
|
+
end
|
12
|
+
|
13
|
+
alias_method :attr_reader_without_tracking, :attr_reader
|
14
|
+
alias_method :attr_reader, :attr_reader_with_tracking
|
15
|
+
|
16
|
+
|
17
|
+
def attr_readers
|
18
|
+
@attr_readers ||= []
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# The exception that caused this notice, if any
|
23
|
+
attr_reader :exception
|
24
|
+
|
25
|
+
# The API key for the project to which this notice should be sent
|
26
|
+
attr_reader :api_key
|
27
|
+
|
28
|
+
# The backtrace from the given exception or hash.
|
29
|
+
attr_reader :backtrace
|
30
|
+
|
31
|
+
# The name of the class of error (such as RuntimeError)
|
32
|
+
attr_reader :error_class
|
33
|
+
|
34
|
+
# The name of the server environment (such as "production")
|
35
|
+
attr_reader :environment_name
|
36
|
+
|
37
|
+
# CGI variables such as HTTP_METHOD
|
38
|
+
attr_reader :cgi_data
|
39
|
+
|
40
|
+
# The message from the exception, or a general description of the error
|
41
|
+
attr_reader :error_message
|
42
|
+
|
43
|
+
# See Configuration#backtrace_filters
|
44
|
+
attr_reader :backtrace_filters
|
45
|
+
|
46
|
+
# See Configuration#params_filters
|
47
|
+
attr_reader :params_filters
|
48
|
+
|
49
|
+
# A hash of parameters from the query string or post body.
|
50
|
+
attr_reader :parameters
|
51
|
+
alias_method :params, :parameters
|
52
|
+
|
53
|
+
# The component (if any) which was used in this request (usually the controller)
|
54
|
+
attr_reader :component
|
55
|
+
alias_method :controller, :component
|
56
|
+
|
57
|
+
# The action (if any) that was called in this request
|
58
|
+
attr_reader :action
|
59
|
+
|
60
|
+
# A hash of session data from the request
|
61
|
+
attr_reader :session_data
|
62
|
+
|
63
|
+
# The path to the project that caused the error (usually Rails.root)
|
64
|
+
attr_reader :project_root
|
65
|
+
|
66
|
+
# The URL at which the error occurred (if any)
|
67
|
+
attr_reader :url
|
68
|
+
|
69
|
+
# The name of the notifier library sending this notice, such as "HydraulicBrake Notifier"
|
70
|
+
attr_reader :notifier_name
|
71
|
+
|
72
|
+
# The version number of the notifier library sending this notice, such as "2.1.3"
|
73
|
+
attr_reader :notifier_version
|
74
|
+
|
75
|
+
# A URL for more information about the notifier library sending this notice
|
76
|
+
attr_reader :notifier_url
|
77
|
+
|
78
|
+
# The host name where this error occurred (if any)
|
79
|
+
attr_reader :hostname
|
80
|
+
|
81
|
+
# Details about the user who experienced the error
|
82
|
+
attr_reader :user
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
# Private writers for all the attributes
|
87
|
+
attr_writer :exception, :api_key, :backtrace, :error_class, :error_message,
|
88
|
+
:backtrace_filters, :parameters, :params_filters, :project_root, :url,
|
89
|
+
:notifier_name, :notifier_url, :notifier_version, :component, :action,
|
90
|
+
:cgi_data, :environment_name, :hostname, :user, :session_data
|
91
|
+
|
92
|
+
# Arguments given in the initializer
|
93
|
+
attr_accessor :args
|
94
|
+
|
95
|
+
public
|
96
|
+
|
97
|
+
def initialize(args)
|
98
|
+
self.args = args
|
99
|
+
self.exception = args[:exception]
|
100
|
+
self.api_key = args[:api_key]
|
101
|
+
self.project_root = args[:project_root]
|
102
|
+
self.url = args[:url]
|
103
|
+
|
104
|
+
self.notifier_name = args[:notifier_name]
|
105
|
+
self.notifier_version = args[:notifier_version]
|
106
|
+
self.notifier_url = args[:notifier_url]
|
107
|
+
|
108
|
+
self.backtrace_filters = args[:backtrace_filters] || []
|
109
|
+
self.params_filters = args[:params_filters] || []
|
110
|
+
self.parameters = args[:parameters] || {}
|
111
|
+
self.component = args[:component] || args[:controller] || nil
|
112
|
+
self.action = args[:action] || nil
|
113
|
+
|
114
|
+
self.environment_name = args[:environment_name]
|
115
|
+
self.cgi_data = args[:cgi_data] || {}
|
116
|
+
self.backtrace = Backtrace.parse(exception_attribute(:backtrace, caller), :filters => self.backtrace_filters)
|
117
|
+
self.error_class = exception_attribute(:error_class) {|exception| exception.class.name }
|
118
|
+
self.error_message = exception_attribute(:error_message, 'Notification') do |exception|
|
119
|
+
"#{exception.class.name}: #{args[:error_message] || exception.message}"
|
120
|
+
end
|
121
|
+
self.session_data = args[:session_data] || {}
|
122
|
+
|
123
|
+
self.hostname = local_hostname
|
124
|
+
self.user = args[:user] || {}
|
125
|
+
|
126
|
+
clean_params
|
127
|
+
end
|
128
|
+
|
129
|
+
# Converts the given notice to XML
|
130
|
+
def to_xml
|
131
|
+
builder = Builder::XmlMarkup.new
|
132
|
+
builder.instruct!
|
133
|
+
xml = builder.notice(:version => HydraulicBrake::API_VERSION) do |notice|
|
134
|
+
notice.tag!("api-key", api_key)
|
135
|
+
notice.notifier do |notifier|
|
136
|
+
notifier.name(notifier_name)
|
137
|
+
notifier.version(notifier_version)
|
138
|
+
notifier.url(notifier_url)
|
139
|
+
end
|
140
|
+
notice.error do |error|
|
141
|
+
error.tag!('class', error_class)
|
142
|
+
error.message(error_message)
|
143
|
+
error.backtrace do |backtrace|
|
144
|
+
self.backtrace.lines.each do |line|
|
145
|
+
backtrace.line(:number => line.number,
|
146
|
+
:file => line.file,
|
147
|
+
:method => line.method)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
if request_present?
|
153
|
+
notice.request do |request|
|
154
|
+
request.url(url)
|
155
|
+
request.component(controller)
|
156
|
+
request.action(action)
|
157
|
+
unless parameters.nil? || parameters.empty?
|
158
|
+
request.params do |params|
|
159
|
+
xml_vars_for(params, parameters)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
unless session_data.empty?
|
163
|
+
request.session do |session|
|
164
|
+
xml_vars_for(session, session_data)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
unless cgi_data.nil? || cgi_data.empty?
|
168
|
+
request.tag!("cgi-data") do |cgi_datum|
|
169
|
+
xml_vars_for(cgi_datum, cgi_data)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
notice.tag!("server-environment") do |env|
|
176
|
+
env.tag!("project-root", project_root)
|
177
|
+
env.tag!("environment-name", environment_name)
|
178
|
+
env.tag!("hostname", hostname)
|
179
|
+
end
|
180
|
+
unless user.empty?
|
181
|
+
notice.tag!("current-user") do |u|
|
182
|
+
u.tag!("id",user[:id])
|
183
|
+
u.tag!("name",user[:name])
|
184
|
+
u.tag!("email",user[:email])
|
185
|
+
u.tag!("username",user[:username])
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
xml.to_s
|
190
|
+
end
|
191
|
+
|
192
|
+
# Allows properties to be accessed using a hash-like syntax
|
193
|
+
#
|
194
|
+
# @example
|
195
|
+
# notice[:error_message]
|
196
|
+
# @param [String] method The given key for an attribute
|
197
|
+
# @return The attribute value, or self if given +:request+
|
198
|
+
def [](method)
|
199
|
+
case method
|
200
|
+
when :request
|
201
|
+
self
|
202
|
+
else
|
203
|
+
send(method)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
private
|
208
|
+
|
209
|
+
def request_present?
|
210
|
+
url ||
|
211
|
+
controller ||
|
212
|
+
action ||
|
213
|
+
!parameters.empty? ||
|
214
|
+
!cgi_data.empty? ||
|
215
|
+
!session_data.empty?
|
216
|
+
end
|
217
|
+
|
218
|
+
# Gets a property named +attribute+ of an exception, either from an actual
|
219
|
+
# exception or a hash.
|
220
|
+
#
|
221
|
+
# If an exception is available, #from_exception will be used. Otherwise,
|
222
|
+
# a key named +attribute+ will be used from the #args.
|
223
|
+
#
|
224
|
+
# If no exception or hash key is available, +default+ will be used.
|
225
|
+
def exception_attribute(attribute, default = nil, &block)
|
226
|
+
(exception && from_exception(attribute, &block)) || args[attribute] || default
|
227
|
+
end
|
228
|
+
|
229
|
+
# Gets a property named +attribute+ from an exception.
|
230
|
+
#
|
231
|
+
# If a block is given, it will be used when getting the property from an
|
232
|
+
# exception. The block should accept and exception and return the value for
|
233
|
+
# the property.
|
234
|
+
#
|
235
|
+
# If no block is given, a method with the same name as +attribute+ will be
|
236
|
+
# invoked for the value.
|
237
|
+
def from_exception(attribute)
|
238
|
+
if block_given?
|
239
|
+
yield(exception)
|
240
|
+
else
|
241
|
+
exception.send(attribute)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
# Removes non-serializable data from the given attribute.
|
246
|
+
# See #clean_unserializable_data
|
247
|
+
def clean_unserializable_data_from(attribute)
|
248
|
+
self.send(:"#{attribute}=", clean_unserializable_data(send(attribute)))
|
249
|
+
end
|
250
|
+
|
251
|
+
# Removes non-serializable data. Allowed data types are strings, arrays,
|
252
|
+
# and hashes. All other types are converted to strings.
|
253
|
+
# TODO: move this onto Hash
|
254
|
+
def clean_unserializable_data(data, stack = [])
|
255
|
+
return "[possible infinite recursion halted]" if stack.any?{|item| item == data.object_id }
|
256
|
+
|
257
|
+
if data.respond_to?(:to_hash)
|
258
|
+
data.to_hash.inject({}) do |result, (key, value)|
|
259
|
+
result.merge(key => clean_unserializable_data(value, stack + [data.object_id]))
|
260
|
+
end
|
261
|
+
elsif data.respond_to?(:to_ary)
|
262
|
+
data.to_ary.collect do |value|
|
263
|
+
clean_unserializable_data(value, stack + [data.object_id])
|
264
|
+
end
|
265
|
+
else
|
266
|
+
data.to_s
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
# Replaces the contents of params that match params_filters.
|
271
|
+
# TODO: extract this to a different class
|
272
|
+
def clean_params
|
273
|
+
clean_unserializable_data_from(:parameters)
|
274
|
+
filter(parameters)
|
275
|
+
if cgi_data
|
276
|
+
clean_unserializable_data_from(:cgi_data)
|
277
|
+
filter(cgi_data)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
def filter(hash)
|
282
|
+
if params_filters
|
283
|
+
hash.each do |key, value|
|
284
|
+
if filter_key?(key)
|
285
|
+
hash[key] = "[FILTERED]"
|
286
|
+
elsif value.respond_to?(:to_hash)
|
287
|
+
filter(hash[key])
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
def filter_key?(key)
|
294
|
+
params_filters.any? do |filter|
|
295
|
+
key.to_s.eql?(filter.to_s)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def xml_vars_for(builder, hash)
|
300
|
+
hash.each do |key, value|
|
301
|
+
if value.respond_to?(:to_hash)
|
302
|
+
builder.var(:key => key){|b| xml_vars_for(b, value.to_hash) }
|
303
|
+
else
|
304
|
+
builder.var(value.to_s, :key => key)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def local_hostname
|
310
|
+
Socket.gethostname
|
311
|
+
end
|
312
|
+
|
313
|
+
def to_s
|
314
|
+
content = []
|
315
|
+
self.class.attr_readers.each do |attr|
|
316
|
+
content << " #{attr}: #{send(attr)}"
|
317
|
+
end
|
318
|
+
content.join("\n")
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module HydraulicBrake
|
2
|
+
# Sends out the notice to Airbrake
|
3
|
+
class Sender
|
4
|
+
|
5
|
+
NOTICES_URI = '/notifier_api/v2/notices/'.freeze
|
6
|
+
HTTP_ERRORS = [Timeout::Error,
|
7
|
+
Errno::EINVAL,
|
8
|
+
Errno::ECONNRESET,
|
9
|
+
EOFError,
|
10
|
+
Net::HTTPBadResponse,
|
11
|
+
Net::HTTPHeaderSyntaxError,
|
12
|
+
Net::ProtocolError,
|
13
|
+
Errno::ECONNREFUSED].freeze
|
14
|
+
|
15
|
+
def initialize(options = {})
|
16
|
+
[ :proxy_host,
|
17
|
+
:proxy_port,
|
18
|
+
:proxy_user,
|
19
|
+
:proxy_pass,
|
20
|
+
:protocol,
|
21
|
+
:host,
|
22
|
+
:port,
|
23
|
+
:secure,
|
24
|
+
:use_system_ssl_cert_chain,
|
25
|
+
:http_open_timeout,
|
26
|
+
:http_read_timeout
|
27
|
+
].each do |option|
|
28
|
+
instance_variable_set("@#{option}", options[option])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Sends the notice data off to Airbrake for processing.
|
33
|
+
#
|
34
|
+
# @param [Notice or String] notice The notice to be sent off
|
35
|
+
def send_to_airbrake(notice)
|
36
|
+
data = notice.respond_to?(:to_xml) ? notice.to_xml : notice
|
37
|
+
http = setup_http_connection
|
38
|
+
|
39
|
+
response = begin
|
40
|
+
http.post(url.path, data, HEADERS)
|
41
|
+
rescue *HTTP_ERRORS => e
|
42
|
+
log :level => :error,
|
43
|
+
:message => "Unable to contact the Airbrake server. HTTP Error=#{e}"
|
44
|
+
nil
|
45
|
+
end
|
46
|
+
|
47
|
+
case response
|
48
|
+
when Net::HTTPSuccess then
|
49
|
+
log :level => :info,
|
50
|
+
:message => "Success: #{response.class}",
|
51
|
+
:response => response
|
52
|
+
else
|
53
|
+
log :level => :error,
|
54
|
+
:message => "Failure: #{response.class}",
|
55
|
+
:response => response,
|
56
|
+
:notice => notice
|
57
|
+
end
|
58
|
+
|
59
|
+
if response && response.respond_to?(:body)
|
60
|
+
error_id = response.body.match(%r{<id[^>]*>(.*?)</id>})
|
61
|
+
error_id[1] if error_id
|
62
|
+
end
|
63
|
+
rescue => e
|
64
|
+
log :level => :error,
|
65
|
+
:message => "[HydraulicBrake::Sender#send_to_airbrake] Cannot send notification. Error: #{e.class}" +
|
66
|
+
" - #{e.message}\nBacktrace:\n#{e.backtrace.join("\n\t")}"
|
67
|
+
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
|
71
|
+
attr_reader :proxy_host,
|
72
|
+
:proxy_port,
|
73
|
+
:proxy_user,
|
74
|
+
:proxy_pass,
|
75
|
+
:protocol,
|
76
|
+
:host,
|
77
|
+
:port,
|
78
|
+
:secure,
|
79
|
+
:use_system_ssl_cert_chain,
|
80
|
+
:http_open_timeout,
|
81
|
+
:http_read_timeout
|
82
|
+
|
83
|
+
alias_method :secure?, :secure
|
84
|
+
alias_method :use_system_ssl_cert_chain?, :use_system_ssl_cert_chain
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def url
|
89
|
+
URI.parse("#{protocol}://#{host}:#{port}").merge(NOTICES_URI)
|
90
|
+
end
|
91
|
+
|
92
|
+
def log(opts = {})
|
93
|
+
opts[:logger].send opts[:level], LOG_PREFIX + opts[:message] if opts[:logger]
|
94
|
+
HydraulicBrake.report_environment_info
|
95
|
+
HydraulicBrake.report_response_body(opts[:response].body) if opts[:response] && opts[:response].respond_to?(:body)
|
96
|
+
HydraulicBrake.report_notice(opts[:notice]) if opts[:notice]
|
97
|
+
end
|
98
|
+
|
99
|
+
def logger
|
100
|
+
HydraulicBrake.logger
|
101
|
+
end
|
102
|
+
|
103
|
+
def setup_http_connection
|
104
|
+
http =
|
105
|
+
Net::HTTP::Proxy(proxy_host, proxy_port, proxy_user, proxy_pass).
|
106
|
+
new(url.host, url.port)
|
107
|
+
|
108
|
+
http.read_timeout = http_read_timeout
|
109
|
+
http.open_timeout = http_open_timeout
|
110
|
+
|
111
|
+
if secure?
|
112
|
+
http.use_ssl = true
|
113
|
+
|
114
|
+
http.ca_file = HydraulicBrake.configuration.ca_bundle_path
|
115
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
116
|
+
else
|
117
|
+
http.use_ssl = false
|
118
|
+
end
|
119
|
+
|
120
|
+
http
|
121
|
+
rescue => e
|
122
|
+
log :level => :error,
|
123
|
+
:message => "[HydraulicBrake::Sender#setup_http_connection] Failure initializing the HTTP connection.\n" +
|
124
|
+
"Error: #{e.class} - #{e.message}\nBacktrace:\n#{e.backtrace.join("\n\t")}"
|
125
|
+
raise e
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|