├── .all-contributorsrc ├── .codecov.yml ├── .gitignore ├── .styleci.yml ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── composer.json ├── config └── loggable.php ├── database └── migrations │ └── 2020_01_01_201821_create_model_logs_table.php ├── photo.jpg ├── phpunit.xml ├── src ├── Accessors │ └── Accessor.php ├── Drivers │ ├── Database.php │ ├── File.php │ └── LoggerDriver.php ├── Events │ └── Logged.php ├── Exceptions │ └── LoggableFieldsNotSetException.php ├── Facades │ └── Loggable.php ├── LaravelLoggableServiceProvider.php ├── Loggable.php ├── Logger.php └── Models │ └── LoggableModel.php └── tests └── TestCase.php /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "contributorsPerLine": 7, 7 | "badgeTemplate": "[![All Contributors](https://img.shields.io/badge/all_contributors-<%= contributors.length %>-orange.svg?style=flat-square)](#contributors)", 8 | "contributorTemplate": "\">\" width=\"<%= options.imageSize %>px;\" alt=\"\"/>
<%= contributor.name %>
", 9 | "types": { 10 | "custom": { 11 | "symbol": "🔭", 12 | "description": "A custom contribution type.", 13 | "link": "[<%= symbol %>](<%= url %> \"<%= description %>\")," 14 | } 15 | }, 16 | "skipCi": true, 17 | "contributors": [ 18 | { 19 | "login": "alkhachatryan", 20 | "name": "Alexey", 21 | "avatar_url": "https://avatars1.githubusercontent.com/u/22774727?v=4", 22 | "profile": "https://khachatryan.org/", 23 | "contributions": [ 24 | "code" 25 | ] 26 | }, 27 | { 28 | "login": "imliam", 29 | "name": "Liam Hammett", 30 | "avatar_url": "https://avatars0.githubusercontent.com/u/4326337?v=4", 31 | "profile": "https://liamhammett.com", 32 | "contributions": [ 33 | "code" 34 | ] 35 | }, 36 | { 37 | "login": "deligoez", 38 | "name": "Yunus Emre Deligöz", 39 | "avatar_url": "https://avatars1.githubusercontent.com/u/3030815?v=4", 40 | "profile": "https://github.com/deligoez", 41 | "contributions": [ 42 | "code" 43 | ] 44 | } 45 | ], 46 | "projectName": "laravel-loggable", 47 | "projectOwner": "alkhachatryan", 48 | "repoType": "github", 49 | "repoHost": "https://github.com" 50 | } 51 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | notify: 3 | require_ci_to_pass: yes 4 | 5 | coverage: 6 | precision: 2 7 | round: down 8 | range: “70…100” 9 | 10 | status: 11 | project: yes 12 | patch: yes 13 | changes: no 14 | 15 | comment: 16 | layout: "reach, diff, flags, files, footer" 17 | behavior: default 18 | require_changes: no 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /vendor -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: laravel -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.1 5 | - 7.2 6 | 7 | env: 8 | matrix: 9 | - COMPOSER_OPTIONS="" 10 | 11 | before_install: 12 | - sudo apt-get update 13 | - travis_retry composer self-update 14 | 15 | install: 16 | - travis_retry composer update ${COMPOSER_OPTIONS} --prefer-source 17 | 18 | script: 19 | - phpunit -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # laravel-loggable 2 | 3 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) 4 | [![Total Downloads](https://img.shields.io/packagist/dt/alkhachatryan/laravel-loggable.svg?style=flat-square)](https://packagist.org/packages/alkhachatryan/laravel-loggable) 5 | 6 | Laravel Loggable is a package for eloquent models, which will monitor the changes on the models and log. 7 | It suuports two drivers: File and Database. 8 | 9 | # Features 10 | - High-configurable 11 | - Two drivers (database and file) 12 | - Possibillity to use two drivers at once 13 | - Possibillity to select the columns for the model which should be logged 14 | - Possibillity to select the actions for the model which should be logged (create, edit, delete) 15 | - Facade-based structure to fetch the logs for specific model 16 | - Much more 17 | 18 | # Installation 19 | ##### Install the package. 20 | `composer require alkhachatryan/laravel-loggable` 21 | 22 | ##### Publish the configuration file 23 | `php artisan vendor:publish --tag=loggable` 24 | 25 | ##### Run migration 26 | `php artisan migrate` 27 | 28 | # Configuration 29 | Open the configuration file at /config/loggable.php 30 | 31 | Set the driver whhich will log the model changes (can be both). 32 | However, it's recommended to use the database driver so you can fetch the logs in the future. 33 | ```php 34 | 'driver' => 'database' 35 | ``` 36 | 37 | 38 | That's it! 39 | 40 | # Usage 41 | ```php 42 | class Post extends Model 43 | { 44 | /** Include the loggable trait */ 45 | use Loggable; 46 | 47 | /** Specified actions for this model */ 48 | public $loggable_actions = ['edit', 'create', 'delete']; 49 | 50 | /** Specified fields for this model */ 51 | public $loggable_fields = ['title', 'body']; 52 | 53 | protected $fillable = ['title', 'body']; 54 | } 55 | ``` 56 | 57 | ##### Retriving the model logs via Facade 58 | ```php 59 | Loggable::model('App\Post'); 60 | ``` 61 | ##### Retriving the model logs via Model 62 | ```php 63 | LoggableModel::whereModelName('App\Post')->orderBy('id', 'DESC')->paginate(10); 64 | ``` 65 | 66 | ##### Event 67 | You can use the event *Alkhachatryan\LaravelLoggable\Events\Logged* in pair with your listeners. 68 | 69 | # Changelog 70 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. 71 | 72 | # Credits 73 | 74 | - [Alexey Khachatryan](https://github.com/alkhachatryan) 75 | - [All Contributors](https://github.com/alkhachatryan/laravel-loggable/contributors) 76 | 77 | # Todo 78 | Tests!!! Tests!!! Tests!!! 79 | 80 | # Security 81 | If you discover any security-related issues, please email info@khachatryan.org instead of using the issue tracker. 82 | 83 | # License 84 | The MIT License (MIT). Please see [License File](/LICENSE.md) for more information. 85 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 MIT 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Loggable - Log you model changes 2 | 3 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) 4 | ![Packagist Version](https://img.shields.io/packagist/v/alkhachatryan/laravel-loggable) 5 | ![CodeFactor Grade](https://img.shields.io/codefactor/grade/github/alkhachatryan/laravel-loggable) 6 | [![Total Downloads][ico-downloads]][link-downloads] 7 | ![](https://komarev.com/ghpvc/?username=alkhachatryan-laravel-loggable&label=Repo+views&color=brightgreen&style=flat-square) 8 | 9 | Laravel Loggable is a package for eloquent models, which will monitor the changes on the models and log. 10 | It supports two drivers: File and Database. 11 | 12 | # Features 13 | - High-configurable 14 | - Two drivers (database and file) 15 | - Possibillity to use two drivers at once 16 | - Possibillity to select the columns for the model which should be logged 17 | - Possibillity to select the actions for the model which should be logged (create, edit, delete) 18 | - Facade-based structure to fetch the logs for specific model 19 | - Much more 20 | 21 | ![Logs](https://raw.githubusercontent.com/alkhachatryan/laravel-loggable/master/photo.jpg) 22 | 23 | # Installation 24 | ##### Install the package. 25 | `composer require alkhachatryan/laravel-loggable` 26 | 27 | ##### Publish the configuration file 28 | `php artisan vendor:publish --tag=loggable` 29 | 30 | ##### Run migration 31 | `php artisan migrate` 32 | 33 | # Configuration 34 | Open the configuration file at /config/loggable.php 35 | 36 | Set the driver whhich will log the model changes (can be both). 37 | However, it's recommended to use the database driver so you can fetch the logs in the future. 38 | ```php 39 | 'driver' => 'database' 40 | ``` 41 | 42 | 43 | That's it! 44 | 45 | # Usage 46 | ```php 47 | class Post extends Model 48 | { 49 | /** Include the loggable trait */ 50 | use Loggable; 51 | 52 | /** Specified actions for this model */ 53 | public $loggable_actions = ['edit', 'create', 'delete']; 54 | 55 | /** Specified fields for this model */ 56 | public $loggable_fields = ['title', 'body']; 57 | 58 | protected $fillable = ['title', 'body']; 59 | } 60 | ``` 61 | 62 | ##### Retriving the model logs via Facade 63 | ```php 64 | Loggable::model('App\Post'); 65 | ``` 66 | ##### Retriving the model logs via Model 67 | ```php 68 | LoggableModel::whereModelName('App\Post')->orderBy('id', 'DESC')->paginate(10); 69 | ``` 70 | 71 | ##### Event 72 | You can use the event *Alkhachatryan\LaravelLoggable\Events\Logged* in pair with your listeners. 73 | 74 | # Changelog 75 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. 76 | 77 | # Todo 78 | Tests!!! Tests!!! Tests!!! 79 | 80 | # Security 81 | If you discover any security-related issues, please email info@khachatryan.org instead of using the issue tracker. 82 | 83 | # License 84 | The MIT License (MIT). Please see [License File](/LICENSE.md) for more information. 85 | 86 | [ico-downloads]: https://img.shields.io/packagist/dt/alkhachatryan/laravel-loggable.svg?style=flat-square&color=brightgreen 87 | [link-downloads]: https://packagist.org/packages/alkhachatryan/laravel-loggable 88 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "alkhachatryan/laravel-loggable", 3 | "description": "Laravel Loggable - Log your model changes in multiple ways.", 4 | "type": "package", 5 | "license": "MIT", 6 | "keywords": [ 7 | "laravel" 8 | ], 9 | "authors": [ 10 | { 11 | "name": "Alexey Khachatryan", 12 | "email": "info@khachatryan.org" 13 | } 14 | ], 15 | "require": { 16 | "illuminate/support": "^5.0" 17 | }, 18 | "autoload": { 19 | "psr-4": { 20 | "Alkhachatryan\\LaravelLoggable\\": "./src" 21 | } 22 | }, 23 | "autoload-dev": { 24 | "psr-4": { 25 | "Alkhachatryan\\LaravelLoggable\\Tests\\": "tests" 26 | } 27 | }, 28 | "scripts": { 29 | "test": "vendor/bin/phpunit" 30 | }, 31 | "extra": { 32 | "laravel": { 33 | "providers": [ 34 | "Alkhachatryan\\LaravelLoggable\\LaravelLoggableServiceProvider" 35 | ] 36 | } 37 | }, 38 | "require-dev": { 39 | "orchestra/testbench": "^4.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /config/loggable.php: -------------------------------------------------------------------------------- 1 | 'database', 20 | 21 | // This is the path of the log files. 22 | // This is required if the driver is/contains the file type. 23 | // The full path of the file will include the path set below + /ModelName/Month/Date.log 24 | 'storage_path' => storage_path('logs/models'), 25 | 26 | // The list of the actions which should be logged. 27 | // This list will affect of all models, where the loggable function is enabled. 28 | // If there is no specified actions list in the model, the default list will be this one. 29 | // 30 | // You can specify one action as string or multiple as array. 31 | // Supported actions: create, edit, delete 32 | 'actions' => ['edit', 'delete'], 33 | ]; 34 | -------------------------------------------------------------------------------- /database/migrations/2020_01_01_201821_create_model_logs_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->unsignedBigInteger('user_id')->nullable(); 19 | $table->string('model_name')->index(); 20 | $table->unsignedBigInteger('model_id')->index(); 21 | $table->enum('action', ['create', 'edit', 'delete']); 22 | $table->text('data')->nullable(); 23 | $table->timestamp('date'); 24 | 25 | $table->index(['model_name', 'model_id']); 26 | 27 | $table->foreign('user_id')->references('id')->on('users'); 28 | }); 29 | } 30 | 31 | /** 32 | * Reverse the migrations. 33 | * 34 | * @return void 35 | */ 36 | public function down() 37 | { 38 | Schema::dropIfExists('model_logs'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /photo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alkhachatryan/laravel-loggable/f266508370fe15b96b3f1a94ea8cb1db6841bc16/photo.jpg -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | tests 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | src/ 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Accessors/Accessor.php: -------------------------------------------------------------------------------- 1 | orderBy('id', 'DESC')->paginate($limit); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Drivers/Database.php: -------------------------------------------------------------------------------- 1 | user ? $this->user->id : null; 17 | $data = null; 18 | 19 | if ($this->action === 'create') { 20 | $data = $this->model; 21 | } elseif ($this->action === 'edit') { 22 | $data = [ 23 | 'before' => array_intersect_key($this->model->getOriginal(), 24 | array_intersect_key($this->model->getChanges(), 25 | array_flip($this->loggable_fields))), 26 | 'after' => array_intersect_key($this->model->getChanges(), 27 | array_flip($this->loggable_fields)), 28 | ]; 29 | } 30 | 31 | LoggableModel::create([ 32 | 'user_id' => $user_id, 33 | 'model_name' => $this->model_name, 34 | 'model_id' => $this->model->id, 35 | 'action' => $this->action, 36 | 'data' => json_encode($data), 37 | ]); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Drivers/File.php: -------------------------------------------------------------------------------- 1 | model_name); 16 | $storage_path = $this->config['storage_path'] 17 | .'/'.$model_name 18 | .'/'.date('YF'); 19 | 20 | $file_path = $storage_path.'/'.date('d').'.log'; 21 | 22 | if (! \Illuminate\Support\Facades\File::exists($storage_path)) { 23 | mkdir($storage_path, 0755, true); 24 | } 25 | 26 | $text = $this->getLogTemplate(); 27 | 28 | \Illuminate\Support\Facades\File::prepend($file_path, $text); 29 | } 30 | 31 | /** 32 | * Get the template for incoming action. 33 | * 34 | * @return string 35 | */ 36 | private function getLogTemplate() 37 | { 38 | $user_id = $this->user ? $this->user->id : 'N/A'; 39 | 40 | $template = now()->toDateTimeString().PHP_EOL; 41 | 42 | if ($this->action === 'create') { 43 | $template .= "New model was created by $user_id." 44 | .PHP_EOL.'Inserted data: ' 45 | .PHP_EOL.http_build_query($this->model->toArray(), '', PHP_EOL); 46 | } elseif ($this->action === 'edit') { 47 | $template .= "Model {$this->model->id} was update by $user_id." 48 | .PHP_EOL.'MODEL BEFORE ' 49 | .PHP_EOL.http_build_query(array_intersect_key($this->model->getOriginal(), 50 | array_intersect_key($this->model->getChanges(), 51 | array_flip($this->loggable_fields))), '', PHP_EOL) 52 | 53 | .PHP_EOL.'MODEL AFTER ' 54 | .PHP_EOL.http_build_query(array_intersect_key($this->model->getChanges(), 55 | array_flip($this->loggable_fields)), '', PHP_EOL); 56 | } elseif ($this->action === 'delete') { 57 | $template .= "Model {$this->model->id} was deleted by $user_id"; 58 | } 59 | 60 | $template .= PHP_EOL.PHP_EOL; 61 | 62 | return $template; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Drivers/LoggerDriver.php: -------------------------------------------------------------------------------- 1 | model_name = get_class($model); 63 | $this->model = $model; 64 | $this->action = $action; 65 | $this->config = $config; 66 | $this->loggable_fields = $loggable_fields; 67 | $this->user = $user; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Events/Logged.php: -------------------------------------------------------------------------------- 1 | model = $model; 51 | $this->action = $action; 52 | $this->driver = $driver; 53 | $this->user = $user; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Exceptions/LoggableFieldsNotSetException.php: -------------------------------------------------------------------------------- 1 | app->bind('loggable', function () { 18 | return new Accessor(); 19 | }); 20 | } 21 | 22 | /** 23 | * Bootstrap services. 24 | * 25 | * @return void 26 | */ 27 | public function boot() 28 | { 29 | $this->publishes([ 30 | __DIR__.'/../config/loggable.php' => config_path('loggable.php'), 31 | ], 'loggable'); 32 | 33 | $this->loadMigrationsFrom(__DIR__.'/../database/migrations'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Loggable.php: -------------------------------------------------------------------------------- 1 | record(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Logger.php: -------------------------------------------------------------------------------- 1 | model = $model; 88 | $this->action = $action; 89 | $this->model_name = get_class($model); 90 | $this->config = config('loggable'); 91 | $this->loggable_actions = $model->loggable_actions; 92 | $this->loggable_fields = $model->loggable_fields; 93 | $this->user = Auth::user(); 94 | 95 | $this->driver = new \stdClass(); 96 | $this->driver->file = new \Alkhachatryan\LaravelLoggable\Drivers\File( 97 | $this->model, $this->action, $this->config, $this->user, $this->loggable_fields 98 | ); 99 | $this->driver->database = new \Alkhachatryan\LaravelLoggable\Drivers\Database( 100 | $this->model, $this->action, $this->config, $this->user, $this->loggable_fields 101 | ); 102 | 103 | $this->prepareData(); 104 | } 105 | 106 | /** 107 | * Set the model properties which are not set. 108 | * In case if the loggable actions not specified - get from config file. 109 | * @see config.loggable.actions 110 | * 111 | * If the action is EDIT, but there is no specified fields - throw new exception. 112 | * Notice: before throwing new exception - the model already saved. 113 | * 114 | * Check if this action should be logged, depending on the model properties or configuration file. 115 | * 116 | * @throws LoggableFieldsNotSetException 117 | * 118 | * @return void 119 | */ 120 | private function prepareData() 121 | { 122 | if ($this->action === 'edit' && ! $this->model->loggable_fields) { 123 | throw new LoggableFieldsNotSetException($this->model_name); 124 | } 125 | if ( 126 | // If there is no specified actions in the model - get from config. 127 | // If the config field is an array - check if the action in the array. 128 | // Else if the field is a string - check if the action equals incoming action. 129 | // If false - not log. 130 | (! $this->loggable_actions 131 | && ( 132 | (is_array($this->config['actions']) && ! in_array($this->action, $this->config['actions'])) 133 | || (is_string($this->config['actions']) && $this->config['actions'] !== $this->action) 134 | ) 135 | ) 136 | 137 | // If there is/are specified action(s) in the model, 138 | // And If the field is an array - check if the action in the array. 139 | // Else if the field is a string - check if the action equals incoming action. 140 | // If false - not log. 141 | || ( 142 | (is_array($this->loggable_actions) && ! in_array($this->action, $this->loggable_actions)) 143 | || (is_string($this->loggable_actions) && $this->loggable_actions !== $this->action) 144 | ) 145 | ) { 146 | $this->should_log = false; 147 | } 148 | } 149 | 150 | /** 151 | * Create a new log record if SHOULD_LOG is true. 152 | * 153 | * @return void 154 | */ 155 | public function record() 156 | { 157 | if (! $this->should_log) { 158 | return; 159 | } 160 | 161 | if ($this->action === 'edit' && empty(array_intersect_key($this->model->getChanges(), 162 | array_flip($this->loggable_fields)))) { 163 | return; 164 | } 165 | 166 | $drivers = $this->config['driver']; 167 | 168 | if (is_string($drivers)) { 169 | $drivers = [$drivers]; 170 | } 171 | 172 | foreach (array_unique($drivers) as $driver) { 173 | if ($driver === 'file') { 174 | $this->logAddInFile(); 175 | } else { 176 | $this->logAddInDatabase(); 177 | } 178 | 179 | event(new Logged($this->model, $this->action, $this->driver, $this->user)); 180 | } 181 | } 182 | 183 | /** 184 | * Add a log record in a file. 185 | * 186 | * @return void 187 | */ 188 | private function logAddInFile() 189 | { 190 | $this->driver->file->prepend(); 191 | } 192 | 193 | /** 194 | * Add a log record in a database. 195 | * 196 | * @return void 197 | */ 198 | private function logAddInDatabase() 199 | { 200 | $this->driver->database->insert(); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/Models/LoggableModel.php: -------------------------------------------------------------------------------- 1 | date = $model->freshTimestamp(); 41 | }); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | set('app.key', '6rE9Nz59bGRbeMATftriyQjrpF7DcOQm'); 33 | } 34 | } 35 | --------------------------------------------------------------------------------