diff --git a/Gemfile b/Gemfile index 865d5fe5..1dd22178 100644 --- a/Gemfile +++ b/Gemfile @@ -12,3 +12,9 @@ gem 'racc', :platform => :rbx gem 'minitest', :platform => :rbx gem 'rubysl-test-unit', :platform => :rbx gem 'rubinius-developer_tools', :platform => :rbx + +if RUBY_VERSION.chars.first.to_i > 1 + gem 'byebug' +else + gem 'debugger' +end diff --git a/README.md b/README.md index 041f7417..cc97da30 100644 --- a/README.md +++ b/README.md @@ -379,6 +379,23 @@ Rollbar.silenced { } ``` +# Sending backtrace without rescued exceptions + +If you use the gem in this way: + +```ruby +exception = MyException.new('this is a message') +Rollbar.error(exception) +``` + +You will notice a backtrace doesn't appear in your Rollbar dashboard. This is because `exception.backtrace` is `nil` in these cases. We can send the current backtrace for you even your exception doesn't have it. In order to enable this feature you should configure Rollbar in this way: + +```ruby +Rollbar.configure do |config| + config.populate_empty_backtraces = true +end +``` + ## Delayed::Job integration If `delayed_job` is defined, Rollbar will automatically install a plugin that reports any uncaught exceptions that occur in jobs. diff --git a/lib/rollbar.rb b/lib/rollbar.rb index 9118ed3e..0d0a75c4 100644 --- a/lib/rollbar.rb +++ b/lib/rollbar.rb @@ -339,23 +339,20 @@ def build_payload_body_exception(message, exception, extra) end def trace_data(exception) - # parse backtrace - if exception.backtrace.respond_to?( :map ) - frames = exception.backtrace.map { |frame| - # parse the line - match = frame.match(/(.*):(\d+)(?::in `([^']+)')?/) - if match - { :filename => match[1], :lineno => match[2].to_i, :method => match[3] } - else - { :filename => "", :lineno => 0, :method => frame } - end - } - # reverse so that the order is as rollbar expects - frames.reverse! - else - frames = [] + frames = exception_backtrace(exception).map do |frame| + # parse the line + match = frame.match(/(.*):(\d+)(?::in `([^']+)')?/) + + if match + { :filename => match[1], :lineno => match[2].to_i, :method => match[3] } + else + { :filename => "", :lineno => 0, :method => frame } + end end + # reverse so that the order is as rollbar expects + frames.reverse! + { :frames => frames, :exception => { @@ -365,6 +362,28 @@ def trace_data(exception) } end + # Returns the backtrace to be sent to our API. There are 3 options: + # + # 1. The exception received has a backtrace, then that backtrace is returned. + # 2. configuration.populate_empty_backtraces is disabled, we return [] here + # 3. The user has configuration.populate_empty_backtraces is enabled, then: + # + # We want to send the caller as backtrace, but the first lines of that array + # are those from the user's Rollbar.error line until this method. We want + # to remove those lines. + def exception_backtrace(exception) + return exception.backtrace if exception.backtrace.respond_to?( :map ) + return [] unless configuration.populate_empty_backtraces + + caller_backtrace = caller + caller_backtrace.shift while caller_backtrace[0].include?(rollbar_lib_gem_dir) + caller_backtrace + end + + def rollbar_lib_gem_dir + Gem::Specification.find_by_name('rollbar').gem_dir + '/lib' + end + def trace_chain(exception) traces = [trace_data(exception)] visited = [exception] diff --git a/lib/rollbar/configuration.rb b/lib/rollbar/configuration.rb index bcf1c500..74f9755f 100644 --- a/lib/rollbar/configuration.rb +++ b/lib/rollbar/configuration.rb @@ -26,6 +26,7 @@ class Configuration attr_accessor :person_id_method attr_accessor :person_username_method attr_accessor :person_email_method + attr_accessor :populate_empty_backtraces attr_accessor :report_dj_data attr_accessor :request_timeout attr_accessor :root @@ -67,6 +68,7 @@ def initialize @person_username_method = 'username' @person_email_method = 'email' @project_gems = [] + @populate_empty_backtraces = false @report_dj_data = true @request_timeout = 3 @scrub_fields = [:passwd, :password, :password_confirmation, :secret, diff --git a/spec/rollbar_spec.rb b/spec/rollbar_spec.rb index f004cd69..9bfd61be 100644 --- a/spec/rollbar_spec.rb +++ b/spec/rollbar_spec.rb @@ -879,6 +879,25 @@ payload["data"][:body][:trace][:exception][:message].should == "oops" end + it 'gets the backtrace from the caller' do + Rollbar.configure do |config| + config.populate_empty_backtraces = true + end + + exception = Exception.new + + Rollbar.error(exception) + + gem_dir = Gem::Specification.find_by_name('rollbar').gem_dir + gem_lib_dir = gem_dir + '/lib' + last_report = Rollbar.last_report + + filepaths = last_report[:body][:trace][:frames].map {|frame| frame[:filename] }.reverse + + expect(filepaths[0]).not_to include(gem_lib_dir) + expect(filepaths.any? {|filepath| filepath.include?(gem_dir) }).to be_true + end + it 'should return the exception data with a uuid, on platforms with SecureRandom' do if defined?(SecureRandom) and SecureRandom.respond_to?(:uuid) exception_data = Rollbar.error(StandardError.new("oops"))