├── Gemfile ├── LICENSE.txt ├── UPGRADING.md ├── composer.json ├── config └── bugsnag.php └── src ├── BugsnagServiceProvider.php ├── Commands └── DeployCommand.php ├── EventTrait.php ├── Facades └── Bugsnag.php ├── Internal └── BacktraceProcessor.php ├── LaravelLogger.php ├── Middleware └── UnhandledState.php ├── MultiLogger.php ├── OctaneEventSubscriber.php ├── OomBootstrapper.php ├── Queue └── Tracker.php └── Request ├── LaravelRequest.php └── LaravelResolver.php /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem "bugsnag-maze-runner", "~> 9.14" 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Bugsnag 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /UPGRADING.md: -------------------------------------------------------------------------------- 1 | Upgrading 2 | ========= 3 | 4 | 5 | ## Laravel 5.5 to 5.6 6 | 7 | Laravel 5.6 has a brand new Monolog-base logging system changing the integration 8 | point for the Bugsnag handlers. The `Illuminate\Contracts\Logging\Log` class has 9 | been removed in favor of `Illuminate\Log\LogManager`. 10 | 11 | To upgrade, remove the existing Bugsnag logging integration from the `register` 12 | method of `app/Providers/AppServiceProvider.php`: 13 | 14 | ```diff 15 | - $this->app->alias('bugsnag.logger', \Illuminate\Contracts\Logging\Log::class); 16 | - $this->app->alias('bugsnag.logger', \Psr\Log\LoggerInterface::class); 17 | ``` 18 | 19 | Or if using the multi-logger: 20 | 21 | ```diff 22 | - $this->app->alias('bugsnag.multi', \Illuminate\Contracts\Logging\Log::class); 23 | - $this->app->alias('bugsnag.multi', \Psr\Log\LoggerInterface::class); 24 | ``` 25 | 26 | 27 | Then add Bugsnag to your logging stack in `config/logging.php`: 28 | 29 | ```php 30 | 'channels' => [ 31 | 'stack' => [ 32 | 'driver' => 'stack', 33 | // Add bugsnag to the stack: 34 | 'channels' => ['single', 'bugsnag'], 35 | ], 36 | 37 | // ... 38 | 39 | // Create a bugsnag logging channel: 40 | 'bugsnag' => [ 41 | 'driver' => 'bugsnag', 42 | ], 43 | ], 44 | ``` 45 | 46 | References: 47 | * The [bugsnag-laravel integration guide](https://docs.bugsnag.com/platforms/php/laravel/) 48 | * Our [blog post about the new changes in Laravel 5.6](https://blog.bugsnag.com/laravel-5-6/) 49 | * [Laravel 5.6 Logging documentation](https://laravel.com/docs/5.6/logging) 50 | 51 | 52 | ## 1.x to 2.x 53 | 54 | *Our library has gone through some major improvements. The primary change to watch out for is we're no longer overriding your exception handler.* 55 | 56 | Since we're no longer overriding your exception handler, you'll need to restore your original handler, and then see our [new integration guide](http://docs.bugsnag.com/platforms/php/laravel/) for how to bind our new logger to the container. 57 | 58 | If you'd like access to all our new configuration, you'll need to re-publish our config file. 59 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bugsnag/bugsnag-laravel", 3 | "description": "Official Bugsnag notifier for Laravel applications.", 4 | "keywords": ["bugsnag", "exceptions", "errors", "logging", "tracking", "laravel"], 5 | "homepage": "https://github.com/bugsnag/bugsnag-laravel", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "James Smith", 10 | "email": "notifiers@bugsnag.com" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=5.5", 15 | "bugsnag/bugsnag": "^3.29.0", 16 | "bugsnag/bugsnag-psr-logger": "^1.4|^2.0", 17 | "illuminate/contracts": "^5.0|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", 18 | "illuminate/support": "^5.0|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", 19 | "monolog/monolog": "^1.12|^2.0|^3.0" 20 | }, 21 | "require-dev": { 22 | "orchestra/testbench": "^3.1|^4.0|^5.0|^6.0|^7.0|^8.0|^9.0|^10.0", 23 | "phpunit/phpunit": "^4.8.36|^6.3.1|^7.5.15|^8.3.5|^9.3.10|^10.0|^11.0" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "Bugsnag\\BugsnagLaravel\\": "src/" 28 | } 29 | }, 30 | "autoload-dev": { 31 | "psr-4" : { 32 | "Bugsnag\\BugsnagLaravel\\Tests\\" : "tests/" 33 | }, 34 | "files": [ 35 | "tests/bc.php" 36 | ] 37 | }, 38 | "extra": { 39 | "branch-alias": { 40 | "dev-master": "2.18-dev" 41 | } 42 | }, 43 | "scripts": { 44 | "test": "phpunit" 45 | }, 46 | "minimum-stability": "dev", 47 | "prefer-stable": true, 48 | "config": { 49 | "allow-plugins": { 50 | "kylekatarnls/update-helper": false 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /config/bugsnag.php: -------------------------------------------------------------------------------- 1 | env('BUGSNAG_API_KEY', ''), 18 | 19 | /* 20 | |-------------------------------------------------------------------------- 21 | | App Type 22 | |-------------------------------------------------------------------------- 23 | | 24 | | Set the type of application executing the current code. 25 | | 26 | */ 27 | 28 | 'app_type' => env('BUGSNAG_APP_TYPE'), 29 | 30 | /* 31 | |-------------------------------------------------------------------------- 32 | | App Version 33 | |-------------------------------------------------------------------------- 34 | | 35 | | Set the version of application executing the current code. 36 | | 37 | */ 38 | 39 | 'app_version' => env('BUGSNAG_APP_VERSION'), 40 | 41 | /* 42 | |-------------------------------------------------------------------------- 43 | | Batch Sending 44 | |-------------------------------------------------------------------------- 45 | | 46 | | Set to true to send the errors through to Bugsnag when the PHP process 47 | | shuts down, in order to prevent your app waiting on HTTP requests. 48 | | 49 | | Setting this to false will send an HTTP request straight away for each 50 | | error. 51 | | 52 | */ 53 | 54 | 'batch_sending' => env('BUGSNAG_BATCH_SENDING'), 55 | 56 | /* 57 | |-------------------------------------------------------------------------- 58 | | Endpoint 59 | |-------------------------------------------------------------------------- 60 | | 61 | | Set what server the Bugsnag notifier should send errors to. By default 62 | | this is set to 'https://notify.bugsnag.com', but for Bugsnag Enterprise 63 | | this should be the URL to your Bugsnag instance. 64 | | 65 | */ 66 | 67 | 'endpoint' => env('BUGSNAG_ENDPOINT'), 68 | 69 | /* 70 | |-------------------------------------------------------------------------- 71 | | Filters 72 | |-------------------------------------------------------------------------- 73 | | 74 | | Use this if you want to ensure you don't send sensitive data such as 75 | | passwords, and credit card numbers to our servers. Any keys which 76 | | contain these strings will be filtered. 77 | | 78 | | This option has been deprecated in favour of 'redacted_keys' 79 | | 80 | */ 81 | 82 | 'filters' => empty(env('BUGSNAG_FILTERS')) ? null : explode(',', str_replace(' ', '', env('BUGSNAG_FILTERS'))), 83 | 84 | /* 85 | |-------------------------------------------------------------------------- 86 | | Hostname 87 | |-------------------------------------------------------------------------- 88 | | 89 | | You can set the hostname of your server to something specific for you to 90 | | identify it by if needed. 91 | | 92 | */ 93 | 94 | 'hostname' => env('BUGSNAG_HOSTNAME'), 95 | 96 | /* 97 | |-------------------------------------------------------------------------- 98 | | Proxy 99 | |-------------------------------------------------------------------------- 100 | | 101 | | This is where you can set the proxy settings you'd like us to use when 102 | | communicating with Bugsnag when reporting errors. 103 | | 104 | */ 105 | 106 | 'proxy' => array_filter([ 107 | 'http' => env('HTTP_PROXY'), 108 | 'https' => env('HTTPS_PROXY'), 109 | 'no' => empty(env('NO_PROXY')) ? null : explode(',', str_replace(' ', '', env('NO_PROXY'))), 110 | ]), 111 | 112 | /* 113 | |-------------------------------------------------------------------------- 114 | | Project Root 115 | |-------------------------------------------------------------------------- 116 | | 117 | | Bugsnag marks stacktrace lines as in-project if they come from files 118 | | inside your “project root”. You can set this here. 119 | | 120 | | If this is not set, we will automatically try to detect it. 121 | | 122 | */ 123 | 124 | 'project_root' => env('BUGSNAG_PROJECT_ROOT'), 125 | 126 | /* 127 | |-------------------------------------------------------------------------- 128 | | Project Root Regex 129 | |-------------------------------------------------------------------------- 130 | | 131 | | Bugsnag marks stacktrace lines as in-project if they come from files 132 | | inside your “project root”. You can set this here. 133 | | 134 | | This option allows you to set it as a regular expression and will take 135 | | precedence over "project_root" if both are defined. 136 | | 137 | */ 138 | 139 | 'project_root_regex' => env('BUGSNAG_PROJECT_ROOT_REGEX'), 140 | 141 | /* 142 | |-------------------------------------------------------------------------- 143 | | Strip Path 144 | |-------------------------------------------------------------------------- 145 | | 146 | | The strip path is a path to be trimmed from the start of any filepaths in 147 | | your stacktraces. 148 | | 149 | | If this is not set, we will automatically try to detect it. 150 | | 151 | */ 152 | 153 | 'strip_path' => env('BUGSNAG_STRIP_PATH'), 154 | 155 | /* 156 | |-------------------------------------------------------------------------- 157 | | Strip Path Regex 158 | |-------------------------------------------------------------------------- 159 | | 160 | | The strip path is a path to be trimmed from the start of any filepaths in 161 | | your stacktraces. 162 | | 163 | | This option allows you to set it as a regular expression and will take 164 | | precedence over "strip_path" if both are defined. 165 | | 166 | */ 167 | 168 | 'strip_path_regex' => env('BUGSNAG_STRIP_PATH_REGEX'), 169 | 170 | /* 171 | |-------------------------------------------------------------------------- 172 | | Query 173 | |-------------------------------------------------------------------------- 174 | | 175 | | Enable this if you'd like us to automatically record all queries executed 176 | | as breadcrumbs. 177 | | 178 | */ 179 | 180 | 'query' => env('BUGSNAG_QUERY', true), 181 | 182 | /* 183 | |-------------------------------------------------------------------------- 184 | | Bindings 185 | |-------------------------------------------------------------------------- 186 | | 187 | | Enable this if you'd like us to include the query bindings in our query 188 | | breadcrumbs. 189 | | 190 | */ 191 | 192 | 'bindings' => env('BUGSNAG_QUERY_BINDINGS', false), 193 | 194 | /* 195 | |-------------------------------------------------------------------------- 196 | | Octane breadcrumbs 197 | |-------------------------------------------------------------------------- 198 | | 199 | | Enable this if you'd like us to automatically record breadcrumbs from 200 | | octane events. 201 | | 202 | */ 203 | 204 | 'octane_breadcrumbs' => env('BUGSNAG_OCTANE_BREADCRUMBS', true), 205 | 206 | /* 207 | |-------------------------------------------------------------------------- 208 | | Release Stage 209 | |-------------------------------------------------------------------------- 210 | | 211 | | Set the release stage to use when sending notifications to Bugsnag. 212 | | 213 | | Leaving this unset will default to using the application environment. 214 | | 215 | */ 216 | 217 | 'release_stage' => env('BUGSNAG_RELEASE_STAGE'), 218 | 219 | /* 220 | |-------------------------------------------------------------------------- 221 | | Notify Release Stages 222 | |-------------------------------------------------------------------------- 223 | | 224 | | Set which release stages should send notifications to Bugsnag. 225 | | 226 | */ 227 | 228 | 'notify_release_stages' => empty(env('BUGSNAG_NOTIFY_RELEASE_STAGES')) ? null : explode(',', str_replace(' ', '', env('BUGSNAG_NOTIFY_RELEASE_STAGES'))), 229 | 230 | /* 231 | |-------------------------------------------------------------------------- 232 | | Send Code 233 | |-------------------------------------------------------------------------- 234 | | 235 | | Bugsnag automatically sends a small snippet of the code that crashed to 236 | | help you diagnose even faster from within your dashboard. If you don’t 237 | | want to send this snippet, then set this to false. 238 | | 239 | */ 240 | 241 | 'send_code' => env('BUGSNAG_SEND_CODE', true), 242 | 243 | /* 244 | |-------------------------------------------------------------------------- 245 | | Callbacks 246 | |-------------------------------------------------------------------------- 247 | | 248 | | Enable this if you'd like us to enable our default set of notification 249 | | callbacks. These add things like the cookie information and session 250 | | details to the error to be sent to Bugsnag. 251 | | 252 | | If you'd like to add your own callbacks, you can call the 253 | | Bugsnag::registerCallback method from the boot method of your app 254 | | service provider. 255 | | 256 | */ 257 | 258 | 'callbacks' => env('BUGSNAG_CALLBACKS', true), 259 | 260 | /* 261 | |-------------------------------------------------------------------------- 262 | | User 263 | |-------------------------------------------------------------------------- 264 | | 265 | | Enable this if you'd like us to set the current user logged in via 266 | | Laravel's authentication system. 267 | | 268 | | If you'd like to add your own user resolver, you can do this by using 269 | | callbacks via Bugsnag::registerCallback. 270 | | 271 | */ 272 | 273 | 'user' => env('BUGSNAG_USER', true), 274 | 275 | /* 276 | |-------------------------------------------------------------------------- 277 | | Logger Notify Level 278 | |-------------------------------------------------------------------------- 279 | | 280 | | This sets the level at which a logged message will trigger a notification 281 | | to Bugsnag. By default this level will be 'notice'. 282 | | 283 | | Must be one of the Psr\Log\LogLevel levels from the Psr specification. 284 | | 285 | */ 286 | 287 | 'logger_notify_level' => env('BUGSNAG_LOGGER_LEVEL'), 288 | 289 | /* 290 | |-------------------------------------------------------------------------- 291 | | Auto Capture Sessions 292 | |-------------------------------------------------------------------------- 293 | | 294 | | Enable this to start tracking sessions and deliver them to Bugsnag. 295 | | 296 | */ 297 | 298 | 'auto_capture_sessions' => env('BUGSNAG_CAPTURE_SESSIONS', false), 299 | 300 | /* 301 | |-------------------------------------------------------------------------- 302 | | Sessions Endpoint 303 | |-------------------------------------------------------------------------- 304 | | 305 | | Sets a url to send tracked sessions to. 306 | | 307 | */ 308 | 309 | 'session_endpoint' => env('BUGSNAG_SESSION_ENDPOINT'), 310 | 311 | /* 312 | |-------------------------------------------------------------------------- 313 | | Builds Endpoint 314 | |-------------------------------------------------------------------------- 315 | | 316 | | Sets a url to send build reports to. 317 | | 318 | */ 319 | 320 | 'build_endpoint' => env('BUGSNAG_BUILD_ENDPOINT'), 321 | 322 | /* 323 | |-------------------------------------------------------------------------- 324 | | Discard Classes 325 | |-------------------------------------------------------------------------- 326 | | 327 | | An array of classes that should not be sent to Bugsnag. 328 | | 329 | | This can contain both fully qualified class names and regular expressions. 330 | | 331 | */ 332 | 333 | 'discard_classes' => empty(env('BUGSNAG_DISCARD_CLASSES')) ? null : explode(',', env('BUGSNAG_DISCARD_CLASSES')), 334 | 335 | /* 336 | |-------------------------------------------------------------------------- 337 | | Redacted Keys 338 | |-------------------------------------------------------------------------- 339 | | 340 | | An array of metadata keys that should be redacted. 341 | | 342 | */ 343 | 344 | 'redacted_keys' => empty(env('BUGSNAG_REDACTED_KEYS')) ? null : explode(',', env('BUGSNAG_REDACTED_KEYS')), 345 | 346 | /* 347 | |-------------------------------------------------------------------------- 348 | | Feature flags 349 | |-------------------------------------------------------------------------- 350 | | 351 | | An array of feature flags to add to all reports. 352 | | 353 | | Each element in the array must have a "name" key and can optionally have a 354 | | "variant" key, for example: 355 | | 356 | | [ 357 | | ['name' => 'example without a variant'], 358 | | ['name' => 'example with a variant', 'variant' => 'example of a variant'], 359 | | ] 360 | | 361 | */ 362 | 363 | 'feature_flags' => [], 364 | 365 | /* 366 | |-------------------------------------------------------------------------- 367 | | Max breadcrumbs 368 | |-------------------------------------------------------------------------- 369 | | 370 | | The maximum number of breadcrumbs to send with a report. 371 | | 372 | | This should be an integer between 0-100 (inclusive). 373 | | 374 | */ 375 | 376 | 'max_breadcrumbs' => null, 377 | 378 | /* 379 | |-------------------------------------------------------------------------- 380 | | Attach hidden context 381 | |-------------------------------------------------------------------------- 382 | | 383 | | Whether to attach hidden Context data to events as metadata. 384 | | 385 | */ 386 | 387 | 'attach_hidden_context' => env('BUGSNAG_ATTACH_HIDDEN_CONTEXT', false), 388 | ]; 389 | -------------------------------------------------------------------------------- /src/BugsnagServiceProvider.php: -------------------------------------------------------------------------------- 1 | setupConfig($this->app); 53 | 54 | $this->setupEvents($this->app->events, $this->app->config->get('bugsnag')); 55 | 56 | $this->setupQueue($this->app->queue); 57 | 58 | // Load the Client instance up-front if the OOM bootstrapper has been 59 | // loaded. This avoids the possibility of initialising during an OOM, 60 | // which can take a non-trivial amount of memory 61 | if (class_exists(OomBootstrapper::class, false) && !$this->app->runningUnitTests()) { 62 | $this->app->make('bugsnag'); 63 | } 64 | 65 | // Octane support - register event listeners to flush Bugsnag data on request/task/worker termination 66 | // TODO set up automatic breadcrumbs for each event 67 | if ($this->app->bound('octane')) { 68 | Event::subscribe(OctaneEventSubscriber::class); 69 | } 70 | } 71 | 72 | /** 73 | * Setup the config. 74 | * 75 | * @param \Illuminate\Contracts\Container\Container $app 76 | * 77 | * @return void 78 | */ 79 | protected function setupConfig(Container $app) 80 | { 81 | $source = realpath($raw = __DIR__.'/../config/bugsnag.php') ?: $raw; 82 | 83 | if ($app instanceof LaravelApplication && $app->runningInConsole()) { 84 | $this->publishes([$source => config_path('bugsnag.php')]); 85 | } elseif ($app instanceof LumenApplication) { 86 | $app->configure('bugsnag'); 87 | } 88 | 89 | $this->mergeConfigFrom($source, 'bugsnag'); 90 | } 91 | 92 | /** 93 | * Setup the events. 94 | * 95 | * @param \Illuminate\Contracts\Events\Dispatcher $events 96 | * @param array $config 97 | * 98 | * @return void 99 | */ 100 | protected function setupEvents(Dispatcher $events, array $config) 101 | { 102 | if ($this->isSessionTrackingAllowed($config)) { 103 | $events->listen(RouteMatched::class, function ($event) { 104 | $this->app->bugsnag->getSessionTracker()->startSession(); 105 | }); 106 | } 107 | 108 | if (isset($config['query']) && !$config['query']) { 109 | return; 110 | } 111 | 112 | $show = isset($config['bindings']) && $config['bindings']; 113 | 114 | if (class_exists(QueryExecuted::class)) { 115 | $events->listen(QueryExecuted::class, function (QueryExecuted $query) use ($show) { 116 | $this->app->bugsnag->leaveBreadcrumb( 117 | 'Query executed', 118 | Breadcrumb::PROCESS_TYPE, 119 | $this->formatQuery($query->sql, $show ? $query->bindings : [], $query->time, $query->connectionName) 120 | ); 121 | }); 122 | } else { 123 | $events->listen('illuminate.query', function ($sql, array $bindings, $time, $connection) use ($show) { 124 | $this->app->bugsnag->leaveBreadcrumb( 125 | 'Query executed', 126 | Breadcrumb::PROCESS_TYPE, 127 | $this->formatQuery($sql, $show ? $bindings : [], $time, $connection) 128 | ); 129 | }); 130 | } 131 | } 132 | 133 | /** 134 | * Format the query as breadcrumb metadata. 135 | * 136 | * @param string $sql 137 | * @param array $bindings 138 | * @param float $time 139 | * @param string $connection 140 | * 141 | * @return array 142 | */ 143 | protected function formatQuery($sql, array $bindings, $time, $connection) 144 | { 145 | $data = ['sql' => $sql]; 146 | 147 | foreach ($bindings as $index => $binding) { 148 | $data["binding {$index}"] = $binding; 149 | } 150 | 151 | $data['time'] = "{$time}ms"; 152 | $data['connection'] = $connection; 153 | 154 | return $data; 155 | } 156 | 157 | /** 158 | * Setup the queue. 159 | * 160 | * @param \Illuminate\Queue\QueueManager $queue 161 | * 162 | * @return void 163 | */ 164 | protected function setupQueue(QueueManager $queue) 165 | { 166 | $queue->looping(function () { 167 | $this->app->bugsnag->flush(); 168 | $this->app->bugsnag->clearBreadcrumbs(); 169 | $this->app->make(Tracker::class)->clear(); 170 | }); 171 | 172 | if (!class_exists(JobProcessing::class)) { 173 | return; 174 | } 175 | 176 | $queue->before(function (JobProcessing $event) { 177 | $this->app->bugsnag->setFallbackType('Queue'); 178 | 179 | $job = [ 180 | 'name' => $event->job->getName(), 181 | 'queue' => $event->job->getQueue(), 182 | 'attempts' => $event->job->attempts(), 183 | 'connection' => $event->connectionName, 184 | ]; 185 | 186 | if (method_exists($event->job, 'resolveName')) { 187 | $job['resolved'] = $event->job->resolveName(); 188 | } 189 | 190 | $this->app->make(Tracker::class)->set($job); 191 | }); 192 | 193 | $this->setupQueueForLaravelVapor($queue); 194 | } 195 | 196 | /** 197 | * Setup queue events for Laravel Vapor. 198 | * 199 | * This is required because Laravel Vapor's queue system doesn't behave as 200 | * a daemonised queue worker (the 'looping' event never fires) but also 201 | * doesn't behave as a non-daemonised queue worker (our shutdown function 202 | * never fires). 203 | * 204 | * @param QueueManager $queue 205 | * 206 | * @return void 207 | */ 208 | private function setupQueueForLaravelVapor(QueueManager $queue) 209 | { 210 | // ensure we're running on vapor 211 | // this is how vapor-core does it, e.g.: 212 | // https://github.com/laravel/vapor-core/blob/61437221090850ba6e51dce15d0058d362654f9b/src/ConfiguresAssets.php#L16-L19 213 | if (!isset($_ENV['VAPOR_SSM_PATH'])) { 214 | return; 215 | } 216 | 217 | // used to keep track of if we're the ones disabling batch sending, so we 218 | // know if we need to re-enable it - if the user disables batch sending 219 | // then they don't want it enabled at all 220 | static $batchSendingWasDisabledByUs = false; 221 | 222 | $queue->before(function () use (&$batchSendingWasDisabledByUs) { 223 | // clear breadcrumbs to stop them leaking between jobs 224 | $this->app->bugsnag->clearBreadcrumbs(); 225 | 226 | // only re-enable batch sending if we're the ones disabling it 227 | // this allows users to disable batch sending entirely 228 | if ($batchSendingWasDisabledByUs) { 229 | $this->app->bugsnag->setBatchSending(true); 230 | } 231 | }); 232 | 233 | $flush = function () use (&$batchSendingWasDisabledByUs) { 234 | // flush any events created in this job 235 | $this->app->bugsnag->flush(); 236 | 237 | // disable batch sending so any events after this get sent synchronously 238 | // this is important as exceptions are logged after the 'exceptionOccurred' 239 | // event fires, so the above flush is too early to send them 240 | // these exceptions would get sent after processing the next queued job, 241 | // but we'd still drop the last event when this queue worker stops running 242 | if ($this->app->bugsnag->isBatchSending()) { 243 | $this->app->bugsnag->setBatchSending(false); 244 | $batchSendingWasDisabledByUs = true; 245 | } 246 | }; 247 | 248 | // added in 5.2.41 249 | if (method_exists($queue, 'after')) { 250 | $queue->after($flush); 251 | } 252 | 253 | // added in 5.2.41 254 | if (method_exists($queue, 'exceptionOccurred')) { 255 | $queue->exceptionOccurred($flush); 256 | } 257 | } 258 | 259 | /** 260 | * Register the service provider. 261 | * 262 | * @return void 263 | */ 264 | public function register() 265 | { 266 | $this->app->singleton('bugsnag', function (Container $app) { 267 | $config = $app->config->get('bugsnag'); 268 | $client = new Client(new Configuration($config['api_key']), new LaravelResolver($app), $this->getGuzzle($config)); 269 | 270 | $this->setupCallbacks($client, $app, $config); 271 | $this->setupPaths($client, $app, $config); 272 | 273 | $client->setReleaseStage(isset($config['release_stage']) ? $config['release_stage'] : $app->environment()); 274 | $client->setHostname(isset($config['hostname']) ? $config['hostname'] : null); 275 | $client->getConfig()->mergeDeviceData(['runtimeVersions' => $this->getRuntimeVersion()]); 276 | 277 | $client->setFallbackType($app->runningInConsole() ? 'Console' : 'HTTP'); 278 | $client->setAppType(isset($config['app_type']) ? $config['app_type'] : null); 279 | $client->setAppVersion(isset($config['app_version']) ? $config['app_version'] : null); 280 | $client->setBatchSending(isset($config['batch_sending']) ? $config['batch_sending'] : true); 281 | $client->setSendCode(isset($config['send_code']) ? $config['send_code'] : true); 282 | $client->getPipeline()->insertBefore(new UnhandledState(), 'Bugsnag\\Middleware\\SessionData'); 283 | 284 | $client->setNotifier([ 285 | 'name' => 'Bugsnag Laravel', 286 | 'version' => static::VERSION, 287 | 'url' => 'https://github.com/bugsnag/bugsnag-laravel', 288 | ]); 289 | 290 | if (isset($config['notify_release_stages']) && is_array($config['notify_release_stages'])) { 291 | $client->setNotifyReleaseStages($config['notify_release_stages']); 292 | } 293 | 294 | if (isset($config['filters']) && is_array($config['filters'])) { 295 | $client->setFilters($config['filters']); 296 | } 297 | 298 | if (isset($config['endpoint'])) { 299 | $client->setNotifyEndpoint($config['endpoint']); 300 | } 301 | 302 | if ($this->isSessionTrackingAllowed($config)) { 303 | $endpoint = isset($config['session_endpoint']) ? $config['session_endpoint'] : null; 304 | $this->setupSessionTracking($client, $endpoint, $this->app->events); 305 | } 306 | 307 | if (isset($config['build_endpoint'])) { 308 | $client->setBuildEndpoint($config['build_endpoint']); 309 | } 310 | 311 | if (array_key_exists('memory_limit_increase', $config)) { 312 | $client->setMemoryLimitIncrease($config['memory_limit_increase']); 313 | } 314 | 315 | if (isset($config['discard_classes']) && is_array($config['discard_classes'])) { 316 | $client->setDiscardClasses($config['discard_classes']); 317 | } 318 | 319 | if (isset($config['redacted_keys']) && is_array($config['redacted_keys'])) { 320 | $client->setRedactedKeys($config['redacted_keys']); 321 | } 322 | 323 | if (isset($config['feature_flags']) && is_array($config['feature_flags']) && $config['feature_flags'] !== []) { 324 | $featureFlags = []; 325 | 326 | foreach ($config['feature_flags'] as $flag) { 327 | if (!is_array($flag) || !array_key_exists('name', $flag)) { 328 | continue; 329 | } 330 | 331 | if (array_key_exists('variant', $flag)) { 332 | $featureFlags[] = new FeatureFlag($flag['name'], $flag['variant']); 333 | } else { 334 | $featureFlags[] = new FeatureFlag($flag['name']); 335 | } 336 | } 337 | 338 | $client->addFeatureFlags($featureFlags); 339 | } 340 | 341 | if (isset($config['max_breadcrumbs'])) { 342 | $client->setMaxBreadcrumbs($config['max_breadcrumbs']); 343 | } 344 | 345 | return $client; 346 | }); 347 | 348 | $this->app->singleton('bugsnag.tracker', function () { 349 | return new Tracker(); 350 | }); 351 | 352 | $this->app->singleton('bugsnag.logger', function (Container $app) { 353 | $config = $app->config->get('bugsnag'); 354 | $logger = interface_exists(Log::class) ? new LaravelLogger($app['bugsnag'], $app['events']) : new BugsnagLogger($app['bugsnag']); 355 | if (isset($config['logger_notify_level'])) { 356 | $logger->setNotifyLevel($config['logger_notify_level']); 357 | } 358 | 359 | return $logger; 360 | }); 361 | 362 | $this->app->singleton('bugsnag.multi', function (Container $app) { 363 | return interface_exists(Log::class) ? new MultiLogger([$app['log'], $app['bugsnag.logger']]) : new BaseMultiLogger([$app['log'], $app['bugsnag.logger']]); 364 | }); 365 | 366 | if ($this->app['log'] instanceof LogManager) { 367 | $this->app['log']->extend('bugsnag', function (Container $app, array $config) { 368 | $handler = new PsrHandler($app['bugsnag.logger']); 369 | 370 | return new Logger('bugsnag', [$handler]); 371 | }); 372 | } 373 | 374 | $this->app->alias('bugsnag', Client::class); 375 | $this->app->alias('bugsnag.tracker', Tracker::class); 376 | $this->app->alias('bugsnag.logger', interface_exists(Log::class) ? LaravelLogger::class : BugsnagLogger::class); 377 | $this->app->alias('bugsnag.multi', interface_exists(Log::class) ? MultiLogger::class : BaseMultiLogger::class); 378 | } 379 | 380 | /** 381 | * Get the guzzle client instance. 382 | * 383 | * @param array $config 384 | * 385 | * @return \GuzzleHttp\ClientInterface 386 | */ 387 | protected function getGuzzle(array $config) 388 | { 389 | // If a 'bugsnag.guzzle' instance exists in the container, use it 390 | if ($this->app->bound('bugsnag.guzzle')) { 391 | return $this->app->make('bugsnag.guzzle'); 392 | } 393 | 394 | $options = []; 395 | 396 | if (isset($config['proxy']) && $config['proxy']) { 397 | if (isset($config['proxy']['http']) && php_sapi_name() != 'cli') { 398 | unset($config['proxy']['http']); 399 | } 400 | 401 | $options['proxy'] = $config['proxy']; 402 | } 403 | 404 | return Client::makeGuzzle(null, $options); 405 | } 406 | 407 | /** 408 | * Setup the callbacks. 409 | * 410 | * @param \Bugsnag\Client $client 411 | * @param \Illuminate\Contracts\Container\Container $app 412 | * @param array $config 413 | * 414 | * @return void 415 | */ 416 | protected function setupCallbacks(Client $client, Container $app, array $config) 417 | { 418 | if (!isset($config['callbacks']) || $config['callbacks']) { 419 | $client->registerDefaultCallbacks(); 420 | 421 | $client->registerCallback(function (Report $report) use ($app) { 422 | $tracker = $app->make(Tracker::class); 423 | 424 | if ($context = $tracker->context()) { 425 | $report->setContext($context); 426 | } 427 | 428 | if ($job = $tracker->get()) { 429 | $report->setMetaData(['job' => $job]); 430 | } 431 | }); 432 | } 433 | 434 | if (!isset($config['user']) || $config['user']) { 435 | $client->registerCallback(new CustomUser(function () use ($app) { 436 | if ($user = $app->auth->user()) { 437 | if (method_exists($user, 'attributesToArray') && is_callable([$user, 'attributesToArray'])) { 438 | return $user->attributesToArray(); 439 | } 440 | 441 | if ($user instanceof GenericUser) { 442 | $reflection = new ReflectionClass($user); 443 | $property = $reflection->getProperty('attributes'); 444 | $property->setAccessible(true); 445 | 446 | return $property->getValue($user); 447 | } 448 | } 449 | })); 450 | } 451 | 452 | // Laravel 11 has a 'Context' class for storing metadata 453 | // https://laravel.com/docs/11.x/context 454 | if (class_exists(Context::class)) { 455 | $client->registerCallback(function (Report $report) { 456 | $context = Context::all(); 457 | 458 | $report->setMetaData(['Laravel Context' => $context]); 459 | }); 460 | 461 | // only attach hidden context if enabled, otherwise sensitive data 462 | // could leak to bugsnag 463 | if (isset($config['attach_hidden_context']) && $config['attach_hidden_context']) { 464 | $client->registerCallback(function (Report $report) { 465 | $hiddenContext = Context::allHidden(); 466 | 467 | $report->setMetaData(['Laravel Hidden Context' => $hiddenContext]); 468 | }); 469 | 470 | } 471 | } 472 | } 473 | 474 | /** 475 | * Setup the client paths. 476 | * 477 | * @param \Bugsnag\Client $client 478 | * @param \Illuminate\Contracts\Container\Container $app 479 | * @param array $config 480 | * 481 | * @return void 482 | */ 483 | protected function setupPaths(Client $client, Container $app, array $config) 484 | { 485 | if (isset($config['project_root_regex'])) { 486 | $client->setProjectRootRegex($config['project_root_regex']); 487 | } elseif (isset($config['project_root'])) { 488 | $client->setProjectRoot($config['project_root']); 489 | } else { 490 | $client->setProjectRoot($app->path()); 491 | } 492 | 493 | if (isset($config['strip_path_regex'])) { 494 | $client->setStripPathRegex($config['strip_path_regex']); 495 | } elseif (isset($config['strip_path'])) { 496 | $client->setStripPath($config['strip_path']); 497 | } else { 498 | $client->setStripPath($app->basePath()); 499 | } 500 | } 501 | 502 | /** 503 | * Setup session tracking. 504 | * 505 | * @param \Bugsnag\Client $client 506 | * @param string $endpoint 507 | * 508 | * @return void 509 | */ 510 | protected function setupSessionTracking(Client $client, $endpoint, $events) 511 | { 512 | $client->setAutoCaptureSessions(true); 513 | if (!is_null($endpoint)) { 514 | $client->setSessionEndpoint($endpoint); 515 | } 516 | $sessionTracker = $client->getSessionTracker(); 517 | 518 | $sessionStorage = function ($session = null) { 519 | if (is_null($session)) { 520 | return session('bugsnag-session', []); 521 | } else { 522 | session(['bugsnag-session' => $session]); 523 | } 524 | }; 525 | 526 | $sessionTracker->setSessionFunction($sessionStorage); 527 | 528 | $cache = $this->app->cache; 529 | 530 | $genericStorage = function ($key, $value = null) use ($cache) { 531 | if (is_null($value)) { 532 | return $cache->get($key, null); 533 | } else { 534 | $cache->put($key, $value, new DateTime('+ 1 hour')); 535 | } 536 | }; 537 | 538 | $sessionTracker->setStorageFunction($genericStorage); 539 | } 540 | 541 | /** 542 | * Returns the framework name and version to add to the device data. 543 | * 544 | * Attempt to parse a semantic framework version from $app or else return 545 | * the full version string. 546 | * e.g. Lumen: "Lumen (x.x.x) (Laravel Components y.y.*)" => "x.x.x" 547 | * 548 | * @return array 549 | */ 550 | protected function getRuntimeVersion() 551 | { 552 | $version = $this->app->version(); 553 | if (preg_match('/(\d+\.\d+\.\d+)/', $version, $versionMatches)) { 554 | $version = $versionMatches[0]; 555 | } 556 | 557 | return [($this->app instanceof LumenApplication ? 'lumen' : 'laravel') => $version]; 558 | } 559 | 560 | /** 561 | * Tests whether session tracking can/should be enabled. 562 | * 563 | * @param array $config The configuration array 564 | * 565 | * @return bool true if session tracking should be enabled. 566 | */ 567 | protected function isSessionTrackingAllowed($config) 568 | { 569 | // Session support removed in Lumen 5.3 - only setup automatic session 570 | // tracking if the session function is avaiable 571 | return isset($config['auto_capture_sessions']) 572 | && $config['auto_capture_sessions'] 573 | && function_exists('session'); 574 | } 575 | 576 | /** 577 | * Get the services provided by the provider. 578 | * 579 | * @return array 580 | */ 581 | public function provides() 582 | { 583 | return ['bugsnag', 'bugsnag.tracker', 'bugsnag.logger', 'bugsnag.multi']; 584 | } 585 | } 586 | -------------------------------------------------------------------------------- /src/Commands/DeployCommand.php: -------------------------------------------------------------------------------- 1 | option('repository'), 35 | $this->option('revision'), 36 | $this->option('provider'), 37 | $this->option('builder') ?: Utils::getBuilderName() 38 | ); 39 | 40 | $this->info('Notified Bugsnag of the build!'); 41 | } 42 | 43 | /** 44 | * Execute the console command. 45 | * 46 | * @return void 47 | */ 48 | public function fire() 49 | { 50 | $this->handle(); 51 | } 52 | 53 | /** 54 | * Get the console command options. 55 | * 56 | * @return array 57 | */ 58 | protected function getOptions() 59 | { 60 | return [ 61 | ['repository', null, InputOption::VALUE_OPTIONAL, 'The repository from which you are deploying the code.', null], 62 | ['branch', null, InputOption::VALUE_OPTIONAL, 'The source control branch from which you are deploying. Deprecated.', null], 63 | ['revision', null, InputOption::VALUE_OPTIONAL, 'The source control revision you are currently deploying.', null], 64 | ['provider', null, InputOption::VALUE_OPTIONAL, 'The provider of your source control repository.', null], 65 | ['builder', null, InputOption::VALUE_OPTIONAL, 'The machine or person who has executed the build', null], 66 | ]; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/EventTrait.php: -------------------------------------------------------------------------------- 1 | dispatcher; 27 | } 28 | 29 | /** 30 | * Set the event dispatcher instance. 31 | * 32 | * @param \Illuminate\Contracts\Events\Dispatcher $dispatcher 33 | * 34 | * @return void 35 | */ 36 | public function setEventDispatcher(Dispatcher $dispatcher) 37 | { 38 | $this->dispatcher = $dispatcher; 39 | } 40 | 41 | /** 42 | * Register a new callback handler for when a log event is triggered. 43 | * 44 | * @param \Closure $callback 45 | * 46 | * @throws \RuntimeException 47 | * 48 | * @return void 49 | */ 50 | public function listen(Closure $callback) 51 | { 52 | if (!isset($this->dispatcher)) { 53 | throw new RuntimeException('Events dispatcher has not been set.'); 54 | } 55 | 56 | $this->dispatcher->listen(class_exists(MessageLogged::class) ? MessageLogged::class : 'illuminate.log', $callback); 57 | } 58 | 59 | /** 60 | * Log a message to the logs. 61 | * 62 | * @param string $level 63 | * @param mixed $message 64 | * @param array $context 65 | * 66 | * @return void 67 | */ 68 | public function log($level, $message, array $context = []) 69 | { 70 | parent::log($level, $message, $context); 71 | 72 | $this->fireLogEvent($level, $message, $context); 73 | } 74 | 75 | /** 76 | * Fires a log event. 77 | * 78 | * @param string $level 79 | * @param string $message 80 | * @param array $context 81 | * 82 | * @return void 83 | */ 84 | protected function fireLogEvent($level, $message, array $context = []) 85 | { 86 | // If the event dispatcher is set, we will pass along the parameters to the 87 | // log listeners. These are useful for building profilers or other tools 88 | // that aggregate all of the log messages for a given "request" cycle. 89 | if (!isset($this->dispatcher)) { 90 | return; 91 | } 92 | 93 | if (class_exists(MessageLogged::class)) { 94 | $this->dispatcher->dispatch(new MessageLogged($level, $message, $context)); 95 | } else { 96 | $this->dispatcher->fire('illuminate.log', compact('level', 'message', 'context')); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Facades/Bugsnag.php: -------------------------------------------------------------------------------- 1 | backtrace = $backtrace; 97 | } 98 | 99 | /** 100 | * Determine if the backtrace was from an unhandled error. 101 | * 102 | * @return bool 103 | */ 104 | public function isUnhandled() 105 | { 106 | foreach ($this->backtrace as $frame) { 107 | $this->processFrame($frame); 108 | 109 | // stop iterating early if we know we're done 110 | if ($this->state === self::STATE_DONE) { 111 | break; 112 | } 113 | } 114 | 115 | return $this->unhandled; 116 | } 117 | 118 | /** 119 | * @param array $frame 120 | * 121 | * @return void 122 | */ 123 | private function processFrame(array $frame) 124 | { 125 | if (!isset($frame['class'])) { 126 | return; 127 | } 128 | 129 | $class = $frame['class']; 130 | 131 | switch ($this->state) { 132 | case self::STATE_FRAMEWORK_HANDLER: 133 | // if this class is a framework exception handler and the function 134 | // matches self::HANDLER_METHOD, we can move on to searching for 135 | // the caller 136 | if ($this->isFrameworkExceptionHandler($class) 137 | && isset($frame['function']) 138 | && $frame['function'] === self::HANDLER_METHOD 139 | ) { 140 | $this->state = self::STATE_HANDLER_CALLER; 141 | } 142 | 143 | break; 144 | 145 | case self::STATE_HANDLER_CALLER: 146 | // if this is an app exception handler or a framework class, we 147 | // can move on to determine if this was unhandled or not 148 | if ($this->isAppExceptionHandler($class) || $this->isVendor($class)) { 149 | $this->state = self::STATE_IS_UNHANDLED; 150 | } 151 | 152 | break; 153 | 154 | case self::STATE_IS_UNHANDLED: 155 | // we are only interested in running this once so move immediately 156 | // into the "done" state. This ensures we only check the frame 157 | // immediately before the caller of the exception handler 158 | $this->state = self::STATE_DONE; 159 | 160 | // if this class is internal to the framework then the exception 161 | // was unhandled 162 | if ($this->isVendor($class)) { 163 | $this->unhandled = true; 164 | } 165 | 166 | break; 167 | } 168 | } 169 | 170 | /** 171 | * Does the given class belong to a vendor namespace? 172 | * 173 | * @see self::VENDOR_NAMESPACES 174 | * 175 | * @param string $class 176 | * 177 | * @return bool 178 | */ 179 | private function isVendor($class) 180 | { 181 | return $this->isInNamespace($class, self::LARAVEL_VENDOR_NAMESPACE) 182 | || $this->isInNamespace($class, self::LUMEN_VENDOR_NAMESPACE) 183 | || $this->isInNamespace($class, self::COLLISION_VENDOR_NAMESPACE); 184 | } 185 | 186 | /** 187 | * Check if the given class is in the given namespace. 188 | * 189 | * @param string $class 190 | * @param string $namespace 191 | * 192 | * @return bool 193 | */ 194 | private function isInNamespace($class, $namespace) 195 | { 196 | return substr($class, 0, strlen($namespace)) === $namespace; 197 | } 198 | 199 | /** 200 | * Is the given class Laravel or Lumen's exception handler? 201 | * 202 | * @param string $class 203 | * 204 | * @return bool 205 | */ 206 | private function isFrameworkExceptionHandler($class) 207 | { 208 | return $class === self::LARAVEL_HANDLER_CLASS 209 | || $class === self::LUMEN_HANDLER_CLASS; 210 | } 211 | 212 | /** 213 | * Is the given class an App's exception handler? 214 | * 215 | * @param string $class 216 | * 217 | * @return bool 218 | */ 219 | private function isAppExceptionHandler($class) 220 | { 221 | return $class === self::LARAVEL_APP_EXCEPTION_HANDLER 222 | || $class === self::LUMEN_APP_EXCEPTION_HANDLER; 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/LaravelLogger.php: -------------------------------------------------------------------------------- 1 | dispatcher = $dispatcher; 29 | } 30 | 31 | /** 32 | * Register a file log handler. 33 | * 34 | * @param string $path 35 | * @param string $level 36 | * 37 | * @return void 38 | */ 39 | public function useFiles($path, $level = 'debug') 40 | { 41 | // 42 | } 43 | 44 | /** 45 | * Register a daily file log handler. 46 | * 47 | * @param string $path 48 | * @param int $days 49 | * @param string $level 50 | * 51 | * @return void 52 | */ 53 | public function useDailyFiles($path, $days = 0, $level = 'debug') 54 | { 55 | // 56 | } 57 | 58 | /** 59 | * Get the underlying Monolog instance. 60 | * 61 | * @return \Monolog\Logger 62 | */ 63 | public function getMonolog() 64 | { 65 | // 66 | } 67 | 68 | /** 69 | * Format the parameters for the logger. 70 | * 71 | * @param mixed $message 72 | * 73 | * @return string 74 | */ 75 | protected function formatMessage($message) 76 | { 77 | if (is_array($message)) { 78 | return var_export($message, true); 79 | } 80 | 81 | if ($message instanceof Jsonable) { 82 | return $message->toJson(); 83 | } 84 | 85 | if ($message instanceof Arrayable) { 86 | return var_export($message->toArray(), true); 87 | } 88 | 89 | return $message; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Middleware/UnhandledState.php: -------------------------------------------------------------------------------- 1 | isUnhandled()) { 29 | $report->setUnhandled(true); 30 | $report->setSeverityReason([ 31 | 'type' => 'unhandledExceptionMiddleware', 32 | 'attributes' => ['framework' => 'Laravel'], 33 | ]); 34 | } 35 | 36 | $next($report); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/MultiLogger.php: -------------------------------------------------------------------------------- 1 | dispatcher = $dispatcher; 26 | } 27 | 28 | /** 29 | * Register a file log handler. 30 | * 31 | * @param string $path 32 | * @param string $level 33 | * 34 | * @return void 35 | */ 36 | public function useFiles($path, $level = 'debug') 37 | { 38 | foreach ($this->loggers as $logger) { 39 | if ($logger instanceof Log) { 40 | $logger->useFiles($path, $level); 41 | } 42 | } 43 | } 44 | 45 | /** 46 | * Register a daily file log handler. 47 | * 48 | * @param string $path 49 | * @param int $days 50 | * @param string $level 51 | * 52 | * @return void 53 | */ 54 | public function useDailyFiles($path, $days = 0, $level = 'debug') 55 | { 56 | foreach ($this->loggers as $logger) { 57 | if ($logger instanceof Log) { 58 | $logger->useDailyFiles($path, $days, $level); 59 | } 60 | } 61 | } 62 | 63 | /** 64 | * Get the underlying Monolog instance. 65 | * 66 | * @return \Monolog\Logger 67 | */ 68 | public function getMonolog() 69 | { 70 | foreach ($this->loggers as $logger) { 71 | if (is_callable([$logger, 'getMonolog'])) { 72 | $monolog = $logger->getMonolog(); 73 | 74 | if ($monolog === null) { 75 | continue; 76 | } 77 | 78 | return $monolog; 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/OctaneEventSubscriber.php: -------------------------------------------------------------------------------- 1 | breadcrumb('Octane request handled'); 54 | } 55 | public function handleRequestReceived(RequestReceived $event) : void { 56 | $this->breadcrumb('Octane request received'); 57 | } 58 | public function handleRequestTerminated(RequestTerminated $event): void 59 | { 60 | $this->breadcrumb('Octane request terminated'); 61 | $this->cleanup(); 62 | } 63 | 64 | public function handleTaskReceived(TaskReceived $event) : void { 65 | $this->breadcrumb('Octane task received'); 66 | } 67 | public function handleTaskTerminated(TaskTerminated $event): void 68 | { 69 | $this->breadcrumb('Octane task terminated'); 70 | $this->cleanup(); 71 | } 72 | 73 | public function handleTickReceived(TickReceived $event) : void { 74 | $this->breadcrumb('Octane tick received'); 75 | } 76 | public function handleTickTerminated(TickTerminated $event) : void { 77 | $this->breadcrumb('Octane tick terminated'); 78 | } 79 | 80 | public function handleWorkerStarting(WorkerStarting $event) : void { 81 | $this->breadcrumb('Octane worker starting'); 82 | } 83 | public function handleWorkerErrorOccurred(WorkerErrorOccurred $event) : void { 84 | $this->breadcrumb('Octane worker error occurred'); 85 | } 86 | public function handleWorkerStopping(WorkerStopping $event): void 87 | { 88 | $this->breadcrumb('Octane worker stopping'); 89 | $this->cleanup(); 90 | } 91 | 92 | /** 93 | * Register the listeners for the subscriber. 94 | * 95 | * @return array 96 | */ 97 | public function subscribe(Dispatcher $events): array 98 | { 99 | return [ 100 | RequestHandled::class => 'handleRequestHandled', 101 | RequestReceived::class => 'handleRequestReceived', 102 | RequestTerminated::class => 'handleRequestTerminated', 103 | TaskReceived::class => 'handleTaskReceived', 104 | TaskTerminated::class => 'handleTaskTerminated', 105 | TickReceived::class => 'handleTickReceived', 106 | TickTerminated::class => 'handleTickTerminated', 107 | WorkerStarting::class => 'handleWorkerStarting', 108 | WorkerErrorOccurred::class => 'handleWorkerErrorOccurred', 109 | WorkerStopping::class => 'handleWorkerStopping', 110 | ]; 111 | } 112 | } -------------------------------------------------------------------------------- /src/OomBootstrapper.php: -------------------------------------------------------------------------------- 1 | reservedMemory = str_repeat(' ', 1024 * 256); 36 | 37 | register_shutdown_function(function () { 38 | $this->reservedMemory = null; 39 | 40 | $lastError = error_get_last(); 41 | 42 | if (!$lastError) { 43 | return; 44 | } 45 | 46 | $isOom = preg_match($this->oomRegex, $lastError['message'], $matches) === 1; 47 | 48 | if (!$isOom) { 49 | return; 50 | } 51 | 52 | /** @var \Bugsnag\Client|null $client */ 53 | $client = app('bugsnag'); 54 | 55 | // If the client exists and memory increase is enabled, bump the 56 | // memory limit so we can report it. The client can be missing when 57 | // the container isn't complete, e.g. when unit tests are running 58 | if ($client && $client->getMemoryLimitIncrease() !== null) { 59 | $currentMemoryLimit = (int) $matches[1]; 60 | 61 | ini_set('memory_limit', $currentMemoryLimit + $client->getMemoryLimitIncrease()); 62 | } 63 | }); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Queue/Tracker.php: -------------------------------------------------------------------------------- 1 | job['resolved'])) { 22 | return $this->job['resolved']; 23 | } 24 | } 25 | 26 | /** 27 | * Get the current job information. 28 | * 29 | * @return array|null 30 | */ 31 | public function get() 32 | { 33 | return $this->job; 34 | } 35 | 36 | /** 37 | * Set the current job information. 38 | * 39 | * @param array $job 40 | * 41 | * @return void 42 | */ 43 | public function set(array $job) 44 | { 45 | $this->job = $job; 46 | } 47 | 48 | /** 49 | * Clear the current job information. 50 | * 51 | * @return void 52 | */ 53 | public function clear() 54 | { 55 | $this->job = null; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Request/LaravelRequest.php: -------------------------------------------------------------------------------- 1 | request = $request; 28 | } 29 | 30 | /** 31 | * Are we currently processing a request? 32 | * 33 | * @return bool 34 | */ 35 | public function isRequest() 36 | { 37 | return true; 38 | } 39 | 40 | /** 41 | * Get the session data. 42 | * 43 | * @return array 44 | */ 45 | public function getSession() 46 | { 47 | try { 48 | $session = $this->request->getSession(); 49 | } catch (Exception $e) { 50 | return []; 51 | } 52 | 53 | return $session ? $session->all() : []; 54 | } 55 | 56 | /** 57 | * Get the cookies. 58 | * 59 | * @return array 60 | */ 61 | public function getCookies() 62 | { 63 | return $this->request->cookies->all(); 64 | } 65 | 66 | /** 67 | * Get the request formatted as meta data. 68 | * 69 | * @return array 70 | */ 71 | public function getMetaData() 72 | { 73 | $data = []; 74 | 75 | $data['url'] = $this->request->fullUrl(); 76 | 77 | $data['httpMethod'] = $this->request->getMethod(); 78 | 79 | $data['params'] = $this->request->input(); 80 | 81 | $data['clientIp'] = $this->request->getClientIp(); 82 | 83 | if ($agent = $this->request->header('User-Agent')) { 84 | $data['userAgent'] = $agent; 85 | } 86 | 87 | if ($headers = $this->request->headers->all()) { 88 | $data['headers'] = $headers; 89 | } 90 | 91 | return ['request' => $data]; 92 | } 93 | 94 | /** 95 | * Get the request context. 96 | * 97 | * @return string|null 98 | */ 99 | public function getContext() 100 | { 101 | return $this->request->getMethod().' '.$this->request->getPathInfo(); 102 | } 103 | 104 | /** 105 | * Get the request user id. 106 | * 107 | * @return string|null 108 | */ 109 | public function getUserId() 110 | { 111 | return $this->request->getClientIp(); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Request/LaravelResolver.php: -------------------------------------------------------------------------------- 1 | app = $app; 29 | } 30 | 31 | /** 32 | * Resolve the current request. 33 | * 34 | * @return \Bugsnag\Request\RequestInterface 35 | */ 36 | public function resolve() 37 | { 38 | $request = $this->app->make(Request::class); 39 | 40 | if ($this->app->runningInConsole()) { 41 | $command = $request->server('argv', []); 42 | if (!is_array($command)) { 43 | $command = explode(' ', $command); 44 | } 45 | 46 | return new ConsoleRequest($command); 47 | } 48 | 49 | return new LaravelRequest($request); 50 | } 51 | } 52 | --------------------------------------------------------------------------------