Skip to content

Commit

Permalink
Merge pull request #634 from bugsnag/bundler-friendly-errors
Browse files Browse the repository at this point in the history
Workaround Bundler's friendly errors swallowing error reports
  • Loading branch information
imjoehaines authored Sep 8, 2020
2 parents fe82dca + d428bf6 commit d9a4afe
Show file tree
Hide file tree
Showing 14 changed files with 109 additions and 45 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
Changelog
=========

## TBD

### Enhancements

* Bugsnag should now report uncaught exceptions inside Bundler's 'friendly errors'
| [#634](https://github.com/bugsnag/bugsnag-ruby/pull/634)

## 6.17.0 (27 August 2020)

### Enhancements
Expand Down
6 changes: 3 additions & 3 deletions features/fixtures/plain/app/unhandled/custom_error.rb
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
require './app'
#!/usr/bin/env ruby
require_relative '../app'

configure_basics
add_at_exit

class CustomError < RuntimeError
end

raise CustomError.new "Oh no"
raise CustomError.new "Oh no"
10 changes: 10 additions & 0 deletions features/fixtures/plain/app/unhandled/exit_after_exception.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env ruby
require_relative '../app'

configure_basics

begin
raise 'oh no'
rescue
exit
end
6 changes: 3 additions & 3 deletions features/fixtures/plain/app/unhandled/interrupt.rb
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require './app'
#!/usr/bin/env ruby
require_relative '../app'

configure_basics
add_at_exit

Process.kill("INT", Process.pid)
Process.kill("INT", Process.pid)
6 changes: 3 additions & 3 deletions features/fixtures/plain/app/unhandled/load_error.rb
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require './app'
#!/usr/bin/env ruby
require_relative '../app'

configure_basics
add_at_exit

require 'abc/def/ghi'
require 'abc/def/ghi'
6 changes: 3 additions & 3 deletions features/fixtures/plain/app/unhandled/local_jump_error.rb
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
require './app'
#!/usr/bin/env ruby
require_relative '../app'

configure_basics
add_at_exit

def call_block
yield 50
end

call_block
call_block
6 changes: 3 additions & 3 deletions features/fixtures/plain/app/unhandled/name_error.rb
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require './app'
#!/usr/bin/env ruby
require_relative '../app'

configure_basics
add_at_exit

foo
foo
6 changes: 3 additions & 3 deletions features/fixtures/plain/app/unhandled/no_method_error.rb
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require './app'
#!/usr/bin/env ruby
require_relative '../app'

configure_basics
add_at_exit

Kernel.foo
Kernel.foo
6 changes: 3 additions & 3 deletions features/fixtures/plain/app/unhandled/runtime_error.rb
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require './app'
#!/usr/bin/env ruby
require_relative '../app'

configure_basics
add_at_exit

fail
fail
6 changes: 3 additions & 3 deletions features/fixtures/plain/app/unhandled/syntax_error.rb
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require './app'
#!/usr/bin/env ruby
require_relative '../app'

configure_basics
add_at_exit

require './unhandled/bad_syntax.rb'
require './unhandled/bad_syntax.rb'
6 changes: 3 additions & 3 deletions features/fixtures/plain/app/unhandled/system_call_error.rb
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require './app'
#!/usr/bin/env ruby
require_relative '../app'

configure_basics
add_at_exit

File.open("abc/def/ghi")
File.open("abc/def/ghi")
6 changes: 3 additions & 3 deletions features/fixtures/plain/app/unhandled/system_exit.rb
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require './app'
#!/usr/bin/env ruby
require_relative '../app'

configure_basics
add_at_exit

exit
exit
40 changes: 26 additions & 14 deletions features/plain_features/unhandled_errors.feature
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Feature: Plain unhandled errors

Scenario Outline: An unhandled error sends a report
Given I run the service "plain-ruby" with the command "bundle exec ruby unhandled/<file>.rb"
Given I run the service "plain-ruby" with the command "<command> unhandled/<file>.rb"
And I wait to receive a request
Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier"
And the event "unhandled" is true
Expand All @@ -12,22 +12,34 @@ Scenario Outline: An unhandled error sends a report
And the "lineNumber" of stack frame 0 equals <lineNumber>

Examples:
| file | error | lineNumber |
| runtime_error | RuntimeError | 6 |
| load_error | LoadError | 6 |
| syntax_error | SyntaxError | 6 |
| local_jump_error | LocalJumpError | 7 |
| name_error | NameError | 6 |
| no_method_error | NoMethodError | 6 |
| system_call_error | Errno::ENOENT | 6 |
| custom_error | CustomError | 9 |
| file | error | lineNumber | command |
| runtime_error | RuntimeError | 6 | bundle exec |
| load_error | LoadError | 6 | bundle exec |
| syntax_error | SyntaxError | 6 | bundle exec |
| local_jump_error | LocalJumpError | 7 | bundle exec |
| name_error | NameError | 6 | bundle exec |
| no_method_error | NoMethodError | 6 | bundle exec |
| system_call_error | Errno::ENOENT | 6 | bundle exec |
| custom_error | CustomError | 9 | bundle exec |
| runtime_error | RuntimeError | 6 | bundle exec ruby |
| load_error | LoadError | 6 | bundle exec ruby |
| syntax_error | SyntaxError | 6 | bundle exec ruby |
| local_jump_error | LocalJumpError | 7 | bundle exec ruby |
| name_error | NameError | 6 | bundle exec ruby |
| no_method_error | NoMethodError | 6 | bundle exec ruby |
| system_call_error | Errno::ENOENT | 6 | bundle exec ruby |
| custom_error | CustomError | 9 | bundle exec ruby |

Scenario Outline: An unhandled error doesn't send a report
When I run the service "plain-ruby" with the command "bundle exec ruby unhandled/<file>.rb"
When I run the service "plain-ruby" with the command "<command> unhandled/<file>.rb"
And I wait for 1 second
Then I should receive no requests

Examples:
| file |
| interrupt |
| system_exit |
| file | command |
| interrupt | bundle exec |
| system_exit | bundle exec |
| exit_after_exception | bundle exec |
| interrupt | bundle exec ruby |
| system_exit | bundle exec ruby |
| exit_after_exception | bundle exec ruby |
37 changes: 36 additions & 1 deletion lib/bugsnag.rb
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,9 @@ def register_at_exit
@exit_handler_added = true
at_exit do
if $!
Bugsnag.notify($!, true) do |report|
exception = unwrap_bundler_exception($!)

Bugsnag.notify(exception, true) do |report|
report.severity = 'error'
report.severity_reason = {
:type => Bugsnag::Report::UNHANDLED_EXCEPTION
Expand Down Expand Up @@ -390,6 +392,39 @@ def report_to_json(report)

::JSON.dump(trimmed)
end

##
# When running a script with 'bundle exec', uncaught exceptions will be
# converted to "friendly errors" which has the side effect of wrapping them
# in a SystemExit
#
# By default we ignore SystemExit, so need to unwrap the original exception
# in order to avoid ignoring real errors
#
# @param exception [Exception]
# @return [Exception]
def unwrap_bundler_exception(exception)
running_in_bundler = ENV.include?('BUNDLE_BIN_PATH')

# See if this exception came from Bundler's 'with_friendly_errors' method
return exception unless running_in_bundler
return exception unless exception.is_a?(SystemExit)
return exception unless exception.respond_to?(:cause)
return exception unless exception.backtrace.first.include?('/bundler/friendly_errors.rb')
return exception if exception.cause.nil?

unwrapped = exception.cause

# We may need to unwrap another level if the exception came from running
# an executable file directly (i.e. 'bundle exec <file>'). In this case
# there can be a SystemExit from 'with_friendly_errors' _and_ a SystemExit
# from 'kernel_load'
return unwrapped unless unwrapped.is_a?(SystemExit)
return unwrapped unless unwrapped.backtrace.first.include?('/bundler/cli/exec.rb')
return unwrapped if unwrapped.cause.nil?

unwrapped.cause
end
end
end
# rubocop:enable Metrics/ModuleLength
Expand Down

0 comments on commit d9a4afe

Please sign in to comment.