bugsnag 5.5.0 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +33 -11
  3. data/CHANGELOG.md +23 -0
  4. data/Gemfile +20 -0
  5. data/Rakefile +19 -12
  6. data/UPGRADING.md +58 -0
  7. data/VERSION +1 -1
  8. data/bugsnag.gemspec +0 -9
  9. data/lib/bugsnag.rb +64 -86
  10. data/lib/bugsnag/configuration.rb +42 -26
  11. data/lib/bugsnag/delivery.rb +9 -0
  12. data/lib/bugsnag/delivery/synchronous.rb +3 -5
  13. data/lib/bugsnag/delivery/thread_queue.rb +4 -2
  14. data/lib/bugsnag/helpers.rb +5 -16
  15. data/lib/bugsnag/{delayed_job.rb → integrations/delayed_job.rb} +15 -7
  16. data/lib/bugsnag/{mailman.rb → integrations/mailman.rb} +13 -11
  17. data/lib/bugsnag/{que.rb → integrations/que.rb} +11 -11
  18. data/lib/bugsnag/{rack.rb → integrations/rack.rb} +22 -23
  19. data/lib/bugsnag/{rails → integrations/rails}/active_record_rescue.rb +9 -7
  20. data/lib/bugsnag/{rails → integrations/rails}/controller_methods.rb +0 -9
  21. data/lib/bugsnag/{railtie.rb → integrations/railtie.rb} +24 -21
  22. data/lib/bugsnag/{rake.rb → integrations/rake.rb} +12 -9
  23. data/lib/bugsnag/{resque.rb → integrations/resque.rb} +12 -9
  24. data/lib/bugsnag/integrations/shoryuken.rb +49 -0
  25. data/lib/bugsnag/{sidekiq.rb → integrations/sidekiq.rb} +13 -9
  26. data/lib/bugsnag/middleware/callbacks.rb +4 -8
  27. data/lib/bugsnag/middleware/classify_error.rb +7 -13
  28. data/lib/bugsnag/middleware/clearance_user.rb +8 -8
  29. data/lib/bugsnag/middleware/exception_meta_data.rb +34 -0
  30. data/lib/bugsnag/middleware/ignore_error_class.rb +21 -0
  31. data/lib/bugsnag/middleware/mailman.rb +4 -4
  32. data/lib/bugsnag/middleware/rack_request.rb +13 -13
  33. data/lib/bugsnag/middleware/rails3_request.rb +10 -10
  34. data/lib/bugsnag/middleware/rake.rb +5 -5
  35. data/lib/bugsnag/middleware/sidekiq.rb +5 -5
  36. data/lib/bugsnag/middleware/suggestion_data.rb +30 -0
  37. data/lib/bugsnag/middleware/warden_user.rb +6 -6
  38. data/lib/bugsnag/middleware_stack.rb +5 -5
  39. data/lib/bugsnag/report.rb +187 -0
  40. data/lib/bugsnag/stacktrace.rb +113 -0
  41. data/lib/bugsnag/tasks/bugsnag.rake +2 -70
  42. data/spec/cleaner_spec.rb +6 -0
  43. data/spec/configuration_spec.rb +1 -1
  44. data/spec/fixtures/middleware/internal_info_setter.rb +3 -3
  45. data/spec/fixtures/middleware/public_info_setter.rb +3 -3
  46. data/spec/fixtures/tasks/Rakefile +2 -3
  47. data/spec/integration_spec.rb +5 -20
  48. data/spec/{delayed_job_spec.rb → integrations/delayed_job_spec.rb} +0 -0
  49. data/spec/integrations/sidekiq_spec.rb +34 -0
  50. data/spec/middleware_spec.rb +108 -35
  51. data/spec/rack_spec.rb +1 -1
  52. data/spec/{notification_spec.rb → report_spec.rb} +226 -209
  53. data/spec/spec_helper.rb +18 -0
  54. data/spec/{code_spec.rb → stacktrace_spec.rb} +1 -1
  55. metadata +23 -139
  56. data/.document +0 -5
  57. data/lib/bugsnag/capistrano.rb +0 -7
  58. data/lib/bugsnag/capistrano2.rb +0 -32
  59. data/lib/bugsnag/delay/resque.rb +0 -21
  60. data/lib/bugsnag/deploy.rb +0 -35
  61. data/lib/bugsnag/middleware/rails2_request.rb +0 -52
  62. data/lib/bugsnag/notification.rb +0 -506
  63. data/lib/bugsnag/rails.rb +0 -70
  64. data/lib/bugsnag/rails/action_controller_rescue.rb +0 -74
  65. data/lib/bugsnag/shoryuken.rb +0 -41
  66. data/lib/bugsnag/tasks/bugsnag.cap +0 -48
  67. data/rails/init.rb +0 -7
