├── Gemfile ├── src ├── Facades │ └── Bugsnag.php ├── Queue │ └── Tracker.php ├── Request │ ├── LaravelResolver.php │ └── LaravelRequest.php ├── MultiLogger.php ├── LaravelLogger.php ├── Commands │ └── DeployCommand.php ├── EventTrait.php ├── Middleware │ └── UnhandledState.php └── BugsnagServiceProvider.php ├── LICENSE.txt ├── composer.json ├── UPGRADING.md └── config └── bugsnag.php /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'bugsnag-maze-runner', :git => 'https://github.com/bugsnag/maze-runner' 4 | gem "os", "~> 1.0" 5 | -------------------------------------------------------------------------------- /src/Facades/Bugsnag.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/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 | -------------------------------------------------------------------------------- /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 | "illuminate/contracts": "^5.0", 16 | "illuminate/support": "^5.0", 17 | "bugsnag/bugsnag": "^3.17.0", 18 | "bugsnag/bugsnag-psr-logger": "^1.4", 19 | "monolog/monolog": "^1.12" 20 | }, 21 | "require-dev": { 22 | "graham-campbell/testbench": "^3.1|^4.0|^5.0", 23 | "mockery/mockery": "^0.9.4|~1.1.0", 24 | "phpunit/phpunit": "^4.8|^5.0|^6.0|^7.0" 25 | }, 26 | "autoload": { 27 | "psr-4": { 28 | "Bugsnag\\BugsnagLaravel\\": "src/" 29 | } 30 | }, 31 | "autoload-dev": { 32 | "psr-4" : { 33 | "Bugsnag\\BugsnagLaravel\\Tests\\" : "tests/" 34 | }, 35 | "files": [ 36 | "tests/bc.php" 37 | ] 38 | }, 39 | "extra": { 40 | "branch-alias": { 41 | "dev-master": "2.15-dev" 42 | } 43 | }, 44 | "minimum-stability": "dev", 45 | "prefer-stable": true 46 | } 47 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/Commands/DeployCommand.php: -------------------------------------------------------------------------------- 1 | option('builder'); 35 | if (is_null($builderName)) { 36 | if (class_exists(Process::class)) { 37 | $process = new Process('whoami'); 38 | $process->run(); 39 | if ($process->isSuccessful()) { 40 | $builderName = trim($process->getOutput()); 41 | } 42 | } else { 43 | $builderName = Utils::getBuilderName(); 44 | } 45 | } 46 | Bugsnag::build($this->option('repository'), $this->option('revision'), $this->option('provider'), $builderName); 47 | 48 | $this->info('Notified Bugsnag of the build!'); 49 | } 50 | 51 | /** 52 | * Execute the console command. 53 | * 54 | * @return void 55 | */ 56 | public function fire() 57 | { 58 | $this->handle(); 59 | } 60 | 61 | /** 62 | * Get the console command options. 63 | * 64 | * @return array 65 | */ 66 | protected function getOptions() 67 | { 68 | return [ 69 | ['repository', null, InputOption::VALUE_OPTIONAL, 'The repository from which you are deploying the code.', null], 70 | ['branch', null, InputOption::VALUE_OPTIONAL, 'The source control branch from which you are deploying. Deprecated.', null], 71 | ['revision', null, InputOption::VALUE_OPTIONAL, 'The source control revision you are currently deploying.', null], 72 | ['provider', null, InputOption::VALUE_OPTIONAL, 'The provider of your source control repository.', null], 73 | ['builder', null, InputOption::VALUE_OPTIONAL, 'The machine or person who has executed the build', null], 74 | ]; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Request/LaravelRequest.php: -------------------------------------------------------------------------------- 1 | request = $request; 27 | } 28 | 29 | /** 30 | * Are we currently processing a request? 31 | * 32 | * @return bool 33 | */ 34 | public function isRequest() 35 | { 36 | return true; 37 | } 38 | 39 | /** 40 | * Get the session data. 41 | * 42 | * @return array 43 | */ 44 | public function getSession() 45 | { 46 | $session = $this->request->getSession(); 47 | 48 | return $session ? $session->all() : []; 49 | } 50 | 51 | /** 52 | * Get the cookies. 53 | * 54 | * @return array 55 | */ 56 | public function getCookies() 57 | { 58 | return $this->request->cookies->all(); 59 | } 60 | 61 | /** 62 | * Get the request formatted as meta data. 63 | * 64 | * @return array 65 | */ 66 | public function getMetaData() 67 | { 68 | $data = []; 69 | 70 | $data['url'] = $this->request->fullUrl(); 71 | 72 | $data['httpMethod'] = $this->request->getMethod(); 73 | 74 | $data['params'] = $this->request->input(); 75 | 76 | $data['clientIp'] = $this->request->getClientIp(); 77 | 78 | if ($agent = $this->request->header('User-Agent')) { 79 | $data['userAgent'] = $agent; 80 | } 81 | 82 | if ($headers = $this->request->headers->all()) { 83 | $data['headers'] = $headers; 84 | } 85 | 86 | return ['request' => $data]; 87 | } 88 | 89 | /** 90 | * Get the request context. 91 | * 92 | * @return string|null 93 | */ 94 | public function getContext() 95 | { 96 | return $this->request->getMethod().' '.$this->request->getPathInfo(); 97 | } 98 | 99 | /** 100 | * Get the request user id. 101 | * 102 | * @return string|null 103 | */ 104 | public function getUserId() 105 | { 106 | return $this->request->getClientIp(); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /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/Middleware/UnhandledState.php: -------------------------------------------------------------------------------- 1 | setUnhandled(true); 72 | $report->setSeverityReason([ 73 | 'type' => 'unhandledExceptionMiddleware', 74 | 'attributes' => [ 75 | 'framework' => 'Laravel', 76 | ], 77 | ]); 78 | } 79 | $next($report); 80 | } 81 | 82 | protected function stringStartsWith($haystack, $needle) 83 | { 84 | return substr($haystack, 0, strlen($needle)) === $needle; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /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 | */ 79 | 80 | 'filters' => empty(env('BUGSNAG_FILTERS')) ? ['password'] : explode(',', str_replace(' ', '', env('BUGSNAG_FILTERS'))), 81 | 82 | /* 83 | |-------------------------------------------------------------------------- 84 | | Hostname 85 | |-------------------------------------------------------------------------- 86 | | 87 | | You can set the hostname of your server to something specific for you to 88 | | identify it by if needed. 89 | | 90 | */ 91 | 92 | 'hostname' => env('BUGSNAG_HOSTNAME'), 93 | 94 | /* 95 | |-------------------------------------------------------------------------- 96 | | Proxy 97 | |-------------------------------------------------------------------------- 98 | | 99 | | This is where you can set the proxy settings you'd like us to use when 100 | | communicating with Bugsnag when reporting errors. 101 | | 102 | */ 103 | 104 | 'proxy' => array_filter([ 105 | 'http' => env('HTTP_PROXY'), 106 | 'https' => env('HTTPS_PROXY'), 107 | 'no' => empty(env('NO_PROXY')) ? null : explode(',', str_replace(' ', '', env('NO_PROXY'))), 108 | ]), 109 | 110 | /* 111 | |-------------------------------------------------------------------------- 112 | | Project Root 113 | |-------------------------------------------------------------------------- 114 | | 115 | | Bugsnag marks stacktrace lines as in-project if they come from files 116 | | inside your “project root”. You can set this here. 117 | | 118 | | If this is not set, we will automatically try to detect it. 119 | | 120 | */ 121 | 122 | 'project_root' => env('BUGSNAG_PROJECT_ROOT'), 123 | 124 | /* 125 | |-------------------------------------------------------------------------- 126 | | Strip Path 127 | |-------------------------------------------------------------------------- 128 | | 129 | | You can set a strip path to have it also trimmed from the start of any 130 | | filepath in your stacktraces. 131 | | 132 | | If this is not set, we will automatically try to detect it. 133 | | 134 | */ 135 | 136 | 'strip_path' => env('BUGSNAG_STRIP_PATH'), 137 | 138 | /* 139 | |-------------------------------------------------------------------------- 140 | | Query 141 | |-------------------------------------------------------------------------- 142 | | 143 | | Enable this if you'd like us to automatically record all queries executed 144 | | as breadcrumbs. 145 | | 146 | */ 147 | 148 | 'query' => env('BUGSNAG_QUERY', true), 149 | 150 | /* 151 | |-------------------------------------------------------------------------- 152 | | Bindings 153 | |-------------------------------------------------------------------------- 154 | | 155 | | Enable this if you'd like us to include the query bindings in our query 156 | | breadcrumbs. 157 | | 158 | */ 159 | 160 | 'bindings' => env('BUGSNAG_QUERY_BINDINGS', false), 161 | 162 | /* 163 | |-------------------------------------------------------------------------- 164 | | Release Stage 165 | |-------------------------------------------------------------------------- 166 | | 167 | | Set the release stage to use when sending notifications to Bugsnag. 168 | | 169 | | Leaving this unset will default to using the application environment. 170 | | 171 | */ 172 | 173 | 'release_stage' => env('BUGSNAG_RELEASE_STAGE'), 174 | 175 | /* 176 | |-------------------------------------------------------------------------- 177 | | Notify Release Stages 178 | |-------------------------------------------------------------------------- 179 | | 180 | | Set which release stages should send notifications to Bugsnag. 181 | | 182 | */ 183 | 184 | 'notify_release_stages' => empty(env('BUGSNAG_NOTIFY_RELEASE_STAGES')) ? null : explode(',', str_replace(' ', '', env('BUGSNAG_NOTIFY_RELEASE_STAGES'))), 185 | 186 | /* 187 | |-------------------------------------------------------------------------- 188 | | Send Code 189 | |-------------------------------------------------------------------------- 190 | | 191 | | Bugsnag automatically sends a small snippet of the code that crashed to 192 | | help you diagnose even faster from within your dashboard. If you don’t 193 | | want to send this snippet, then set this to false. 194 | | 195 | */ 196 | 197 | 'send_code' => env('BUGSNAG_SEND_CODE', true), 198 | 199 | /* 200 | |-------------------------------------------------------------------------- 201 | | Callbacks 202 | |-------------------------------------------------------------------------- 203 | | 204 | | Enable this if you'd like us to enable our default set of notification 205 | | callbacks. These add things like the cookie information and session 206 | | details to the error to be sent to Bugsnag. 207 | | 208 | | If you'd like to add your own callbacks, you can call the 209 | | Bugsnag::registerCallback method from the boot method of your app 210 | | service provider. 211 | | 212 | */ 213 | 214 | 'callbacks' => env('BUGSNAG_CALLBACKS', true), 215 | 216 | /* 217 | |-------------------------------------------------------------------------- 218 | | User 219 | |-------------------------------------------------------------------------- 220 | | 221 | | Enable this if you'd like us to set the current user logged in via 222 | | Laravel's authentication system. 223 | | 224 | | If you'd like to add your own user resolver, you can do this by using 225 | | callbacks via Bugsnag::registerCallback. 226 | | 227 | */ 228 | 229 | 'user' => env('BUGSNAG_USER', true), 230 | 231 | /* 232 | |-------------------------------------------------------------------------- 233 | | Logger Notify Level 234 | |-------------------------------------------------------------------------- 235 | | 236 | | This sets the level at which a logged message will trigger a notification 237 | | to Bugsnag. By default this level will be 'notice'. 238 | | 239 | | Must be one of the Psr\Log\LogLevel levels from the Psr specification. 240 | | 241 | */ 242 | 243 | 'logger_notify_level' => env('BUGSNAG_LOGGER_LEVEL'), 244 | 245 | /* 246 | |-------------------------------------------------------------------------- 247 | | Auto Capture Sessions 248 | |-------------------------------------------------------------------------- 249 | | 250 | | Enable this to start tracking sessions and deliver them to Bugsnag. 251 | | 252 | */ 253 | 254 | 'auto_capture_sessions' => env('BUGSNAG_CAPTURE_SESSIONS', false), 255 | 256 | /* 257 | |-------------------------------------------------------------------------- 258 | | Sessions Endpoint 259 | |-------------------------------------------------------------------------- 260 | | 261 | | Sets a url to send tracked sessions to. 262 | | 263 | */ 264 | 265 | 'session_endpoint' => env('BUGSNAG_SESSION_ENDPOINT'), 266 | 267 | /* 268 | |-------------------------------------------------------------------------- 269 | | Builds Endpoint 270 | |-------------------------------------------------------------------------- 271 | | 272 | | Sets a url to send build reports to. 273 | | 274 | */ 275 | 276 | 'build_endpoint' => env('BUGSNAG_BUILD_ENDPOINT'), 277 | 278 | ]; 279 | -------------------------------------------------------------------------------- /src/BugsnagServiceProvider.php: -------------------------------------------------------------------------------- 1 | setupConfig($this->app); 49 | 50 | $this->setupEvents($this->app->events, $this->app->config->get('bugsnag')); 51 | 52 | $this->setupQueue($this->app->queue); 53 | } 54 | 55 | /** 56 | * Setup the config. 57 | * 58 | * @param \Illuminate\Contracts\Container\Container $app 59 | * 60 | * @return void 61 | */ 62 | protected function setupConfig(Container $app) 63 | { 64 | $source = realpath($raw = __DIR__.'/../config/bugsnag.php') ?: $raw; 65 | 66 | if ($app instanceof LaravelApplication && $app->runningInConsole()) { 67 | $this->publishes([$source => config_path('bugsnag.php')]); 68 | } elseif ($app instanceof LumenApplication) { 69 | $app->configure('bugsnag'); 70 | } 71 | 72 | $this->mergeConfigFrom($source, 'bugsnag'); 73 | } 74 | 75 | /** 76 | * Setup the events. 77 | * 78 | * @param \Illuminate\Contracts\Events\Dispatcher $events 79 | * @param array $config 80 | * 81 | * @return void 82 | */ 83 | protected function setupEvents(Dispatcher $events, array $config) 84 | { 85 | if (isset($config['auto_capture_sessions']) && $config['auto_capture_sessions']) { 86 | $events->listen(RouteMatched::class, function ($event) { 87 | $this->app->bugsnag->getSessionTracker()->startSession(); 88 | }); 89 | } 90 | 91 | if (isset($config['query']) && !$config['query']) { 92 | return; 93 | } 94 | 95 | $show = isset($config['bindings']) && $config['bindings']; 96 | 97 | if (class_exists(QueryExecuted::class)) { 98 | $events->listen(QueryExecuted::class, function (QueryExecuted $query) use ($show) { 99 | $this->app->bugsnag->leaveBreadcrumb( 100 | 'Query executed', 101 | Breadcrumb::PROCESS_TYPE, 102 | $this->formatQuery($query->sql, $show ? $query->bindings : [], $query->time, $query->connectionName) 103 | ); 104 | }); 105 | } else { 106 | $events->listen('illuminate.query', function ($sql, array $bindings, $time, $connection) use ($show) { 107 | $this->app->bugsnag->leaveBreadcrumb( 108 | 'Query executed', 109 | Breadcrumb::PROCESS_TYPE, 110 | $this->formatQuery($sql, $show ? $bindings : [], $time, $connection) 111 | ); 112 | }); 113 | } 114 | } 115 | 116 | /** 117 | * Format the query as breadcrumb metadata. 118 | * 119 | * @param string $sql 120 | * @param array $bindings 121 | * @param float $time 122 | * @param string $connection 123 | * 124 | * @return array 125 | */ 126 | protected function formatQuery($sql, array $bindings, $time, $connection) 127 | { 128 | $data = ['sql' => $sql]; 129 | 130 | foreach ($bindings as $index => $binding) { 131 | $data["binding {$index}"] = $binding; 132 | } 133 | 134 | $data['time'] = "{$time}ms"; 135 | $data['connection'] = $connection; 136 | 137 | return $data; 138 | } 139 | 140 | /** 141 | * Setup the queue. 142 | * 143 | * @param \Illuminate\Queue\QueueManager $queue 144 | * 145 | * @return void 146 | */ 147 | protected function setupQueue(QueueManager $queue) 148 | { 149 | $queue->looping(function () { 150 | $this->app->bugsnag->flush(); 151 | $this->app->bugsnag->clearBreadcrumbs(); 152 | $this->app->make(Tracker::class)->clear(); 153 | }); 154 | 155 | if (!class_exists(JobProcessing::class)) { 156 | return; 157 | } 158 | 159 | $queue->before(function (JobProcessing $event) { 160 | $this->app->bugsnag->setFallbackType('Queue'); 161 | 162 | $job = [ 163 | 'name' => $event->job->getName(), 164 | 'queue' => $event->job->getQueue(), 165 | 'attempts' => $event->job->attempts(), 166 | 'connection' => $event->connectionName, 167 | ]; 168 | 169 | if (method_exists($event->job, 'resolveName')) { 170 | $job['resolved'] = $event->job->resolveName(); 171 | } 172 | 173 | $this->app->make(Tracker::class)->set($job); 174 | }); 175 | } 176 | 177 | /** 178 | * Register the service provider. 179 | * 180 | * @return void 181 | */ 182 | public function register() 183 | { 184 | $this->app->singleton('bugsnag', function (Container $app) { 185 | $config = $app->config->get('bugsnag'); 186 | $client = new Client(new Configuration($config['api_key']), new LaravelResolver($app), $this->getGuzzle($config)); 187 | 188 | $this->setupCallbacks($client, $app, $config); 189 | $this->setupPaths($client, $app->basePath(), $app->path(), isset($config['strip_path']) ? $config['strip_path'] : null, isset($config['project_root']) ? $config['project_root'] : null); 190 | 191 | $client->setReleaseStage(isset($config['release_stage']) ? $config['release_stage'] : $app->environment()); 192 | $client->setHostname(isset($config['hostname']) ? $config['hostname'] : null); 193 | $client->getConfig()->mergeDeviceData(['runtimeVersions' => $this->getRuntimeVersion()]); 194 | 195 | $client->setFallbackType($app->runningInConsole() ? 'Console' : 'HTTP'); 196 | $client->setAppType(isset($config['app_type']) ? $config['app_type'] : null); 197 | $client->setAppVersion(isset($config['app_version']) ? $config['app_version'] : null); 198 | $client->setBatchSending(isset($config['batch_sending']) ? $config['batch_sending'] : true); 199 | $client->setSendCode(isset($config['send_code']) ? $config['send_code'] : true); 200 | $client->getPipeline()->insertBefore(new UnhandledState(), 'Bugsnag\\Middleware\\SessionData'); 201 | 202 | $client->setNotifier([ 203 | 'name' => 'Bugsnag Laravel', 204 | 'version' => static::VERSION, 205 | 'url' => 'https://github.com/bugsnag/bugsnag-laravel', 206 | ]); 207 | 208 | if (isset($config['notify_release_stages']) && is_array($config['notify_release_stages'])) { 209 | $client->setNotifyReleaseStages($config['notify_release_stages']); 210 | } 211 | 212 | if (isset($config['filters']) && is_array($config['filters'])) { 213 | $client->setFilters($config['filters']); 214 | } 215 | 216 | if (isset($config['auto_capture_sessions']) && $config['auto_capture_sessions']) { 217 | $endpoint = isset($config['session_endpoint']) ? $config['session_endpoint'] : null; 218 | $this->setupSessionTracking($client, $endpoint, $this->app->events); 219 | } 220 | 221 | if (isset($config['build_endpoint'])) { 222 | $client->setBuildEndpoint($config['build_endpoint']); 223 | } 224 | 225 | return $client; 226 | }); 227 | 228 | $this->app->singleton('bugsnag.tracker', function () { 229 | return new Tracker(); 230 | }); 231 | 232 | $this->app->singleton('bugsnag.logger', function (Container $app) { 233 | $config = $app->config->get('bugsnag'); 234 | $logger = interface_exists(Log::class) ? new LaravelLogger($app['bugsnag'], $app['events']) : new BugsnagLogger($app['bugsnag']); 235 | if (isset($config['logger_notify_level'])) { 236 | $logger->setNotifyLevel($config['logger_notify_level']); 237 | } 238 | 239 | return $logger; 240 | }); 241 | 242 | $this->app->singleton('bugsnag.multi', function (Container $app) { 243 | return interface_exists(Log::class) ? new MultiLogger([$app['log'], $app['bugsnag.logger']]) : new BaseMultiLogger([$app['log'], $app['bugsnag.logger']]); 244 | }); 245 | 246 | if ($this->app['log'] instanceof LogManager) { 247 | $this->app['log']->extend('bugsnag', function (Container $app, array $config) { 248 | $handler = new PsrHandler($app['bugsnag.logger']); 249 | 250 | return new Logger('bugsnag', [$handler]); 251 | }); 252 | } 253 | 254 | $this->app->alias('bugsnag', Client::class); 255 | $this->app->alias('bugsnag.tracker', Tracker::class); 256 | $this->app->alias('bugsnag.logger', interface_exists(Log::class) ? LaravelLogger::class : BugsnagLogger::class); 257 | $this->app->alias('bugsnag.multi', interface_exists(Log::class) ? MultiLogger::class : BaseMultiLogger::class); 258 | } 259 | 260 | /** 261 | * Get the guzzle client instance. 262 | * 263 | * @param array $config 264 | * 265 | * @return \GuzzleHttp\ClientInterface 266 | */ 267 | protected function getGuzzle(array $config) 268 | { 269 | $options = []; 270 | 271 | if (isset($config['proxy']) && $config['proxy']) { 272 | if (isset($config['proxy']['http']) && php_sapi_name() != 'cli') { 273 | unset($config['proxy']['http']); 274 | } 275 | 276 | $options['proxy'] = $config['proxy']; 277 | } 278 | 279 | return Client::makeGuzzle(isset($config['endpoint']) ? $config['endpoint'] : null, $options); 280 | } 281 | 282 | /** 283 | * Setup the callbacks. 284 | * 285 | * @param \Bugsnag\Client $client 286 | * @param \Illuminate\Contracts\Container\Container $app 287 | * @param array $config 288 | * 289 | * @return void 290 | */ 291 | protected function setupCallbacks(Client $client, Container $app, array $config) 292 | { 293 | if (!isset($config['callbacks']) || $config['callbacks']) { 294 | $client->registerDefaultCallbacks(); 295 | 296 | $client->registerCallback(function (Report $report) use ($app) { 297 | $tracker = $app->make(Tracker::class); 298 | 299 | if ($context = $tracker->context()) { 300 | $report->setContext($context); 301 | } 302 | 303 | if ($job = $tracker->get()) { 304 | $report->setMetaData(['job' => $job]); 305 | } 306 | }); 307 | } 308 | 309 | if (!isset($config['user']) || $config['user']) { 310 | $client->registerCallback(new CustomUser(function () use ($app) { 311 | if ($user = $app->auth->user()) { 312 | if (method_exists($user, 'attributesToArray') && is_callable([$user, 'attributesToArray'])) { 313 | return $user->attributesToArray(); 314 | } 315 | 316 | if ($user instanceof GenericUser) { 317 | $reflection = new ReflectionClass($user); 318 | $property = $reflection->getProperty('attributes'); 319 | $property->setAccessible(true); 320 | 321 | return $property->getValue($user); 322 | } 323 | } 324 | })); 325 | } 326 | } 327 | 328 | /** 329 | * Setup the client paths. 330 | * 331 | * @param \Bugsnag\Client $client 332 | * @param string $base 333 | * @param string $path 334 | * @param string|null $strip 335 | * @param string|null $project 336 | * 337 | * @return void 338 | */ 339 | protected function setupPaths(Client $client, $base, $path, $strip, $project) 340 | { 341 | if ($strip) { 342 | $client->setStripPath($strip); 343 | 344 | if (!$project) { 345 | $client->setProjectRoot("{$strip}/app"); 346 | } 347 | 348 | return; 349 | } 350 | 351 | if ($project) { 352 | if ($base && substr($project, 0, strlen($base)) === $base) { 353 | $client->setStripPath($base); 354 | } 355 | 356 | $client->setProjectRoot($project); 357 | 358 | return; 359 | } 360 | 361 | $client->setStripPath($base); 362 | $client->setProjectRoot($path); 363 | } 364 | 365 | /** 366 | * Setup session tracking. 367 | * 368 | * @param \Bugsnag\Client $client 369 | * @param string $endpoint 370 | * 371 | * @return void 372 | */ 373 | protected function setupSessionTracking(Client $client, $endpoint, $events) 374 | { 375 | $client->setAutoCaptureSessions(true); 376 | if (!is_null($endpoint)) { 377 | $client->setSessionEndpoint($endpoint); 378 | } 379 | $sessionTracker = $client->getSessionTracker(); 380 | 381 | $sessionStorage = function ($session = null) { 382 | if (is_null($session)) { 383 | return session('bugsnag-session', []); 384 | } else { 385 | session(['bugsnag-session' => $session]); 386 | } 387 | }; 388 | 389 | $sessionTracker->setSessionFunction($sessionStorage); 390 | 391 | $cache = $this->app->cache; 392 | 393 | $genericStorage = function ($key, $value = null) use ($cache) { 394 | if (is_null($value)) { 395 | return $cache->get($key, null); 396 | } else { 397 | $cache->put($key, $value, new DateTime('+ 1 hour')); 398 | } 399 | }; 400 | 401 | $sessionTracker->setStorageFunction($genericStorage); 402 | } 403 | 404 | /** 405 | * Returns the framework name and version to add to the device data. 406 | * 407 | * Attempt to parse a semantic framework version from $app or else return 408 | * the full version string. 409 | * e.g. Lumen: "Lumen (x.x.x) (Laravel Components y.y.*)" => "x.x.x" 410 | * 411 | * @return array 412 | */ 413 | protected function getRuntimeVersion() 414 | { 415 | $version = $this->app->version(); 416 | if (preg_match('/(\d+\.\d+\.\d+)/', $version, $versionMatches)) { 417 | $version = $versionMatches[0]; 418 | } 419 | return [ ($this->app instanceof LumenApplication ? 'lumen' : 'laravel' ) => $version ]; 420 | 421 | } 422 | 423 | /** 424 | * Get the services provided by the provider. 425 | * 426 | * @return array 427 | */ 428 | public function provides() 429 | { 430 | return ['bugsnag', 'bugsnag.tracker', 'bugsnag.logger', 'bugsnag.multi']; 431 | } 432 | } 433 | --------------------------------------------------------------------------------