bugsnag-maglev- 2.8.12
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.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.gitignore +52 -0
- data/.rspec +3 -0
- data/.travis.yml +15 -0
- data/CHANGELOG.md +425 -0
- data/CONTRIBUTING.md +43 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +20 -0
- data/README.md +804 -0
- data/Rakefile +29 -0
- data/VERSION +1 -0
- data/bugsnag.gemspec +32 -0
- data/lib/bugsnag.rb +129 -0
- data/lib/bugsnag/capistrano.rb +7 -0
- data/lib/bugsnag/capistrano2.rb +32 -0
- data/lib/bugsnag/configuration.rb +129 -0
- data/lib/bugsnag/delay/resque.rb +21 -0
- data/lib/bugsnag/delayed_job.rb +57 -0
- data/lib/bugsnag/delivery.rb +18 -0
- data/lib/bugsnag/delivery/synchronous.rb +51 -0
- data/lib/bugsnag/delivery/thread_queue.rb +53 -0
- data/lib/bugsnag/deploy.rb +35 -0
- data/lib/bugsnag/helpers.rb +127 -0
- data/lib/bugsnag/mailman.rb +28 -0
- data/lib/bugsnag/meta_data.rb +7 -0
- data/lib/bugsnag/middleware/callbacks.rb +19 -0
- data/lib/bugsnag/middleware/mailman.rb +13 -0
- data/lib/bugsnag/middleware/rack_request.rb +72 -0
- data/lib/bugsnag/middleware/rails2_request.rb +52 -0
- data/lib/bugsnag/middleware/rails3_request.rb +42 -0
- data/lib/bugsnag/middleware/rake.rb +23 -0
- data/lib/bugsnag/middleware/sidekiq.rb +13 -0
- data/lib/bugsnag/middleware/warden_user.rb +39 -0
- data/lib/bugsnag/middleware_stack.rb +98 -0
- data/lib/bugsnag/notification.rb +452 -0
- data/lib/bugsnag/rack.rb +53 -0
- data/lib/bugsnag/rails.rb +66 -0
- data/lib/bugsnag/rails/action_controller_rescue.rb +62 -0
- data/lib/bugsnag/rails/active_record_rescue.rb +20 -0
- data/lib/bugsnag/rails/controller_methods.rb +44 -0
- data/lib/bugsnag/railtie.rb +78 -0
- data/lib/bugsnag/rake.rb +25 -0
- data/lib/bugsnag/resque.rb +40 -0
- data/lib/bugsnag/sidekiq.rb +38 -0
- data/lib/bugsnag/tasks.rb +3 -0
- data/lib/bugsnag/tasks/bugsnag.cap +48 -0
- data/lib/bugsnag/tasks/bugsnag.rake +89 -0
- data/lib/bugsnag/version.rb +3 -0
- data/lib/generators/bugsnag/bugsnag_generator.rb +24 -0
- data/rails/init.rb +3 -0
- data/spec/code_spec.rb +86 -0
- data/spec/fixtures/crashes/end_of_file.rb +9 -0
- data/spec/fixtures/crashes/short_file.rb +1 -0
- data/spec/fixtures/crashes/start_of_file.rb +9 -0
- data/spec/fixtures/middleware/internal_info_setter.rb +11 -0
- data/spec/fixtures/middleware/public_info_setter.rb +11 -0
- data/spec/fixtures/tasks/Rakefile +15 -0
- data/spec/helper_spec.rb +144 -0
- data/spec/integration_spec.rb +110 -0
- data/spec/middleware_spec.rb +181 -0
- data/spec/notification_spec.rb +822 -0
- data/spec/rack_spec.rb +56 -0
- data/spec/spec_helper.rb +53 -0
- metadata +198 -0
@@ -0,0 +1,42 @@
|
|
1
|
+
module Bugsnag::Middleware
|
2
|
+
class Rails3Request
|
3
|
+
def initialize(bugsnag)
|
4
|
+
@bugsnag = bugsnag
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(notification)
|
8
|
+
if notification.request_data[:rack_env]
|
9
|
+
env = notification.request_data[:rack_env]
|
10
|
+
params = env["action_dispatch.request.parameters"]
|
11
|
+
|
12
|
+
if params
|
13
|
+
# Set the context
|
14
|
+
notification.context = "#{params[:controller]}##{params[:action]}"
|
15
|
+
|
16
|
+
# Augment the request tab
|
17
|
+
notification.add_tab(:request, {
|
18
|
+
:railsAction => "#{params[:controller]}##{params[:action]}",
|
19
|
+
:params => params
|
20
|
+
})
|
21
|
+
end
|
22
|
+
|
23
|
+
# Use action_dispatch.remote_ip for IP address fields and send request id
|
24
|
+
notification.add_tab(:request, {
|
25
|
+
:clientIp => env["action_dispatch.remote_ip"],
|
26
|
+
:requestId => env["action_dispatch.request_id"]
|
27
|
+
})
|
28
|
+
|
29
|
+
notification.user_id = env["action_dispatch.remote_ip"]
|
30
|
+
|
31
|
+
# Add the rails version
|
32
|
+
if notification.configuration.send_environment
|
33
|
+
notification.add_tab(:environment, {
|
34
|
+
:railsVersion => Rails::VERSION::STRING
|
35
|
+
})
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
@bugsnag.call(notification)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Bugsnag::Middleware
|
2
|
+
class Rake
|
3
|
+
def initialize(bugsnag)
|
4
|
+
@bugsnag = bugsnag
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(notification)
|
8
|
+
task = notification.request_data[:bugsnag_running_task]
|
9
|
+
|
10
|
+
if task
|
11
|
+
notification.add_tab(:rake_task, {
|
12
|
+
:name => task.name,
|
13
|
+
:description => task.full_comment,
|
14
|
+
:arguments => task.arg_description
|
15
|
+
})
|
16
|
+
|
17
|
+
notification.context ||= task.name
|
18
|
+
end
|
19
|
+
|
20
|
+
@bugsnag.call(notification)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Bugsnag::Middleware
|
2
|
+
class Sidekiq
|
3
|
+
def initialize(bugsnag)
|
4
|
+
@bugsnag = bugsnag
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(notification)
|
8
|
+
sidekiq = notification.request_data[:sidekiq]
|
9
|
+
notification.add_tab(:sidekiq, sidekiq) if sidekiq
|
10
|
+
@bugsnag.call(notification)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Bugsnag::Middleware
|
2
|
+
class WardenUser
|
3
|
+
SCOPE_PATTERN = /^warden\.user\.([^.]+)\.key$/
|
4
|
+
COMMON_USER_FIELDS = [:email, :name, :first_name, :last_name, :created_at, :id]
|
5
|
+
|
6
|
+
def initialize(bugsnag)
|
7
|
+
@bugsnag = bugsnag
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(notification)
|
11
|
+
if notification.request_data[:rack_env] && notification.request_data[:rack_env]["warden"]
|
12
|
+
env = notification.request_data[:rack_env]
|
13
|
+
session = env["rack.session"] || {}
|
14
|
+
|
15
|
+
# Find all warden user scopes
|
16
|
+
warden_scopes = session.keys.select {|k| k.match(SCOPE_PATTERN)}.map {|k| k.gsub(SCOPE_PATTERN, '\1')}
|
17
|
+
unless warden_scopes.empty?
|
18
|
+
# Pick the best scope for unique id (the default is "user")
|
19
|
+
best_scope = warden_scopes.include?("user") ? "user" : warden_scopes.first
|
20
|
+
|
21
|
+
# Extract useful user information
|
22
|
+
user = {}
|
23
|
+
user_object = env["warden"].user({:scope => best_scope, :run_callbacks => false}) rescue nil
|
24
|
+
if user_object
|
25
|
+
# Build the user info for this scope
|
26
|
+
COMMON_USER_FIELDS.each do |field|
|
27
|
+
user[field] = user_object.send(field) if user_object.respond_to?(field)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# We merge the first warden scope down, so that it is the main "user" for the request
|
32
|
+
notification.user = user unless user.empty?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
@bugsnag.call(notification)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Bugsnag
|
2
|
+
class MiddlewareStack
|
3
|
+
def initialize
|
4
|
+
@middlewares = []
|
5
|
+
@disabled_middleware = []
|
6
|
+
@mutex = Mutex.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def use(new_middleware)
|
10
|
+
@mutex.synchronize do
|
11
|
+
return if @disabled_middleware.include?(new_middleware)
|
12
|
+
return if @middlewares.include?(new_middleware)
|
13
|
+
|
14
|
+
@middlewares << new_middleware
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def insert_after(after, new_middleware)
|
19
|
+
@mutex.synchronize do
|
20
|
+
return if @disabled_middleware.include?(new_middleware)
|
21
|
+
return if @middlewares.include?(new_middleware)
|
22
|
+
|
23
|
+
if after.is_a? Array
|
24
|
+
index = @middlewares.rindex {|el| after.include?(el)}
|
25
|
+
else
|
26
|
+
index = @middlewares.rindex(after)
|
27
|
+
end
|
28
|
+
|
29
|
+
if index.nil?
|
30
|
+
@middlewares << new_middleware
|
31
|
+
else
|
32
|
+
@middlewares.insert index + 1, new_middleware
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def insert_before(before, new_middleware)
|
38
|
+
@mutex.synchronize do
|
39
|
+
return if @disabled_middleware.include?(new_middleware)
|
40
|
+
return if @middlewares.include?(new_middleware)
|
41
|
+
|
42
|
+
if before.is_a? Array
|
43
|
+
index = @middlewares.index {|el| before.include?(el)}
|
44
|
+
else
|
45
|
+
index = @middlewares.index(before)
|
46
|
+
end
|
47
|
+
|
48
|
+
@middlewares.insert index || @middlewares.length, new_middleware
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def disable(*middlewares)
|
53
|
+
@mutex.synchronize do
|
54
|
+
@disabled_middleware += middlewares
|
55
|
+
|
56
|
+
@middlewares.delete_if {|m| @disabled_middleware.include?(m)}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# This allows people to proxy methods to the array if they want to do more complex stuff
|
61
|
+
def method_missing(method, *args, &block)
|
62
|
+
@middlewares.send(method, *args, &block)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Runs the middleware stack and calls
|
66
|
+
def run(notification)
|
67
|
+
# The final lambda is the termination of the middleware stack. It calls deliver on the notification
|
68
|
+
lambda_has_run = false
|
69
|
+
notify_lambda = lambda do |notif|
|
70
|
+
lambda_has_run = true
|
71
|
+
yield if block_given?
|
72
|
+
end
|
73
|
+
|
74
|
+
begin
|
75
|
+
# We reverse them, so we can call "call" on the first middleware
|
76
|
+
middleware_procs.reverse.inject(notify_lambda) { |n,e| e.call(n) }.call(notification)
|
77
|
+
rescue StandardError => e
|
78
|
+
# KLUDGE: Since we don't re-raise middleware exceptions, this breaks rspec
|
79
|
+
raise if e.class.to_s == "RSpec::Expectations::ExpectationNotMetError"
|
80
|
+
|
81
|
+
# We dont notify, as we dont want to loop forever in the case of really broken middleware, we will
|
82
|
+
# still send this notify
|
83
|
+
Bugsnag.warn "Bugsnag middleware error: #{e}"
|
84
|
+
Bugsnag.log "Middleware error stacktrace: #{e.backtrace.inspect}"
|
85
|
+
end
|
86
|
+
|
87
|
+
# Ensure that the deliver has been performed, and no middleware has botched it
|
88
|
+
notify_lambda.call(notification) unless lambda_has_run
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
# Generates a list of middleware procs that are ready to be run
|
93
|
+
# Pass each one a reference to the next in the queue
|
94
|
+
def middleware_procs
|
95
|
+
@middlewares.map{|middleware| proc { |next_middleware| middleware.new(next_middleware) } }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,452 @@
|
|
1
|
+
require "json"
|
2
|
+
|
3
|
+
if RUBY_VERSION =~ /^1\.8/
|
4
|
+
begin
|
5
|
+
require "iconv"
|
6
|
+
rescue LoadError
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
require "pathname"
|
11
|
+
|
12
|
+
module Bugsnag
|
13
|
+
class Notification
|
14
|
+
NOTIFIER_NAME = "Ruby Bugsnag Notifier"
|
15
|
+
NOTIFIER_VERSION = Bugsnag::VERSION
|
16
|
+
NOTIFIER_URL = "http://www.bugsnag.com"
|
17
|
+
|
18
|
+
API_KEY_REGEX = /[0-9a-f]{32}/i
|
19
|
+
|
20
|
+
# e.g. "org/jruby/RubyKernel.java:1264:in `catch'"
|
21
|
+
BACKTRACE_LINE_REGEX = /^((?:[a-zA-Z]:)?[^:]+):(\d+)(?::in `([^']+)')?$/
|
22
|
+
|
23
|
+
# e.g. "org.jruby.Ruby.runScript(Ruby.java:807)"
|
24
|
+
JAVA_BACKTRACE_REGEX = /^(.*)\((.*)(?::([0-9]+))?\)$/
|
25
|
+
|
26
|
+
MAX_EXCEPTIONS_TO_UNWRAP = 5
|
27
|
+
|
28
|
+
SUPPORTED_SEVERITIES = ["error", "warning", "info"]
|
29
|
+
|
30
|
+
CURRENT_PAYLOAD_VERSION = "2"
|
31
|
+
|
32
|
+
attr_accessor :context
|
33
|
+
attr_accessor :user
|
34
|
+
attr_accessor :configuration
|
35
|
+
attr_accessor :meta_data
|
36
|
+
|
37
|
+
class << self
|
38
|
+
def deliver_exception_payload(url, payload, configuration=Bugsnag.configuration, delivery_method=nil)
|
39
|
+
|
40
|
+
# If the payload is going to be too long, we trim the hashes to send
|
41
|
+
# a minimal payload instead
|
42
|
+
payload_string = ::JSON.dump(payload)
|
43
|
+
if payload_string.length > 128000
|
44
|
+
payload[:events].each {|e| e[:metaData] = Bugsnag::Helpers.reduce_hash_size(e[:metaData])}
|
45
|
+
payload_string = ::JSON.dump(payload)
|
46
|
+
end
|
47
|
+
|
48
|
+
Bugsnag::Delivery[delivery_method || configuration.delivery_method].deliver(url, payload_string, configuration)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def initialize(exception, configuration, overrides = nil, request_data = nil)
|
53
|
+
@configuration = configuration
|
54
|
+
@overrides = Bugsnag::Helpers.flatten_meta_data(overrides) || {}
|
55
|
+
@request_data = request_data
|
56
|
+
@meta_data = {}
|
57
|
+
@user = {}
|
58
|
+
@should_ignore = false
|
59
|
+
|
60
|
+
self.severity = @overrides[:severity]
|
61
|
+
@overrides.delete :severity
|
62
|
+
|
63
|
+
if @overrides.key? :grouping_hash
|
64
|
+
self.grouping_hash = @overrides[:grouping_hash]
|
65
|
+
@overrides.delete :grouping_hash
|
66
|
+
end
|
67
|
+
|
68
|
+
if @overrides.key? :api_key
|
69
|
+
self.api_key = @overrides[:api_key]
|
70
|
+
@overrides.delete :api_key
|
71
|
+
end
|
72
|
+
|
73
|
+
if @overrides.key? :delivery_method
|
74
|
+
@delivery_method = @overrides[:delivery_method]
|
75
|
+
@overrides.delete :delivery_method
|
76
|
+
end
|
77
|
+
|
78
|
+
# Unwrap exceptions
|
79
|
+
@exceptions = []
|
80
|
+
|
81
|
+
ex = exception
|
82
|
+
while ex != nil && !@exceptions.include?(ex) && @exceptions.length < MAX_EXCEPTIONS_TO_UNWRAP
|
83
|
+
|
84
|
+
unless ex.is_a? Exception
|
85
|
+
if ex.respond_to?(:to_exception)
|
86
|
+
ex = ex.to_exception
|
87
|
+
elsif ex.respond_to?(:exception)
|
88
|
+
ex = ex.exception
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
unless ex.is_a?(Exception) || (defined?(Java::JavaLang::Throwable) && ex.is_a?(Java::JavaLang::Throwable))
|
93
|
+
Bugsnag.warn("Converting non-Exception to RuntimeError: #{ex.inspect}")
|
94
|
+
ex = RuntimeError.new(ex.to_s)
|
95
|
+
ex.set_backtrace caller
|
96
|
+
end
|
97
|
+
|
98
|
+
@exceptions << ex
|
99
|
+
|
100
|
+
if ex.respond_to?(:cause) && ex.cause
|
101
|
+
ex = ex.cause
|
102
|
+
elsif ex.respond_to?(:continued_exception) && ex.continued_exception
|
103
|
+
ex = ex.continued_exception
|
104
|
+
elsif ex.respond_to?(:original_exception) && ex.original_exception
|
105
|
+
ex = ex.original_exception
|
106
|
+
else
|
107
|
+
ex = nil
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Add a single value as custom data, to this notification
|
113
|
+
def add_custom_data(name, value)
|
114
|
+
@meta_data[:custom] ||= {}
|
115
|
+
@meta_data[:custom][name.to_sym] = value
|
116
|
+
end
|
117
|
+
|
118
|
+
# Add a new tab to this notification
|
119
|
+
def add_tab(name, value)
|
120
|
+
return if name.nil?
|
121
|
+
|
122
|
+
if value.is_a? Hash
|
123
|
+
@meta_data[name.to_sym] ||= {}
|
124
|
+
@meta_data[name.to_sym].merge! value
|
125
|
+
else
|
126
|
+
self.add_custom_data(name, value)
|
127
|
+
Bugsnag.warn "Adding a tab requires a hash, adding to custom tab instead (name=#{name})"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Remove a tab from this notification
|
132
|
+
def remove_tab(name)
|
133
|
+
return if name.nil?
|
134
|
+
|
135
|
+
@meta_data.delete(name.to_sym)
|
136
|
+
end
|
137
|
+
|
138
|
+
def user_id=(user_id)
|
139
|
+
@user[:id] = user_id
|
140
|
+
end
|
141
|
+
|
142
|
+
def user_id
|
143
|
+
@user[:id]
|
144
|
+
end
|
145
|
+
|
146
|
+
def user=(user = {})
|
147
|
+
return unless user.is_a? Hash
|
148
|
+
@user.merge!(user).delete_if{|k,v| v == nil}
|
149
|
+
end
|
150
|
+
|
151
|
+
def severity=(severity)
|
152
|
+
@severity = severity if SUPPORTED_SEVERITIES.include?(severity)
|
153
|
+
end
|
154
|
+
|
155
|
+
def severity
|
156
|
+
@severity || "warning"
|
157
|
+
end
|
158
|
+
|
159
|
+
def payload_version
|
160
|
+
CURRENT_PAYLOAD_VERSION
|
161
|
+
end
|
162
|
+
|
163
|
+
def grouping_hash=(grouping_hash)
|
164
|
+
@grouping_hash = grouping_hash
|
165
|
+
end
|
166
|
+
|
167
|
+
def grouping_hash
|
168
|
+
@grouping_hash || nil
|
169
|
+
end
|
170
|
+
|
171
|
+
def api_key=(api_key)
|
172
|
+
@api_key = api_key
|
173
|
+
end
|
174
|
+
|
175
|
+
def api_key
|
176
|
+
@api_key ||= @configuration.api_key
|
177
|
+
end
|
178
|
+
|
179
|
+
# Deliver this notification to bugsnag.com Also runs through the middleware as required.
|
180
|
+
def deliver
|
181
|
+
return unless @configuration.should_notify?
|
182
|
+
|
183
|
+
# Check we have at least an api_key
|
184
|
+
if api_key.nil?
|
185
|
+
Bugsnag.warn "No API key configured, couldn't notify"
|
186
|
+
return
|
187
|
+
elsif api_key !~ API_KEY_REGEX
|
188
|
+
Bugsnag.warn "Your API key (#{api_key}) is not valid, couldn't notify"
|
189
|
+
return
|
190
|
+
end
|
191
|
+
|
192
|
+
# Warn if no release_stage is set
|
193
|
+
Bugsnag.warn "You should set your app's release_stage (see https://bugsnag.com/docs/notifiers/ruby#release_stage)." unless @configuration.release_stage
|
194
|
+
|
195
|
+
@configuration.internal_middleware.run(self)
|
196
|
+
|
197
|
+
exceptions.each do |exception|
|
198
|
+
if exception.class.include?(Bugsnag::MetaData)
|
199
|
+
if exception.bugsnag_user_id.is_a?(String)
|
200
|
+
self.user_id = exception.bugsnag_user_id
|
201
|
+
end
|
202
|
+
if exception.bugsnag_context.is_a?(String)
|
203
|
+
self.context = exception.bugsnag_context
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
[:user_id, :context, :user, :grouping_hash].each do |symbol|
|
209
|
+
if @overrides[symbol]
|
210
|
+
self.send("#{symbol}=", @overrides[symbol])
|
211
|
+
@overrides.delete symbol
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# make meta_data available to public middleware
|
216
|
+
@meta_data = generate_meta_data(@exceptions, @overrides)
|
217
|
+
|
218
|
+
# Run the middleware here (including Bugsnag::Middleware::Callbacks)
|
219
|
+
# at the end of the middleware stack, execute the actual notification delivery
|
220
|
+
@configuration.middleware.run(self) do
|
221
|
+
# This supports self.ignore! for before_notify_callbacks.
|
222
|
+
return if @should_ignore
|
223
|
+
|
224
|
+
# Build the endpoint url
|
225
|
+
endpoint = (@configuration.use_ssl ? "https://" : "http://") + @configuration.endpoint
|
226
|
+
Bugsnag.log("Notifying #{endpoint} of #{@exceptions.last.class} from api_key #{api_key}")
|
227
|
+
|
228
|
+
# Deliver the payload
|
229
|
+
self.class.deliver_exception_payload(endpoint, build_exception_payload, @configuration, @delivery_method)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# Build an exception payload
|
234
|
+
def build_exception_payload
|
235
|
+
# Build the payload's exception event
|
236
|
+
payload_event = {
|
237
|
+
:app => {
|
238
|
+
:version => @configuration.app_version,
|
239
|
+
:releaseStage => @configuration.release_stage,
|
240
|
+
:type => @configuration.app_type
|
241
|
+
},
|
242
|
+
:context => self.context,
|
243
|
+
:user => @user,
|
244
|
+
:payloadVersion => payload_version,
|
245
|
+
:exceptions => exception_list,
|
246
|
+
:severity => self.severity,
|
247
|
+
:groupingHash => self.grouping_hash,
|
248
|
+
}
|
249
|
+
|
250
|
+
payload_event[:device] = {:hostname => @configuration.hostname} if @configuration.hostname
|
251
|
+
|
252
|
+
# cleanup character encodings
|
253
|
+
payload_event = Bugsnag::Helpers.cleanup_obj_encoding(payload_event)
|
254
|
+
|
255
|
+
# filter out sensitive values in (and cleanup encodings) metaData
|
256
|
+
payload_event[:metaData] = Bugsnag::Helpers.cleanup_obj(@meta_data, @configuration.params_filters)
|
257
|
+
payload_event.reject! {|k,v| v.nil? }
|
258
|
+
|
259
|
+
# return the payload hash
|
260
|
+
{
|
261
|
+
:apiKey => api_key,
|
262
|
+
:notifier => {
|
263
|
+
:name => NOTIFIER_NAME,
|
264
|
+
:version => NOTIFIER_VERSION,
|
265
|
+
:url => NOTIFIER_URL
|
266
|
+
},
|
267
|
+
:events => [payload_event]
|
268
|
+
}
|
269
|
+
end
|
270
|
+
|
271
|
+
def ignore?
|
272
|
+
@should_ignore || ignore_exception_class? || ignore_user_agent?
|
273
|
+
end
|
274
|
+
|
275
|
+
def request_data
|
276
|
+
@request_data || Bugsnag.configuration.request_data
|
277
|
+
end
|
278
|
+
|
279
|
+
def exceptions
|
280
|
+
@exceptions
|
281
|
+
end
|
282
|
+
|
283
|
+
def ignore!
|
284
|
+
@should_ignore = true
|
285
|
+
end
|
286
|
+
|
287
|
+
private
|
288
|
+
|
289
|
+
def ignore_exception_class?
|
290
|
+
@exceptions.any? do |ex|
|
291
|
+
ancestor_chain = ex.class.ancestors.select { |ancestor| ancestor.is_a?(Class) }.map { |ancestor| error_class(ancestor) }.to_set
|
292
|
+
|
293
|
+
@configuration.ignore_classes.any? do |to_ignore|
|
294
|
+
to_ignore.is_a?(Proc) ? to_ignore.call(ex) : ancestor_chain.include?(to_ignore)
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def ignore_user_agent?
|
300
|
+
if @configuration.request_data && @configuration.request_data[:rack_env] && (agent = @configuration.request_data[:rack_env]["HTTP_USER_AGENT"])
|
301
|
+
@configuration.ignore_user_agents.any? do |to_ignore|
|
302
|
+
agent =~ to_ignore
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
# Generate the meta data from both the request configuration, the overrides and the exceptions for this notification
|
308
|
+
def generate_meta_data(exceptions, overrides)
|
309
|
+
# Copy the request meta data so we dont edit it by mistake
|
310
|
+
meta_data = @meta_data.dup
|
311
|
+
|
312
|
+
exceptions.each do |exception|
|
313
|
+
if exception.respond_to?(:bugsnag_meta_data) && exception.bugsnag_meta_data
|
314
|
+
exception.bugsnag_meta_data.each do |key, value|
|
315
|
+
add_to_meta_data key, value, meta_data
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
overrides.each do |key, value|
|
321
|
+
add_to_meta_data key, value, meta_data
|
322
|
+
end
|
323
|
+
|
324
|
+
meta_data
|
325
|
+
end
|
326
|
+
|
327
|
+
def add_to_meta_data(key, value, meta_data)
|
328
|
+
# If its a hash, its a tab so we can just add it providing its not reserved
|
329
|
+
if value.is_a? Hash
|
330
|
+
key = key.to_sym
|
331
|
+
|
332
|
+
if meta_data[key]
|
333
|
+
# If its a clash, merge with the existing data
|
334
|
+
meta_data[key].merge! value
|
335
|
+
else
|
336
|
+
# Add it as is if its not special
|
337
|
+
meta_data[key] = value
|
338
|
+
end
|
339
|
+
else
|
340
|
+
meta_data[:custom] ||= {}
|
341
|
+
meta_data[:custom][key] = value
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
def exception_list
|
346
|
+
@exceptions.map do |exception|
|
347
|
+
{
|
348
|
+
:errorClass => error_class(exception),
|
349
|
+
:message => exception.message,
|
350
|
+
:stacktrace => stacktrace(exception)
|
351
|
+
}
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
def error_class(exception)
|
356
|
+
# The "Class" check is for some strange exceptions like Timeout::Error
|
357
|
+
# which throw the error class instead of an instance
|
358
|
+
(exception.is_a? Class) ? exception.name : exception.class.name
|
359
|
+
end
|
360
|
+
|
361
|
+
def stacktrace(exception)
|
362
|
+
(exception.backtrace || caller).map do |trace|
|
363
|
+
|
364
|
+
if trace.match(BACKTRACE_LINE_REGEX)
|
365
|
+
file, line_str, method = [$1, $2, $3]
|
366
|
+
elsif trace.match(JAVA_BACKTRACE_REGEX)
|
367
|
+
method, file, line_str = [$1, $2, $3]
|
368
|
+
end
|
369
|
+
|
370
|
+
# Parse the stacktrace line
|
371
|
+
|
372
|
+
# Skip stacktrace lines inside lib/bugsnag
|
373
|
+
next(nil) if file.nil? || file =~ %r{lib/bugsnag(/|\.rb)}
|
374
|
+
|
375
|
+
# Expand relative paths
|
376
|
+
p = Pathname.new(file)
|
377
|
+
if p.relative?
|
378
|
+
file = p.realpath.to_s rescue file
|
379
|
+
end
|
380
|
+
|
381
|
+
# Generate the stacktrace line hash
|
382
|
+
trace_hash = {}
|
383
|
+
trace_hash[:inProject] = true if @configuration.project_root && file.match(/^#{@configuration.project_root}/) && !file.match(/vendor\//)
|
384
|
+
trace_hash[:lineNumber] = line_str.to_i
|
385
|
+
|
386
|
+
if @configuration.send_code
|
387
|
+
trace_hash[:code] = code(file, trace_hash[:lineNumber])
|
388
|
+
end
|
389
|
+
|
390
|
+
# Clean up the file path in the stacktrace
|
391
|
+
if defined?(Bugsnag.configuration.project_root) && Bugsnag.configuration.project_root.to_s != ''
|
392
|
+
file.sub!(/#{Bugsnag.configuration.project_root}\//, "")
|
393
|
+
end
|
394
|
+
|
395
|
+
# Strip common gem path prefixes
|
396
|
+
if defined?(Gem)
|
397
|
+
file = Gem.path.inject(file) {|line, path| line.sub(/#{path}\//, "") }
|
398
|
+
end
|
399
|
+
|
400
|
+
trace_hash[:file] = file
|
401
|
+
|
402
|
+
# Add a method if we have it
|
403
|
+
trace_hash[:method] = method if method && (method =~ /^__bind/).nil?
|
404
|
+
|
405
|
+
if trace_hash[:file] && !trace_hash[:file].empty?
|
406
|
+
trace_hash
|
407
|
+
else
|
408
|
+
nil
|
409
|
+
end
|
410
|
+
end.compact
|
411
|
+
end
|
412
|
+
|
413
|
+
def code(file, line_number, num_lines = 7)
|
414
|
+
code_hash = {}
|
415
|
+
|
416
|
+
from_line = [line_number - num_lines, 1].max
|
417
|
+
|
418
|
+
# don't try and open '(irb)' or '-e'
|
419
|
+
return unless File.exist?(file)
|
420
|
+
|
421
|
+
# Populate code hash with line numbers and code lines
|
422
|
+
File.open(file) do |f|
|
423
|
+
current_line_number = 0
|
424
|
+
f.each_line do |line|
|
425
|
+
current_line_number += 1
|
426
|
+
|
427
|
+
next if current_line_number < from_line
|
428
|
+
|
429
|
+
code_hash[current_line_number] = line[0...200].rstrip
|
430
|
+
|
431
|
+
break if code_hash.length >= ( num_lines * 1.5 ).ceil
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
while code_hash.length > num_lines
|
436
|
+
last_line = code_hash.keys.max
|
437
|
+
first_line = code_hash.keys.min
|
438
|
+
|
439
|
+
if (last_line - line_number) > (line_number - first_line)
|
440
|
+
code_hash.delete(last_line)
|
441
|
+
else
|
442
|
+
code_hash.delete(first_line)
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
code_hash
|
447
|
+
rescue
|
448
|
+
Bugsnag.warn("Error fetching code: #{$!.inspect}")
|
449
|
+
nil
|
450
|
+
end
|
451
|
+
end
|
452
|
+
end
|