@@ -1,3 +1,13 @@
1
+ if ARGV.include? "--coverage"
2
+ require 'simplecov'
3
+ require 'coveralls'
4
+
5
+ SimpleCov.formatter = Coveralls::SimpleCov::Formatter
6
+ SimpleCov.start do
7
+ add_filter 'spec'
8
+ end
9
+ end
10
+
1
11
  require 'bugsnag'
2
12
 
3
13
  require 'webmock/rspec'
@@ -20,6 +30,14 @@ def notify_test_exception(*args)
20
30
  Bugsnag.notify(RuntimeError.new("test message"), *args)
21
31
  end
22
32
 
33
+ def ruby_version_greater_equal?(version)
34
+ current_version = RUBY_VERSION.split "."
35
+ target_version = version.split "."
36
+ (Integer(current_version[0]) >= Integer(target_version[0])) &&
37
+ (Integer(current_version[1]) >= Integer(target_version[1])) &&
38
+ (Integer(current_version[2]) >= Integer(target_version[2]))
39
+ end
40
+
23
41
  RSpec.configure do |config|
24
42
  config.order = "random"
25
43
 
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Bugsnag::Notification do
3
+ describe Bugsnag::Stacktrace do
4
4
  it "includes code in the stack trace" do
5
5
  _a = 1
6
6
  _b = 2
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bugsnag
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.5.0
4
+ version: 6.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Smith
@@ -9,119 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
  date: 2017-11-09 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: rake
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: 10.1.1
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: 10.1.1
27
- - !ruby/object:Gem::Dependency
28
- name: rspec
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: rdoc
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: pry
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
69
- - !ruby/object:Gem::Dependency
70
- name: addressable
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: 2.3.8
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: 2.3.8
83
- - !ruby/object:Gem::Dependency
84
- name: webmock
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - '='
88
- - !ruby/object:Gem::Version
89
- version: 2.1.0
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - '='
95
- - !ruby/object:Gem::Version
96
- version: 2.1.0
97
- - !ruby/object:Gem::Dependency
98
- name: delayed_job
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - ">="
102
- - !ruby/object:Gem::Version
103
- version: '0'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - ">="
109
- - !ruby/object:Gem::Version
110
- version: '0'
111
- - !ruby/object:Gem::Dependency
112
- name: activesupport
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - "~>"
116
- - !ruby/object:Gem::Version
117
- version: 4.2.10
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - "~>"
123
- - !ruby/object:Gem::Version
124
- version: 4.2.10
12
+ dependencies: []
125
13
  description: Ruby notifier for bugsnag.com
126
14
  email: james@bugsnag.com
127
15
  executables: []
@@ -131,7 +19,6 @@ extra_rdoc_files:
131
19
  - README.md
132
20
  - CHANGELOG.md
133
21
  files:
134
- - ".document"
135
22
  - ".gitignore"
136
23
  - ".rspec"
137
24
  - ".travis.yml"
@@ -141,56 +28,50 @@ files:
141
28
  - LICENSE.txt
142
29
  - README.md
143
30
  - Rakefile
31
+ - UPGRADING.md
144
32
  - VERSION
145
33
  - bugsnag.gemspec
146
34
  - issue_template.md
147
35
  - lib/bugsnag.rb
148
- - lib/bugsnag/capistrano.rb
149
- - lib/bugsnag/capistrano2.rb
150
36
  - lib/bugsnag/cleaner.rb
151
37
  - lib/bugsnag/configuration.rb
152
- - lib/bugsnag/delay/resque.rb
153
- - lib/bugsnag/delayed_job.rb
154
38
  - lib/bugsnag/delivery.rb
155
39
  - lib/bugsnag/delivery/synchronous.rb
156
40
  - lib/bugsnag/delivery/thread_queue.rb
157
- - lib/bugsnag/deploy.rb
158
41
  - lib/bugsnag/helpers.rb
159
- - lib/bugsnag/mailman.rb
42
+ - lib/bugsnag/integrations/delayed_job.rb
43
+ - lib/bugsnag/integrations/mailman.rb
44
+ - lib/bugsnag/integrations/que.rb
45
+ - lib/bugsnag/integrations/rack.rb
46
+ - lib/bugsnag/integrations/rails/active_record_rescue.rb
47
+ - lib/bugsnag/integrations/rails/controller_methods.rb
48
+ - lib/bugsnag/integrations/railtie.rb
49
+ - lib/bugsnag/integrations/rake.rb
50
+ - lib/bugsnag/integrations/resque.rb
51
+ - lib/bugsnag/integrations/shoryuken.rb
52
+ - lib/bugsnag/integrations/sidekiq.rb
160
53
  - lib/bugsnag/meta_data.rb
