bugsnag 5.5.0 → 6.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.
- 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
|