Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Workaround Bundler's friendly errors swallowing error reports #634

Merged
merged 5 commits into from
Sep 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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