├── .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": "[](#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 | [](LICENSE.md)
4 | [](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 | [](LICENSE.md)
4 | 
5 | 
6 | [![Total Downloads][ico-downloads]][link-downloads]
7 | 
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 | 
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 |
--------------------------------------------------------------------------------