161
54
  - lib/bugsnag/middleware/callbacks.rb
162
55
  - lib/bugsnag/middleware/classify_error.rb
163
56
  - lib/bugsnag/middleware/clearance_user.rb
57
+ - lib/bugsnag/middleware/exception_meta_data.rb
58
+ - lib/bugsnag/middleware/ignore_error_class.rb
164
59
  - lib/bugsnag/middleware/mailman.rb
165
60
  - lib/bugsnag/middleware/rack_request.rb
166
- - lib/bugsnag/middleware/rails2_request.rb
167
61
  - lib/bugsnag/middleware/rails3_request.rb
168
62
  - lib/bugsnag/middleware/rake.rb
169
63
  - lib/bugsnag/middleware/sidekiq.rb
64
+ - lib/bugsnag/middleware/suggestion_data.rb
170
65
  - lib/bugsnag/middleware/warden_user.rb
171
66
  - lib/bugsnag/middleware_stack.rb
172
- - lib/bugsnag/notification.rb
173
- - lib/bugsnag/que.rb
174
- - lib/bugsnag/rack.rb
175
- - lib/bugsnag/rails.rb
176
- - lib/bugsnag/rails/action_controller_rescue.rb
177
- - lib/bugsnag/rails/active_record_rescue.rb
178
- - lib/bugsnag/rails/controller_methods.rb
179
- - lib/bugsnag/railtie.rb
180
- - lib/bugsnag/rake.rb
181
- - lib/bugsnag/resque.rb
182
- - lib/bugsnag/shoryuken.rb
183
- - lib/bugsnag/sidekiq.rb
67
+ - lib/bugsnag/report.rb
68
+ - lib/bugsnag/stacktrace.rb
184
69
  - lib/bugsnag/tasks.rb
185
- - lib/bugsnag/tasks/bugsnag.cap
186
70
  - lib/bugsnag/tasks/bugsnag.rake
187
71
  - lib/bugsnag/version.rb
188
72
  - lib/generators/bugsnag/bugsnag_generator.rb
189
- - rails/init.rb
190
73
  - spec/cleaner_spec.rb
191
- - spec/code_spec.rb
192
74
  - spec/configuration_spec.rb
193
- - spec/delayed_job_spec.rb
194
75
  - spec/fixtures/crashes/end_of_file.rb
195
76
  - spec/fixtures/crashes/short_file.rb
196
77
  - spec/fixtures/crashes/start_of_file.rb
@@ -199,11 +80,14 @@ files:
199
80
  - spec/fixtures/tasks/Rakefile
200
81
  - spec/helper_spec.rb
201
82
  - spec/integration_spec.rb
83
+ - spec/integrations/delayed_job_spec.rb
84
+ - spec/integrations/sidekiq_spec.rb
202
85
  - spec/middleware_spec.rb
203
- - spec/notification_spec.rb
204
86
  - spec/rack_spec.rb
205
87
  - spec/rails3_request_spec.rb
88
+ - spec/report_spec.rb
206
89
  - spec/spec_helper.rb
90
+ - spec/stacktrace_spec.rb
207
91
  homepage: http://github.com/bugsnag/bugsnag-ruby
208
92
  licenses:
209
93
  - MIT
