From 7847cbf84f38c94b03152ec52ff2d9ed10992f1f Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Thu, 28 Jan 2021 15:37:53 +0000 Subject: [PATCH 01/22] Refactor Laravel specific MR helpers --- features/steps/laravel_steps.rb | 31 +++--------------------------- features/support/env.rb | 1 + features/support/laravel.rb | 34 +++++++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 28 deletions(-) create mode 100644 features/support/laravel.rb diff --git a/features/steps/laravel_steps.rb b/features/steps/laravel_steps.rb index ba9c9eaa..d1a1424d 100644 --- a/features/steps/laravel_steps.rb +++ b/features/steps/laravel_steps.rb @@ -1,5 +1,3 @@ -require 'net/http' - Given(/^I enable session tracking$/) do steps %{ When I set environment variable "BUGSNAG_CAPTURE_SESSIONS" to "true" @@ -15,13 +13,13 @@ When(/^I start the laravel fixture$/) do steps %{ - When I start the service "#{fixture}" - And I wait for the host "localhost" to open port "#{fixture_port}" + When I start the service "#{Laravel.fixture}" + And I wait for the host "localhost" to open port "#{Laravel.fixture_port}" } end When("I navigate to the route {string}") do |route| - navigate_to(route) + Laravel.navigate_to(route) end Then("the exception {string} matches one of the following:") do |path, values| @@ -29,20 +27,6 @@ assert_includes(values.raw.flatten, desired_value) end -def fixture - ENV['LARAVEL_FIXTURE'] || 'laravel56' -end - -def fixture_port - case fixture - when 'laravel-latest' then 61299 - when 'laravel66' then 61266 - when 'laravel58' then 61258 - when 'laravel56' then 61256 - else raise "Unknown laravel fixture '#{ENV['LARAVEL_FIXTURE']}'!" - end -end - def current_ip return 'host.docker.internal' if OS.mac? @@ -50,12 +34,3 @@ def current_ip ip_list = /((?:[0-9]*\.){3}[0-9]*)/.match(ip_addr) ip_list.captures.first end - -def navigate_to(route, attempts = 0) - Net::HTTP.get('localhost', route, fixture_port) -rescue => e - raise "Failed to navigate to #{route} (#{e})" if attempts > 15 - - sleep 1 - navigate_to(route, attempts + 1) -end diff --git a/features/support/env.rb b/features/support/env.rb index b8711b61..a39311dc 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -37,6 +37,7 @@ Before do ENV["BUGSNAG_API_KEY"] = $api_key ENV["BUGSNAG_ENDPOINT"] = "http://#{current_ip}:9339" + Laravel.reset! end at_exit do diff --git a/features/support/laravel.rb b/features/support/laravel.rb new file mode 100644 index 00000000..c37081e8 --- /dev/null +++ b/features/support/laravel.rb @@ -0,0 +1,34 @@ +require 'net/http' + +class Laravel + class << self + attr_reader :last_response + + def reset! + @last_response = nil + end + + def navigate_to(route, attempts = 0) + @last_response = Net::HTTP.get('localhost', route, fixture_port) + rescue => e + raise "Failed to navigate to #{route} (#{e})" if attempts > 15 + + sleep 1 + navigate_to(route, attempts + 1) + end + + def fixture + ENV.fetch('LARAVEL_FIXTURE', 'laravel56') + end + + def fixture_port + case fixture + when 'laravel-latest' then 61299 + when 'laravel66' then 61266 + when 'laravel58' then 61258 + when 'laravel56' then 61256 + else raise "Unknown laravel fixture '#{ENV['LARAVEL_FIXTURE']}'!" + end + end + end +end From 8de4f252d4b0f3a4f9719ee591543f4b5e3fd301 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Thu, 28 Jan 2021 15:38:15 +0000 Subject: [PATCH 02/22] Add a step to check the Laravel response --- features/steps/laravel_steps.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/features/steps/laravel_steps.rb b/features/steps/laravel_steps.rb index d1a1424d..f561563e 100644 --- a/features/steps/laravel_steps.rb +++ b/features/steps/laravel_steps.rb @@ -22,6 +22,15 @@ Laravel.navigate_to(route) end +Then("the Laravel response matches {string}") do |regex| + wait = Maze::Wait.new(timeout: 10) + success = wait.until { Laravel.last_response != nil } + + raise 'No response from the Laravel fixture!' unless success + + assert_match(Regexp.new(regex), Laravel.last_response) +end + Then("the exception {string} matches one of the following:") do |path, values| desired_value = read_key_path(Server.current_request[:body], "events.0.exceptions.0.#{path}") assert_includes(values.raw.flatten, desired_value) From d92387e9477f5fa0fbf2864a008496137c7ae6fd Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Thu, 28 Jan 2021 15:48:17 +0000 Subject: [PATCH 03/22] Clean up more thoroughly after running MR --- features/support/env.rb | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/features/support/env.rb b/features/support/env.rb index a39311dc..d0238797 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -3,31 +3,39 @@ # Copy bugsnag-laravel into fixture directories FIXTURE_DIR = 'features/fixtures' -VENDORED_LIB = FIXTURE_DIR + '/bugsnag-laravel.zip' +VENDORED_LIB = "#{FIXTURE_DIR}/bugsnag-laravel.zip" +FILES_TO_CLEANUP = [VENDORED_LIB] `composer archive -f zip --dir=#{File.dirname(VENDORED_LIB)} --file=#{File.basename(VENDORED_LIB, '.zip')}` -Dir.glob(FIXTURE_DIR + '/laravel*').each do |directory| + +Dir.glob("#{FIXTURE_DIR}/laravel*").each do |directory| next if directory.end_with?('laravel-latest') - FileUtils.cp(VENDORED_LIB, directory + '/bugsnag-laravel.zip') + zip = "#{directory}/bugsnag-laravel.zip" + FILES_TO_CLEANUP << zip + + FileUtils.cp(VENDORED_LIB, zip) + # Remove any locally installed composer deps - FileUtils.rm_rf(directory + '/vendor') + FileUtils.rm_rf("#{directory}/vendor") end - # Copy current requirements into fixture requirements File.open('composer.json', 'r') do |source| parsed_composer = JSON.parse(source.read) requirements = parsed_composer["require"] - Dir.glob(FIXTURE_DIR + '/laravel*').each do |directory| + Dir.glob("#{FIXTURE_DIR}/laravel*").each do |directory| next if directory.end_with?('laravel-latest') File.open(directory + '/composer.json.template', 'r') do |template| parsed_template = JSON.parse template.read parsed_template["repositories"][0]["package"]["require"] = requirements - File.open(directory + '/composer.json', 'w') do |target| + composer_json = "#{directory}/composer.json" + FILES_TO_CLEANUP << composer_json + + File.open(composer_json, 'w') do |target| target.write(JSON.pretty_generate(parsed_template)) end end @@ -41,7 +49,11 @@ end at_exit do - FileUtils.rm_rf(VENDORED_LIB) + project_root = File.dirname(File.dirname(__dir__)) + + FILES_TO_CLEANUP.each do |file| + FileUtils.rm_rf("#{project_root}/#{file}") + end end AfterConfiguration do |_config| From 7bd49e0f68ecee3f09282b2ab9debc8c50fff56e Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Thu, 28 Jan 2021 15:43:15 +0000 Subject: [PATCH 04/22] Add MR tests for OOMs with no OomBootstrapper --- .ci/patches/web-routes.patch | 48 ++++++++++++++++++++-- features/fixtures/laravel56/routes/web.php | 42 +++++++++++++++++++ features/fixtures/laravel58/routes/web.php | 42 +++++++++++++++++++ features/fixtures/laravel66/routes/web.php | 44 +++++++++++++++++++- features/ooms.feature | 25 +++++++++++ 5 files changed, 196 insertions(+), 5 deletions(-) create mode 100644 features/ooms.feature diff --git a/.ci/patches/web-routes.patch b/.ci/patches/web-routes.patch index d99df7f4..a315f6a0 100644 --- a/.ci/patches/web-routes.patch +++ b/.ci/patches/web-routes.patch @@ -1,15 +1,15 @@ diff --git a/routes/web.php b/routes/web.php -index b130397..4b89d73 100644 +index b130397..0c687ad 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,5 +1,6 @@ b = $a; ++ } ++ ++ return noOomResponse(); ++}); diff --git a/features/fixtures/laravel56/routes/web.php b/features/fixtures/laravel56/routes/web.php index c9215202..805ced2f 100644 --- a/features/fixtures/laravel56/routes/web.php +++ b/features/fixtures/laravel56/routes/web.php @@ -11,6 +11,8 @@ | */ +use Illuminate\Support\Facades\Route; + Route::get('/', function () { return view('welcome'); }); @@ -49,3 +51,43 @@ Route::view('/unhandled_view_error', 'unhandlederror'); Route::view('/handled_view_exception', 'handledexception'); Route::view('/handled_view_error', 'handlederror'); + +/** + * Return some diagnostics if an OOM did not happen when it should have. + * + * @return string + */ +function noOomResponse() { + $limit = ini_get('memory_limit'); + $memory = var_export(memory_get_usage(), true); + $peak = var_export(memory_get_peak_usage(), true); + + return <<b = $a; + } + + return noOomResponse(); +}); diff --git a/features/fixtures/laravel58/routes/web.php b/features/fixtures/laravel58/routes/web.php index c9215202..805ced2f 100644 --- a/features/fixtures/laravel58/routes/web.php +++ b/features/fixtures/laravel58/routes/web.php @@ -11,6 +11,8 @@ | */ +use Illuminate\Support\Facades\Route; + Route::get('/', function () { return view('welcome'); }); @@ -49,3 +51,43 @@ Route::view('/unhandled_view_error', 'unhandlederror'); Route::view('/handled_view_exception', 'handledexception'); Route::view('/handled_view_error', 'handlederror'); + +/** + * Return some diagnostics if an OOM did not happen when it should have. + * + * @return string + */ +function noOomResponse() { + $limit = ini_get('memory_limit'); + $memory = var_export(memory_get_usage(), true); + $peak = var_export(memory_get_peak_usage(), true); + + return <<b = $a; + } + + return noOomResponse(); +}); diff --git a/features/fixtures/laravel66/routes/web.php b/features/fixtures/laravel66/routes/web.php index 4ef50c58..805ced2f 100644 --- a/features/fixtures/laravel66/routes/web.php +++ b/features/fixtures/laravel66/routes/web.php @@ -11,6 +11,8 @@ | */ +use Illuminate\Support\Facades\Route; + Route::get('/', function () { return view('welcome'); }); @@ -48,4 +50,44 @@ Route::view('/unhandled_view_exception', 'unhandledexception'); Route::view('/unhandled_view_error', 'unhandlederror'); Route::view('/handled_view_exception', 'handledexception'); -Route::view('/handled_view_error', 'handlederror'); \ No newline at end of file +Route::view('/handled_view_error', 'handlederror'); + +/** + * Return some diagnostics if an OOM did not happen when it should have. + * + * @return string + */ +function noOomResponse() { + $limit = ini_get('memory_limit'); + $memory = var_export(memory_get_usage(), true); + $peak = var_export(memory_get_peak_usage(), true); + + return <<b = $a; + } + + return noOomResponse(); +}); diff --git a/features/ooms.feature b/features/ooms.feature new file mode 100644 index 00000000..2a48aa9b --- /dev/null +++ b/features/ooms.feature @@ -0,0 +1,25 @@ +Feature: Out of memory error support + +Scenario: Big OOM without the OOM bootstrapper + Given I start the laravel fixture + When I navigate to the route "/oom/big" + Then the Laravel response matches "Allowed memory size of \d+ bytes exhausted \(tried to allocate \d+ bytes\)" + When I wait to receive a request + Then the request is valid for the error reporting API version "4.0" for the "Bugsnag Laravel" notifier + And the exception "errorClass" matches one of the following: + | Symfony\Component\ErrorHandler\Error\FatalError | + | Symfony\Component\Debug\Exception\FatalErrorException | + And the exception "message" matches "Allowed memory size of \d+ bytes exhausted \(tried to allocate \d+ bytes\)" + And the event "metaData.request.httpMethod" equals "GET" + And the event "app.type" equals "HTTP" + And the event "context" equals "GET /oom/big" + And the event "severity" equals "error" + And the event "unhandled" is true + And the event "severityReason.type" equals "unhandledExceptionMiddleware" + And the event "severityReason.attributes.framework" equals "Laravel" + +Scenario: Small OOM without the OOM bootstrapper + Given I start the laravel fixture + When I navigate to the route "/oom/small" + Then the Laravel response matches "Allowed memory size of \d+ bytes exhausted \(tried to allocate \d+ bytes\)" + And I should receive no requests From fb0aba92db7ab8c44dd56415391163f7cc0e1db4 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Fri, 29 Jan 2021 09:12:57 +0000 Subject: [PATCH 05/22] Bump bugsnag-php version to 3.26.0 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 2573334c..8556103a 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ ], "require": { "php": ">=5.5", - "bugsnag/bugsnag": "^3.20", + "bugsnag/bugsnag": "^3.26.0", "bugsnag/bugsnag-psr-logger": "^1.4", "illuminate/contracts": "^5.0|^6.0|^7.0|^8.0", "illuminate/support": "^5.0|^6.0|^7.0|^8.0", From 53011c8c5085cd9de02da99f4bd6cae4c2a20092 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Fri, 29 Jan 2021 10:32:24 +0000 Subject: [PATCH 06/22] Support 'memory_limit_increase' option --- src/BugsnagServiceProvider.php | 4 ++++ src/Facades/Bugsnag.php | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/BugsnagServiceProvider.php b/src/BugsnagServiceProvider.php index 9ecd96da..0885a2a2 100644 --- a/src/BugsnagServiceProvider.php +++ b/src/BugsnagServiceProvider.php @@ -226,6 +226,10 @@ public function register() $client->setBuildEndpoint($config['build_endpoint']); } + if (array_key_exists('memory_limit_increase', $config)) { + $client->setMemoryLimitIncrease($config['memory_limit_increase']); + } + return $client; }); diff --git a/src/Facades/Bugsnag.php b/src/Facades/Bugsnag.php index 340bdf45..8413c76c 100644 --- a/src/Facades/Bugsnag.php +++ b/src/Facades/Bugsnag.php @@ -14,6 +14,7 @@ * @method static \Bugsnag\Configuration getConfig() * @method static array getDeviceData() * @method static array getFilters() + * @method static int|null getMemoryLimitIncrease() * @method static array getMetaData() * @method static array getNotifier() * @method static string getNotifyEndpoint() @@ -39,6 +40,7 @@ * @method static \Bugsnag\Client setFallbackType(string|null $type) * @method static \Bugsnag\Client setFilters(array $filters) * @method static \Bugsnag\Client setHostname(string|null $hostname) + * @method static \Bugsnag\Client setMemoryLimitIncrease(int|null $value) * @method static \Bugsnag\Client setMetaData(array $metaData, bool $merge = true) * @method static \Bugsnag\Client setNotifier(array $notifier) * @method static \Bugsnag\Client setNotifyEndpoint(string $endpoint) From 60c61c6bb0dd16bbebde431b8ee12609ae73bcc9 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Fri, 29 Jan 2021 10:32:48 +0000 Subject: [PATCH 07/22] Add a bootstrapper to allow OOMs to be handled --- src/OomBootstrapper.php | 54 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/OomBootstrapper.php diff --git a/src/OomBootstrapper.php b/src/OomBootstrapper.php new file mode 100644 index 00000000..1e7826db --- /dev/null +++ b/src/OomBootstrapper.php @@ -0,0 +1,54 @@ +reservedMemory = str_repeat(' ', 1024 * 256); + + register_shutdown_function(function () { + $this->reservedMemory = null; + + $lastError = error_get_last(); + + // If this is an OOM and memory increase is enabled, bump the memory + // limit so we can report it + if ($lastError !== null + && app('bugsnag')->getMemoryLimitIncrease() !== null + && preg_match($this->oomRegex, $lastError['message'], $matches) === 1 + ) { + $currentMemoryLimit = (int) $matches[1]; + + ini_set('memory_limit', $currentMemoryLimit + app('bugsnag')->getMemoryLimitIncrease()); + } + }); + } +} From 43384168916327c9e68879de816e23663981b2f8 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Fri, 29 Jan 2021 10:33:35 +0000 Subject: [PATCH 08/22] Add MR tests for OOMs with the new bootstrapper --- .ci/patches/oom-bootstrapper.patch | 21 ++++++++++ features/fixtures/docker-compose.yml | 4 ++ .../fixtures/laravel56/app/Http/Kernel.php | 12 ++++++ .../fixtures/laravel58/app/Http/Kernel.php | 12 ++++++ .../fixtures/laravel66/app/Http/Kernel.php | 12 ++++++ features/ooms.feature | 38 +++++++++++++++++++ 6 files changed, 99 insertions(+) create mode 100644 .ci/patches/oom-bootstrapper.patch diff --git a/.ci/patches/oom-bootstrapper.patch b/.ci/patches/oom-bootstrapper.patch new file mode 100644 index 00000000..53e7c5ad --- /dev/null +++ b/.ci/patches/oom-bootstrapper.patch @@ -0,0 +1,21 @@ +diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php +index 17b4866..d1dcc9e 100644 +--- a/app/Http/Kernel.php ++++ b/app/Http/Kernel.php +@@ -67,4 +67,16 @@ class Kernel extends HttpKernel + 'hanMidEx' => \App\Http\Middleware\HandledMiddlewareEx::class, + 'hanMidErr' => \App\Http\Middleware\HandledMiddlewareErr::class, + ]; ++ ++ protected function bootstrappers() ++ { ++ if (!getenv('BUGSNAG_REGISTER_OOM_BOOTSTRAPPER')) { ++ return parent::bootstrappers(); ++ } ++ ++ return array_merge( ++ [\Bugsnag\BugsnagLaravel\OomBootstrapper::class], ++ parent::bootstrappers(), ++ ); ++ } + } diff --git a/features/fixtures/docker-compose.yml b/features/fixtures/docker-compose.yml index c0f7dcdc..e4aa02bf 100644 --- a/features/fixtures/docker-compose.yml +++ b/features/fixtures/docker-compose.yml @@ -11,6 +11,7 @@ services: - BUGSNAG_SESSION_ENDPOINT - BUGSNAG_CAPTURE_SESSIONS - BUGSNAG_USE_CUSTOM_GUZZLE + - BUGSNAG_REGISTER_OOM_BOOTSTRAPPER restart: "no" ports: - target: 8000 @@ -27,6 +28,7 @@ services: - BUGSNAG_SESSION_ENDPOINT - BUGSNAG_CAPTURE_SESSIONS - BUGSNAG_USE_CUSTOM_GUZZLE + - BUGSNAG_REGISTER_OOM_BOOTSTRAPPER restart: "no" ports: - target: 8000 @@ -43,6 +45,7 @@ services: - BUGSNAG_SESSION_ENDPOINT - BUGSNAG_CAPTURE_SESSIONS - BUGSNAG_USE_CUSTOM_GUZZLE + - BUGSNAG_REGISTER_OOM_BOOTSTRAPPER restart: "no" ports: - target: 8000 @@ -59,6 +62,7 @@ services: - BUGSNAG_SESSION_ENDPOINT - BUGSNAG_CAPTURE_SESSIONS - BUGSNAG_USE_CUSTOM_GUZZLE + - BUGSNAG_REGISTER_OOM_BOOTSTRAPPER restart: "no" ports: - target: 8000 diff --git a/features/fixtures/laravel56/app/Http/Kernel.php b/features/fixtures/laravel56/app/Http/Kernel.php index f03f81ae..edecf8c5 100644 --- a/features/fixtures/laravel56/app/Http/Kernel.php +++ b/features/fixtures/laravel56/app/Http/Kernel.php @@ -56,4 +56,16 @@ class Kernel extends HttpKernel 'hanMidEx' => \App\Http\Middleware\HandledMiddlewareEx::class, 'hanMidErr' => \App\Http\Middleware\HandledMiddlewareErr::class, ]; + + protected function bootstrappers() + { + if (!getenv('BUGSNAG_REGISTER_OOM_BOOTSTRAPPER')) { + return parent::bootstrappers(); + } + + return array_merge( + [\Bugsnag\BugsnagLaravel\OomBootstrapper::class], + parent::bootstrappers(), + ); + } } diff --git a/features/fixtures/laravel58/app/Http/Kernel.php b/features/fixtures/laravel58/app/Http/Kernel.php index c374ec93..74c0556d 100644 --- a/features/fixtures/laravel58/app/Http/Kernel.php +++ b/features/fixtures/laravel58/app/Http/Kernel.php @@ -68,4 +68,16 @@ class Kernel extends HttpKernel */ protected $middlewarePriority = [ ]; + + protected function bootstrappers() + { + if (!getenv('BUGSNAG_REGISTER_OOM_BOOTSTRAPPER')) { + return parent::bootstrappers(); + } + + return array_merge( + [\Bugsnag\BugsnagLaravel\OomBootstrapper::class], + parent::bootstrappers(), + ); + } } diff --git a/features/fixtures/laravel66/app/Http/Kernel.php b/features/fixtures/laravel66/app/Http/Kernel.php index 69d4290b..e3e3b38e 100644 --- a/features/fixtures/laravel66/app/Http/Kernel.php +++ b/features/fixtures/laravel66/app/Http/Kernel.php @@ -83,4 +83,16 @@ class Kernel extends HttpKernel \Illuminate\Routing\Middleware\SubstituteBindings::class, \Illuminate\Auth\Middleware\Authorize::class, ]; + + protected function bootstrappers() + { + if (!getenv('BUGSNAG_REGISTER_OOM_BOOTSTRAPPER')) { + return parent::bootstrappers(); + } + + return array_merge( + [\Bugsnag\BugsnagLaravel\OomBootstrapper::class], + parent::bootstrappers(), + ); + } } diff --git a/features/ooms.feature b/features/ooms.feature index 2a48aa9b..3e01d40a 100644 --- a/features/ooms.feature +++ b/features/ooms.feature @@ -23,3 +23,41 @@ Scenario: Small OOM without the OOM bootstrapper When I navigate to the route "/oom/small" Then the Laravel response matches "Allowed memory size of \d+ bytes exhausted \(tried to allocate \d+ bytes\)" And I should receive no requests + +Scenario: Big OOM with the OOM bootstrapper + Given I set environment variable "BUGSNAG_REGISTER_OOM_BOOTSTRAPPER" to "true" + And I start the laravel fixture + When I navigate to the route "/oom/big" + Then the Laravel response matches "Allowed memory size of \d+ bytes exhausted \(tried to allocate \d+ bytes\)" + When I wait to receive a request + Then the request is valid for the error reporting API version "4.0" for the "Bugsnag Laravel" notifier + And the exception "errorClass" matches one of the following: + | Symfony\Component\ErrorHandler\Error\FatalError | + | Symfony\Component\Debug\Exception\FatalErrorException | + And the exception "message" matches "Allowed memory size of \d+ bytes exhausted \(tried to allocate \d+ bytes\)" + And the event "metaData.request.httpMethod" equals "GET" + And the event "app.type" equals "HTTP" + And the event "context" equals "GET /oom/big" + And the event "severity" equals "error" + And the event "unhandled" is true + And the event "severityReason.type" equals "unhandledExceptionMiddleware" + And the event "severityReason.attributes.framework" equals "Laravel" + +Scenario: Small OOM with the OOM bootstrapper + Given I set environment variable "BUGSNAG_REGISTER_OOM_BOOTSTRAPPER" to "true" + And I start the laravel fixture + When I navigate to the route "/oom/small" + Then the Laravel response matches "Allowed memory size of \d+ bytes exhausted \(tried to allocate \d+ bytes\)" + When I wait to receive a request + Then the request is valid for the error reporting API version "4.0" for the "Bugsnag Laravel" notifier + And the exception "errorClass" matches one of the following: + | Symfony\Component\ErrorHandler\Error\FatalError | + | Symfony\Component\Debug\Exception\FatalErrorException | + And the exception "message" matches "Allowed memory size of \d+ bytes exhausted \(tried to allocate \d+ bytes\)" + And the event "metaData.request.httpMethod" equals "GET" + And the event "app.type" equals "HTTP" + And the event "context" equals "GET /oom/small" + And the event "severity" equals "error" + And the event "unhandled" is true + And the event "severityReason.type" equals "unhandledExceptionMiddleware" + And the event "severityReason.attributes.framework" equals "Laravel" From df113f1a9b59345d667e6a7c1c1d017aa15a2ab5 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Fri, 29 Jan 2021 12:40:29 +0000 Subject: [PATCH 09/22] Load the Client up-front This avoids the possibility of initialising the Client during an OOM, which will not end well! --- src/BugsnagServiceProvider.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/BugsnagServiceProvider.php b/src/BugsnagServiceProvider.php index 0885a2a2..3c3f5712 100644 --- a/src/BugsnagServiceProvider.php +++ b/src/BugsnagServiceProvider.php @@ -50,6 +50,13 @@ public function boot() $this->setupEvents($this->app->events, $this->app->config->get('bugsnag')); $this->setupQueue($this->app->queue); + + // Load the Client instance up-front if the OOM bootstrapper has been + // loaded. This avoids the possibility of initialising during an OOM, + // which can take a non-trivial amount of memory + if (class_exists(OomBootstrapper::class, false) && !$this->app->runningUnitTests()) { + $this->app->make('bugsnag'); + } } /** From 590cd8f3eb0d5f8fa1d0025bd97621dd63d22a6b Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Fri, 29 Jan 2021 14:56:44 +0000 Subject: [PATCH 10/22] Add unit test to ensure config is picked up --- tests/ServiceProviderTest.php | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/ServiceProviderTest.php b/tests/ServiceProviderTest.php index 40cf307f..29e3acca 100644 --- a/tests/ServiceProviderTest.php +++ b/tests/ServiceProviderTest.php @@ -440,6 +440,41 @@ public function testItUsesGuzzleInstanceFromTheContainer() $this->assertSame($expected, $actual); } + /** + * @param int|null $memoryLimitIncrease + * + * @return void + * + * @dataProvider memoryLimitIncreaseProvider + */ + public function testMemoryLimitIncreaseIsSetCorrectly($memoryLimitIncrease) + { + /** @var \Illuminate\Config\Repository $laravelConfig */ + $laravelConfig = $this->app->config; + $bugsnagConfig = $laravelConfig->get('bugsnag'); + + $this->assertFalse(array_key_exists('memory_limit_increase', $bugsnagConfig)); + + $bugsnagConfig['memory_limit_increase'] = $memoryLimitIncrease; + + $laravelConfig->set('bugsnag', $bugsnagConfig); + + /** @var Client $client */ + $client = $this->app->make(Client::class); + + $this->assertInstanceOf(Client::class, $client); + $this->assertSame($memoryLimitIncrease, $client->getMemoryLimitIncrease()); + } + + public function memoryLimitIncreaseProvider() + { + return [ + [null], + [1234], + [1024 * 1024 * 20], + ]; + } + /** * Set the environment variable "$name" to the given value. * From 6d5d698987fab94d772ff13b247564fb8a5fe0c3 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Mon, 1 Feb 2021 13:50:07 +0000 Subject: [PATCH 11/22] Add changelog entry --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27d94fd9..819e4c32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ Changelog ========= +## TBD + +### Enhancements + +* Out of memory errors can now be reported by registering the new `OomBootstrapper` in your HTTP kernel, which will increase the memory limit by 5 MiB when an OOM occurs. See the docs for more details: + [Laravel](https://docs.bugsnag.com/platforms/php/laravel/#reporting-out-of-memory-exceptions) + [Lumen](https://docs.bugsnag.com/platforms/php/lumen/#reporting-out-of-memory-exceptions) + [#430](https://github.com/bugsnag/bugsnag-laravel/pull/430) + ## 2.21.0 (2020-11-25) ### Enhancements From ccb511f02706c54c5390322080e7463ecd642bbd Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Wed, 3 Feb 2021 17:32:55 +0000 Subject: [PATCH 12/22] Support the new discard classes option --- config/bugsnag.php | 12 ++++++++++++ src/BugsnagServiceProvider.php | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/config/bugsnag.php b/config/bugsnag.php index d500b515..c3204549 100644 --- a/config/bugsnag.php +++ b/config/bugsnag.php @@ -305,4 +305,16 @@ 'build_endpoint' => env('BUGSNAG_BUILD_ENDPOINT'), + /* + |-------------------------------------------------------------------------- + | Discard Classes + |-------------------------------------------------------------------------- + | + | An array of classes that should not be sent to Bugsnag. + | + | This can contain both fully qualified class names and regular expressions. + | + */ + + 'discard_classes' => empty(env('BUGSNAG_DISCARD_CLASSES')) ? null : explode(',', env('BUGSNAG_DISCARD_CLASSES')), ]; diff --git a/src/BugsnagServiceProvider.php b/src/BugsnagServiceProvider.php index 3c3f5712..a5b7dfff 100644 --- a/src/BugsnagServiceProvider.php +++ b/src/BugsnagServiceProvider.php @@ -237,6 +237,10 @@ public function register() $client->setMemoryLimitIncrease($config['memory_limit_increase']); } + if (isset($config['discard_classes']) && is_array($config['discard_classes'])) { + $client->setDiscardClasses($config['discard_classes']); + } + return $client; }); From b8fe71e3920ebbe1f0a2c7ca8d056b28e5bc438a Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Wed, 3 Feb 2021 17:33:35 +0000 Subject: [PATCH 13/22] Add MazeRunner tests for discard classes --- features/discard_classes.feature | 29 ++++++++++++++++++++++++++++ features/fixtures/docker-compose.yml | 4 ++++ 2 files changed, 33 insertions(+) create mode 100644 features/discard_classes.feature diff --git a/features/discard_classes.feature b/features/discard_classes.feature new file mode 100644 index 00000000..cb3dcbc5 --- /dev/null +++ b/features/discard_classes.feature @@ -0,0 +1,29 @@ +Feature: Discard classes + +Scenario: Exceptions can be discarded by name + Given I set environment variable "BUGSNAG_DISCARD_CLASSES" to "Exception" + And I start the laravel fixture + When I navigate to the route "/unhandled_controller_exception" + Then I should receive no requests + +Scenario: Exceptions can be discarded by regex + Given I set environment variable "BUGSNAG_DISCARD_CLASSES" to "/Exception$/" + And I start the laravel fixture + When I navigate to the route "/unhandled_controller_exception" + Then I should receive no requests + +Scenario: Exceptions will be delivered when discard classes does not match + Given I set environment variable "BUGSNAG_DISCARD_CLASSES" to "DifferentException,/^NotThatException$/" + And I start the laravel fixture + When I navigate to the route "/unhandled_controller_exception" + Then I wait to receive a request + And the request is valid for the error reporting API version "4.0" for the "Bugsnag Laravel" notifier + And the exception "errorClass" equals "Exception" + And the exception "message" starts with "Crashing exception!" + And the event "metaData.request.httpMethod" equals "GET" + And the event "app.type" equals "HTTP" + And the event "context" equals "GET /unhandled_controller_exception" + And the event "severity" equals "error" + And the event "unhandled" is true + And the event "severityReason.type" equals "unhandledExceptionMiddleware" + And the event "severityReason.attributes.framework" equals "Laravel" diff --git a/features/fixtures/docker-compose.yml b/features/fixtures/docker-compose.yml index e4aa02bf..542d94f8 100644 --- a/features/fixtures/docker-compose.yml +++ b/features/fixtures/docker-compose.yml @@ -12,6 +12,7 @@ services: - BUGSNAG_CAPTURE_SESSIONS - BUGSNAG_USE_CUSTOM_GUZZLE - BUGSNAG_REGISTER_OOM_BOOTSTRAPPER + - BUGSNAG_DISCARD_CLASSES restart: "no" ports: - target: 8000 @@ -29,6 +30,7 @@ services: - BUGSNAG_CAPTURE_SESSIONS - BUGSNAG_USE_CUSTOM_GUZZLE - BUGSNAG_REGISTER_OOM_BOOTSTRAPPER + - BUGSNAG_DISCARD_CLASSES restart: "no" ports: - target: 8000 @@ -46,6 +48,7 @@ services: - BUGSNAG_CAPTURE_SESSIONS - BUGSNAG_USE_CUSTOM_GUZZLE - BUGSNAG_REGISTER_OOM_BOOTSTRAPPER + - BUGSNAG_DISCARD_CLASSES restart: "no" ports: - target: 8000 @@ -63,6 +66,7 @@ services: - BUGSNAG_CAPTURE_SESSIONS - BUGSNAG_USE_CUSTOM_GUZZLE - BUGSNAG_REGISTER_OOM_BOOTSTRAPPER + - BUGSNAG_DISCARD_CLASSES restart: "no" ports: - target: 8000 From 38b65aea92d13b92e52bbe2a3f797395b9a4c8c8 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Thu, 4 Feb 2021 09:57:59 +0000 Subject: [PATCH 14/22] Add changelog entry --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 819e4c32..980201ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ Changelog [Lumen](https://docs.bugsnag.com/platforms/php/lumen/#reporting-out-of-memory-exceptions) [#430](https://github.com/bugsnag/bugsnag-laravel/pull/430) +* Support the new `discardClasses` configuration option. This allows events to be discarded based on the exception class name or PHP error name. + [#431](https://github.com/bugsnag/bugsnag-laravel/pull/431) + ## 2.21.0 (2020-11-25) ### Enhancements From d9c8354264a7d56e139d728e7850f602327e6592 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Mon, 8 Feb 2021 10:15:59 +0000 Subject: [PATCH 15/22] Add discard classes to facade methods --- src/Facades/Bugsnag.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Facades/Bugsnag.php b/src/Facades/Bugsnag.php index 8413c76c..0fc4f290 100644 --- a/src/Facades/Bugsnag.php +++ b/src/Facades/Bugsnag.php @@ -13,6 +13,7 @@ * @method static string getBuildEndpoint() * @method static \Bugsnag\Configuration getConfig() * @method static array getDeviceData() + * @method static array getDiscardClasses() * @method static array getFilters() * @method static int|null getMemoryLimitIncrease() * @method static array getMetaData() @@ -36,6 +37,7 @@ * @method static \Bugsnag\Client setAutoCaptureSessions(bool $track) * @method static \Bugsnag\Client setBatchSending(bool $batchSending) * @method static \Bugsnag\Client setBuildEndpoint(string $endpoint) + * @method static \Bugsnag\Client setDiscardClasses(array $discardClasses) * @method static \Bugsnag\Client setErrorReportingLevel(int|null $errorReportingLevel) * @method static \Bugsnag\Client setFallbackType(string|null $type) * @method static \Bugsnag\Client setFilters(array $filters) From 0ec68943dddc9cefd962a613d3bc4728f4c99275 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Mon, 8 Feb 2021 10:10:20 +0000 Subject: [PATCH 16/22] Support the new redacted keys option --- config/bugsnag.php | 11 +++++++++++ src/BugsnagServiceProvider.php | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/config/bugsnag.php b/config/bugsnag.php index c3204549..a5c6da42 100644 --- a/config/bugsnag.php +++ b/config/bugsnag.php @@ -317,4 +317,15 @@ */ 'discard_classes' => empty(env('BUGSNAG_DISCARD_CLASSES')) ? null : explode(',', env('BUGSNAG_DISCARD_CLASSES')), + + /* + |-------------------------------------------------------------------------- + | Redacted Keys + |-------------------------------------------------------------------------- + | + | An array of metadata keys that should be redacted. + | + */ + + 'redacted_keys' => empty(env('BUGSNAG_REDACTED_KEYS')) ? null : explode(',', env('BUGSNAG_REDACTED_KEYS')), ]; diff --git a/src/BugsnagServiceProvider.php b/src/BugsnagServiceProvider.php index a5b7dfff..d37d6deb 100644 --- a/src/BugsnagServiceProvider.php +++ b/src/BugsnagServiceProvider.php @@ -241,6 +241,10 @@ public function register() $client->setDiscardClasses($config['discard_classes']); } + if (isset($config['redacted_keys']) && is_array($config['redacted_keys'])) { + $client->setRedactedKeys($config['redacted_keys']); + } + return $client; }); From 2354d661dead9070b641a34c6fd0058c02a35ffc Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Mon, 8 Feb 2021 10:10:31 +0000 Subject: [PATCH 17/22] Add MazeRunner tests for redacted keys --- features/fixtures/docker-compose.yml | 4 ++++ features/redacted_keys.feature | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 features/redacted_keys.feature diff --git a/features/fixtures/docker-compose.yml b/features/fixtures/docker-compose.yml index 542d94f8..6477d37e 100644 --- a/features/fixtures/docker-compose.yml +++ b/features/fixtures/docker-compose.yml @@ -13,6 +13,7 @@ services: - BUGSNAG_USE_CUSTOM_GUZZLE - BUGSNAG_REGISTER_OOM_BOOTSTRAPPER - BUGSNAG_DISCARD_CLASSES + - BUGSNAG_REDACTED_KEYS restart: "no" ports: - target: 8000 @@ -31,6 +32,7 @@ services: - BUGSNAG_USE_CUSTOM_GUZZLE - BUGSNAG_REGISTER_OOM_BOOTSTRAPPER - BUGSNAG_DISCARD_CLASSES + - BUGSNAG_REDACTED_KEYS restart: "no" ports: - target: 8000 @@ -49,6 +51,7 @@ services: - BUGSNAG_USE_CUSTOM_GUZZLE - BUGSNAG_REGISTER_OOM_BOOTSTRAPPER - BUGSNAG_DISCARD_CLASSES + - BUGSNAG_REDACTED_KEYS restart: "no" ports: - target: 8000 @@ -67,6 +70,7 @@ services: - BUGSNAG_USE_CUSTOM_GUZZLE - BUGSNAG_REGISTER_OOM_BOOTSTRAPPER - BUGSNAG_DISCARD_CLASSES + - BUGSNAG_REDACTED_KEYS restart: "no" ports: - target: 8000 diff --git a/features/redacted_keys.feature b/features/redacted_keys.feature new file mode 100644 index 00000000..9354bef6 --- /dev/null +++ b/features/redacted_keys.feature @@ -0,0 +1,20 @@ +Feature: Redacted keys + +Scenario: Keys won't be redacted with no redacted keys + Given I start the laravel fixture + When I navigate to the route "/unhandled_controller_exception" + Then I wait to receive a request + And the request is valid for the error reporting API version "4.0" for the "Bugsnag Laravel" notifier + And the event "metaData.request.httpMethod" equals "GET" + And the event "metaData.request.url" ends with "/unhandled_controller_exception" + And the event "metaData.request.userAgent" equals "Ruby" + +Scenario: Keys can be redacted from metadata + Given I set environment variable "BUGSNAG_REDACTED_KEYS" to "HTTPmethod,/^url$/" + And I start the laravel fixture + When I navigate to the route "/unhandled_controller_exception" + Then I wait to receive a request + And the request is valid for the error reporting API version "4.0" for the "Bugsnag Laravel" notifier + And the event "metaData.request.httpMethod" equals "[FILTERED]" + And the event "metaData.request.url" equals "[FILTERED]" + And the event "metaData.request.userAgent" equals "Ruby" From a0c07ffc9747ad8e62fa9780e28cd5e5ea3e15d5 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Mon, 8 Feb 2021 10:14:24 +0000 Subject: [PATCH 18/22] Add deprecation notice to filters --- config/bugsnag.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/bugsnag.php b/config/bugsnag.php index a5c6da42..d4016be6 100644 --- a/config/bugsnag.php +++ b/config/bugsnag.php @@ -75,6 +75,8 @@ | passwords, and credit card numbers to our servers. Any keys which | contain these strings will be filtered. | + | This option has been deprecated in favour of 'redacted_keys' + | */ 'filters' => empty(env('BUGSNAG_FILTERS')) ? null : explode(',', str_replace(' ', '', env('BUGSNAG_FILTERS'))), From 8dd3757295166f57316b50e686e977fa25621b0c Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Mon, 8 Feb 2021 10:14:39 +0000 Subject: [PATCH 19/22] Add redacted keys to facade methods --- src/Facades/Bugsnag.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Facades/Bugsnag.php b/src/Facades/Bugsnag.php index 0fc4f290..6ab69d17 100644 --- a/src/Facades/Bugsnag.php +++ b/src/Facades/Bugsnag.php @@ -20,6 +20,7 @@ * @method static array getNotifier() * @method static string getNotifyEndpoint() * @method static \Bugsnag\Pipeline getPipeline() + * @method static array getRedactedKeys() * @method static string getSessionEndpoint() * @method static \Bugsnag\SessionTracker getSessionTracker() * @method static string getStrippedFilePath(string $file) @@ -50,6 +51,7 @@ * @method static \Bugsnag\Client setProjectRoot(string|null $projectRoot) * @method static \Bugsnag\Client setProjectRootRegex(string|null $projectRootRegex) * @method static \Bugsnag\Client setReleaseStage(string|null $releaseStage) + * @method static \Bugsnag\Client setRedactedKeys(array $redactedKeys) * @method static \Bugsnag\Client setSendCode(bool $sendCode) * @method static \Bugsnag\Client setSessionEndpoint(string $endpoint) * @method static \Bugsnag\Client setStripPath(string|null $stripPath) From 88d6a32b54134639512cc12ea25a1133682cee6e Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Mon, 8 Feb 2021 10:14:49 +0000 Subject: [PATCH 20/22] Add redacted keys to changelog --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 980201ee..70153e2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,13 @@ Changelog * Support the new `discardClasses` configuration option. This allows events to be discarded based on the exception class name or PHP error name. [#431](https://github.com/bugsnag/bugsnag-laravel/pull/431) +* Support the new `redactedKeys` configuration option. This is similar to `filters` but allows both strings and regexes. String matching is exact but case-insensitive. Regex matching allows for partial and wildcard matching. + [#432](https://github.com/bugsnag/bugsnag-laravel/pull/432) + +### Deprecations + +* The `filters` configuration option is now deprecated as `redactedKeys` can express everything that filters could and more. + ## 2.21.0 (2020-11-25) ### Enhancements From 389cbca3dd169ab8d908dcd4c21c81cb2fbf382e Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Wed, 10 Feb 2021 10:44:23 +0000 Subject: [PATCH 21/22] Add version to changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70153e2a..b68830e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ Changelog ========= -## TBD +## 2.22.0 (2021-02-10) ### Enhancements From 3a913a03d6fe118b345db4f66f39652fce5432b5 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Wed, 10 Feb 2021 10:44:29 +0000 Subject: [PATCH 22/22] Update version --- src/BugsnagServiceProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BugsnagServiceProvider.php b/src/BugsnagServiceProvider.php index d37d6deb..e5499e87 100644 --- a/src/BugsnagServiceProvider.php +++ b/src/BugsnagServiceProvider.php @@ -36,7 +36,7 @@ class BugsnagServiceProvider extends ServiceProvider * * @var string */ - const VERSION = '2.21.0'; + const VERSION = '2.22.0'; /** * Boot the service provider.