diff --git a/lib/rollbar/exception_reporter.rb b/lib/rollbar/exception_reporter.rb index 18b334f3..91ac5433 100644 --- a/lib/rollbar/exception_reporter.rb +++ b/lib/rollbar/exception_reporter.rb @@ -38,8 +38,12 @@ def log_exception_message(exception) end def exception_data(exception) - Rollbar.log(Rollbar.configuration.uncaught_exception_level, exception, - :use_exception_level_filters => true) + Rollbar.log( + Rollbar.configuration.uncaught_exception_level, + exception, + :is_uncaught => true, + :use_exception_level_filters => true + ) end end end diff --git a/lib/rollbar/item.rb b/lib/rollbar/item.rb index afcbd733..885ed22d 100644 --- a/lib/rollbar/item.rb +++ b/lib/rollbar/item.rb @@ -24,7 +24,7 @@ class Item attr_writer :payload attr_reader :level, :message, :exception, :extra, :configuration, :scope, :logger, - :notifier, :context + :notifier, :context, :is_uncaught def_delegators :payload, :[] @@ -47,6 +47,7 @@ def initialize(options) @payload = nil @notifier = options[:notifier] @context = options[:context] + @is_uncaught = options[:is_uncaught] end def payload @@ -101,6 +102,11 @@ def build_optional_data(data) data[:code_version] = configuration.code_version if configuration.code_version + if is_uncaught + data[:attributes] ||= [] + data[:attributes] << { :key => 'is_uncaught', :value => 'true' } + end + return unless defined?(SecureRandom) && SecureRandom.respond_to?(:uuid) data[:uuid] = SecureRandom.uuid diff --git a/lib/rollbar/notifier.rb b/lib/rollbar/notifier.rb index c4c1623b..935faf5c 100644 --- a/lib/rollbar/notifier.rb +++ b/lib/rollbar/notifier.rb @@ -132,6 +132,7 @@ def log(level, *args) message, exception, extra, context = extract_arguments(args) use_exception_level_filters = use_exception_level_filters?(extra) + is_uncaught = uncaught?(extra) return 'ignored' if ignored?(exception, use_exception_level_filters) || ignore_before_process?(level, exception, message, extra) @@ -139,7 +140,9 @@ def log(level, *args) level = lookup_exception_level(level, exception, use_exception_level_filters) - ret = report_with_rescue(level, message, exception, extra, context) + ret = report_with_rescue( + level, message, exception, extra, context, is_uncaught + ) raise(exception) if configuration.raise_on_error && exception @@ -157,8 +160,8 @@ def ignore_before_process?(level, exception, message, extra) true end - def report_with_rescue(level, message, exception, extra, context) - report(level, message, exception, extra, context) + def report_with_rescue(level, message, exception, extra, context, is_uncaught) + report(level, message, exception, extra, context, is_uncaught) rescue StandardError, SystemStackError => e original_error = { :message => message, @@ -412,6 +415,12 @@ def use_exception_level_filters?(options) configuration.use_exception_level_filters_default end + def uncaught?(options) + option_value = options && options.delete(:is_uncaught) + + !!option_value + end + def call_before_process(options) options = options_for_handler(options) handlers = configuration.before_process @@ -494,7 +503,7 @@ def filtered_level(exception) end end - def report(level, message, exception, extra, context) + def report(level, message, exception, extra, context, is_uncaught) unless message || exception || extra log_error( '[Rollbar] Tried to send a report with no message, exception or extra data.' @@ -503,7 +512,7 @@ def report(level, message, exception, extra, context) return 'error' end - item = build_item(level, message, exception, extra, context) + item = build_item(level, message, exception, extra, context, is_uncaught) return 'ignored' if item.ignored? @@ -540,7 +549,7 @@ def report_internal_error(exception, original_error = nil) configuration.execute_hook(:on_report_internal_error, exception) failsafe_message = 'build_item in exception_data' - item = build_item('error', nil, exception, { :internal => true }, nil) + item = build_item('error', nil, exception, { :internal => true }, nil, false) failsafe_message = 'error in process_item' process_item(item) @@ -571,7 +580,7 @@ def skip_reporting_internal_error(exception) ## Payload building functions - def build_item(level, message, exception, extra, context) + def build_item(level, message, exception, extra, context, is_uncaught) options = { :level => level, :message => message, @@ -581,7 +590,8 @@ def build_item(level, message, exception, extra, context) :logger => logger, :scope => scope_object, :notifier => self, - :context => context + :context => context, + :is_uncaught => is_uncaught } item = Item.new(options) diff --git a/lib/rollbar/plugins/rails/error_subscriber.rb b/lib/rollbar/plugins/rails/error_subscriber.rb index a56c8664..fd4763e7 100644 --- a/lib/rollbar/plugins/rails/error_subscriber.rb +++ b/lib/rollbar/plugins/rails/error_subscriber.rb @@ -10,6 +10,8 @@ def report(error, handled:, severity:, context:, source: nil) # Rails auto injected context extra[:controller] = extra[:controller].class.name if extra[:controller]&.respond_to?(:class) extra[:job] = extra[:job].class.name if extra[:job]&.respond_to?(:class) + extra[:is_uncaught] = !handled + extra[:use_exception_level_filters] = !handled Rollbar.log(severity, error, extra) end diff --git a/spec/controllers/home_controller_spec.rb b/spec/controllers/home_controller_spec.rb index 6a1e3b5d..7086dcd8 100644 --- a/spec/controllers/home_controller_spec.rb +++ b/spec/controllers/home_controller_spec.rb @@ -37,7 +37,7 @@ def send_req(meth, path, args) context 'rollbar base_data' do it 'should have the Rails environment' do - data = Rollbar.notifier.send(:build_item, 'error', 'message', nil, nil, nil) + data = Rollbar.notifier.send(:build_item, 'error', 'message', nil, nil, nil, false) data['data'][:environment].should eq(::Rails.env) end @@ -46,7 +46,7 @@ def send_req(meth, path, args) config.environment = 'dev' end - data = Rollbar.notifier.send(:build_item, 'error', 'message', nil, nil, nil) + data = Rollbar.notifier.send(:build_item, 'error', 'message', nil, nil, nil, false) data['data'][:environment].should eq('dev') end @@ -55,7 +55,7 @@ def send_req(meth, path, args) ::Rails.env = '' preconfigure_rails_notifier - data = Rollbar.notifier.send(:build_item, 'error', 'message', nil, nil, nil) + data = Rollbar.notifier.send(:build_item, 'error', 'message', nil, nil, nil, false) data['data'][:environment].should eq('unspecified') ::Rails.env = old_env diff --git a/spec/rollbar/item_spec.rb b/spec/rollbar/item_spec.rb index b4649cec..e5c709b5 100644 --- a/spec/rollbar/item_spec.rb +++ b/spec/rollbar/item_spec.rb @@ -35,7 +35,8 @@ :logger => logger, :scope => scope, :notifier => notifier, - :context => context + :context => context, + :is_uncaught => true } end @@ -130,6 +131,11 @@ payload['data'][:environment].should eq('overridden') end + it 'should have the is_uncaught attribute' do + payload['data'][:attributes][0][:key].should eq('is_uncaught') + payload['data'][:attributes][0][:value].should eq('true') + end + it 'should not have custom data under default configuration' do payload['data'][:body][:message][:extra].should be_nil end diff --git a/spec/rollbar/middleware/rack_spec.rb b/spec/rollbar/middleware/rack_spec.rb index 82f5523b..49de0446 100644 --- a/spec/rollbar/middleware/rack_spec.rb +++ b/spec/rollbar/middleware/rack_spec.rb @@ -21,7 +21,11 @@ class RackMockError < RuntimeError; end end let(:expected_report_args) do - [uncaught_level, exception, { :use_exception_level_filters => true }] + [ + uncaught_level, + exception, + { :is_uncaught => true, :use_exception_level_filters => true } + ] end describe '#call' do diff --git a/spec/rollbar/middleware/sinatra_spec.rb b/spec/rollbar/middleware/sinatra_spec.rb index 04eb4ed2..9913570c 100644 --- a/spec/rollbar/middleware/sinatra_spec.rb +++ b/spec/rollbar/middleware/sinatra_spec.rb @@ -64,7 +64,7 @@ def app end let(:expected_report_args) do - [uncaught_level, exception, { :use_exception_level_filters => true }] + [uncaught_level, exception, { :is_uncaught => true, :use_exception_level_filters => true }] end describe '#call' do diff --git a/spec/rollbar/plugins/rack_spec.rb b/spec/rollbar/plugins/rack_spec.rb index ab7b0026..0333b81e 100644 --- a/spec/rollbar/plugins/rack_spec.rb +++ b/spec/rollbar/plugins/rack_spec.rb @@ -27,8 +27,12 @@ class RackMockError < RuntimeError; end let(:uncaught_level) { Rollbar.configuration.uncaught_exception_level } it 'reports the error to Rollbar' do - expect(Rollbar).to receive(:log).with(uncaught_level, exception, - :use_exception_level_filters => true) + expect(Rollbar).to receive(:log).with( + uncaught_level, + exception, + :is_uncaught => true, + :use_exception_level_filters => true + ) expect { request.get('/will_crash') }.to raise_error(exception) end diff --git a/spec/rollbar/plugins/rake_spec.rb b/spec/rollbar/plugins/rake_spec.rb index c7173074..6619e107 100644 --- a/spec/rollbar/plugins/rake_spec.rb +++ b/spec/rollbar/plugins/rake_spec.rb @@ -12,8 +12,10 @@ end it 'reports error to Rollbar' do - expect(Rollbar).to receive(:error).with(exception, - :use_exception_level_filters => true) + expect(Rollbar).to receive(:error).with( + exception, + :use_exception_level_filters => true + ) expect(application).to receive(:orig_display_error_message).with(exception) Rollbar::Rake.patch! # Really here Rake is already patched diff --git a/spec/rollbar_spec.rb b/spec/rollbar_spec.rb index 97aed8f1..a8424943 100644 --- a/spec/rollbar_spec.rb +++ b/spec/rollbar_spec.rb @@ -158,7 +158,9 @@ end it 'should report a simple message' do - expect(notifier).to receive(:report).with('error', 'test message', nil, nil, nil) + expect(notifier).to receive(:report).with( + 'error', 'test message', nil, nil, nil, false + ) notifier.log('error', 'test message') end @@ -166,12 +168,14 @@ extra_data = { :key => 'value', :hash => { :inner_key => 'inner_value' } } expect(notifier).to receive(:report).with('error', 'test message', nil, - extra_data, nil) + extra_data, nil, false) notifier.log('error', 'test message', extra_data) end it 'should report an exception' do - expect(notifier).to receive(:report).with('error', nil, exception, nil, nil) + expect(notifier).to receive(:report).with( + 'error', nil, exception, nil, nil, false + ) notifier.log('error', exception) end @@ -179,13 +183,13 @@ extra_data = { :key => 'value', :hash => { :inner_key => 'inner_value' } } expect(notifier).to receive(:report).with('error', nil, exception, extra_data, - nil) + nil, false) notifier.log('error', exception, extra_data) end it 'should report an exception with a description' do expect(notifier).to receive(:report).with('error', 'exception description', - exception, nil, nil) + exception, nil, nil, false) notifier.log('error', exception, 'exception description') end @@ -193,7 +197,7 @@ extra_data = { :key => 'value', :hash => { :inner_key => 'inner_value' } } expect(notifier).to receive(:report).with('error', 'exception description', - exception, extra_data, nil) + exception, extra_data, nil, false) notifier.log('error', exception, extra_data, 'exception description') end @@ -434,7 +438,9 @@ } expect(handler).to receive(:call).with(options) - expect(notifier).to receive(:report).with(level, message, exception, extra, nil) + expect(notifier).to receive(:report).with( + level, message, exception, extra, nil, false + ) notifier.log(level, message, exception, extra) end @@ -569,80 +575,90 @@ let(:extra_data) { { :key => 'value', :hash => { :inner_key => 'inner_value' } } } it 'should report with a debug level' do - expect(notifier).to receive(:report).with('debug', nil, exception, nil, nil) + expect(notifier).to receive(:report).with( + 'debug', nil, exception, nil, nil, false + ) notifier.debug(exception) expect(notifier).to receive(:report).with('debug', 'description', exception, nil, - nil) + nil, false) notifier.debug(exception, 'description') expect(notifier).to receive(:report).with('debug', 'description', exception, - extra_data, nil) + extra_data, nil, false) notifier.debug(exception, 'description', extra_data) end it 'should report with an info level' do - expect(notifier).to receive(:report).with('info', nil, exception, nil, nil) + expect(notifier).to receive(:report).with('info', nil, exception, nil, nil, false) notifier.info(exception) expect(notifier).to receive(:report).with('info', 'description', exception, nil, - nil) + nil, false) notifier.info(exception, 'description') expect(notifier).to receive(:report).with('info', 'description', exception, - extra_data, nil) + extra_data, nil, false) notifier.info(exception, 'description', extra_data) end it 'should report with a warning level' do - expect(notifier).to receive(:report).with('warning', nil, exception, nil, nil) + expect(notifier).to receive(:report).with( + 'warning', nil, exception, nil, nil, false + ) notifier.warning(exception) expect(notifier).to receive(:report).with('warning', 'description', exception, - nil, nil) + nil, nil, false) notifier.warning(exception, 'description') expect(notifier).to receive(:report).with('warning', 'description', exception, - extra_data, nil) + extra_data, nil, false) notifier.warning(exception, 'description', extra_data) end it 'should report using warn method with a warning level' do - expect(notifier).to receive(:report).with('warning', nil, exception, nil, nil) + expect(notifier).to receive(:report).with( + 'warning', nil, exception, nil, nil, false + ) notifier.warn(exception) expect(notifier).to receive(:report).with('warning', 'description', exception, - nil, nil) + nil, nil, false) notifier.warn(exception, 'description') expect(notifier).to receive(:report).with('warning', 'description', exception, - extra_data, nil) + extra_data, nil, false) notifier.warn(exception, 'description', extra_data) end it 'should report with an error level' do - expect(notifier).to receive(:report).with('error', nil, exception, nil, nil) + expect(notifier).to receive(:report).with( + 'error', nil, exception, nil, nil, false + ) notifier.error(exception) expect(notifier).to receive(:report).with('error', 'description', exception, nil, - nil) + nil, false) notifier.error(exception, 'description') expect(notifier).to receive(:report).with('error', 'description', exception, - extra_data, nil) + extra_data, nil, false) notifier.error(exception, 'description', extra_data) end it 'should report with a critical level' do - expect(notifier).to receive(:report).with('critical', nil, exception, nil, nil) + expect(notifier).to receive(:report).with( + 'critical', nil, exception, nil, nil, false + ) notifier.critical(exception) expect(notifier).to receive(:report).with('critical', 'description', exception, - nil, nil) + nil, nil, false) notifier.critical(exception, 'description') expect(notifier).to receive(:report).with('critical', 'description', exception, - extra_data, nil) + extra_data, nil, false) notifier.critical(exception, 'description', extra_data) end end @@ -743,7 +759,7 @@ expect(logger_mock).to receive(:error).with(message) expect(notifier).not_to receive(:schedule_payload) - result = notifier.send(:report, 'info', nil, nil, nil, nil) + result = notifier.send(:report, 'info', nil, nil, nil, nil, false) result.should == 'error' end @@ -761,7 +777,7 @@ expect(notifier).not_to receive(:schedule_payload) - result = notifier.send(:report, 'info', 'message', nil, nil, nil) + result = notifier.send(:report, 'info', 'message', nil, nil, nil, false) result.should == 'ignored' end end @@ -1947,7 +1963,7 @@ def backtrace gem_spec.gem_dir if gem_spec end.compact - data = notifier.send(:build_item, 'info', 'test', nil, {}, nil)['data'] + data = notifier.send(:build_item, 'info', 'test', nil, {}, nil, false)['data'] data[:project_package_paths].is_a?(Array).should eq(true) data[:project_package_paths].length.should eq(gem_paths.length) @@ -1972,7 +1988,7 @@ def backtrace gem_paths.any? { |path| path.include? 'rollbar-gem' }.should eq(true) gem_paths.any? { |path| path.include? 'rspec-rails' }.should eq(true) - data = notifier.send(:build_item, 'info', 'test', nil, {}, nil)['data'] + data = notifier.send(:build_item, 'info', 'test', nil, {}, nil, false)['data'] data[:project_package_paths].is_a?(Array).should eq(true) data[:project_package_paths].length.should eq(gem_paths.length) (data[:project_package_paths] - gem_paths).length.should eq(0) @@ -1985,7 +2001,7 @@ def backtrace config.project_gems = gems end - data = notifier.send(:build_item, 'info', 'test', nil, {}, nil)['data'] + data = notifier.send(:build_item, 'info', 'test', nil, {}, nil, false)['data'] data[:project_package_paths].is_a?(Array).should eq(true) data[:project_package_paths].length.should eq(1) end