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.
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