├── .gitignore ├── .scrutinizer.yml ├── .travis.yml ├── README.md ├── composer.json ├── phpunit.xml ├── src ├── Client.php ├── Facades │ └── Raven.php ├── Job.php ├── Log.php ├── Processors │ ├── LocaleProcessor.php │ └── UserDataProcessor.php ├── RavenServiceProvider.php └── config │ └── config.php └── tests ├── ClientTest.php └── LogTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor 3 | .DS_Store 4 | build 5 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | excluded_paths: [tests/*] 3 | checks: 4 | php: 5 | code_rating: true 6 | remove_extra_empty_lines: true 7 | remove_php_closing_tag: true 8 | remove_trailing_whitespace: true 9 | fix_use_statements: 10 | remove_unused: true 11 | preserve_multiple: false 12 | preserve_blanklines: true 13 | order_alphabetically: true 14 | fix_php_opening_tag: true 15 | fix_linefeed: true 16 | fix_line_ending: true 17 | fix_identation_4spaces: true 18 | fix_doc_comments: true 19 | tools: 20 | external_code_coverage: 21 | timeout: 1800 22 | runs: 3 23 | php_code_coverage: false 24 | php_code_sniffer: 25 | config: 26 | standard: PSR2 27 | filter: 28 | paths: ['src'] 29 | php_loc: 30 | enabled: true 31 | excluded_dirs: [vendor, tests] 32 | php_cpd: 33 | enabled: true 34 | excluded_dirs: [vendor, tests] -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: php 3 | 4 | php: 5 | - 5.5.9 6 | - 5.5 7 | - 5.6 8 | - 7.0 9 | - hhvm 10 | 11 | before_script: 12 | - travis_retry composer self-update 13 | - travis_retry composer install --no-interaction --prefer-source --dev 14 | 15 | script: 16 | - phpunit --coverage-text --coverage-clover=coverage.clover 17 | 18 | after_script: 19 | - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "7.0" ] && [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then wget https://scrutinizer-ci.com/ocular.phar; fi;' 20 | - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "7.0" ] && [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi;' -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Abandoned use [twineis/raven-php](https://github.com/twineis/raven-php) instead 2 | ============= 3 | 4 | > Laravel Raven 5 | > Sentry (Raven) error monitoring for Laravel 4 & 5 with send in background using queues 6 | 7 | [![Build Status](http://img.shields.io/travis/clowdy/laravel-raven/master.svg?style=flat-square)](https://travis-ci.org/clowdy/laravel-raven) 8 | [![Scrutinizer Code Quality](http://img.shields.io/scrutinizer/g/clowdy/laravel-raven/master.svg?style=flat-square)](https://scrutinizer-ci.com/g/clowdy/laravel-raven/) 9 | [![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/clowdy/laravel-raven/master.svg?style=flat-square)](https://scrutinizer-ci.com/g/clowdy/laravel-raven/code-structure/master) 10 | [![License](http://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](http://www.opensource.org/licenses/MIT) 11 | [![Latest Version](http://img.shields.io/packagist/v/clowdy/laravel-raven.svg?style=flat-square)](https://packagist.org/packages/clowdy/laravel-raven) 12 | [![Total Downloads](https://img.shields.io/packagist/dt/clowdy/laravel-raven.svg?style=flat-square)](https://packagist.org/packages/clowdy/laravel-raven) 13 | 14 | Sentry (Raven) error monitoring for Laravel 5 with send in background using queues. This will add a listener to Laravel's existing log system. It makes use to Laravel's queues to push messages into the background without slowing down the application. 15 | 16 | ![rollbar](https://www.getsentry.com/_static/getsentry/images/hero.png) 17 | 18 | ## Installation 19 | 20 | Add the package to your `composer.json` and run `composer update`. 21 | 22 | ```js 23 | { 24 | "require": { 25 | "clowdy/laravel-raven": "2.1.*" 26 | } 27 | } 28 | ``` 29 | 30 | Add the service provider in `config/app.php`: 31 | 32 | ```php 33 | Clowdy\Raven\RavenServiceProvider::class, 34 | ``` 35 | 36 | Register the Raven alias: 37 | 38 | ```php 39 | 'Raven' => Clowdy\Raven\Facades\Raven::class, 40 | ``` 41 | 42 | ### Looking for a Laravel 4 compatible version? 43 | 44 | Checkout the [1.0 branch](https://github.com/clowdy/laravel-raven/tree/1.0) 45 | 46 | ## Configuration 47 | 48 | Publish the included configuration file: 49 | 50 | ```bash 51 | $ php artisan vendor:publish 52 | ``` 53 | 54 | Change the Sentry DSN by using the `RAVEN_DSN` env variable or changing the config file: 55 | 56 | ```php 57 | RAVEN_DSN=your-raven-dsn 58 | ``` 59 | 60 | This library uses the queue system, make sure your `config/queue.php` file is configured correctly. You can also specify the connection and the queue to use in the raven config. The connection and queue must exist in `config/queue.php`. These can be set using the `RAVEN_QUEUE_CONNECTION` for connection and `RAVEN_QUEUE_NAME` for the queue. 61 | 62 | ```php 63 | RAVEN_QUEUE_CONNECTION=redis 64 | RAVEN_QUEUE_NAME=error 65 | ``` 66 | 67 | **The job data can be quite large, ensure you are using a queue that can support large data sets like `redis` or `sqs`**. 68 | If the job fails to add into the queue, it will be sent directly to sentry, slowing down the request, so its not lost. 69 | 70 | ## Usage 71 | 72 | To monitor exceptions, simply use the `Log` facade or helper: 73 | 74 | ```php 75 | Log::error($exception); 76 | 77 | // or 78 | 79 | logger()->error($exception); 80 | ``` 81 | 82 | You can change the logs used by changing the log level in the config by modifying the env var. 83 | 84 | ```php 85 | RAVEN_LEVEL=error 86 | ``` 87 | 88 | ### Event Id 89 | 90 | There is a method to get the **latest** event id so it can be used on things like the error page. 91 | 92 | ```php 93 | Raven::getLastEventId(); 94 | ``` 95 | 96 | ### Context information 97 | 98 | You can pass additional information as context like this: 99 | 100 | ```php 101 | Log::error('Oops, Something went wrong', [ 102 | 'user' => ['name' => $user->name, 'email' => $user->email] 103 | ]); 104 | ``` 105 | 106 | User data can alternatively be added to the context of any messages sent via a processor, such as the included `UserDataProcessor`; see below. 107 | 108 | #### Included optional processors 109 | 110 | ##### `UserDataProcessor` 111 | 112 | This processor attaches data about the current user to the error report. 113 | 114 | ```php 115 | 'processors' => [ 116 | Clowdy\Raven\Processors\UserDataProcessor::class, 117 | ], 118 | ``` 119 | 120 | Or, to configure the `UserDataProcessor` object: 121 | 122 | ```php 123 | 'processors' => [ 124 | new Clowdy\Raven\Processors\UserDataProcessor([ 125 | 'only' => ['id', 'username', 'email'], // This is ['id'] by default; pass [] or null to include all fields 126 | 'appends' => ['normallyHiddenProperty'], 127 | ]), 128 | ], 129 | ``` 130 | 131 | See the `UserDataProcessor` source for full details. 132 | 133 | ##### `LocaleProcessor` 134 | 135 | This processor attaches to the error report the current application locale as a tag. 136 | 137 | ```php 138 | 'processors' => [ 139 | Clowdy\Raven\Processors\LocaleProcessor::class, 140 | ], 141 | ``` 142 | 143 | ## Credits 144 | 145 | This package was inspired [rcrowe/Raven](https://github.com/rcrowe/Raven). 146 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "twine-net/laravel-raven", 3 | "description": "Sentry (Raven) error monitoring for Laravel with send in background", 4 | "keywords": ["laravel", "sentry", "getsentry", "raven", "monitoring", "error", "logging"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Pulkit Jalan", 9 | "homepage": "https://github.com/pulkitjalan" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=5.5.9", 14 | "illuminate/support": "~5", 15 | "illuminate/queue": "~5", 16 | "illuminate/log": "~5", 17 | "raven/raven": "~0.13" 18 | }, 19 | "require-dev": { 20 | "phpunit/phpunit": "~4.0", 21 | "mockery/mockery": "~0.9.1" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "Clowdy\\Raven\\": "src/" 26 | } 27 | }, 28 | "minimum-stability": "dev", 29 | "prefer-stable": true 30 | } 31 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | tests 15 | 16 | 17 | 18 | 19 | src/ 20 | 21 | src/RavenServiceProvider.php 22 | src/Job.php 23 | src/config/ 24 | src/Facades/ 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/Client.php: -------------------------------------------------------------------------------- 1 | config = $config; 36 | $this->queue = $queue; 37 | 38 | // merge env into options if set 39 | $options = array_replace_recursive( 40 | [ 41 | 'tags' => [ 42 | 'environment' => $env, 43 | 'logger' => 'laravel-raven', 44 | ], 45 | ], 46 | array_get($config, 'options', []) 47 | ); 48 | 49 | parent::__construct(array_get($config, 'dsn', ''), $options); 50 | } 51 | 52 | /** 53 | * Get the last stored event id 54 | * 55 | * @return string 56 | */ 57 | public function getLastEventId() 58 | { 59 | return $this->eventId; 60 | } 61 | 62 | /** 63 | * {@inheritdoc} 64 | */ 65 | public function send($data) 66 | { 67 | $this->eventId = array_get($data, 'event_id'); 68 | 69 | // send error now if queue not set 70 | if (is_null($this->queue)) { 71 | return $this->sendError($data); 72 | } 73 | 74 | // put the job into the queue 75 | // Sync connection will sent directly 76 | // if failed to add job to queue send it now 77 | try { 78 | $this->queue 79 | ->connection(array_get($this->config, 'queue.connection')) 80 | ->push( 81 | Job::class, 82 | $data, 83 | array_get($this->config, 'queue.name') 84 | ); 85 | } catch (Exception $e) { 86 | return $this->sendError($data); 87 | } 88 | 89 | return; 90 | } 91 | 92 | /** 93 | * Send the error to sentry without queue. 94 | * 95 | * @return void 96 | */ 97 | public function sendError($data) 98 | { 99 | return parent::send($data); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Facades/Raven.php: -------------------------------------------------------------------------------- 1 | raven = $raven; 18 | } 19 | 20 | /** 21 | * Fire the job. 22 | * 23 | * @return void 24 | */ 25 | public function fire($job, $data) 26 | { 27 | try { 28 | // Send the data to Sentry. 29 | $this->raven->sendError($data); 30 | 31 | // Delete the processed job. 32 | $job->delete(); 33 | } catch (\Exception $e) { 34 | // Release Job with delay. 35 | $job->release(30); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Log.php: -------------------------------------------------------------------------------- 1 | $message]); 27 | 28 | $message = $message->getMessage(); 29 | } 30 | 31 | parent::writeLog($level, $message, $context); 32 | } 33 | 34 | /** 35 | * Register a new Monolog handler. 36 | * 37 | * @param string $level Laravel log level. 38 | * @param \Closure $closure Return an instance of \Monolog\Handler\HandlerInterface. 39 | * 40 | * @throws \InvalidArgumentException Unknown log level. 41 | * 42 | * @return bool Whether handler was registered. 43 | */ 44 | public function registerHandler($level, Closure $callback) 45 | { 46 | $level = $this->parseLevel($level); 47 | $handler = call_user_func($callback, $level); 48 | 49 | // Add handler to Monolog 50 | $this->getMonolog()->pushHandler($handler); 51 | 52 | return true; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Processors/LocaleProcessor.php: -------------------------------------------------------------------------------- 1 | getTags()); 21 | return $record; 22 | } 23 | 24 | /** 25 | * Get the tags to be added 26 | * 27 | * @return array 28 | */ 29 | public function getTags() 30 | { 31 | return [ 32 | 'locale' => $this->getLocale(), 33 | ]; 34 | } 35 | 36 | /** 37 | * Get the locale 38 | * 39 | * @return string 40 | */ 41 | public function getLocale() 42 | { 43 | return App::getLocale(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Processors/UserDataProcessor.php: -------------------------------------------------------------------------------- 1 | null]` is still attached to the 24 | * error object, to differentiate a case where no data on the current user is 25 | * available from a case where it is known that no user is logged in. 26 | */ 27 | class UserDataProcessor 28 | { 29 | /** 30 | * @var array 31 | */ 32 | protected $options = [ 33 | 'appends' => [], 34 | 'except' => [], 35 | 'only' => [ 36 | 'id', 37 | ], 38 | ]; 39 | 40 | /** 41 | * Make a new UserDataProcessor. 42 | * 43 | * Options: 44 | * - array 'appends': extra fields from the user to include 45 | * - array 'except': fields from the user not to include 46 | * - array 'only': the only fields from the user to include, or null to 47 | * include all fields (other than those removed by 'except') 48 | * 49 | * Note that rather than using these options it may be preferable in certain 50 | * cases to use the User model's `$hidden` and `$appends` properties, or 51 | * overriding its `toArray` method. 52 | * 53 | * @param array $options 54 | */ 55 | public function __construct(array $options = []) 56 | { 57 | $this->options = array_merge($this->options, $options); 58 | } 59 | 60 | /** 61 | * Run the processor: attach user data to a Monolog record. 62 | * 63 | * @param array $record Monolog record 64 | * 65 | * @return array $record 66 | */ 67 | public function __invoke(array $record) 68 | { 69 | $record['context']['user'] = array_merge($this->getUserContext(), array_get($record, 'context.user', [])); 70 | return $record; 71 | } 72 | 73 | /** 74 | * Get the user context array 75 | * 76 | * @return array 77 | */ 78 | public function getUserContext() 79 | { 80 | $data = ['id' => null]; 81 | if ($user = $this->getUser()) { 82 | $data = $user->toArray(); 83 | 84 | $data = array_except($data, $this->options['except']); 85 | 86 | if (! empty($this->options['only'])) { 87 | $data = array_only($data, $this->options['only']); 88 | } 89 | 90 | foreach ($this->options['appends'] as $key) { 91 | $data[$key] = $user->{$key}; 92 | } 93 | } 94 | 95 | // Nest all but the particular keys Sentry treats specially in a 'data' 96 | // substructure; see 97 | // https://github.com/getsentry/sentry/blob/e7a295784f10a296ace7aa98e1b7216328feac5d/src/sentry/interfaces/user.py#L31 98 | $topLevel = [ 99 | 'id', 100 | 'username', 101 | 'email', 102 | 'ip_address', 103 | ]; 104 | $userArray = array_only($data, $topLevel); 105 | $userArray['data'] = array_except($data, $topLevel); 106 | 107 | return $userArray; 108 | } 109 | 110 | /** 111 | * Get user function. 112 | * 113 | * @return User 114 | */ 115 | public function getUser() 116 | { 117 | return Auth::user(); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/RavenServiceProvider.php: -------------------------------------------------------------------------------- 1 | publishes([ 18 | __DIR__.'/config/config.php' => config_path('raven.php'), 19 | ], 'config'); 20 | 21 | if (! config('raven.enabled')) { 22 | return; 23 | } 24 | 25 | $this->app['log']->registerHandler( 26 | config('raven.level', 'error'), 27 | function ($level) { 28 | $handler = new RavenHandler($this->app[Client::class], $level); 29 | 30 | // Add processors 31 | $processors = config('raven.monolog.processors', []); 32 | 33 | if (is_array($processors)) { 34 | foreach ($processors as $process) { 35 | // Get callable 36 | if (is_callable($process)) { 37 | $callable = $process; 38 | } elseif (is_string($process)) { 39 | $callable = new $process(); 40 | } else { 41 | throw new \Exception('Raven: Invalid processor'); 42 | } 43 | 44 | // Add processor to Raven handler 45 | $handler->pushProcessor($callable); 46 | } 47 | } 48 | 49 | return $handler; 50 | } 51 | ); 52 | } 53 | 54 | /** 55 | * Register the service provider. 56 | * 57 | * @return void 58 | */ 59 | public function register() 60 | { 61 | $this->mergeConfigFrom(__DIR__.'/config/config.php', 'raven'); 62 | 63 | $this->app[Client::class] = $this->app->share(function ($app) { 64 | return new Client(config('raven'), $app['queue'], $app->environment()); 65 | }); 66 | 67 | $this->app->instance('log', new Log($this->app['log']->getMonolog(), $this->app['log']->getEventDispatcher())); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/config/config.php: -------------------------------------------------------------------------------- 1 | env('RAVEN_ENABLED', true), 14 | 15 | /* 16 | |-------------------------------------------------------------------------- 17 | | Raven DSN 18 | |-------------------------------------------------------------------------- 19 | | 20 | | Your project's DSN, found under 'API Keys' in your project's settings. 21 | | 22 | */ 23 | 'dsn' => env('RAVEN_DSN', ''), 24 | 25 | /* 26 | |-------------------------------------------------------------------------- 27 | | Log Level 28 | |-------------------------------------------------------------------------- 29 | | 30 | | Log level at which to log to Sentry. Default `error`. 31 | | 32 | | Available: 'debug', 'info', 'notice', 'warning', 'error', 33 | | 'critical', 'alert', 'emergency' 34 | | 35 | */ 36 | 'level' => env('RAVEN_LEVEL', 'error'), 37 | 38 | /* 39 | |-------------------------------------------------------------------------- 40 | | Queue connection 41 | |-------------------------------------------------------------------------- 42 | | 43 | | Choose a custom queue connection to use from your config/queue.php 44 | | 45 | | Defaults to the default connection and queue from config/queue.php 46 | | 47 | */ 48 | 'queue' => [ 49 | 'connection' => env('RAVEN_QUEUE_CONNECTION', ''), 50 | 'name' => env('RAVEN_QUEUE_NAME', ''), 51 | ], 52 | 53 | /* 54 | |-------------------------------------------------------------------------- 55 | | Monolog 56 | |-------------------------------------------------------------------------- 57 | | 58 | | Customise the Monolog Raven handler. 59 | | 60 | */ 61 | 'monolog' => [ 62 | 63 | /* 64 | |-------------------------------------------------------------------------- 65 | | Processors 66 | |-------------------------------------------------------------------------- 67 | | 68 | | Set extra data on every log made to Sentry. 69 | | Monolog has a number of built-in processors which you can find here: 70 | | 71 | | https://github.com/Seldaek/monolog/tree/master/src/Monolog/Processor 72 | | 73 | | Processors can be an instance of a class, a clousure or the path to a class 74 | | 75 | */ 76 | 'processors' => [ 77 | // Uncomment the following line to include user data from Auth 78 | // Clowdy\Raven\Processors\UserDataProcessor::class, 79 | 80 | // Use a built-in Monolog processor 81 | // Monolog\Processor\GitProcessor::class 82 | ], 83 | 84 | ], 85 | 86 | /* 87 | |-------------------------------------------------------------------------- 88 | | Raven Configuration 89 | |-------------------------------------------------------------------------- 90 | | 91 | | Any values below will be passed directly as configuration to the Raven 92 | | instance. For more information about the possible values check 93 | | out: https://github.com/getsentry/raven-php 94 | | 95 | | Example: "name", "release", "tags", "trace", "timeout", "exclude", "extra", ... 96 | | 97 | */ 98 | 'options' => [], 99 | 100 | ]; 101 | -------------------------------------------------------------------------------- /tests/ClientTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf('Raven_Client', new Client([])); 21 | } 22 | 23 | public function test_queue_data() 24 | { 25 | $queue = Mockery::mock(QueueManager::class); 26 | $client = new Client([], $queue); 27 | 28 | $queue->shouldReceive('connection')->once()->with(null)->andReturn($queue); 29 | $queue->shouldReceive('push')->once()->with(Job::class, [], null); 30 | 31 | $client->send([]); 32 | } 33 | 34 | public function test_setting_custom_queue() 35 | { 36 | $queue = Mockery::mock(QueueManager::class); 37 | $client = new Client(['queue' => ['name' => 'errors']], $queue); 38 | 39 | $queue->shouldReceive('connection')->once()->with(null)->andReturn($queue); 40 | $queue->shouldReceive('push')->once()->with(Job::class, [], 'errors'); 41 | 42 | $client->send([]); 43 | } 44 | 45 | public function test_should_send_error_directly_if_queue_not_set() 46 | { 47 | $client = Mockery::mock(Client::class)->makePartial(); 48 | 49 | $client->shouldReceive('sendError')->once()->with([]); 50 | 51 | $client->send([]); 52 | } 53 | 54 | public function test_sending_error_no_server() 55 | { 56 | $client = new Client([]); 57 | 58 | $this->assertNull($client->sendError([])); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/LogTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(Writer::class, $log); 24 | } 25 | 26 | public function test_register_handler() 27 | { 28 | $monolog = Mockery::mock(Logger::class)->makePartial(); 29 | $log = Mockery::mock(Log::class, [$monolog])->makePartial(); 30 | $handler = Mockery::mock(NullHandler::class); 31 | 32 | $monolog->shouldReceive('pushHandler')->once()->with($handler); 33 | 34 | $log->registerHandler('error', function ($level) use ($handler) { 35 | return $handler; 36 | }); 37 | } 38 | 39 | public function test_using_context_for_exceptions() 40 | { 41 | $monolog = Mockery::mock(Logger::class)->makePartial(); 42 | $log = new Log($monolog); 43 | 44 | $exception = new \Exception('error'); 45 | 46 | $monolog->shouldReceive('error')->once()->with('error', ['exception' => $exception]); 47 | 48 | $log->error($exception); 49 | } 50 | } 51 | --------------------------------------------------------------------------------