data/.document DELETED
@@ -1,5 +0,0 @@
1
- lib/**/*.rb
2
- bin/*
3
- -
4
- features/**/*.feature
5
- LICENSE.txt
@@ -1,7 +0,0 @@
1
- require "bugsnag"
2
-
3
- if defined?(Capistrano::VERSION) && Gem::Version.new(Capistrano::VERSION).release >= Gem::Version.new('3.0.0')
4
- load File.expand_path('../tasks/bugsnag.cap', __FILE__)
5
- else
6
- require_relative 'capistrano2'
7
- end
@@ -1,32 +0,0 @@
1
- module Bugsnag
2
- module Capistrano
3
- def self.load_into(configuration)
4
- configuration.load do
5
- after "deploy", "bugsnag:deploy"
6
- after "deploy:migrations", "bugsnag:deploy"
7
-
8
- namespace :bugsnag do
9
- desc "Notify Bugsnag that new production code has been deployed"
10
- task :deploy, :except => { :no_release => true }, :on_error => :continue do
11
- begin
12
- Bugsnag::Deploy.notify({
13
- :api_key => fetch(:bugsnag_api_key, ENV["BUGSNAG_API_KEY"]),
14
- :release_stage => ENV["BUGSNAG_RELEASE_STAGE"] || fetch(:rails_env, "production"),
15
- :revision => fetch(:current_revision, ENV["BUGSNAG_REVISION"]),
16
- :repository => fetch(:repository, ENV["BUGSNAG_REPOSITORY"]),
17
- :branch => fetch(:branch, ENV["BUGSNAG_BRANCH"],
18
- :app_version => fetch(:app_version, ENV["BUGSNAG_APP_VERSION"]))
19
- })
20
- rescue
21
- logger.important("Bugsnag deploy notification failed, #{$!.inspect}")
22
- end
23
-
24
- logger.info "Bugsnag deploy notification complete."
25
- end
26
- end
27
- end
28
- end
29
- end
30
- end
31
-
32
- Bugsnag::Capistrano.load_into(Capistrano::Configuration.instance) if Capistrano::Configuration.instance
@@ -1,21 +0,0 @@
1
- module Bugsnag
2
- module Delay
3
- class Resque
4
- @queue = "bugsnag"
5
- def self.perform(*args)
6
- Bugsnag::Notification.deliver_exception_payload_without_resque(*args)
7
- end
8
- end
9
- end
10
- end
11
-
12
- Bugsnag::Notification.class_eval do
13
- class << self
14
- def deliver_exception_payload_with_resque(*args)
15
- Resque.enqueue(Bugsnag::Delay::Resque, *args)
16
- end
17
-
18
- alias_method :deliver_exception_payload_without_resque, :deliver_exception_payload
19
- alias_method :deliver_exception_payload, :deliver_exception_payload_with_resque
20
- end
21
- end
@@ -1,35 +0,0 @@
1
- require "json"
2
-
3
- module Bugsnag
4
- class Deploy
5
- def self.notify(opts = {})
6
-
7
- configuration = Bugsnag.configuration.dup
8
-
9
- # update configuration based on parameters passed in
10
- [:api_key, :app_version, :release_stage, :endpoint, :use_ssl,
11
- :proxy_host, :proxy_port, :proxy_user, :proxy_password].each do |param|
12
- unless opts[param].nil?
13
- configuration.send :"#{param}=", opts[param]
14
- end
15
- end
16
-
17
- endpoint = (configuration.use_ssl ? "https://" : "http://") + configuration.endpoint + "/deploy"
18
-
19
- parameters = {
20
- "apiKey" => configuration.api_key,
21
- "releaseStage" => configuration.release_stage,
22
- "appVersion" => configuration.app_version,
23
- "revision" => opts[:revision],
24
- "repository" => opts[:repository],
25
- "branch" => opts[:branch],
26
- "provider" => opts[:provider]
27
- }.reject {|k,v| v == nil}
28
-
29
- raise RuntimeError.new("No API key found when notifying of deploy") if !parameters["apiKey"] || parameters["apiKey"].empty?
30
-
31
- payload_string = ::JSON.dump(parameters)
32
- Bugsnag::Delivery::Synchronous.deliver(endpoint, payload_string, configuration)
33
- end
34
- end
35
- end
@@ -1,52 +0,0 @@
1
- module Bugsnag::Middleware
2
- class Rails2Request
3
- def initialize(bugsnag)
4
- @bugsnag = bugsnag
5
- end
6
-
7
- def call(notification)
8
- if notification.request_data[:rails2_request]
9
- request = notification.request_data[:rails2_request]
10
- params = request.parameters || {}
11
- session_data = request.session.respond_to?(:to_hash) ? request.session.to_hash : request.session.data
12
-
13
- # Set the context
14
- notification.context = "#{params[:controller]}##{params[:action]}"
15
-
16
- # Set a sensible default for user_id
17
- notification.user_id = request.remote_ip if request.respond_to?(:remote_ip)
18
-
19
- # Build the clean url
20
- url = "#{request.protocol}#{request.host}"
21
- url << ":#{request.port}" unless [80, 443].include?(request.port)
22
- url << Bugsnag::Cleaner.new(notification.configuration.params_filters).clean_url(request.fullpath)
23
-
24
- # Add a request tab
25
- notification.add_tab(:request, {
26
- :url => url,
27
- :params => params.to_hash,
28
- :controller => params[:controller],
29
- :action => params[:action]
30
- })
31
-
32
- # Add an environment tab
33
- if request.env && notification.configuration.send_environment
34
- notification.add_tab(:environment, request.env)
35
- end
36
-
37
- # Add a session tab
38
- notification.add_tab(:session, session_data) if session_data
39
-
40
- # Add a cookies tab
41
- notification.add_tab(:cookies, request.cookies) if request.cookies
42
-
43
- # Add the rails version
44
- notification.add_tab(:environment, {
45
- :railsVersion => Rails::VERSION::STRING
46
- })
47
- end
48
-
49
- @bugsnag.call(notification)
50
- end
51
- end
52
- end
@@ -1,506 +0,0 @@
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
- HANDLED_EXCEPTION = "handledException"
19
- UNHANDLED_EXCEPTION = "unhandledException"
20
- UNHANDLED_EXCEPTION_MIDDLEWARE = "unhandledExceptionMiddleware"
21
- ERROR_CLASS = "errorClass"
22
- USER_SPECIFIED_SEVERITY = "userSpecifiedSeverity"
23
- USER_CALLBACK_SET_SEVERITY = "userCallbackSetSeverity"
24
-
25
- API_KEY_REGEX = /[0-9a-f]{32}/i
26
-
27
- # e.g. "org/jruby/RubyKernel.java:1264:in `catch'"
28
- BACKTRACE_LINE_REGEX = /^((?:[a-zA-Z]:)?[^:]+):(\d+)(?::in `([^']+)')?$/
29
-
30
- # e.g. "org.jruby.Ruby.runScript(Ruby.java:807)"
31
- JAVA_BACKTRACE_REGEX = /^(.*)\((.*)(?::([0-9]+))?\)$/
32
-
33
- MAX_EXCEPTIONS_TO_UNWRAP = 5
34
-
35
- SUPPORTED_SEVERITIES = ["error", "warning", "info"]
36
-
37
- CURRENT_PAYLOAD_VERSION = "2"
38
-
39
- attr_accessor :context
40
- attr_reader :user
41
- attr_reader :severity
42
- attr_accessor :severity_reason
43
- attr_accessor :configuration
44
- attr_accessor :meta_data
45
-
46
- class << self
47
- def deliver_exception_payload(url, payload, configuration=Bugsnag.configuration, delivery_method=nil)
48
- payload_string = ::JSON.dump(Bugsnag::Helpers.trim_if_needed(payload))
49
- delivery_method = delivery_method || configuration.delivery_method
50
- Bugsnag::Delivery[delivery_method].deliver(url, payload_string, configuration)
51
- end
52
- end
53
-
54
- def initialize(exception, configuration, overrides = nil, request_data = nil)
55
- @configuration = configuration
56
- @overrides = Bugsnag::Helpers.flatten_meta_data(overrides) || {}
57
- @request_data = request_data
58
- @meta_data = {}
59
- @user = {}
60
- @should_ignore = false
61
- @severity = nil
62
- @unhandled = false
63
- @severity_reason = nil
64
- @grouping_hash = nil
65
- @delivery_method = nil
66
-
67
- if @overrides.key? :unhandled
68
- @unhandled = @overrides[:unhandled]
69
- @overrides.delete :unhandled
70
- end
71
-
72
- valid_severity = @overrides.key?(:severity) && SUPPORTED_SEVERITIES.include?(@overrides[:severity])
73
- has_reason = @overrides.key? :severity_reason
74
-
75
- if valid_severity && has_reason
76
- @severity = @overrides[:severity]
77
- @severity_reason = @overrides[:severity_reason]
78
- elsif valid_severity
79
- @severity = @overrides[:severity]
80
- @severity_reason = {
81
- :type => USER_SPECIFIED_SEVERITY
82
- }
83
- elsif has_reason
84
- @severity_reason = @overrides[:severity_reason]
85
- else
86
- @severity_reason = {
87
- :type => HANDLED_EXCEPTION
88
- }
89
- end
90
-
91
- @overrides.delete :severity_reason
92
- @overrides.delete :severity
93
-
94
- if @overrides.key? :grouping_hash
95
- self.grouping_hash = @overrides[:grouping_hash]
96
- @overrides.delete :grouping_hash
97
- end
98
-
99
- if @overrides.key? :api_key
100
- self.api_key = @overrides[:api_key]
101
- @overrides.delete :api_key
102
- end
103
-
104
- if @overrides.key? :delivery_method
105
- @delivery_method = @overrides[:delivery_method]
106
- @overrides.delete :delivery_method
107
- end
108
-
109
- # Unwrap exceptions
110
- @exceptions = []
111
-
112
- ex = exception
113
- while ex != nil && !@exceptions.include?(ex) && @exceptions.length < MAX_EXCEPTIONS_TO_UNWRAP
114
-
115
- unless ex.is_a? Exception
116
- if ex.respond_to?(:to_exception)
117
- ex = ex.to_exception
118
- elsif ex.respond_to?(:exception)
119
- ex = ex.exception
120
- end
121
- end
122
-
123
- unless ex.is_a?(Exception) || (defined?(Java::JavaLang::Throwable) && ex.is_a?(Java::JavaLang::Throwable))
124
- Bugsnag.warn("Converting non-Exception to RuntimeError: #{ex.inspect}")
125
- ex = RuntimeError.new(ex.to_s)
126
- ex.set_backtrace caller
127
- end
128
-
129
- @exceptions << ex
130
-
131
- if ex.respond_to?(:cause) && ex.cause
132
- ex = ex.cause
133
- elsif ex.respond_to?(:continued_exception) && ex.continued_exception
134
- ex = ex.continued_exception
135
- elsif ex.respond_to?(:original_exception) && ex.original_exception
136
- ex = ex.original_exception
137
- else
138
- ex = nil
139
- end
140
- end
141
- end
142
-
143
- # Add a single value as custom data, to this notification
144
- def add_custom_data(name, value)
145
- @meta_data[:custom] ||= {}
146
- @meta_data[:custom][name.to_sym] = value
147
- end
148
-
149
- # Add a new tab to this notification
150
- def add_tab(name, value)
151
- return if name.nil?
152
-
153
- if value.is_a? Hash
154
- @meta_data[name.to_sym] ||= {}
155
- @meta_data[name.to_sym].merge! value
156
- else
157
- self.add_custom_data(name, value)
158
- Bugsnag.warn "Adding a tab requires a hash, adding to custom tab instead (name=#{name})"
159
- end
160
- end
161
-
162
- # Remove a tab from this notification
163
- def remove_tab(name)
164
- return if name.nil?
165
-
166
- @meta_data.delete(name.to_sym)
167
- end
168
-
169
- def user_id=(user_id)
170
- @user[:id] = user_id
171
- end
172
-
173
- def user_id
174
- @user[:id]
175
- end
176
-
177
- def user=(user = {})
178
- return unless user.is_a? Hash
179
- @user.merge!(user).delete_if{|k,v| v == nil}
180
- end
181
-
182
- def severity=(severity)
183
- @severity = severity if SUPPORTED_SEVERITIES.include?(severity)
184
- end
185
-
186
- def severity
187
- @severity || "warning"
188
- end
189
-
190
- def payload_version
191
- CURRENT_PAYLOAD_VERSION
192
- end
193
-
194
- def grouping_hash=(grouping_hash)
195
- @grouping_hash = grouping_hash
196
- end
197
-
198
- def grouping_hash
199
- @grouping_hash || nil
200
- end
201
-
202
- def api_key=(api_key)
203
- @api_key = api_key
204
- end
205
-
206
- def api_key
207
- @api_key ||= @configuration.api_key
208
- end
209
-
210
- # Deliver this notification to bugsnag.com Also runs through the middleware as required.
211
- def deliver
212
- return unless @configuration.should_notify?
213
-
214
- # Check we have at least an api_key
215
- if api_key.nil?
216
- Bugsnag.warn "No API key configured, couldn't notify"
217
- return
218
- elsif api_key !~ API_KEY_REGEX
219
- Bugsnag.warn "Your API key (#{api_key}) is not valid, couldn't notify"
220
- return
221
- end
222
-
223
- # Warn if no release_stage is set
224
- Bugsnag.warn "You should set your app's release_stage (see https://bugsnag.com/docs/notifiers/ruby#release_stage)." unless @configuration.release_stage
225
-
226
- @configuration.internal_middleware.run(self)
227
-
228
- exceptions.each do |exception|
229
- if exception.class.include?(Bugsnag::MetaData)
230
- if exception.bugsnag_user_id.is_a?(String)
231
- self.user_id = exception.bugsnag_user_id
232
- end
233
- if exception.bugsnag_context.is_a?(String)
234
- self.context = exception.bugsnag_context
235
- end
236
- if exception.bugsnag_grouping_hash.is_a?(String)
237
- self.grouping_hash = exception.bugsnag_grouping_hash
238
- end
239
- end
240
- end
241
-
242
- [:user_id, :context, :user, :grouping_hash].each do |symbol|
243
- if @overrides[symbol]
244
- self.send("#{symbol}=", @overrides[symbol])
245
- @overrides.delete symbol
246
- end
247
- end
248
-
249
- # make meta_data available to public middleware
250
- @meta_data = generate_meta_data(@exceptions, @overrides)
251
-
252
- initial_severity = self.severity
253
-
254
- # Run the middleware here (including Bugsnag::Middleware::Callbacks)
255
- # at the end of the middleware stack, execute the actual notification delivery
256
- @configuration.middleware.run(self) do
257
- # This supports self.ignore! for before_notify_callbacks.
258
- return if @should_ignore
259
-
260
- # Check to see if the severity has been changed
261
- if initial_severity != self.severity
262
-
263
- end
264
-
265
- # Build the endpoint url
266
- endpoint = (@configuration.use_ssl ? "https://" : "http://") + @configuration.endpoint
267
- Bugsnag.log("Notifying #{endpoint} of #{@exceptions.last.class}")
268
-
269
- # Deliver the payload
270
- self.class.deliver_exception_payload(endpoint, build_exception_payload, @configuration, @delivery_method)
271
- end
272
- end
273
-
274
- # Build an exception payload
275
- def build_exception_payload
276
- # Build the payload's exception event
277
- payload_event = {
278
- :app => {
279
- :version => @configuration.app_version,
280
- :releaseStage => @configuration.release_stage,
281
- :type => @configuration.app_type
282
- },
283
- :context => self.context,
284
- :user => @user,
285
- :payloadVersion => payload_version,
286
- :exceptions => exception_list,
287
- :severity => self.severity,
288
- :unhandled => @unhandled,
289
- :severityReason => @severity_reason,
290
- :groupingHash => self.grouping_hash,
291
- }
292
-
293
- payload_event[:device] = {:hostname => @configuration.hostname} if @configuration.hostname
294
-
295
- # cleanup character encodings
296
- payload_event = Bugsnag::Cleaner.clean_object_encoding(payload_event)
297
-
298
- # filter out sensitive values in (and cleanup encodings) metaData
299
- payload_event[:metaData] = Bugsnag::Cleaner.new(@configuration.params_filters).clean_object(@meta_data)
300
- payload_event.reject! {|k,v| v.nil? }
301
-
302
- # return the payload hash
303
- {
304
- :apiKey => api_key,
305
- :notifier => {
306
- :name => NOTIFIER_NAME,
307
- :version => NOTIFIER_VERSION,
308
- :url => NOTIFIER_URL
309
- },
310
- :events => [payload_event]
311
- }
312
- end
313
-
314
- def ignore?
315
- @should_ignore || ignore_exception_class? || ignore_user_agent?
316
- end
317
-
318
- def request_data
319
- @request_data || Bugsnag.configuration.request_data
320
- end
321
-
322
- def exceptions
323
- @exceptions
324
- end
325
-
326
- def ignore!
327
- @should_ignore = true
328
- end
329
-
330
- private
331
-
332
- def ignore_exception_class?
333
- @exceptions.any? do |ex|
334
- ancestor_chain = ex.class.ancestors.select { |ancestor| ancestor.is_a?(Class) }.map { |ancestor| error_class(ancestor) }.to_set
335
-
336
- @configuration.ignore_classes.any? do |to_ignore|
337
- to_ignore.is_a?(Proc) ? to_ignore.call(ex) : ancestor_chain.include?(to_ignore)
338
- end
339
- end
340
- end
341
-
342
- def ignore_user_agent?
343
- if @configuration.request_data && @configuration.request_data[:rack_env] && (agent = @configuration.request_data[:rack_env]["HTTP_USER_AGENT"])
344
- @configuration.ignore_user_agents.any? do |to_ignore|
345
- agent =~ to_ignore
346
- end
347
- end
348
- end
349
-
350
- # Generate the meta data from both the request configuration, the overrides and the exceptions for this notification
351
- def generate_meta_data(exceptions, overrides)
352
- # Copy the request meta data so we dont edit it by mistake
353
- meta_data = @meta_data.dup
354
-
355
- exceptions.each do |exception|
356
- if exception.respond_to?(:bugsnag_meta_data) && exception.bugsnag_meta_data
357
- exception.bugsnag_meta_data.each do |key, value|
358
- add_to_meta_data key, value, meta_data
359
- end
360
- end
361
- end
362
-
363
- overrides.each do |key, value|
364
- add_to_meta_data key, value, meta_data
365
- end
366
-
367
- meta_data
368
- end
369
-
370
- def add_to_meta_data(key, value, meta_data)
371
- # If its a hash, its a tab so we can just add it providing its not reserved
372
- if value.is_a? Hash
373
- key = key.to_sym
374
-
375
- if meta_data[key]
376
- # If its a clash, merge with the existing data
377
- meta_data[key].merge! value
378
- else
379
- # Add it as is if its not special
380
- meta_data[key] = value
381
- end
382
- else
383
- meta_data[:custom] ||= {}
384
- meta_data[:custom][key] = value
385
- end
386
- end
387
-
388
- def exception_list
389
- @exceptions.map do |exception|
390
- {
391
- :errorClass => error_class(exception),
392
- :message => exception.message,
393
- :stacktrace => stacktrace(exception.backtrace)
394
- }
395
- end
396
- end
397
-
398
- def error_class(exception)
399
- # The "Class" check is for some strange exceptions like Timeout::Error
400
- # which throw the error class instead of an instance
401
- (exception.is_a? Class) ? exception.name : exception.class.name
402
- end
403
-
404
- def stacktrace(backtrace)
405
- backtrace = caller if !backtrace || backtrace.empty?
406
- backtrace.map do |trace|
407
- if trace.match(BACKTRACE_LINE_REGEX)
408
- file, line_str, method = [$1, $2, $3]
409
- elsif trace.match(JAVA_BACKTRACE_REGEX)
410
- method, file, line_str = [$1, $2, $3]
411
- end
412
-
413
- # Parse the stacktrace line
414
-
415
- # Skip stacktrace lines inside lib/bugsnag
416
- next(nil) if file.nil? || file =~ %r{lib/bugsnag(/|\.rb)}
417
-
418
- # Expand relative paths
419
- p = Pathname.new(file)
420
- if p.relative?
421
- file = p.realpath.to_s rescue file
422
- end
423
-
424
- # Generate the stacktrace line hash
425
- trace_hash = {}
426
- trace_hash[:inProject] = true if in_project?(file)
427
- trace_hash[:lineNumber] = line_str.to_i
428
-
429
- if @configuration.send_code
430
- trace_hash[:code] = code(file, trace_hash[:lineNumber])
431
- end
432
-
433
- # Clean up the file path in the stacktrace
434
- if defined?(Bugsnag.configuration.project_root) && Bugsnag.configuration.project_root.to_s != ''
435
- file.sub!(/#{Bugsnag.configuration.project_root}\//, "")
436
- end
437
-
438
- # Strip common gem path prefixes
439
- if defined?(Gem)
440
- file = Gem.path.inject(file) {|line, path| line.sub(/#{path}\//, "") }
441
- end
442
-
443
- trace_hash[:file] = file
444
-
445
- # Add a method if we have it
446
- trace_hash[:method] = method if method && (method =~ /^__bind/).nil?
447
-
448
- if trace_hash[:file] && !trace_hash[:file].empty?
449
- trace_hash
450
- else
451
- nil
452
- end
453
- end.compact
454
- end
455
-
456
- def in_project?(line)
457
- return false if @configuration.vendor_paths && @configuration.vendor_paths.any? do |vendor_path|
458
- if vendor_path.is_a?(String)
459
- line.include?(vendor_path)
460
- else
461
- line =~ vendor_path
462
- end
463
- end
464
- @configuration.project_root && line.start_with?(@configuration.project_root.to_s)
465
- end
466
-
467
- def code(file, line_number, num_lines = 7)
468
- code_hash = {}
469
-
470
- from_line = [line_number - num_lines, 1].max
471
-
472
- # don't try and open '(irb)' or '-e'
473
- return unless File.exist?(file)
474
-
475
- # Populate code hash with line numbers and code lines
476
- File.open(file) do |f|
477
- current_line_number = 0
478
- f.each_line do |line|
479
- current_line_number += 1
480
-
481
- next if current_line_number < from_line
482
-
483
- code_hash[current_line_number] = line[0...200].rstrip
484
-
485
- break if code_hash.length >= ( num_lines * 1.5 ).ceil
486
- end
487
- end
488
-
489
- while code_hash.length > num_lines
490
- last_line = code_hash.keys.max
491
- first_line = code_hash.keys.min
492
-
493
- if (last_line - line_number) > (line_number - first_line)
494
- code_hash.delete(last_line)
495
- else
496
- code_hash.delete(first_line)
497
- end
498
- end
499
-
500
- code_hash
501
- rescue
502
- Bugsnag.warn("Error fetching code: #{$!.inspect}")
503
- nil
504
- end
505
- end
506
- end