diff --git a/lib/raven/base.rb b/lib/raven/base.rb index 391de58c2..e373459ff 100644 --- a/lib/raven/base.rb +++ b/lib/raven/base.rb @@ -7,6 +7,7 @@ require 'raven/logger' require 'raven/interfaces/message' require 'raven/interfaces/exception' +require 'raven/interfaces/single_exception' require 'raven/interfaces/stack_trace' require 'raven/interfaces/http' require 'raven/processor' diff --git a/lib/raven/event.rb b/lib/raven/event.rb index c2f78e691..9e33da08a 100644 --- a/lib/raven/event.rb +++ b/lib/raven/event.rb @@ -159,14 +159,25 @@ def self.from_message(message, options = {}) end def self.add_exception_interface(evt, exc) - evt.interface(:exception) do |int| - int.type = exc.class.to_s - int.value = exc.to_s - int.module = exc.class.to_s.split('::')[0...-1].join('::') - - int.stacktrace = if exc.backtrace - StacktraceInterface.new do |stacktrace| - stacktrace_interface_from(stacktrace, evt, exc.backtrace) + evt.interface(:exception) do |exc_int| + exceptions = [exc] + while exc.respond_to?(:cause) && exc.cause + exceptions << exc.cause + exc = exc.cause + end + exceptions.reverse! + + exc_int.values = exceptions.map do |exc| + SingleExceptionInterface.new do |int| + int.type = exc.class.to_s + int.value = exc.to_s + int.module = exc.class.to_s.split('::')[0...-1].join('::') + + int.stacktrace = if exc.backtrace + StacktraceInterface.new do |stacktrace| + stacktrace_interface_from(stacktrace, evt, exc.backtrace) + end + end end end end diff --git a/lib/raven/interfaces/exception.rb b/lib/raven/interfaces/exception.rb index ce046dd9d..bdaad404a 100644 --- a/lib/raven/interfaces/exception.rb +++ b/lib/raven/interfaces/exception.rb @@ -4,15 +4,12 @@ module Raven class ExceptionInterface < Interface name 'exception' - attr_accessor :type - attr_accessor :value - attr_accessor :module - attr_accessor :stacktrace + attr_accessor :values def to_hash(*args) data = super(*args) - if data[:stacktrace] - data[:stacktrace] = data[:stacktrace].to_hash + if data[:values] + data[:values] = data[:values].map(&:to_hash) end data end diff --git a/lib/raven/interfaces/single_exception.rb b/lib/raven/interfaces/single_exception.rb new file mode 100644 index 000000000..43c8e7242 --- /dev/null +++ b/lib/raven/interfaces/single_exception.rb @@ -0,0 +1,19 @@ +require 'raven/interfaces' + +module Raven + class SingleExceptionInterface < Interface + + attr_accessor :type + attr_accessor :value + attr_accessor :module + attr_accessor :stacktrace + + def to_hash(*args) + data = super(*args) + if data[:stacktrace] + data[:stacktrace] = data[:stacktrace].to_hash + end + data + end + end +end diff --git a/lib/raven/processor/removestacktrace.rb b/lib/raven/processor/removestacktrace.rb index 63752cee5..3fa732695 100644 --- a/lib/raven/processor/removestacktrace.rb +++ b/lib/raven/processor/removestacktrace.rb @@ -2,7 +2,11 @@ module Raven class Processor::RemoveStacktrace < Processor def process(value) - value[:exception].delete(:stacktrace) if value[:exception] + if value[:exception] + value[:exception][:values].map do |single_exception| + single_exception.delete(:stacktrace) if single_exception[:stacktrace] + end + end value end diff --git a/spec/raven/event_spec.rb b/spec/raven/event_spec.rb index 43054659b..9b7f169aa 100644 --- a/spec/raven/event_spec.rb +++ b/spec/raven/event_spec.rb @@ -300,15 +300,15 @@ end it 'uses the exception class name as the exception type' do - expect(hash[:exception][:type]).to eq('Exception') + expect(hash[:exception][:values][0][:type]).to eq('Exception') end it 'uses the exception message as the exception value' do - expect(hash[:exception][:value]).to eq(message) + expect(hash[:exception][:values][0][:value]).to eq(message) end it 'does not belong to a module' do - expect(hash[:exception][:module]).to eq('') + expect(hash[:exception][:values][0][:module]).to eq('') end end @@ -319,7 +319,7 @@ class Exception < Exception; end let(:exception) { Raven::Test::Exception.new(message) } it 'sends the module name as part of the exception info' do - expect(hash[:exception][:module]).to eq('Raven::Test') + expect(hash[:exception][:values][0][:module]).to eq('Raven::Test') end end @@ -352,6 +352,25 @@ class SubExc < BaseExc; end end end + # Only check causes when they're supported + if Exception.new.respond_to? :cause + context 'when the exception has a cause' do + let(:exception) { build_exception_with_cause } + + it 'captures the cause' do + expect(hash[:exception][:values].length).to eq(2) + end + end + + context 'when the exception has nested causes' do + let(:exception) { build_exception_with_two_causes } + + it 'captures nested causes' do + expect(hash[:exception][:values].length).to eq(3) + end + end + end + context 'when the exception has a backtrace' do let(:exception) do e = Exception.new(message) @@ -363,7 +382,7 @@ class SubExc < BaseExc; end end it 'parses the backtrace' do - frames = hash[:exception][:stacktrace][:frames] + frames = hash[:exception][:values][0][:stacktrace][:frames] expect(frames.length).to eq(2) expect(frames[0][:lineno]).to eq(1412) expect(frames[0][:function]).to eq('other_function') @@ -382,7 +401,7 @@ class SubExc < BaseExc; end end it 'marks filename and in_app correctly' do - frames = hash[:exception][:stacktrace][:frames] + frames = hash[:exception][:values][0][:stacktrace][:frames] expect(frames[0][:lineno]).to eq(10) expect(frames[0][:function]).to eq("synchronize") expect(frames[0][:filename]).to eq("") @@ -415,7 +434,7 @@ class SubExc < BaseExc; end it 'marks in_app correctly' do expect(Raven.configuration.project_root).to eq('/rails/root') - frames = hash[:exception][:stacktrace][:frames] + frames = hash[:exception][:values][0][:stacktrace][:frames] expect(frames[0][:filename]).to eq("test/some/other/path") expect(frames[0][:in_app]).to eq(true) expect(frames[1][:filename]).to eq("/app/some/other/path") @@ -440,7 +459,7 @@ class SubExc < BaseExc; end end it "doesn't remove any path information under project_root" do - frames = hash[:exception][:stacktrace][:frames] + frames = hash[:exception][:values][0][:stacktrace][:frames] expect(frames[3][:filename]).to eq("app/models/user.rb") end end @@ -461,7 +480,7 @@ class SubExc < BaseExc; end end it 'strips prefixes in the load path from frame filenames' do - frames = hash[:exception][:stacktrace][:frames] + frames = hash[:exception][:values][0][:stacktrace][:frames] expect(frames[0][:filename]).to eq('other/path') end end diff --git a/spec/raven/processors/removestacktrace_spec.rb b/spec/raven/processors/removestacktrace_spec.rb index 0c12e70fb..22870e5cd 100644 --- a/spec/raven/processors/removestacktrace_spec.rb +++ b/spec/raven/processors/removestacktrace_spec.rb @@ -10,10 +10,37 @@ it 'should remove stacktraces' do data = Raven::Event.capture_exception(build_exception).to_hash - expect(data[:exception][:stacktrace]).to_not eq(nil) + expect(data[:exception][:values][0][:stacktrace]).to_not eq(nil) result = @processor.process(data) - expect(result[:exception][:stacktrace]).to eq(nil) + expect(result[:exception][:values][0][:stacktrace]).to eq(nil) + end + + # Only check causes when they're supported + if Exception.new.respond_to? :cause + it 'should remove stacktraces from causes' do + data = Raven::Event.capture_exception(build_exception_with_cause).to_hash + + expect(data[:exception][:values][0][:stacktrace]).to_not eq(nil) + expect(data[:exception][:values][1][:stacktrace]).to_not eq(nil) + result = @processor.process(data) + + expect(result[:exception][:values][0][:stacktrace]).to eq(nil) + expect(result[:exception][:values][1][:stacktrace]).to eq(nil) + end + + it 'should remove stacktraces from nested causes' do + data = Raven::Event.capture_exception(build_exception_with_two_causes).to_hash + + expect(data[:exception][:values][0][:stacktrace]).to_not eq(nil) + expect(data[:exception][:values][1][:stacktrace]).to_not eq(nil) + expect(data[:exception][:values][2][:stacktrace]).to_not eq(nil) + result = @processor.process(data) + + expect(result[:exception][:values][0][:stacktrace]).to eq(nil) + expect(result[:exception][:values][1][:stacktrace]).to eq(nil) + expect(result[:exception][:values][2][:stacktrace]).to eq(nil) + end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 7b5730c82..880a9d301 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -10,3 +10,27 @@ def build_exception rescue ZeroDivisionError => exception return exception end + +def build_exception_with_cause + begin + 1 / 0 + rescue ZeroDivisionError + 1 / 0 + end +rescue ZeroDivisionError => exception + return exception +end + +def build_exception_with_two_causes + begin + begin + 1 / 0 + rescue ZeroDivisionError + 1 / 0 + end + rescue ZeroDivisionError + 1 / 0 + end +rescue ZeroDivisionError => exception + return exception +end