├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── config └── log-envelope.php ├── envelope-email.png ├── envelope-slack.jpg ├── envelope-telegram.jpg ├── envelope.png ├── resources ├── migrations │ └── create_exceptions_table.php.stub └── views │ ├── main.blade.php │ └── storage.blade.php └── src ├── Drivers ├── AbstractDriver.php ├── Database.php ├── DriverFactory.php ├── Dummy.php ├── Mail.php ├── Slack.php └── Telegram.php ├── Facade.php ├── LogEnvelope.php ├── Mail └── LogEmail.php ├── Models └── ExceptionModel.php ├── ServiceProvider.php └── SyntaxHighlight.php /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor/ 3 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Yaro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Log Envelope 2 | 3 | Laravel 5-9 package for logging errors to your e-mail(s), telegram, slack and database! 4 | 5 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/Cherry-Pie/LogEnvelope/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/Cherry-Pie/LogEnvelope/?branch=master) 6 | [![Build Status](https://scrutinizer-ci.com/g/Cherry-Pie/LogEnvelope/badges/build.png?b=master)](https://scrutinizer-ci.com/g/Cherry-Pie/LogEnvelope/build-status/master) 7 | [![Total Downloads](https://poser.pugx.org/yaro/log-envelope/downloads)](https://packagist.org/packages/yaro/log-envelope) 8 | 9 | ## Installation 10 | 11 | You can install the package through Composer. 12 | ```bash 13 | composer require yaro/log-envelope 14 | ``` 15 | You must install this service provider. Make this the very first provider in list. 16 | ```php 17 | // config/app.php 18 | 'providers' => [ 19 | // make this very first provider 20 | // so fatal exceptions can be catchable by envelope 21 | Yaro\LogEnvelope\ServiceProvider::class, 22 | //... 23 | ]; 24 | ``` 25 | 26 | Then publish the config and migration file of the package using artisan. 27 | ```bash 28 | php artisan vendor:publish --provider="Yaro\LogEnvelope\ServiceProvider" 29 | ``` 30 | 31 | Change your Exception Handler's (```/app/Exceptions/Handler.php``` by default) ```report``` method like this: 32 | ```php 33 | //... 34 | public function report(Exception $e) 35 | { 36 | $res = parent::report($e); 37 | 38 | \LogEnvelope::send($e); 39 | //... 40 | 41 | return $res; 42 | } 43 | //... 44 | ``` 45 | 46 | Change config ```yaro.log-envelope.php``` for your needs. You can choose to log your errors to your database or send them to your email/telegram/slack. Emails are preferable, cuz they contains more debug information, such as traceback. 47 | 48 | There is a ```censored_fields``` option which will change any fields value to `*****` if it is named in this array. For example by default it will change values for fields called `password` to `*****`. 49 | 50 | Also there is ```force_config``` option, where you can define which configs to override for LogEnvelope execution. E.g., if you using some smtp for mail sending and queue it, you can change configs to send LogEnvelope emails immediately and not via smtp: 51 | ``` 52 | 'force_config' => [ 53 | 'mail.driver' => 'sendmail', 54 | 'queue.default' => 'sync', 55 | ], 56 | ``` 57 | 58 | 59 | ## TODO 60 | - highlight traceback in emails 61 | - page with logs from database 62 | 63 | ## Results 64 | Something like this with other info for debugging. 65 | - - - 66 | Email: 67 | 68 | ![results](https://raw.githubusercontent.com/Cherry-Pie/LogEnvelope/master/envelope-email.png) 69 | - - - 70 | Slack: 71 | 72 | ![results](https://raw.githubusercontent.com/Cherry-Pie/LogEnvelope/master/envelope-slack.jpg) 73 | - - - 74 | Telegram: 75 | 76 | ![results](https://raw.githubusercontent.com/Cherry-Pie/LogEnvelope/master/envelope-telegram.jpg) 77 | 78 | 79 | ## License 80 | The MIT License (MIT). Please see [LICENSE](https://github.com/Cherry-Pie/LogEnvelope/blob/master/LICENSE) for more information. 81 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yaro/log-envelope", 3 | "description": "Laravel 5 email on error", 4 | "keywords": ["laravel", "log", "error", "mail on error"], 5 | "require": { 6 | "php": ">=5.4.0", 7 | "illuminate/support": "5.*|^6.0|^7.0|^8.0|^9.0|^10.0" 8 | }, 9 | "autoload": { 10 | "psr-4": { 11 | "Yaro\\LogEnvelope\\": "src/" 12 | } 13 | }, 14 | "license": "MIT", 15 | "authors": [ 16 | { 17 | "name": "Yaro", 18 | "email": "12fcv4@gmail.com", 19 | "homepage": "http://cherry-pie.co", 20 | "role": "Owner" 21 | } 22 | ], 23 | "minimum-stability": "stable" 24 | } 25 | -------------------------------------------------------------------------------- /config/log-envelope.php: -------------------------------------------------------------------------------- 1 | [ 9 | 'mail' => [ 10 | 'enabled' => false, 11 | 'to' => [ 12 | // 'email@example.com', 13 | ], 14 | 'from_name' => 'Log Envelope', 15 | 'from_email' => '', 16 | ], 17 | 18 | 'telegram' => [ 19 | 'enabled' => false, 20 | 'token' => env('LOGENVELOPE_TELEGRAM_TOKEN'), 21 | 'chats' => [ 22 | // telegram user id 23 | ], 24 | ], 25 | 26 | 'slack' => [ 27 | 'enabled' => false, 28 | 'username' => 'Log Envelope', 29 | 'channel' => '#logenvelope', // create channel 30 | 'token' => env('LOGENVELOPE_SLACK_TOKEN'), 31 | ], 32 | 33 | 'database' => [ 34 | 'enabled' => false, 35 | 'model' => Yaro\LogEnvelope\Models\ExceptionModel::class, 36 | ], 37 | ], 38 | 39 | /* 40 | * Change config for LogEnvelope execution. 41 | */ 42 | 'force_config' => [ 43 | // 'mail.driver' => 'sendmail', 44 | // 'queue.default' => 'sync', 45 | ], 46 | 47 | /* 48 | * How many lines to show before exception line and after. 49 | */ 50 | 'lines_count' => 6, 51 | 52 | /* 53 | * List of exceptions to skip sending. 54 | */ 55 | 'except' => [ 56 | //'Exception', 57 | 'Symfony\Component\HttpKernel\Exception\NotFoundHttpException', 58 | 'Symfony\Component\Process\Exception\ProcessTimedOutException', 59 | ], 60 | 61 | /* 62 | * List of fields to censor 63 | */ 64 | 'censored_fields' => [ 65 | 'password', 66 | ], 67 | 68 | ]; 69 | -------------------------------------------------------------------------------- /envelope-email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cherry-Pie/LogEnvelope/b7d153196e8a64fee413c623b6503fb998c1f51c/envelope-email.png -------------------------------------------------------------------------------- /envelope-slack.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cherry-Pie/LogEnvelope/b7d153196e8a64fee413c623b6503fb998c1f51c/envelope-slack.jpg -------------------------------------------------------------------------------- /envelope-telegram.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cherry-Pie/LogEnvelope/b7d153196e8a64fee413c623b6503fb998c1f51c/envelope-telegram.jpg -------------------------------------------------------------------------------- /envelope.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cherry-Pie/LogEnvelope/b7d153196e8a64fee413c623b6503fb998c1f51c/envelope.png -------------------------------------------------------------------------------- /resources/migrations/create_exceptions_table.php.stub: -------------------------------------------------------------------------------- 1 | increments('id'); 19 | 20 | $table->string('host'); 21 | $table->string('method'); 22 | $table->string('fullUrl'); 23 | $table->text('exception'); 24 | $table->text('error'); 25 | $table->string('line'); 26 | $table->string('file'); 27 | $table->string('class'); 28 | $table->text('storage'); 29 | $table->text('exegutor'); 30 | $table->text('file_lines'); 31 | 32 | $table->timestamps(); 33 | }); 34 | } 35 | } 36 | 37 | /** 38 | * Reverse the migrations. 39 | */ 40 | public function down() 41 | { 42 | if (Schema::hasTable('exceptions')) { 43 | Schema::drop('exceptions'); 44 | } 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /resources/views/main.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Log Envelope 6 | 15 | 16 | 17 |
18 |

19 |
[{{ $method }}]: {{ $fullUrl }}
20 |

21 |

22 | {{ $class }} 23 |
24 | {{ $exception }} 25 |

26 |
27 |
{{ $file }}
28 |
{!! $lineInfo['wrap_left'] !!}{!! $lineInfo['line'] !!}{!! $lineInfo['wrap_right'] !!}
29 |
30 |
Stack trace
31 |
32 |
{{ $error }}
33 |
34 |
developed by Yaro @ https://github.com/Cherry-Pie
35 |
36 | 37 | 38 | 39 | @foreach ($storage as $caption => $data) 40 | @include('log-envelope::storage') 41 | @endforeach 42 | 43 |
44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /resources/views/storage.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ $caption }} 5 | 6 | 7 | 8 | 9 | @foreach ($data as $key => $val) 10 | 11 | 14 | 17 | 20 | 21 | @endforeach 22 | 23 |
12 |
{{ $key }}
13 |
15 |
=>
16 |
18 |
{{ print_r($val, true) }}
19 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Drivers/AbstractDriver.php: -------------------------------------------------------------------------------- 1 | data = $data; 14 | } // end __construct 15 | 16 | public function setConfig($config) 17 | { 18 | $this->config = $config; 19 | $this->prepare(); 20 | 21 | return $this; 22 | } // end setConfig 23 | 24 | protected function prepare() {} // end prepare 25 | 26 | protected function isEnabled() 27 | { 28 | return isset($this->config['enabled']) && $this->config['enabled']; 29 | } // end isEnabled 30 | 31 | abstract public function send(); 32 | 33 | abstract protected function check(); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/Drivers/Database.php: -------------------------------------------------------------------------------- 1 | isEnabled(); 12 | } 13 | 14 | public function send() 15 | { 16 | if (!$this->check()) { 17 | return; 18 | } 19 | 20 | $data = $this->data; 21 | 22 | $data['exegutor'] = implode('
', array_map($data['exegutor'], function ($item) { 23 | return $item['wrap_left'] . $item['line'] . $item['wrap_right']; 24 | })); 25 | $data['lines'] = implode("\n", $data['lines']); 26 | $data['storage'] = json_encode($data['storage']); 27 | 28 | $model = $this->config['model']; 29 | $model::create($data); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Drivers/DriverFactory.php: -------------------------------------------------------------------------------- 1 | config['from_name'] = $this->config['from_name'] ?: 'Log Envelope'; 14 | $this->config['from_email'] = $this->config['from_email'] ?: 'logenvelope@'. $this->data['host']; 15 | } // end prepare 16 | 17 | protected function check() 18 | { 19 | return $this->isEnabled() && (isset($this->config['to']) && $this->config['to']); 20 | } // end check 21 | 22 | public function send() 23 | { 24 | if (!$this->check()) { 25 | return; 26 | } 27 | 28 | $data = $this->data; 29 | $config = $this->config; 30 | 31 | if ($this->isMailablePossible()) { 32 | MailFacade::queue(new LogEmail($data, $config)); 33 | return; 34 | } 35 | 36 | MailFacade::queue('log-envelope::main', $data, function($message) use ($data, $config) { 37 | $subject = sprintf('[%s] @ %s: %s', $data['class'], $data['host'], $data['exception']); 38 | 39 | // to protect from gmail's anchors automatic generation 40 | $message->setBody( 41 | preg_replace( 42 | ['~\.~', '~http~'], 43 | ['.', 'http'], 44 | $message->getBody() 45 | ) 46 | ); 47 | 48 | $message->to($config['to']) 49 | ->from($config['from_email'], $config['from_name']) 50 | ->subject($subject); 51 | }); 52 | } // end send 53 | 54 | private function isMailablePossible() 55 | { 56 | return class_exists('Illuminate\Mail\Mailable'); 57 | } // end isMailablePossible 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/Drivers/Slack.php: -------------------------------------------------------------------------------- 1 | isEnabled() && (isset($this->config['channel']) && $this->config['channel'] && isset($this->config['token']) && $this->config['token']); 11 | } // end check 12 | 13 | public function send() 14 | { 15 | if (!$this->check()) { 16 | return; 17 | } 18 | 19 | $data = $this->data; 20 | 21 | $text = sprintf( 22 | '[%s] %s%s[%s] *%s*%sin %s line %s%s', 23 | $data['method'], 24 | $data['fullUrl'], 25 | "\n", 26 | $data['class'], 27 | $data['exception'], 28 | "\n", 29 | $data['file'], 30 | $data['line'], 31 | "\n \n" 32 | ); 33 | 34 | $attachments = [ 35 | 'fallback' => $text, 36 | 'color' => '#55688a', 37 | 'text' => '', 38 | ]; 39 | $biggestNumberLength = strlen(max(array_keys($data['file_lines']))); 40 | foreach ($data['file_lines'] as $num => $line) { 41 | $num = str_pad($num, $biggestNumberLength, ' ', STR_PAD_LEFT); 42 | // to be sure that we'll have no extra endings 43 | $line = preg_replace('~[\r\n]~', '', $line); 44 | // double spaces, so in slack it'll be more readable 45 | $line = preg_replace('~(\s+)~', "$1$1", $line); 46 | $attachments['text'] .= $num .'| '. $line ."\n"; 47 | } 48 | 49 | 50 | $url = 'https://slack.com/api/chat.postMessage?' 51 | . 'token='. $this->config['token'] 52 | . '&channel='. urlencode($this->config['channel']) 53 | . '&text='. urlencode($text) 54 | . '&username='. urlencode($this->config['username']) 55 | . '&as_user=false&icon_url=http%3A%2F%2Fcherry-pie.co%2Fimg%2Flog-envelope.png&mrkdwn=1&pretty=1&attachments='. urlencode(json_encode([$attachments])); 56 | 57 | @file_get_contents($url); 58 | } // end send 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/Drivers/Telegram.php: -------------------------------------------------------------------------------- 1 | isEnabled() && (isset($this->config['chats']) && $this->config['chats'] && is_array($this->config['chats']) && isset($this->config['token']) && $this->config['token']); 11 | } // end check 12 | 13 | public function send() 14 | { 15 | if (!$this->check()) { 16 | return; 17 | } 18 | 19 | //https://api.telegram.org/{$this->config['token']}/getUpdates 20 | $data = $this->data; 21 | 22 | $text = sprintf( 23 | '[%s] %s%s[%s] %s%sin %s line %s%s', 24 | urlencode($data['method']), 25 | urlencode($data['fullUrl']), 26 | urlencode("\n \n"), 27 | urlencode($data['class']), 28 | urlencode($data['exception']), 29 | urlencode("\n"), 30 | urlencode($data['file']), 31 | $data['line'], 32 | urlencode("\n \n") 33 | ); 34 | 35 | $biggestNumberLength = strlen(max(array_keys($data['file_lines']))); 36 | foreach ($data['file_lines'] as $num => $line) { 37 | $num = str_pad($num, $biggestNumberLength, ' ', STR_PAD_LEFT); 38 | $num = ''. $num .''; 39 | $text .= urlencode($num .'|'. htmlentities($line) .''); 40 | } 41 | 42 | foreach ($this->config['chats'] as $idUser) { 43 | $url = 'https://api.telegram.org/'. $this->config['token'] .'/sendMessage?disable_web_page_preview=true&chat_id='. $idUser 44 | . '&parse_mode=HTML&text='; 45 | 46 | @file_get_contents($url . $text); 47 | } 48 | } // end send 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/Facade.php: -------------------------------------------------------------------------------- 1 | config['censored_fields'] = config('yaro.log-envelope.censored_fields', ['password']); 20 | $this->config['except'] = config('yaro.log-envelope.except', []); 21 | $this->config['count'] = config('yaro.log-envelope.lines_count', 6); 22 | $this->config['drivers'] = config('yaro.log-envelope.drivers', []); 23 | } // end __construct 24 | 25 | public function send($exception) 26 | { 27 | $this->onBefore(); 28 | 29 | try { 30 | $data = $this->getExceptionData($exception); 31 | 32 | if ($this->isSkipException($data['class'])) { 33 | return; 34 | } 35 | 36 | foreach ($this->config['drivers'] as $driver => $driverConfig) { 37 | DriverFactory::create($driver, $data)->setConfig($driverConfig)->send(); 38 | } 39 | } catch (Exception $e) { 40 | Log::error($e); 41 | } 42 | 43 | $this->onAfter(); 44 | } // end send 45 | 46 | private function onBefore() 47 | { 48 | $this->cachedConfig = []; 49 | $forcedConfig = config('yaro.log-envelope.force_config', []); 50 | foreach ($forcedConfig as $configKey => $configValue) { 51 | $this->cachedConfig[$configKey] = config($configKey); 52 | } 53 | if ($forcedConfig) { 54 | config($forcedConfig); 55 | } 56 | } // end onBefore 57 | 58 | private function onAfter() 59 | { 60 | if ($this->cachedConfig) { 61 | config($this->cachedConfig); 62 | } 63 | } // end onAfter 64 | 65 | public function isSkipException($exceptionClass) 66 | { 67 | return in_array($exceptionClass, $this->config['except']); 68 | } // end isSkipException 69 | 70 | private function getExceptionData($exception) 71 | { 72 | $data = []; 73 | 74 | $data['host'] = Request::server('HTTP_HOST'); 75 | $data['method'] = Request::method(); 76 | $data['fullUrl'] = Request::fullUrl(); 77 | if (php_sapi_name() === 'cli') { 78 | $data['host'] = parse_url(config('app.url'), PHP_URL_HOST); 79 | $data['method'] = 'CLI'; 80 | } 81 | $data['exception'] = $exception->getMessage(); 82 | $data['error'] = $exception->getTraceAsString(); 83 | $data['line'] = $exception->getLine(); 84 | $data['file'] = $exception->getFile(); 85 | $data['class'] = get_class($exception); 86 | $data['storage'] = array( 87 | 'SERVER' => Request::server(), 88 | 'GET' => Request::query(), 89 | 'POST' => $_POST, 90 | 'FILE' => Request::file(), 91 | 'OLD' => Request::hasSession() ? Request::old() : [], 92 | 'COOKIE' => Request::cookie(), 93 | 'SESSION' => Request::hasSession() ? Session::all() : [], 94 | 'HEADERS' => Request::header(), 95 | ); 96 | 97 | // Remove empty, false and null values 98 | $data['storage'] = array_filter($data['storage']); 99 | 100 | // Censor sensitive field values 101 | array_walk_recursive($data['storage'], self::censorSensitiveFields(...)); 102 | 103 | $count = $this->config['count']; 104 | 105 | $data['exegutor'] = []; 106 | $data['file_lines'] = []; 107 | 108 | $file = new SplFileObject($data['file']); 109 | for ($i = -1 * abs($count); $i <= abs($count); $i++) { 110 | list($line, $exegutorLine) = $this->getLineInfo($file, $data['line'], $i); 111 | if (!$line && !$exegutorLine) { 112 | continue; 113 | } 114 | $data['exegutor'][] = $exegutorLine; 115 | $data['file_lines'][$data['line'] + $i] = $line; 116 | } 117 | 118 | // to make Symfony exception more readable 119 | if ($data['class'] == 'Symfony\Component\Debug\Exception\FatalErrorException') { 120 | preg_match("~^(.+)' in ~", $data['exception'], $matches); 121 | if (isset($matches[1])) { 122 | $data['exception'] = $matches[1]; 123 | } 124 | } 125 | 126 | return $data; 127 | } // end getExceptionData 128 | 129 | /** 130 | * Set the value of specified fields to ***** 131 | * 132 | * @param string $value 133 | * @param string $key 134 | * @return void 135 | */ 136 | public function censorSensitiveFields(&$value, $key) 137 | { 138 | if (in_array($key, $this->config['censored_fields'], true)) { 139 | $value = '*****'; 140 | } 141 | } 142 | 143 | /** 144 | * @param SplFileObject $file 145 | */ 146 | private function getLineInfo($file, $line, $i) 147 | { 148 | $currentLine = $line + $i; 149 | // cuz array starts with 0, when file lines start count from 1 150 | $index = $currentLine - 1; 151 | if ($index < 0) { 152 | return [false, false]; 153 | } 154 | $file->seek($index); 155 | 156 | if ($file->eof()) { 157 | return [false, false]; 158 | } 159 | 160 | return [ 161 | $file->current(), 162 | [ 163 | 'line' => '' . $currentLine . '. ' . SyntaxHighlight::process($file->current()), 164 | 'wrap_left' => $i ? '' : '', 165 | 'wrap_right' => $i ? '' : '', 166 | ] 167 | ]; 168 | } // end getLineInfo 169 | } 170 | -------------------------------------------------------------------------------- /src/Mail/LogEmail.php: -------------------------------------------------------------------------------- 1 | data = $data; 19 | $this->config = $config; 20 | } 21 | 22 | /** 23 | * Build the message. 24 | * 25 | * @return $this 26 | */ 27 | public function build() 28 | { 29 | $subject = sprintf('[%s] @ %s: %s', $this->data['class'], $this->data['host'], $this->data['exception']); 30 | 31 | // to protect from gmail's anchors automatic generating 32 | $this->withSwiftMessage(function ($message) { 33 | $message->setBody( 34 | preg_replace( 35 | ['~\.~', '~http~'], 36 | ['.', 'http'], 37 | $message->getBody() 38 | ) 39 | ); 40 | }); 41 | 42 | return $this->view('log-envelope::main') 43 | ->with($this->data) 44 | ->to($this->config['to']) 45 | ->from($this->config['from_email'], $this->config['from_name']) 46 | ->subject($subject); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Models/ExceptionModel.php: -------------------------------------------------------------------------------- 1 | publishes([ 19 | __DIR__ . '/../config/log-envelope.php' => config_path('yaro.log-envelope.php'), 20 | ]); 21 | 22 | /* 23 | * Publish migration if not published yet 24 | */ 25 | if (!$this->migrationHasAlreadyBeenPublished()) { 26 | $timestamp = date('Y_m_d_His', time()); 27 | $this->publishes([ 28 | __DIR__ . '/../resources/migrations/create_exceptions_table.php.stub' => database_path('migrations/' . $timestamp . '_create_exceptions_table.php'), 29 | ], 'migrations'); 30 | } 31 | 32 | $this->app['view']->addNamespace('log-envelope', __DIR__ . '/../resources/views'); 33 | 34 | $loader = \Illuminate\Foundation\AliasLoader::getInstance(); 35 | $loader->alias('LogEnvelope', 'Yaro\LogEnvelope\Facade'); 36 | } 37 | 38 | /** 39 | * Register the service provider. 40 | */ 41 | public function register() 42 | { 43 | config([ 44 | 'config/yaro.log-envelope.php', 45 | ]); 46 | 47 | $this->app->singleton('yaro.log-envelope', function($app) { 48 | return new LogEnvelope(); 49 | }); 50 | } 51 | 52 | /** 53 | * @return bool 54 | */ 55 | protected function migrationHasAlreadyBeenPublished() 56 | { 57 | $files = glob(database_path('/migrations/*_create_exceptions_table.php')); 58 | return count($files) > 0; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/SyntaxHighlight.php: -------------------------------------------------------------------------------- 1 | highlight($s); 16 | } // end process 17 | 18 | public function highlight($s) 19 | { 20 | $s = htmlspecialchars($s, ENT_COMPAT); 21 | 22 | // Workaround for escaped backslashes 23 | $s = str_replace('\\\\', '\\\\', $s); 24 | 25 | $regexp = array( 26 | // Numbers (also look for Hex) 27 | '/(? '$1', 33 | 34 | // Make the bold assumption that an 35 | // all uppercase word has a special meaning 36 | '/(?|\#)( 37 | [A-Z_0-9]{2,} 38 | )(?!\w)/x' 39 | => '$1', 40 | 41 | // Keywords 42 | '/(?)( 43 | and|or|xor|for|do|while|foreach|as|return|die|exit|if|then|else| 44 | elseif|new|delete|try|throw|catch|finally|class|function|string| 45 | array|object|resource|var|bool|boolean|int|integer|float|double| 46 | real|string|array|global|const|static|public|private|protected| 47 | published|extends|switch|true|false|null|void|this|self|struct| 48 | char|signed|unsigned|short|long 49 | )(?!\w|=")/ix' 50 | => '$1', 51 | 52 | // PHP/Perl-Style Vars: $var, %var, @var 53 | '/(? '$1', 57 | ); 58 | 59 | // Comments/Strings 60 | $s = preg_replace_callback('/( 61 | \/\*.*?\*\/| 62 | \/\/.*?\n| 63 | \#.[^a-fA-F0-9]+?\n| 64 | \<\!\-\-[\s\S]+\-\-\>| 65 | (?tokens), array_values($this->tokens), $s); 73 | 74 | // Delete the "Escaped Backslash Workaround Token" (TM) 75 | // and replace tabs with four spaces. 76 | $s = str_replace(array('', "\t"), array('', ' '), $s); 77 | 78 | return $s; 79 | } // end highlight 80 | 81 | /* 82 | * Regexp-Callback to replace every comment or string with a uniqid and save 83 | * the matched text in an array 84 | * This way, strings and comments will be stripped out and wont be processed 85 | * by the other expressions searching for keywords etc. 86 | */ 87 | private function replaceId($matches) 88 | { 89 | $match = $matches[0]; 90 | $id = "##r" . uniqid() . "##"; 91 | 92 | // String or Comment? 93 | if (substr($match, 0, 2) == '//' || substr($match, 0, 2) == '/*' || substr($match, 0, 2) == '##' || substr($match, 0, 7) == '<!--') { 94 | $this->tokens[$id] = '' . $match . ''; 95 | } else { 96 | $this->tokens[$id] = '' . $match . ''; 97 | } 98 | return $id; 99 | } // end replaceId 100 | } 101 | 102 | --------------------------------------------------------------------------------