bugsnag 5.5.0 → 6.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +33 -11
- data/CHANGELOG.md +23 -0
- data/Gemfile +20 -0
- data/Rakefile +19 -12
- data/UPGRADING.md +58 -0
- data/VERSION +1 -1
- data/bugsnag.gemspec +0 -9
- data/lib/bugsnag.rb +64 -86
- data/lib/bugsnag/configuration.rb +42 -26
- data/lib/bugsnag/delivery.rb +9 -0
- data/lib/bugsnag/delivery/synchronous.rb +3 -5
- data/lib/bugsnag/delivery/thread_queue.rb +4 -2
- data/lib/bugsnag/helpers.rb +5 -16
- data/lib/bugsnag/{delayed_job.rb → integrations/delayed_job.rb} +15 -7
- data/lib/bugsnag/{mailman.rb → integrations/mailman.rb} +13 -11
- data/lib/bugsnag/{que.rb → integrations/que.rb} +11 -11
- data/lib/bugsnag/{rack.rb → integrations/rack.rb} +22 -23
- data/lib/bugsnag/{rails → integrations/rails}/active_record_rescue.rb +9 -7
- data/lib/bugsnag/{rails → integrations/rails}/controller_methods.rb +0 -9
- data/lib/bugsnag/{railtie.rb → integrations/railtie.rb} +24 -21
- data/lib/bugsnag/{rake.rb → integrations/rake.rb} +12 -9
- data/lib/bugsnag/{resque.rb → integrations/resque.rb} +12 -9
- data/lib/bugsnag/integrations/shoryuken.rb +49 -0
- data/lib/bugsnag/{sidekiq.rb → integrations/sidekiq.rb} +13 -9
- data/lib/bugsnag/middleware/callbacks.rb +4 -8
- data/lib/bugsnag/middleware/classify_error.rb +7 -13
- data/lib/bugsnag/middleware/clearance_user.rb +8 -8
- data/lib/bugsnag/middleware/exception_meta_data.rb +34 -0
- data/lib/bugsnag/middleware/ignore_error_class.rb +21 -0
- data/lib/bugsnag/middleware/mailman.rb +4 -4
- data/lib/bugsnag/middleware/rack_request.rb +13 -13
- data/lib/bugsnag/middleware/rails3_request.rb +10 -10
- data/lib/bugsnag/middleware/rake.rb +5 -5
- data/lib/bugsnag/middleware/sidekiq.rb +5 -5
- data/lib/bugsnag/middleware/suggestion_data.rb +30 -0
- data/lib/bugsnag/middleware/warden_user.rb +6 -6
- data/lib/bugsnag/middleware_stack.rb +5 -5
- data/lib/bugsnag/report.rb +187 -0
- data/lib/bugsnag/stacktrace.rb +113 -0
- data/lib/bugsnag/tasks/bugsnag.rake +2 -70
- data/spec/cleaner_spec.rb +6 -0
- data/spec/configuration_spec.rb +1 -1
- data/spec/fixtures/middleware/internal_info_setter.rb +3 -3
- data/spec/fixtures/middleware/public_info_setter.rb +3 -3
- data/spec/fixtures/tasks/Rakefile +2 -3
- data/spec/integration_spec.rb +5 -20
- data/spec/{delayed_job_spec.rb → integrations/delayed_job_spec.rb} +0 -0
- data/spec/integrations/sidekiq_spec.rb +34 -0
- data/spec/middleware_spec.rb +108 -35
- data/spec/rack_spec.rb +1 -1
- data/spec/{notification_spec.rb → report_spec.rb} +226 -209
- data/spec/spec_helper.rb +18 -0
- data/spec/{code_spec.rb → stacktrace_spec.rb} +1 -1
- metadata +23 -139
- data/.document +0 -5
- data/lib/bugsnag/capistrano.rb +0 -7
- data/lib/bugsnag/capistrano2.rb +0 -32
- data/lib/bugsnag/delay/resque.rb +0 -21
- data/lib/bugsnag/deploy.rb +0 -35
- data/lib/bugsnag/middleware/rails2_request.rb +0 -52
- data/lib/bugsnag/notification.rb +0 -506
- data/lib/bugsnag/rails.rb +0 -70
- data/lib/bugsnag/rails/action_controller_rescue.rb +0 -74
- data/lib/bugsnag/shoryuken.rb +0 -41
- data/lib/bugsnag/tasks/bugsnag.cap +0 -48
- data/rails/init.rb +0 -7
@@ -4,20 +4,20 @@ module Bugsnag::Middleware
|
|
4
4
|
@bugsnag = bugsnag
|
5
5
|
end
|
6
6
|
|
7
|
-
def call(
|
8
|
-
task =
|
7
|
+
def call(report)
|
8
|
+
task = report.request_data[:bugsnag_running_task]
|
9
9
|
|
10
10
|
if task
|
11
|
-
|
11
|
+
report.add_tab(:rake_task, {
|
12
12
|
:name => task.name,
|
13
13
|
:description => task.full_comment,
|
14
14
|
:arguments => task.arg_description
|
15
15
|
})
|
16
16
|
|
17
|
-
|
17
|
+
report.context ||= task.name
|
18
18
|
end
|
19
19
|
|
20
|
-
@bugsnag.call(
|
20
|
+
@bugsnag.call(report)
|
21
21
|
end
|
22
22
|
end
|
23
23
|
end
|
@@ -4,13 +4,13 @@ module Bugsnag::Middleware
|
|
4
4
|
@bugsnag = bugsnag
|
5
5
|
end
|
6
6
|
|
7
|
-
def call(
|
8
|
-
sidekiq =
|
7
|
+
def call(report)
|
8
|
+
sidekiq = report.request_data[:sidekiq]
|
9
9
|
if sidekiq
|
10
|
-
|
11
|
-
|
10
|
+
report.add_tab(:sidekiq, sidekiq)
|
11
|
+
report.context ||= "#{sidekiq[:msg]['wrapped'] || sidekiq[:msg]['class']}@#{sidekiq[:msg]['queue']}"
|
12
12
|
end
|
13
|
-
@bugsnag.call(
|
13
|
+
@bugsnag.call(report)
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Bugsnag::Middleware
|
2
|
+
class SuggestionData
|
3
|
+
|
4
|
+
CAPTURE_REGEX = /Did you mean\?([\s\S]+)$/
|
5
|
+
DELIMITER = "\n"
|
6
|
+
|
7
|
+
def initialize(bugsnag)
|
8
|
+
@bugsnag = bugsnag
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(report)
|
12
|
+
matches = []
|
13
|
+
report.raw_exceptions.each do |exception|
|
14
|
+
match = CAPTURE_REGEX.match(exception.message)
|
15
|
+
next unless match
|
16
|
+
|
17
|
+
suggestions = match.captures[0].split(DELIMITER)
|
18
|
+
matches.concat suggestions.map{ |suggestion| suggestion.strip }
|
19
|
+
end
|
20
|
+
|
21
|
+
if matches.size == 1
|
22
|
+
report.add_tab(:error, {:suggestion => matches.first})
|
23
|
+
elsif matches.size > 1
|
24
|
+
report.add_tab(:error, {:suggestions => matches})
|
25
|
+
end
|
26
|
+
|
27
|
+
@bugsnag.call(report)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -7,9 +7,9 @@ module Bugsnag::Middleware
|
|
7
7
|
@bugsnag = bugsnag
|
8
8
|
end
|
9
9
|
|
10
|
-
def call(
|
11
|
-
if
|
12
|
-
env =
|
10
|
+
def call(report)
|
11
|
+
if report.request_data[:rack_env] && report.request_data[:rack_env]["warden"]
|
12
|
+
env = report.request_data[:rack_env]
|
13
13
|
session = env["rack.session"] || {}
|
14
14
|
|
15
15
|
# Find all warden user scopes
|
@@ -29,11 +29,11 @@ module Bugsnag::Middleware
|
|
29
29
|
end
|
30
30
|
|
31
31
|
# We merge the first warden scope down, so that it is the main "user" for the request
|
32
|
-
|
32
|
+
report.user = user unless user.empty?
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
-
@bugsnag.call(
|
36
|
+
@bugsnag.call(report)
|
37
37
|
end
|
38
38
|
end
|
39
|
-
end
|
39
|
+
end
|
@@ -63,7 +63,7 @@ module Bugsnag
|
|
63
63
|
end
|
64
64
|
|
65
65
|
# Runs the middleware stack and calls
|
66
|
-
def run(
|
66
|
+
def run(report)
|
67
67
|
# The final lambda is the termination of the middleware stack. It calls deliver on the notification
|
68
68
|
lambda_has_run = false
|
69
69
|
notify_lambda = lambda do |notif|
|
@@ -73,19 +73,19 @@ module Bugsnag
|
|
73
73
|
|
74
74
|
begin
|
75
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(
|
76
|
+
middleware_procs.reverse.inject(notify_lambda) { |n,e| e.call(n) }.call(report)
|
77
77
|
rescue StandardError => e
|
78
78
|
# KLUDGE: Since we don't re-raise middleware exceptions, this breaks rspec
|
79
79
|
raise if e.class.to_s == "RSpec::Expectations::ExpectationNotMetError"
|
80
80
|
|
81
81
|
# We dont notify, as we dont want to loop forever in the case of really broken middleware, we will
|
82
82
|
# still send this notify
|
83
|
-
Bugsnag.warn "Bugsnag middleware error: #{e}"
|
84
|
-
Bugsnag.
|
83
|
+
Bugsnag.configuration.warn "Bugsnag middleware error: #{e}"
|
84
|
+
Bugsnag.configuration.warn "Middleware error stacktrace: #{e.backtrace.inspect}"
|
85
85
|
end
|
86
86
|
|
87
87
|
# Ensure that the deliver has been performed, and no middleware has botched it
|
88
|
-
notify_lambda.call(
|
88
|
+
notify_lambda.call(report) unless lambda_has_run
|
89
89
|
end
|
90
90
|
|
91
91
|
private
|
@@ -0,0 +1,187 @@
|
|
1
|
+
require "json"
|
2
|
+
require "pathname"
|
3
|
+
require "bugsnag/stacktrace"
|
4
|
+
|
5
|
+
module Bugsnag
|
6
|
+
class Report
|
7
|
+
NOTIFIER_NAME = "Ruby Bugsnag Notifier"
|
8
|
+
NOTIFIER_VERSION = Bugsnag::VERSION
|
9
|
+
NOTIFIER_URL = "http://www.bugsnag.com"
|
10
|
+
|
11
|
+
UNHANDLED_EXCEPTION = "unhandledException"
|
12
|
+
UNHANDLED_EXCEPTION_MIDDLEWARE = "unhandledExceptionMiddleware"
|
13
|
+
ERROR_CLASS = "errorClass"
|
14
|
+
HANDLED_EXCEPTION = "handledException"
|
15
|
+
USER_SPECIFIED_SEVERITY = "userSpecifiedSeverity"
|
16
|
+
USER_CALLBACK_SET_SEVERITY = "userCallbackSetSeverity"
|
17
|
+
|
18
|
+
MAX_EXCEPTIONS_TO_UNWRAP = 5
|
19
|
+
|
20
|
+
CURRENT_PAYLOAD_VERSION = "2"
|
21
|
+
|
22
|
+
attr_accessor :api_key
|
23
|
+
attr_accessor :app_type
|
24
|
+
attr_accessor :app_version
|
25
|
+
attr_accessor :configuration
|
26
|
+
attr_accessor :context
|
27
|
+
attr_accessor :delivery_method
|
28
|
+
attr_accessor :exceptions
|
29
|
+
attr_accessor :hostname
|
30
|
+
attr_accessor :grouping_hash
|
31
|
+
attr_accessor :meta_data
|
32
|
+
attr_accessor :raw_exceptions
|
33
|
+
attr_accessor :release_stage
|
34
|
+
attr_accessor :severity
|
35
|
+
attr_accessor :severity_reason
|
36
|
+
attr_accessor :user
|
37
|
+
|
38
|
+
def initialize(exception, passed_configuration, auto_notify=false)
|
39
|
+
@should_ignore = false
|
40
|
+
@unhandled = auto_notify
|
41
|
+
|
42
|
+
self.configuration = passed_configuration
|
43
|
+
|
44
|
+
self.raw_exceptions = generate_raw_exceptions(exception)
|
45
|
+
self.exceptions = generate_exception_list
|
46
|
+
|
47
|
+
self.api_key = configuration.api_key
|
48
|
+
self.app_type = configuration.app_type
|
49
|
+
self.app_version = configuration.app_version
|
50
|
+
self.delivery_method = configuration.delivery_method
|
51
|
+
self.hostname = configuration.hostname
|
52
|
+
self.meta_data = {}
|
53
|
+
self.release_stage = configuration.release_stage
|
54
|
+
self.severity = auto_notify ? "error" : "warning"
|
55
|
+
self.severity_reason = auto_notify ? {:type => UNHANDLED_EXCEPTION} : {:type => HANDLED_EXCEPTION}
|
56
|
+
self.user = {}
|
57
|
+
end
|
58
|
+
|
59
|
+
# Add a new tab to this notification
|
60
|
+
def add_tab(name, value)
|
61
|
+
return if name.nil?
|
62
|
+
|
63
|
+
if value.is_a? Hash
|
64
|
+
meta_data[name] ||= {}
|
65
|
+
meta_data[name].merge! value
|
66
|
+
else
|
67
|
+
meta_data["custom"] = {} unless meta_data["custom"]
|
68
|
+
|
69
|
+
meta_data["custom"][name.to_s] = value
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Remove a tab from this notification
|
74
|
+
def remove_tab(name)
|
75
|
+
return if name.nil?
|
76
|
+
|
77
|
+
meta_data.delete(name)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Build an exception payload
|
81
|
+
def as_json
|
82
|
+
# Build the payload's exception event
|
83
|
+
payload_event = {
|
84
|
+
app: {
|
85
|
+
version: app_version,
|
86
|
+
releaseStage: release_stage,
|
87
|
+
type: app_type
|
88
|
+
},
|
89
|
+
context: context,
|
90
|
+
device: {
|
91
|
+
hostname: hostname
|
92
|
+
},
|
93
|
+
exceptions: exceptions,
|
94
|
+
groupingHash: grouping_hash,
|
95
|
+
payloadVersion: CURRENT_PAYLOAD_VERSION,
|
96
|
+
severity: severity,
|
97
|
+
severityReason: severity_reason,
|
98
|
+
unhandled: @unhandled,
|
99
|
+
user: user
|
100
|
+
}
|
101
|
+
|
102
|
+
# cleanup character encodings
|
103
|
+
payload_event = Bugsnag::Cleaner.clean_object_encoding(payload_event)
|
104
|
+
|
105
|
+
# filter out sensitive values in (and cleanup encodings) metaData
|
106
|
+
payload_event[:metaData] = Bugsnag::Cleaner.new(configuration.meta_data_filters).clean_object(meta_data)
|
107
|
+
payload_event.reject! {|k,v| v.nil? }
|
108
|
+
|
109
|
+
# return the payload hash
|
110
|
+
{
|
111
|
+
:apiKey => api_key,
|
112
|
+
:notifier => {
|
113
|
+
:name => NOTIFIER_NAME,
|
114
|
+
:version => NOTIFIER_VERSION,
|
115
|
+
:url => NOTIFIER_URL
|
116
|
+
},
|
117
|
+
:events => [payload_event]
|
118
|
+
}
|
119
|
+
end
|
120
|
+
|
121
|
+
def ignore?
|
122
|
+
@should_ignore
|
123
|
+
end
|
124
|
+
|
125
|
+
def request_data
|
126
|
+
configuration.request_data
|
127
|
+
end
|
128
|
+
|
129
|
+
def ignore!
|
130
|
+
@should_ignore = true
|
131
|
+
end
|
132
|
+
|
133
|
+
private
|
134
|
+
|
135
|
+
def generate_exception_list
|
136
|
+
raw_exceptions.map do |exception|
|
137
|
+
{
|
138
|
+
errorClass: error_class(exception),
|
139
|
+
message: exception.message,
|
140
|
+
stacktrace: Stacktrace.new(exception.backtrace, configuration).to_a
|
141
|
+
}
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def error_class(exception)
|
146
|
+
# The "Class" check is for some strange exceptions like Timeout::Error
|
147
|
+
# which throw the error class instead of an instance
|
148
|
+
(exception.is_a? Class) ? exception.name : exception.class.name
|
149
|
+
end
|
150
|
+
|
151
|
+
def generate_raw_exceptions(exception)
|
152
|
+
exceptions = []
|
153
|
+
|
154
|
+
ex = exception
|
155
|
+
while ex != nil && !exceptions.include?(ex) && exceptions.length < MAX_EXCEPTIONS_TO_UNWRAP
|
156
|
+
|
157
|
+
unless ex.is_a? Exception
|
158
|
+
if ex.respond_to?(:to_exception)
|
159
|
+
ex = ex.to_exception
|
160
|
+
elsif ex.respond_to?(:exception)
|
161
|
+
ex = ex.exception
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
unless ex.is_a?(Exception) || (defined?(Java::JavaLang::Throwable) && ex.is_a?(Java::JavaLang::Throwable))
|
166
|
+
configuration.warn("Converting non-Exception to RuntimeError: #{ex.inspect}")
|
167
|
+
ex = RuntimeError.new(ex.to_s)
|
168
|
+
ex.set_backtrace caller
|
169
|
+
end
|
170
|
+
|
171
|
+
exceptions << ex
|
172
|
+
|
173
|
+
if ex.respond_to?(:cause) && ex.cause
|
174
|
+
ex = ex.cause
|
175
|
+
elsif ex.respond_to?(:continued_exception) && ex.continued_exception
|
176
|
+
ex = ex.continued_exception
|
177
|
+
elsif ex.respond_to?(:original_exception) && ex.original_exception
|
178
|
+
ex = ex.original_exception
|
179
|
+
else
|
180
|
+
ex = nil
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
exceptions
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module Bugsnag
|
2
|
+
class Stacktrace
|
3
|
+
|
4
|
+
# e.g. "org/jruby/RubyKernel.java:1264:in `catch'"
|
5
|
+
BACKTRACE_LINE_REGEX = /^((?:[a-zA-Z]:)?[^:]+):(\d+)(?::in `([^']+)')?$/
|
6
|
+
|
7
|
+
# e.g. "org.jruby.Ruby.runScript(Ruby.java:807)"
|
8
|
+
JAVA_BACKTRACE_REGEX = /^(.*)\((.*)(?::([0-9]+))?\)$/
|
9
|
+
|
10
|
+
def initialize(backtrace, configuration)
|
11
|
+
@configuration = configuration
|
12
|
+
|
13
|
+
backtrace = caller if !backtrace || backtrace.empty?
|
14
|
+
@processed_backtrace = backtrace.map do |trace|
|
15
|
+
if trace.match(BACKTRACE_LINE_REGEX)
|
16
|
+
file, line_str, method = [$1, $2, $3]
|
17
|
+
elsif trace.match(JAVA_BACKTRACE_REGEX)
|
18
|
+
method, file, line_str = [$1, $2, $3]
|
19
|
+
end
|
20
|
+
|
21
|
+
# Parse the stacktrace line
|
22
|
+
|
23
|
+
# Skip stacktrace lines inside lib/bugsnag
|
24
|
+
next(nil) if file.nil? || file =~ %r{lib/bugsnag(/|\.rb)}
|
25
|
+
|
26
|
+
# Expand relative paths
|
27
|
+
p = Pathname.new(file)
|
28
|
+
if p.relative?
|
29
|
+
file = p.realpath.to_s rescue file
|
30
|
+
end
|
31
|
+
|
32
|
+
# Generate the stacktrace line hash
|
33
|
+
trace_hash = {}
|
34
|
+
trace_hash[:inProject] = true if in_project?(file)
|
35
|
+
trace_hash[:lineNumber] = line_str.to_i
|
36
|
+
|
37
|
+
if configuration.send_code
|
38
|
+
trace_hash[:code] = code(file, trace_hash[:lineNumber])
|
39
|
+
end
|
40
|
+
|
41
|
+
# Clean up the file path in the stacktrace
|
42
|
+
if defined?(@configuration.project_root) && @configuration.project_root.to_s != ''
|
43
|
+
file.sub!(/#{@configuration.project_root}\//, "")
|
44
|
+
end
|
45
|
+
|
46
|
+
# Strip common gem path prefixes
|
47
|
+
if defined?(Gem)
|
48
|
+
file = Gem.path.inject(file) {|line, path| line.sub(/#{path}\//, "") }
|
49
|
+
end
|
50
|
+
|
51
|
+
trace_hash[:file] = file
|
52
|
+
|
53
|
+
# Add a method if we have it
|
54
|
+
trace_hash[:method] = method if method && (method =~ /^__bind/).nil?
|
55
|
+
|
56
|
+
if trace_hash[:file] && !trace_hash[:file].empty?
|
57
|
+
trace_hash
|
58
|
+
else
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
end.compact
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_a
|
65
|
+
@processed_backtrace
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def in_project?(line)
|
71
|
+
@configuration.project_root && line.start_with?(@configuration.project_root.to_s)
|
72
|
+
end
|
73
|
+
|
74
|
+
def code(file, line_number, num_lines = 7)
|
75
|
+
code_hash = {}
|
76
|
+
|
77
|
+
from_line = [line_number - num_lines, 1].max
|
78
|
+
|
79
|
+
# don't try and open '(irb)' or '-e'
|
80
|
+
return unless File.exist?(file)
|
81
|
+
|
82
|
+
# Populate code hash with line numbers and code lines
|
83
|
+
File.open(file) do |f|
|
84
|
+
current_line_number = 0
|
85
|
+
f.each_line do |line|
|
86
|
+
current_line_number += 1
|
87
|
+
|
88
|
+
next if current_line_number < from_line
|
89
|
+
|
90
|
+
code_hash[current_line_number] = line[0...200].rstrip
|
91
|
+
|
92
|
+
break if code_hash.length >= ( num_lines * 1.5 ).ceil
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
while code_hash.length > num_lines
|
97
|
+
last_line = code_hash.keys.max
|
98
|
+
first_line = code_hash.keys.min
|
99
|
+
|
100
|
+
if (last_line - line_number) > (line_number - first_line)
|
101
|
+
code_hash.delete(last_line)
|
102
|
+
else
|
103
|
+
code_hash.delete(first_line)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
code_hash
|
108
|
+
rescue
|
109
|
+
@configuration.warn("Error fetching code: #{$!.inspect}")
|
110
|
+
nil
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -1,82 +1,14 @@
|
|
1
1
|
require "bugsnag"
|
2
2
|
|
3
3
|
namespace :bugsnag do
|
4
|
-
desc "Notify Bugsnag of a new deploy."
|
5
|
-
task :deploy do
|
6
|
-
api_key = ENV["BUGSNAG_API_KEY"]
|
7
|
-
release_stage = ENV["BUGSNAG_RELEASE_STAGE"]
|
8
|
-
app_version = ENV["BUGSNAG_APP_VERSION"]
|
9
|
-
revision = ENV["BUGSNAG_REVISION"]
|
10
|
-
repository = ENV["BUGSNAG_REPOSITORY"]
|
11
|
-
branch = ENV["BUGSNAG_BRANCH"]
|
12
|
-
|
13
|
-
Rake::Task["load"].invoke unless api_key
|
14
|
-
|
15
|
-
Bugsnag::Deploy.notify({
|
16
|
-
:api_key => api_key,
|
17
|
-
:release_stage => release_stage,
|
18
|
-
:app_version => app_version,
|
19
|
-
:revision => revision,
|
20
|
-
:repository => repository,
|
21
|
-
:branch => branch
|
22
|
-
})
|
23
|
-
end
|
24
|
-
|
25
4
|
desc "Send a test exception to Bugsnag."
|
26
5
|
task :test_exception => :load do
|
27
6
|
begin
|
28
7
|
raise RuntimeError.new("Bugsnag test exception")
|
29
8
|
rescue => e
|
30
|
-
Bugsnag.notify(e
|
31
|
-
|
32
|
-
end
|
33
|
-
|
34
|
-
desc "Show the bugsnag middleware stack"
|
35
|
-
task :middleware => :load do
|
36
|
-
Bugsnag.configuration.middleware.each {|m| puts m.to_s}
|
37
|
-
end
|
38
|
-
|
39
|
-
namespace :heroku do
|
40
|
-
desc "Add a heroku deploy hook to notify Bugsnag of deploys"
|
41
|
-
task :add_deploy_hook => :load do
|
42
|
-
# Wrapper to run command safely even in bundler
|
43
|
-
run_command = lambda { |command|
|
44
|
-
defined?(Bundler.with_clean_env) ? Bundler.with_clean_env { `#{command}` } : `#{command}`
|
45
|
-
}
|
46
|
-
|
47
|
-
# Fetch heroku config settings
|
48
|
-
config_command = "heroku config --shell"
|
49
|
-
config_command += " --app #{ENV["HEROKU_APP"]}" if ENV["HEROKU_APP"]
|
50
|
-
heroku_env = run_command.call(config_command).split(/[\n\r]/).each_with_object({}) do |c, obj|
|
51
|
-
k,v = c.split("=")
|
52
|
-
obj[k] = (v.nil? || v.strip.empty?) ? nil : v
|
53
|
-
end
|
54
|
-
|
55
|
-
# Check for Bugsnag API key (required)
|
56
|
-
api_key = heroku_env["BUGSNAG_API_KEY"] || Bugsnag.configuration.api_key || ENV["BUGSNAG_API_KEY"]
|
57
|
-
unless api_key
|
58
|
-
puts "Error: No API key found, have you run 'heroku config:set BUGSNAG_API_KEY=your-api-key'?"
|
59
|
-
next
|
9
|
+
Bugsnag.notify(e) do |report|
|
10
|
+
report.context = "rake#test_exception"
|
60
11
|
end
|
61
|
-
|
62
|
-
# Build the request, making use of deploy hook variables
|
63
|
-
# (https://devcenter.heroku.com/articles/deploy-hooks#customizing-messages)
|
64
|
-
params = {
|
65
|
-
:apiKey => api_key,
|
66
|
-
:branch => "master",
|
67
|
-
:revision => "{{head_long}}",
|
68
|
-
:releaseStage => heroku_env["RAILS_ENV"] || ENV["RAILS_ENV"] || "production"
|
69
|
-
}
|
70
|
-
repo = `git config --get remote.origin.url`.strip
|
71
|
-
params[:repository] = repo unless repo.empty?
|
72
|
-
|
73
|
-
# Add the hook
|
74
|
-
url = "https://notify.bugsnag.com/deploy?" + params.map {|k,v| "#{k}=#{v}"}.join("&")
|
75
|
-
command = "heroku addons:add deployhooks:http --url=\"#{url}\""
|
76
|
-
command += " --app #{ENV["HEROKU_APP"]}" if ENV["HEROKU_APP"]
|
77
|
-
|
78
|
-
puts "$ #{command}"
|
79
|
-
run_command.call(command)
|
80
12
|
end
|
81
13
|
end
|
82
14
|
end
|