├── .gitattributes ├── .github └── ISSUE_TEMPLATE │ ├── bug.yml │ ├── config.yml │ └── feature.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── config └── request-response-logger.php ├── database └── migrations │ └── create_request_response_loggers_table.php.stub ├── phpunit.xml.dist ├── src ├── Casts │ └── JsonToArray.php ├── Concerns │ ├── FileHelpers.php │ └── JobDispatchableMethod.php ├── Console │ ├── Concerns │ │ └── CommandExceptionHandler.php │ ├── RequestResponseLogCleaner.php │ ├── RequestResponseLoggerExporter.php │ └── RequestResponseLoggerRedisImport.php ├── Facades │ └── RequestResponseLogger.php ├── Jobs │ ├── DeleteRequestResponse.php │ ├── Middleware │ │ └── WithoutOverlappingOfCleaningJob.php │ └── StoreRequestResponse.php ├── Middlewares │ └── LogRequestResponse.php ├── Models │ └── RequestResponseLogger.php ├── RequestResponseLogManager.php └── RequestResponseLoggerServiceProvider.php └── tests ├── App ├── Models │ ├── Profile.php │ └── User.php ├── RedisFaker.php └── database │ ├── factories │ └── RequestResponseLoggerFactory.php │ └── migrations │ ├── 2014_10_12_000000_create_users_table.php │ └── 2014_10_12_000001_create_profiles_table.php ├── CommandsTest.php ├── ConfigTest.php ├── DatabaseTest.php ├── JobsTest.php ├── MiddlewareTest.php ├── ModelTest.php ├── RedisTest.php └── Traits ├── LaravelTestBootstrapping.php └── TestDatabaseBootstraping.php /.gitattributes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touhidurabir/laravel-request-response-logger/0117a2c319f76b466d07c3dba6cfef22d658b315/.gitattributes -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: Bug 2 | description: File a bug report 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | Before opening a bug report, please search for the behaviour in the existing issues. 8 | 9 | --- 10 | 11 | Thank you for taking the time to file a bug report. To address this bug as fast as possible, we need some information. 12 | - type: input 13 | id: os 14 | attributes: 15 | label: Operating system 16 | description: "Which operating system do you use? Please provide the version as well." 17 | placeholder: "macOS Big Sur 11.5.2" 18 | validations: 19 | required: true 20 | - type: input 21 | id: laravel 22 | attributes: 23 | label: Laravel Version 24 | description: "Please provide the full Laravel version of your project." 25 | placeholder: "v8.6.1" 26 | validations: 27 | required: true 28 | - type: input 29 | id: php 30 | attributes: 31 | label: PHP Version 32 | description: "Please provide the full PHP version that beign used." 33 | placeholder: "PHP 8.0.7, built: Jun 4 2021 01:50:04" 34 | validations: 35 | required: true 36 | - type: dropdown 37 | id: location 38 | attributes: 39 | label: Project Location 40 | description: Where is the project located? 41 | options: 42 | - Local 43 | - Remote 44 | - Somewhere else (please specify in the description!) 45 | validations: 46 | required: true 47 | - type: textarea 48 | id: bug-description 49 | attributes: 50 | label: Bug description 51 | description: What happened? 52 | validations: 53 | required: true 54 | - type: textarea 55 | id: steps 56 | attributes: 57 | label: Steps to reproduce 58 | description: Which steps do we need to take to reproduce this error? 59 | - type: textarea 60 | id: logs 61 | attributes: 62 | label: Relevant log output 63 | description: If applicable, provide relevant log output. No need for backticks here. 64 | render: shell -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Propose a new feature for laravel-request-response-logger package 3 | title: "[Feature Request]: " 4 | labels: [feature request] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for proposing a feature for laravel-request-response-logger package. 10 | - type: textarea 11 | id: feature-description 12 | attributes: 13 | label: Feature Description 14 | description: How should this feature look like? 15 | placeholder: Location in the software, usage, output ... 16 | validations: 17 | required: true 18 | - type: textarea 19 | id: valuable 20 | attributes: 21 | label: Is this feature valuable for other users as well and why? 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /vendor 3 | /build 4 | composer.lock 5 | .phpunit.result.cache -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.4 5 | - 8.0 6 | 7 | env: 8 | matrix: 9 | - COMPOSER_FLAGS="" 10 | 11 | before_script: 12 | - travis_retry composer update ${COMPOSER_FLAGS} 13 | 14 | script: 15 | - vendor/bin/phpunit -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | ## [Unreleased] 5 | 6 | ## [1.0.0] - 2021-12-26 7 | - Initial release 8 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Touhidur Rahman Abir 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 Request Response Logger 2 | 3 | A PHP laravel package to log the request/response of an app in database in an elegant way with the utilization of Queue and Redis combined . 4 | 5 | 6 | # Why ? 7 | 8 | The first question that with come into anyones' mind is WHY ? There are probably a good number of such package as this one . Also this is not a very diffuclt stuff for anyone to implement into their app . And I do agree with it . 9 | 10 | However this package aims to solve one crucial issue that is continious Database write. With the combination of Laravel's excellent Queuing system and Redis list storage , it can handle that case where it put each log data into the redis and then only when a certain limit hits, it will move those data into the DB table in batch which perform a lot less write opetation . It is not required on have the redis or queue must for this package to perform the loggin operation but that is what make this package unique . 11 | 12 | For example, lets say you have an app that have one endpoint/route that has need a request/response logging and one average it get hit for like 100/hour . So in traditional way , that just 100 write to DB per hours and that totally acceptable. Now on a high traffic day once or twice a month it get hits 1000 per hours so that 1000 writes to DB which is not too much but still a lot as there are also other read/write from other sections of the app . Using a queue job we can push those DB write task to jobs but thats 1000 jobs per hours which is still a bit unrealistic. But using the Redis list , we can push all those task to redis list and then once a certain limit or time passed , we import those records from redis list to DB through the same job in batch . 13 | 14 | This package also contains several handly commands to export data or delete which make use of laravel's LazyCollection and unique appraoch to delete massive amount of data as describe in [Freek's Blog Post](https://flareapp.io/blog/7-how-to-safely-delete-records-in-massive-tables-on-aws-using-laravel) 15 | 16 | 17 | ## Installation 18 | 19 | Require the package using composer: 20 | 21 | ```bash 22 | composer require touhidurabir/laravel-request-response-logger 23 | ``` 24 | 25 | To publish the config and migration file: 26 | ```bash 27 | php artisan vendor:publish --provider="Touhidurabir\RequestResponseLogger\RequestResponseLoggerServiceProvider" 28 | ``` 29 | 30 | Next, it's required to run the migration for the new database table. Run the following in your console: 31 | ```bash 32 | php artisan migrate 33 | ``` 34 | 35 | ## Usage 36 | 37 | ### Configure the Options 38 | 39 | Before using it , please make sure to go throught he pushlished config file to know the available options/settings that you can modify as per your need as this package provide a lot of flexibility. 40 | 41 | For example, the ability to push the write task to queue jobs defined by option **`log_on_queue`** and utilize the redis capability defined by option **`store_on_redis`** are set to **`false`** . So depending on your app capabilities and requirements, configure the options as you see fit . 42 | 43 | ### Setting Up Middleware 44 | 45 | The most important part is is setting up the middleware as all the request/response loggin is done by the provided middleware **`\Touhidurabir\RequestResponseLogger\Middlewares\LogRequestResponse`** by the package . 46 | 47 | There are several ways to use the middleare like registering it in the **`/app/Http/Kernel.php`** file and then use it like 48 | 49 | Register it as a named middleare and then use it as you like only for the routes where it's needed 50 | 51 | ```php 52 | protected $routeMiddleware = [ 53 | ... 54 | 'requests.responses.logger' => \Touhidurabir\RequestResponseLogger\Middlewares\LogRequestResponse::class, 55 | ]; 56 | 57 | // And then use it for one or more routes 58 | Route::any('/some-route', SomeController::class)->middleware(['requests.responses.logger']); 59 | ``` 60 | 61 | Or register it as route middleware group 62 | 63 | ```php 64 | protected $middlewareGroups = [ 65 | ... 66 | 'api' => [ 67 | \Touhidurabir\RequestResponseLogger\Middlewares\LogRequestResponse::class, 68 | ], 69 | ]; 70 | ``` 71 | 72 | Or register it so that by default it apply for every route 73 | 74 | ```php 75 | protected $middleware = [ 76 | ... 77 | \Touhidurabir\RequestResponseLogger\Middlewares\LogRequestResponse::class, 78 | ]; 79 | ``` 80 | 81 | Thats it. 82 | 83 | > NOTE : It is very unrealistic that this there may be a need to log request/response for every routes of an app, though that depends on the need to application. Register the middleare as per your need and try to avoid it using a global middleare unless that what the app require . 84 | 85 | 86 | ### Register and Run available commands 87 | 88 | This package ships with several userful commands . such as 89 | 90 | #### Log Cleaner 91 | 92 | To clear up the logs, use the command **`request-response-logger:clear`** as 93 | 94 | ```bash 95 | php artisan request-response-logger:clear 96 | ``` 97 | 98 | Or to set up the cleaning process periodically, register it in **`\App\Console\Kernel`** class's **`schedule`** method as : 99 | 100 | ```php 101 | protected function schedule(Schedule $schedule) { 102 | ... 103 | $schedule->command('request-response-logger:clear')->weekly(); 104 | } 105 | ``` 106 | 107 | This package also have some handle options available as such : 108 | | Command | Type | Description | 109 | | ---------------|---------|------------------------------------------------------------------| 110 | | keep-till-last | value | Keep the record that has stored in the last given hours | 111 | | limit | value | Number of records to delete in per dispatch | 112 | | only-unmarked | flag | Delete only the unmarked records | 113 | | on-job | flag | Run the deletion process through a Queue Job | 114 | 115 | 116 | #### Log Exporter 117 | 118 | To export up the logs as CSV and store in storage directory, use the command **`request-response-logger:export`** as 119 | 120 | 121 | ```bash 122 | php artisan request-response-logger:export 123 | ``` 124 | 125 | Or to set up the exporting process periodically, register it in **`\App\Console\Kernel`** class's **`schedule`** method as : 126 | 127 | ```php 128 | protected function schedule(Schedule $schedule) { 129 | ... 130 | $schedule->command('request-response-logger:export')->dailyAt('00:01'); 131 | } 132 | ``` 133 | 134 | Unless a specific file name is passed with the option **`--filename`** for this command , it will use the current Datetime as the name of the file . 135 | 136 | > NOTE that this package currently only export files in CSV format. Also by default it store the exported CSV files in the **`storage`** dierctory unless provided any other path . See the available command options to know more it 137 | 138 | This package also have some handle options available as such : 139 | | Command | Type | Description | 140 | | ---------------|---------|--------------------------------------------------------------------------------| 141 | | filename | value | Name of the CSV file to store in storage directory | 142 | | path | value | The absolute file store path if decided to store other than storage directory | 143 | | of-last | value | Export only last provided hours records | 144 | | replace | flag | If such file exists at given location, replace it with new file | 145 | | only-marked | flag | Export only marked records | 146 | | with-trashed | flag | Export records along with soft deleted entries | 147 | 148 | 149 | #### Redis List Storage Importer 150 | 151 | This is one unique command only applicable if you utilize the redis list storage with this package . What is does that it will import whatever logs that has been stored in the redis (through the specified key of config) into the DB table . Handy if you need to import all the records stored in redis list right away and dont want to wait till it hit the **`max_redis_count`** specificed in the config file . 152 | 153 | Run the command as : 154 | 155 | ```bash 156 | php artisan request-response-logger:redis-import 157 | ``` 158 | 159 | Or to set up the importing process periodically, register it in **`\App\Console\Kernel`** class's **`schedule`** method as : 160 | 161 | ```php 162 | protected function schedule(Schedule $schedule) { 163 | ... 164 | $schedule->command('request-response-logger:redis-import')->hourly(); 165 | } 166 | ``` 167 | 168 | ### Model 169 | 170 | If you would like to work with the data you've logged, you may want to retrieve data based on the http code . Or you may want to mark some records based on some logic which needed to be presist for a longer time . To Do such stuff , use the mode defined in the config file's **`model`** option . By default it will use the **`\Touhidurabir\RequestResponseLogger\Models\RequestResponseLogger::class`** model class . 171 | 172 | Also the default model provide some handle methods/scopes to work with such as : 173 | 174 | ```php 175 | use Touhidurabir\RequestResponseLogger\Models\RequestResponseLogger; 176 | 177 | // Get every logged item with an http response code of 2xx: 178 | RequestResponseLogger::successful()->get(); 179 | 180 | // Get every logged item with an http response code that ISN'T 2xx: 181 | RequestResponseLogger::failed()->get(); 182 | 183 | // Get every logged item which are marked 184 | RequestResponseLogger::marked()->get(); 185 | ``` 186 | 187 | This package also keep track if request are coming from logged in user and make the user data to log data association . So it's possible to find for which user is request log has registered as : 188 | 189 | ```php 190 | RequestResponseLogger::with(['user'])->get(); 191 | ``` 192 | 193 | If you need more methods , just extends the default model class or create your own and register it in the published config file . 194 | 195 | ## Contributing 196 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 197 | 198 | Please make sure to update tests as appropriate. 199 | 200 | ## License 201 | [MIT](./LICENSE.md) 202 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "touhidurabir/laravel-request-response-logger", 3 | "description": "A PHP laravel package to log the request/response of an app in database in an elegant way with the utilization of Queue and Redis combined", 4 | "type": "library", 5 | "license": "MIT", 6 | "autoload": { 7 | "psr-4": { 8 | "Touhidurabir\\RequestResponseLogger\\": "src/" 9 | } 10 | }, 11 | "autoload-dev" : { 12 | "psr-4" : { 13 | "Touhidurabir\\RequestResponseLogger\\Tests\\": "tests/" 14 | } 15 | }, 16 | "authors": [ 17 | { 18 | "name": "Touhidur Rahman", 19 | "email": "abircse06@gmail.com" 20 | } 21 | ], 22 | "require-dev": { 23 | "orchestra/testbench": "^6.22", 24 | "phpunit/phpunit": "^9.5" 25 | }, 26 | "require": { 27 | "php": ">=7.4", 28 | "touhidurabir/laravel-model-uuid": "^1.0", 29 | "illuminate/support": "^8.67" 30 | }, 31 | "extra": { 32 | "laravel": { 33 | "providers": [ 34 | "Touhidurabir\\RequestResponseLogger\\RequestResponseLoggerServiceProvider" 35 | ], 36 | "aliases": { 37 | "RequestResponseLogger": "Touhidurabir\\RequestResponseLogger\\Facades\\RequestResponseLogger" 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /config/request-response-logger.php: -------------------------------------------------------------------------------- 1 | 'request_response_loggers', 14 | 15 | 16 | /* 17 | |-------------------------------------------------------------------------- 18 | | Eloquent Model 19 | |-------------------------------------------------------------------------- 20 | | 21 | | Define the model to for this database table. 22 | | 23 | */ 24 | 'model' => \Touhidurabir\RequestResponseLogger\Models\RequestResponseLogger::class, 25 | 26 | 27 | /* 28 | |-------------------------------------------------------------------------- 29 | | Log/Store On Jobs 30 | |-------------------------------------------------------------------------- 31 | | 32 | | Define should run storing process on Queue Job. 33 | | 34 | */ 35 | 'log_on_queue' => false, 36 | 37 | 38 | /* 39 | |-------------------------------------------------------------------------- 40 | | Process Jobs 41 | |-------------------------------------------------------------------------- 42 | | 43 | | The Queue jobs to handle different type of process like loggin in Database 44 | | table to clear/delete from the database table; 45 | | 46 | */ 47 | 'jobs' => [ 48 | 'log' => \Touhidurabir\RequestResponseLogger\Jobs\StoreRequestResponse::class, 49 | 'clear' => \Touhidurabir\RequestResponseLogger\Jobs\DeleteRequestResponse::class, 50 | ], 51 | 52 | 53 | /* 54 | |-------------------------------------------------------------------------- 55 | | JSON to ARRAY Cast 56 | |-------------------------------------------------------------------------- 57 | | 58 | | Cast JSON data to array and and other way around. 59 | | 60 | */ 61 | 'json_to_array_on_retrieve' => true, 62 | 63 | 64 | /* 65 | |-------------------------------------------------------------------------- 66 | | Redis List to Store 67 | |-------------------------------------------------------------------------- 68 | | 69 | | Should use the redis list to store log data temp way to avoid continuous 70 | | database write. 71 | | 72 | */ 73 | 'store_on_redis' => false, 74 | 75 | 76 | /* 77 | |-------------------------------------------------------------------------- 78 | | Redis Max List Length 79 | |-------------------------------------------------------------------------- 80 | | 81 | | This define the max redis list length on which exceeds, it will initiate 82 | | the process to move those data in batch to database table. 83 | | 84 | */ 85 | 'max_redis_count' => 10000, 86 | 87 | 88 | /* 89 | |-------------------------------------------------------------------------- 90 | | Number of records to store in DB at a time 91 | |-------------------------------------------------------------------------- 92 | | 93 | | This define the number of records to pull from redis list to pull and push 94 | | into the DB table at a time when the max_redis_count hit . This to make 95 | | sure that DB table insertion process not to get halted when inseting a 96 | | large number of data at a time. 97 | | 98 | */ 99 | 'redis_store_in_segment_count' => 500, 100 | 101 | 102 | /* 103 | |-------------------------------------------------------------------------- 104 | | Unique Redis Key name 105 | |-------------------------------------------------------------------------- 106 | | 107 | | The unique list key name for which the data will to stored in the redis. 108 | | 109 | */ 110 | 'redis_key_name' => 'request_response_log', 111 | 112 | 113 | /* 114 | |-------------------------------------------------------------------------- 115 | | Redis Configuration 116 | |-------------------------------------------------------------------------- 117 | | 118 | | Redis is an open source, fast, and advanced key-value store that also 119 | | provides a richer body of commands than a typical key-value system 120 | | such as APC or Memcached. Laravel makes it easy to dig right in. 121 | | 122 | */ 123 | 'redis_configs' => [ 124 | 'url' => env('REDIS_URL'), 125 | 'host' => env('REDIS_HOST', '127.0.0.1'), 126 | 'password' => env('REDIS_PASSWORD', null), 127 | 'port' => env('REDIS_PORT', '6379'), 128 | 'database' => env('REDIS_DB', '0'), 129 | ], 130 | 131 | 132 | /* 133 | |-------------------------------------------------------------------------- 134 | | Redis Failure Fallback 135 | |-------------------------------------------------------------------------- 136 | | 137 | | If choose to store vai redis and for some reason failed , it will 138 | | try to run the log store process normally and in database table if 139 | | set to true. 140 | | 141 | */ 142 | 'fallback_on_redis_failure' => true, 143 | 144 | 145 | /* 146 | |-------------------------------------------------------------------------- 147 | | Number of records to delete in each job dispatch 148 | |-------------------------------------------------------------------------- 149 | | 150 | | The number of records to delete in segmented order in case of async delete 151 | | through queue job. 152 | | 153 | */ 154 | 'delete_in_segment_count' => 1000, 155 | ]; 156 | -------------------------------------------------------------------------------- /database/migrations/create_request_response_loggers_table.php.stub: -------------------------------------------------------------------------------- 1 | verifyTableName($tableName); 19 | 20 | if ( Schema::hasTable($tableName) ) { 21 | 22 | throw new \Exception("Error: a table of name {$table} already exists in database."); 23 | } 24 | 25 | Schema::create($tableName, function (Blueprint $table) { 26 | $table->bigIncrements('id'); 27 | $table->string('uuid')->unique()->nullable(); 28 | $table->string('request_method')->nullable(); 29 | $table->longText('request_headers')->nullable(); 30 | $table->longText('request_body')->nullable(); 31 | $table->text('request_url')->nullable(); 32 | $table->string('request_ip')->nullable(); 33 | $table->bigInteger('request_auth_user_id')->unsigned()->index()->nullable(); 34 | $table->longText('response_headers')->nullable(); 35 | $table->longText('response_body')->nullable(); 36 | $table->string('response_status_code')->nullable(); 37 | $table->boolean('marked')->default(false); 38 | $table->timestamps(); 39 | $table->softDeletes(); 40 | }); 41 | 42 | } 43 | 44 | 45 | /** 46 | * Reverse the migrations. 47 | * 48 | * @return void 49 | */ 50 | public function down() { 51 | 52 | $tableName = config('request-response-logger.table'); 53 | 54 | $this->verifyTableName($tableName); 55 | 56 | Schema::drop($tableName); 57 | } 58 | 59 | 60 | /** 61 | * Reverse the migrations. 62 | * 63 | * @param string $table 64 | * @return void 65 | * 66 | * @throws \Exception 67 | */ 68 | protected function verifyTableName(string $table) { 69 | 70 | if ( ! $table ) { 71 | throw new \Exception("Error: redirector table not defined in config/redirector.php. Make sure that config/redirector.php loaded properly and table defined there. Run [php artisan config:clear] and try again."); 72 | } 73 | } 74 | 75 | } -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | tests 15 | 16 | 17 | 18 | 19 | src/ 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/Casts/JsonToArray.php: -------------------------------------------------------------------------------- 1 | $model 14 | * @param string $key 15 | * @param mixed $value 16 | * @param array $attributes 17 | * 18 | * @return mixed 19 | */ 20 | public function get($model, $key, $value = null, $attributes = []) { 21 | 22 | if ( config('request-response-logger.json_to_array_on_retrieve') && $this->isValidJson($value) ) { 23 | 24 | $value = json_decode($value, true); 25 | } 26 | 27 | return $value; 28 | } 29 | 30 | 31 | /** 32 | * Prepare the given value for storage. 33 | * 34 | * @param object<\Illuminate\Database\Eloquent\Model> $model 35 | * @param string $key 36 | * @param mixed $value 37 | * @param array $attributes 38 | * 39 | * @return mixed 40 | */ 41 | public function set($model, $key, $value = null, $attributes = []) { 42 | 43 | if ( is_array($value) ) { 44 | 45 | $value = json_encode($value); 46 | } 47 | 48 | return $value; 49 | } 50 | 51 | 52 | /** 53 | * Checks if the given expected value is valid json string. 54 | * 55 | * @param mixed $value 56 | * @return boolean 57 | */ 58 | public function isValidJson($value) : bool { 59 | 60 | if ( ! is_string($value) ) { 61 | 62 | return false; 63 | } 64 | 65 | json_decode($value); 66 | 67 | return json_last_error() === JSON_ERROR_NONE; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/Concerns/FileHelpers.php: -------------------------------------------------------------------------------- 1 | isDirectory($path) ) { 69 | 70 | return $path; 71 | } 72 | 73 | $path = $this->sanitizePath(str_replace('/public', ('/'. $path), public_path())); 74 | 75 | return $path; 76 | } 77 | 78 | 79 | /** 80 | * Get the destination class path. 81 | * 82 | * @param string $name 83 | * @return string 84 | */ 85 | protected function getPath(string $path, string $name) { 86 | 87 | return $this->sanitizePath($this->getStoreDirectoryPath($path) . '/') . $name . '.php'; 88 | } 89 | 90 | 91 | /** 92 | * Check if target path directory exists or not 93 | * If not , create the directory in that path 94 | * And return the final directory path in any case 95 | * 96 | * @param string $path 97 | * @return string 98 | */ 99 | protected function generateFilePathDirectory(string $path, bool $fullPath = false) { 100 | 101 | $directoryPath = $fullPath ? $path : $this->getStoreDirectoryPath($path); 102 | 103 | File::ensureDirectoryExists($directoryPath); 104 | 105 | return $directoryPath; 106 | } 107 | 108 | 109 | /** 110 | * Sanitize the path to proper usable path 111 | * Remove any unnecessary slashes 112 | * 113 | * @param string $path 114 | * @return string 115 | */ 116 | protected function sanitizePath(string $path) { 117 | 118 | return preg_replace('#/+#','/', trim($path) . "/"); 119 | } 120 | 121 | 122 | /** 123 | * Get the content of a given full absolute path of the file 124 | * Remove any unecessary slashes 125 | * 126 | * @param string $fileFullPath 127 | * @return string 128 | */ 129 | protected function getFileContent(string $fileFullPath) { 130 | 131 | return File::get($fileFullPath); 132 | } 133 | 134 | 135 | /** 136 | * Get the full file path from given relative path 137 | * 138 | * @param string $fileRelativePath 139 | * @return string 140 | */ 141 | protected function getFileFullPath(string $fileRelativePath) { 142 | 143 | return rtrim($this->sanitizePath(str_replace('/public', '', public_path()) . '/'. $fileRelativePath), '/') ; 144 | } 145 | } -------------------------------------------------------------------------------- /src/Concerns/JobDispatchableMethod.php: -------------------------------------------------------------------------------- 1 | version() < 8 ? 'dispatchNow' : 'dispatchSync'; 22 | } 23 | } -------------------------------------------------------------------------------- /src/Console/Concerns/CommandExceptionHandler.php: -------------------------------------------------------------------------------- 1 | error($message ?? 'Exception Arise During Task Execution'); 21 | 22 | $this->error( 23 | 'Exception : ' 24 | . $exception->getMessage() 25 | . " - " 26 | . $exception->getFile() 27 | . " at line " 28 | . $exception->getLine() 29 | ); 30 | } 31 | } -------------------------------------------------------------------------------- /src/Console/RequestResponseLogCleaner.php: -------------------------------------------------------------------------------- 1 | info('Cleaning the records'); 61 | 62 | try { 63 | 64 | $queueJob = config('request-response-logger.jobs.clear'); 65 | 66 | $method = $this->getDispatchMethod($this->option('on-job')); 67 | 68 | $queueJob::{$method}( 69 | $this->option('keep-till-last'), 70 | $this->option('only-unmarked') ? false : null, 71 | $this->option('limit') ?? config('request-response-logger.delete_in_segment_count'), 72 | $method 73 | ); 74 | 75 | return self::SUCCESS; 76 | 77 | } catch (Throwable $exception) { 78 | 79 | // ray($exception); 80 | 81 | $this->outputConsoleException($exception); 82 | 83 | return self::FAILURE; 84 | } 85 | } 86 | 87 | } -------------------------------------------------------------------------------- /src/Console/RequestResponseLoggerExporter.php: -------------------------------------------------------------------------------- 1 | info('Initiating the exporting'); 66 | 67 | try { 68 | 69 | $file = $this->generateFileToExport(); 70 | 71 | $fp = fopen($file, 'w'); 72 | 73 | // make sure that we do not convery JSON to Array on retrival for CSV export 74 | Config::set('request-response-logger.json_to_array_on_retrieve', false); 75 | 76 | // Write the headers 77 | fputcsv($fp, DB::getSchemaBuilder()->getColumnListing(config('request-response-logger.table'))); 78 | 79 | // load the records to export as lazy collection 80 | $records = RequestResponseLogManager::withQuery() 81 | ->keepTill($this->option('of-last')) 82 | ->withMarkedStatus($this->option('only-marked') ? true : null) 83 | ->withTrashed($this->option('with-trashed')) 84 | ->getQuery() 85 | ->cursor(); 86 | 87 | // write the data/records in the CSV file 88 | foreach ($records as $record) { 89 | 90 | fputcsv($fp, $record->toArray()); 91 | } 92 | 93 | fclose($fp); 94 | 95 | $this->info('Exporting has completed'); 96 | 97 | return self::SUCCESS; 98 | 99 | } catch (Throwable $exception) { 100 | 101 | // ray($exception); 102 | 103 | $this->outputConsoleException($exception); 104 | 105 | return self::FAILURE; 106 | } 107 | } 108 | 109 | 110 | /** 111 | * Generate the file to write content 112 | * 113 | * @return string 114 | * @throws \Exception 115 | */ 116 | protected function generateFileToExport() : string { 117 | 118 | $name = ($this->option('filename') ?? str_replace(' ', '_', now()->toString())) . '.csv'; 119 | 120 | $path = storage_path(); 121 | 122 | if ( $this->option('path') ) { 123 | 124 | $path = $this->option('path'); 125 | 126 | if ( ! $this->isDirectory($path) ) { 127 | 128 | throw new Exception(sprintf("The given path [%s] does not exists", $path)); 129 | } 130 | } 131 | 132 | $fileFullPath = $path . '/' . $name; 133 | 134 | if ( $this->fileExists($fileFullPath) ) { 135 | 136 | if ( ! $this->option('replace') ) { 137 | 138 | throw new Exception(sprintf("The given file [%s] already existed at given [%s] path", $name, $path)); 139 | } 140 | 141 | $this->removeFile($fileFullPath); 142 | 143 | $this->newFileWithContent($fileFullPath, ''); 144 | } 145 | 146 | return $fileFullPath; 147 | } 148 | 149 | } -------------------------------------------------------------------------------- /src/Console/RequestResponseLoggerRedisImport.php: -------------------------------------------------------------------------------- 1 | info('Initiating the importing from redis storage'); 58 | 59 | try { 60 | 61 | $redis = Redis::connection(config('request-response-logger.redis_configs')); 62 | 63 | $listKey = config('request-response-logger.redis_key_name'); 64 | 65 | $itemsInListCount = $redis->llen($listKey); 66 | 67 | if ( $itemsInListCount <= 0 ) { 68 | 69 | $this->info('Redis list is empty. No data to import'); 70 | 71 | return self::SUCCESS; 72 | } 73 | 74 | $storeInSegmentCount = abs(config('request-response-logger.redis_store_in_segment_count') ?? 500); 75 | 76 | $method = $this->getDispatchMethod(config('request-response-logger.log_on_queue')); 77 | 78 | $jobClass = config('request-response-logger.jobs.log'); 79 | 80 | $this->info('Importing ' . $itemsInListCount . ' records into the database table'); 81 | 82 | while( $storeables = $redis->lrange($listKey, 0, $storeInSegmentCount-1) ) { 83 | 84 | $jobClass::$method( 85 | collect($storeables)->map(fn($data) => json_decode($data, true))->toArray(), 86 | true 87 | ); 88 | 89 | $redis->ltrim($listKey, $storeInSegmentCount, -1); 90 | } 91 | 92 | $this->info('Importing process has completed successfully'); 93 | 94 | return self::SUCCESS; 95 | 96 | } catch (Throwable $exception) { 97 | 98 | // ray($exception); 99 | 100 | $this->outputConsoleException($exception); 101 | 102 | return self::FAILURE; 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /src/Facades/RequestResponseLogger.php: -------------------------------------------------------------------------------- 1 | keepTillLast = $keepTillLast; 84 | $this->onlyUnmarked = $onlyUnmarked; 85 | $this->limit = $limit; 86 | $this->method = $dispatchMethod; 87 | } 88 | 89 | 90 | /** 91 | * Execute the job. 92 | * 93 | * @return void 94 | */ 95 | public function handle(): void { 96 | 97 | $numberOfRecordsToDeleted = RequestResponseLogManager::withQuery() 98 | ->keepTill($this->keepTillLast) 99 | ->withMarkedStatus($this->onlyUnmarked) 100 | ->remove($this->limit); 101 | 102 | 103 | if ($numberOfRecordsToDeleted > 0) { 104 | 105 | $this->method = $this->method ?? $this->getDispatchMethod(false); 106 | 107 | self::{$this->method}($this->keepTillLast, $this->onlyUnmarked, $this->limit, $this->method); 108 | } 109 | } 110 | 111 | 112 | /** 113 | * Get the middleware the job should pass through. 114 | * 115 | * @return array 116 | */ 117 | public function middleware() { 118 | 119 | return [new WithoutOverlappingOfCleaningJob]; 120 | } 121 | 122 | } -------------------------------------------------------------------------------- /src/Jobs/Middleware/WithoutOverlappingOfCleaningJob.php: -------------------------------------------------------------------------------- 1 | ping() ) { 27 | 28 | $this->lockHandler($job, $next); 29 | } 30 | 31 | } catch (Throwable $exception) { 32 | 33 | $next($job); 34 | } 35 | } 36 | 37 | 38 | /** 39 | * Handle the redis locking process 40 | * 41 | * @param mixed $job 42 | * @param callable $next 43 | * 44 | * @return mixed 45 | */ 46 | protected function lockHandler($job, $next) { 47 | 48 | $lock = Cache::store(config('cache.default') ?? 'redis') 49 | ->lock("{$job->resolveName()}_lock", 10 * 60); 50 | 51 | if ( ! $lock->get() ) { 52 | 53 | $job->delete(); 54 | 55 | return; 56 | } 57 | 58 | $next($job); 59 | 60 | $lock->release(); 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /src/Jobs/StoreRequestResponse.php: -------------------------------------------------------------------------------- 1 | data = $data; 59 | $this->batch = $batch; 60 | } 61 | 62 | 63 | /** 64 | * Execute the job. 65 | * 66 | * @return void 67 | */ 68 | public function handle(): void { 69 | 70 | if ( $this->batch ) { 71 | 72 | $table = config('request-response-logger.table'); 73 | 74 | DB::table($table)->insert($this->data); 75 | 76 | return; 77 | } 78 | 79 | $model = config('request-response-logger.model'); 80 | 81 | (new $model)->create($this->data); 82 | } 83 | } -------------------------------------------------------------------------------- /src/Middlewares/LogRequestResponse.php: -------------------------------------------------------------------------------- 1 | $request 30 | * @param object<\Illuminate\Http\Response> $response 31 | * 32 | * @return void 33 | */ 34 | public function terminate($request, $response) : void { 35 | 36 | (new RequestResponseLogManager)->store($request, $response); 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /src/Models/RequestResponseLogger.php: -------------------------------------------------------------------------------- 1 | JsonToArray::class, 34 | 'request_body' => JsonToArray::class, 35 | 'response_headers' => JsonToArray::class, 36 | 'response_body' => JsonToArray::class, 37 | ]; 38 | 39 | 40 | /** 41 | * The uuid custom configurations 42 | * 43 | * @return array 44 | */ 45 | public function uuidable() : array { 46 | 47 | return [ 48 | 'column' => 'uuid' 49 | ]; 50 | } 51 | 52 | 53 | /** 54 | * Get the model associated table name 55 | * 56 | * @return string 57 | */ 58 | public function getTable() { 59 | 60 | return config('request-response-logger.table', parent::getTable()); 61 | } 62 | 63 | 64 | /** 65 | * Get the user who owns this record entry 66 | * 67 | * @return object<\Illuminate\Database\Eloquent\Relations\BelongsTo> 68 | */ 69 | public function user() : BelongsTo { 70 | 71 | return $this->belongsTo(config('auth.providers.users.model'), 'request_auth_user_id'); 72 | } 73 | 74 | 75 | /** 76 | * Local scope a query to only include failed status code responses. 77 | * 78 | * @param object<\Illuminate\Database\Eloquent\Builder> $query 79 | * @return object<\Illuminate\Database\Eloquent\Builder> 80 | */ 81 | public function scopeFailed(Builder $query) { 82 | 83 | return $query->whereNotBetween('response_status_code', [200, 299]); 84 | } 85 | 86 | 87 | /** 88 | * Local scope a query to only include success status code responses. 89 | * 90 | * @param \Illuminate\Database\Eloquent\Builder $query 91 | * @return \Illuminate\Database\Eloquent\Builder 92 | */ 93 | public function scopeSuccessful(Builder $query) { 94 | 95 | return $query->whereBetween('response_status_code', [200, 299]); 96 | } 97 | 98 | 99 | /** 100 | * Local scope a query to only include marked records 101 | * 102 | * @param \Illuminate\Database\Eloquent\Builder $query 103 | * @return \Illuminate\Database\Eloquent\Builder 104 | */ 105 | public function scopeMarked(Builder $query) { 106 | 107 | return $query->where('marked', true); 108 | } 109 | 110 | } -------------------------------------------------------------------------------- /src/RequestResponseLogManager.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | protected $query; 25 | 26 | 27 | /** 28 | * The redis connector instance 29 | * 30 | * @var object<\Illuminate\Redis\Connectors\PhpRedisConnector> 31 | */ 32 | protected $redis; 33 | 34 | 35 | /** 36 | * Static constructor to create a new instance with passed pre configured redis connection 37 | * 38 | * @param object redis 39 | * @return self 40 | */ 41 | public static function withRedis($redis) : self { 42 | 43 | $static = new static; 44 | 45 | $static->redis = $redis; 46 | 47 | return $static; 48 | } 49 | 50 | 51 | /** 52 | * Static constructor to create a new instacne with initialized query builder instance 53 | * 54 | * @return static 55 | */ 56 | public static function withQuery() : self { 57 | 58 | $static = new static; 59 | 60 | $modelClass = config('request-response-logger.model'); 61 | 62 | $static->query = $modelClass::query(); 63 | 64 | return $static; 65 | } 66 | 67 | 68 | /** 69 | * Set the keep till last defined hours records 70 | * 71 | * @param int $hours 72 | * @return static 73 | */ 74 | public function keepTill(int $hours = null) : self { 75 | 76 | if ( $hours ) { 77 | 78 | $this->query = $this->query->where('created_at', '<', now()->subHours($hours)); 79 | } 80 | 81 | return $this; 82 | } 83 | 84 | 85 | /** 86 | * Define if should pull records with specific marked status 87 | * 88 | * @param bool $marked 89 | * @return static 90 | */ 91 | public function withMarkedStatus(bool $marked = null) : self { 92 | 93 | if ( ! is_null($marked) ) { 94 | 95 | $this->query = $this->query->where('marked', $marked); 96 | } 97 | 98 | return $this; 99 | } 100 | 101 | 102 | /** 103 | * Define if should take soft deleted record into account 104 | * 105 | * @param bool $onlyTrashed 106 | * @return static 107 | */ 108 | public function withTrashed(bool $onlyTrashed = false) : self { 109 | 110 | if ( $onlyTrashed ) { 111 | 112 | $this->query = $this->query->withTrashed(); 113 | } 114 | 115 | return $this; 116 | } 117 | 118 | 119 | /** 120 | * Force delete records from the table 121 | * 122 | * @param mixed $limit 123 | * @return int 124 | */ 125 | public function remove(int $limit = null) : int { 126 | 127 | if ( $limit ) { 128 | 129 | return $this->query->limit($limit)->forceDelete(); 130 | } 131 | 132 | return $this->query->forceDelete(); 133 | } 134 | 135 | 136 | /** 137 | * Get the current query builder 138 | * 139 | * @return object<\Illuminate\Database\Eloquent\Builder> 140 | */ 141 | public function getQuery() { 142 | 143 | return $this->query; 144 | } 145 | 146 | 147 | /** 148 | * Store the request and response log 149 | * 150 | * @param object<\Illuminate\Http\Request> $request 151 | * @param object<\Illuminate\Http\Response> $response 152 | * 153 | * @return void 154 | */ 155 | public function store($request, $response) : void { 156 | 157 | $storebale = $this->getStoreable($request, $response); 158 | 159 | if ( ! config('request-response-logger.store_on_redis') ) { 160 | 161 | $this->storeInDatabase($storebale); 162 | 163 | return; 164 | } 165 | 166 | $this->storeInRedis($storebale); 167 | } 168 | 169 | 170 | /** 171 | * Get the storeable data 172 | * 173 | * @param object<\Illuminate\Http\Request> $request 174 | * @param object<\Illuminate\Http\Response> $response 175 | * 176 | * @return array 177 | */ 178 | public function getStoreable($request, $response) : array { 179 | 180 | return [ 181 | 'request_method' => $request->method(), 182 | 'request_headers' => collect($request->headers->all()) 183 | ->transform(function ($item) { 184 | return head($item); 185 | }) ?? [], 186 | 'request_body' => $this->getRequestBody($request), 187 | 'request_url' => $request->url(), 188 | 'request_ip' => $request->ip(), 189 | 'request_auth_user_id' => optional($request->user())->id, 190 | 'response_headers' => collect($response->headers->all()) 191 | ->transform(function ($item) { 192 | return head($item); 193 | }) ?? [], 194 | 'response_body' => $response->getContent(), 195 | 'response_status_code' => $response->status(), 196 | ]; 197 | } 198 | 199 | 200 | /** 201 | * Store the log data in the database table 202 | * 203 | * @param array $storeable 204 | * @param bool $onBatch 205 | * 206 | * @return void 207 | */ 208 | protected function storeInDatabase(array $storeable, bool $onBatch = false) : void { 209 | 210 | $method = $this->getDispatchMethod(config('request-response-logger.log_on_queue')); 211 | 212 | $jobClass = config('request-response-logger.jobs.log'); 213 | 214 | $jobClass::$method($storeable, $onBatch); 215 | } 216 | 217 | 218 | /** 219 | * Store the log data in the redis 220 | * 221 | * @param array $storeable 222 | * @return void 223 | * 224 | * @throws \Exception 225 | */ 226 | protected function storeInRedis($storeable) { 227 | 228 | try { 229 | 230 | $redis = $this->getRedisInstance(); 231 | 232 | $listKey = config('request-response-logger.redis_key_name'); 233 | 234 | $storeable = array_merge($storeable, [ 235 | 'uuid' => UuidGenerator::uuid4(), 236 | 'request_headers' => $this->stringify($storeable['request_headers']), 237 | 'request_body' => $this->stringify($storeable['request_body']), 238 | 'response_headers' => $this->stringify($storeable['response_headers']), 239 | 'response_body' => $this->stringify($storeable['response_body']), 240 | ]); 241 | 242 | $redis->rpush($listKey, json_encode($storeable)); 243 | 244 | $maxRedisListLimit = abs(config('request-response-logger.max_redis_count') ?? 10000); 245 | 246 | if ( $redis->llen($listKey) >= $maxRedisListLimit ) { 247 | 248 | $storeInSegmentCount = abs(config('request-response-logger.redis_store_in_segment_count') ?? 500); 249 | 250 | while( $storeables = $redis->lrange($listKey, 0, $storeInSegmentCount-1) ) { 251 | 252 | $this->storeInDatabase( 253 | collect($storeables)->map(fn($data) => json_decode($data, true))->toArray(), 254 | true 255 | ); 256 | 257 | $redis->ltrim($listKey, $storeInSegmentCount, -1); 258 | } 259 | } 260 | 261 | } catch (Throwable $exception) { 262 | 263 | if ( config('request-response-logger.fallback_on_redis_failure') ) { 264 | 265 | $this->storeInDatabase($storeable); 266 | 267 | return; 268 | } 269 | 270 | throw $exception; 271 | } 272 | } 273 | 274 | 275 | /** 276 | * Check and determine if the request is JSON/Arrayable/XML or 277 | * some sort of plain text which is not arrayable 278 | * 279 | * @param object<\Illuminate\Http\Request> $request 280 | * @return mixed 281 | */ 282 | protected function getRequestBody(Request $request) { 283 | 284 | $requestBody = $request->all(); 285 | 286 | if ( ! empty($requestBody) ) { 287 | 288 | $requestBody; 289 | } 290 | 291 | if ( empty($requestBody) && !empty($request->getContent()) ) { 292 | 293 | return $request->getContent(); 294 | } 295 | 296 | return null; 297 | } 298 | 299 | 300 | /** 301 | * Get the useable redis instance 302 | * 303 | * @return object<\Illuminate\Redis\Connectors\PhpRedisConnector> 304 | */ 305 | protected function getRedisInstance() { 306 | 307 | return $this->redis ?? Redis::connection(config('request-response-logger.redis_configs')); 308 | } 309 | 310 | 311 | /** 312 | * Convert passed data to string 313 | * 314 | * @return mixed 315 | */ 316 | protected function stringify($data) : ?string { 317 | 318 | if ( $data instanceof Collection ) { 319 | 320 | return json_encode($data->toArray()); 321 | } 322 | 323 | if ( is_array($data) ) { 324 | 325 | return json_encode($data); 326 | } 327 | 328 | return $data; 329 | } 330 | 331 | } -------------------------------------------------------------------------------- /src/RequestResponseLoggerServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->runningInConsole() ) { 23 | 24 | $this->commands([ 25 | RequestResponseLogCleaner::class, 26 | RequestResponseLoggerExporter::class, 27 | RequestResponseLoggerRedisImport::class, 28 | ]); 29 | } 30 | 31 | $this->publishes([ 32 | __DIR__.'/../config/request-response-logger.php' => base_path('config/request-response-logger.php'), 33 | ], 'config'); 34 | 35 | $this->publishes([ 36 | __DIR__.'/../database/migrations/create_request_response_loggers_table.php.stub' => $this->getMigrationFileName('create_request_response_loggers_table.php'), 37 | ], 'migrations'); 38 | } 39 | 40 | 41 | /** 42 | * Register any application services. 43 | * 44 | * @return void 45 | */ 46 | public function register() { 47 | 48 | $this->mergeConfigFrom( 49 | __DIR__.'/../config/request-response-logger.php', 'request-response-logger' 50 | ); 51 | 52 | $this->app->bind('request-response-logger', function ($app) { 53 | return new RequestResponseLogManager; 54 | }); 55 | } 56 | 57 | 58 | /** 59 | * Returns existing migration file if found, else uses the current timestamp. 60 | * 61 | * @param string $migrationFileName 62 | * @return string 63 | */ 64 | protected function getMigrationFileName($migrationFileName): string { 65 | 66 | $timestamp = date('Y_m_d_His'); 67 | 68 | $filesystem = $this->app->make(Filesystem::class); 69 | 70 | return Collection::make($this->app->databasePath() . DIRECTORY_SEPARATOR . 'migrations' . DIRECTORY_SEPARATOR) 71 | ->flatMap(function ($path) use ($filesystem, $migrationFileName) { 72 | return $filesystem->glob($path . '*_' . $migrationFileName); 73 | }) 74 | ->push($this->app->databasePath()."/migrations/{$timestamp}_{$migrationFileName}") 75 | ->first(); 76 | } 77 | 78 | } -------------------------------------------------------------------------------- /tests/App/Models/Profile.php: -------------------------------------------------------------------------------- 1 | configs = $configs; 32 | $this->storage = []; 33 | } 34 | 35 | 36 | /** 37 | * Get the fake redis connection instance itself 38 | * 39 | * @param array $config 40 | * @return self 41 | */ 42 | public function connection(array $configs = []) : self { 43 | 44 | $this->configs = $configs; 45 | 46 | return $this; 47 | } 48 | 49 | 50 | /** 51 | * Store data at the beginning of list in fake redis storage for specified key 52 | * 53 | * @param string $key 54 | * @param mixed $value 55 | * 56 | * @return int 57 | */ 58 | public function lpush(string $key, $value) : int { 59 | 60 | array_unshift($this->getStoreByKey($key), $value); 61 | 62 | return $this->llen($key); 63 | } 64 | 65 | 66 | /** 67 | * Pull data from the beginning of list in fake redis storage for specified key 68 | * 69 | * @param string $key 70 | * @return mixed 71 | */ 72 | public function lpop(string $key) { 73 | 74 | if ( $this->llen($key) <= 0 ) { 75 | 76 | return false; 77 | } 78 | 79 | return array_shift($this->getStoreByKey($key)); 80 | } 81 | 82 | 83 | /** 84 | * Store data at the end of list in fake redis storage for specified key 85 | * 86 | * @param string $key 87 | * @param mixed $value 88 | * 89 | * @return int 90 | */ 91 | public function rpush(string $key, $value) : int { 92 | 93 | array_push($this->getStoreByKey($key), $value); 94 | 95 | return $this->llen($key); 96 | } 97 | 98 | 99 | /** 100 | * Pull data from the end of list in fake redis storage for specified key 101 | * 102 | * @param string $key 103 | * @return mixed 104 | */ 105 | public function rpop(string $key) { 106 | 107 | if ( $this->llen($key) <= 0 ) { 108 | 109 | return false; 110 | } 111 | 112 | return array_pop($this->getStoreByKey($key)); 113 | } 114 | 115 | 116 | /** 117 | * Get the current lenght of list in fake redis storage for specified key 118 | * 119 | * @param string $key 120 | * @return int 121 | */ 122 | public function llen(string $key) : int { 123 | 124 | return count($this->getStoreByKey($key)); 125 | } 126 | 127 | 128 | /** 129 | * Get the range of data from the list in fake redis storage for specified key 130 | * 131 | * @param string $key 132 | * @param int $start 133 | * @param int $end 134 | * 135 | * @return array 136 | */ 137 | public function lrange(string $key, int $start, int $end) : array { 138 | 139 | if ( $end < 0 ) { 140 | 141 | return $end === -1 142 | ? $this->getStoreByKey($key) 143 | : array_slice($this->getStoreByKey($key), $start, $end+1); 144 | } 145 | 146 | return array_slice($this->getStoreByKey($key), $start, $end+1); 147 | } 148 | 149 | 150 | /** 151 | * Remove specified range of data from the list in fake redis storage for specified key 152 | * 153 | * @param string $key 154 | * @param int $elementsCount 155 | * @param int $direction 156 | * 157 | * @return bool 158 | */ 159 | public function ltrim(string $key, int $elementsCount, int $direction) : bool { 160 | 161 | // remove first $elementsCount elements from the beginning of list 162 | if ( $elementsCount > 0 && $direction < 0 ) { 163 | 164 | array_splice($this->getStoreByKey($key), 0, $elementsCount); 165 | } 166 | 167 | // remove first $direction elements from the end of list 168 | if ( $elementsCount <= 0 && $direction < 0 ) { 169 | 170 | array_splice($this->getStoreByKey($key), -5); 171 | } 172 | 173 | return true; 174 | } 175 | 176 | 177 | /** 178 | * Get the all stored data/values of list in fake redis storage for specified key 179 | * 180 | * @param string $key 181 | * @return int 182 | */ 183 | public function get(string $key) : array { 184 | 185 | return $this->getStoreByKey($key); 186 | } 187 | 188 | 189 | /** 190 | * Ping the redis connection to get status 191 | * 192 | * @param bool $response 193 | * @return bool 194 | */ 195 | public function ping(bool $response = true) : bool { 196 | 197 | return $response; 198 | } 199 | 200 | 201 | /** 202 | * Get the reference of specific storage in the list specified by key 203 | * 204 | * @param string $key 205 | * @return int 206 | */ 207 | protected function &getStoreByKey(string $key) : array { 208 | 209 | if ( !array_key_exists($key, $this->storage) ) { 210 | 211 | $this->storage[$key] = []; 212 | } 213 | 214 | return $this->storage[$key]; 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /tests/App/database/factories/RequestResponseLoggerFactory.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/touhidurabir/laravel-request-response-logger/0117a2c319f76b466d07c3dba6cfef22d658b315/tests/App/database/factories/RequestResponseLoggerFactory.php -------------------------------------------------------------------------------- /tests/App/database/migrations/2014_10_12_000000_create_users_table.php: -------------------------------------------------------------------------------- 1 | getSchemaBuilder()->create('users', function (Blueprint $table) { 16 | $table->increments('id'); 17 | $table->string('email')->unique(); 18 | $table->string('password', 60); 19 | $table->timestamps(); 20 | $table->softDeletes(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | * 27 | * @return void 28 | */ 29 | public function down() 30 | { 31 | DB::connection()->getSchemaBuilder()->dropIfExists('users'); 32 | } 33 | } -------------------------------------------------------------------------------- /tests/App/database/migrations/2014_10_12_000001_create_profiles_table.php: -------------------------------------------------------------------------------- 1 | getSchemaBuilder()->create('profiles', function (Blueprint $table) { 16 | $table->increments('id'); 17 | $table->string('first_name'); 18 | $table->string('last_name'); 19 | $table->timestamps(); 20 | $table->softDeletes(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | * 27 | * @return void 28 | */ 29 | public function down() 30 | { 31 | DB::connection()->getSchemaBuilder()->dropIfExists('profiles'); 32 | } 33 | } -------------------------------------------------------------------------------- /tests/CommandsTest.php: -------------------------------------------------------------------------------- 1 | 27 | */ 28 | protected $clearCommand; 29 | 30 | 31 | /** 32 | * The testable dummy command 33 | * 34 | * @var object<\Symfony\Component\Console\Tester\CommandTester> 35 | */ 36 | protected $exportCommand; 37 | 38 | 39 | /** 40 | * The testable dummy export file name 41 | * 42 | * @var string 43 | */ 44 | protected $exportFileName = 'request-response-logger-export'; 45 | 46 | 47 | /** 48 | * Setup the test environment. 49 | * 50 | * @return void 51 | */ 52 | protected function setUp(): void { 53 | 54 | parent::setUp(); 55 | 56 | $this->clearCommand = $this->configureTestCommand(RequestResponseLogCleaner::class); 57 | $this->exportCommand = $this->configureTestCommand(RequestResponseLoggerExporter::class); 58 | 59 | $this->beforeApplicationDestroyed( 60 | fn() => File::delete(storage_path() . '/' . $this->exportFileName . '.csv') 61 | ); 62 | } 63 | 64 | 65 | /** 66 | * Seed the logger table with dummy data 67 | * 68 | * @return void 69 | */ 70 | protected function seedLoggerTable() { 71 | 72 | DB::table(config('request-response-logger.table')) 73 | ->insert([ 74 | ['response_status_code' => '200', 'marked' => false], 75 | ['response_status_code' => '201', 'marked' => false], 76 | ['response_status_code' => '401', 'marked' => true], 77 | ['response_status_code' => '404', 'marked' => true], 78 | ]); 79 | } 80 | 81 | 82 | /** 83 | * Configure the testable command as dummy command 84 | * 85 | * @param string $commandClass 86 | * @return object<\Symfony\Component\Console\Tester\CommandTester> 87 | */ 88 | protected function configureTestCommand(string $commandClass) { 89 | 90 | $command = new $commandClass; 91 | $command->setLaravel($this->app); 92 | 93 | return new CommandTester($command); 94 | } 95 | 96 | 97 | /** 98 | * @test 99 | */ 100 | public function the_clear_command_will_run() { 101 | 102 | $this->assertEquals($this->clearCommand->execute([]), 0); 103 | 104 | $this->assertEquals($this->clearCommand->execute([ 105 | '--keep-till-last' => 24, 106 | '--only-unmarked' => false, 107 | ]), 0); 108 | } 109 | 110 | 111 | /** 112 | * @test 113 | */ 114 | public function the_clear_command_will_delete_records_from__table() { 115 | 116 | $this->seedLoggerTable(); 117 | 118 | $modelClass = config('request-response-logger.model'); 119 | 120 | $this->assertEquals($modelClass::count(), 4); 121 | 122 | $this->assertEquals($this->clearCommand->execute([]), 0); 123 | 124 | $this->assertEquals($modelClass::count(), 0); 125 | } 126 | 127 | 128 | /** 129 | * @test 130 | */ 131 | public function the_export_command_will_run() { 132 | 133 | $this->assertEquals($this->exportCommand->execute([ 134 | '--filename' => $this->exportFileName, 135 | ]), 0); 136 | 137 | $this->assertEquals($this->exportCommand->execute([ 138 | '--filename' => $this->exportFileName, 139 | '--replace' => true, 140 | ]), 0); 141 | } 142 | 143 | 144 | /** 145 | * @test 146 | */ 147 | public function the_export_command_will_generate_proper_file() { 148 | 149 | $this->assertEquals($this->exportCommand->execute([ 150 | '--filename' => $this->exportFileName, 151 | ]), 0); 152 | 153 | $this->assertTrue(File::exists(storage_path() . '/' . $this->exportFileName . '.csv')); 154 | } 155 | 156 | 157 | /** 158 | * @test 159 | */ 160 | public function the_export_command_will_fail_if_eisxted_file_replacement_no_instructed() { 161 | 162 | $this->exportCommand->execute([ 163 | '--filename' => $this->exportFileName, 164 | ]); 165 | 166 | $this->assertNotEquals($this->exportCommand->execute([ 167 | '--filename' => $this->exportFileName, 168 | ]), 0); 169 | } 170 | 171 | 172 | /** 173 | * @test 174 | */ 175 | public function the_export_command_will_export_data_into_file() { 176 | 177 | $this->seedLoggerTable(); 178 | 179 | $modelClass = config('request-response-logger.model'); 180 | 181 | $this->assertEquals($this->exportCommand->execute([ 182 | '--filename' => $this->exportFileName, 183 | ]), 0); 184 | 185 | $fp = file(storage_path() . '/' . $this->exportFileName . '.csv', FILE_SKIP_EMPTY_LINES); 186 | 187 | $this->assertEquals(count($fp), 5); 188 | } 189 | 190 | } -------------------------------------------------------------------------------- /tests/ConfigTest.php: -------------------------------------------------------------------------------- 1 | assertNotNull(config('request-response-logger')); 19 | $this->assertIsArray(config('request-response-logger')); 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /tests/DatabaseTest.php: -------------------------------------------------------------------------------- 1 | assertNull((new \CreateRequestResponseLoggersTable)->up()); 26 | 27 | $this->assertTrue(Schema::hasTable(config('request-response-logger.table'))); 28 | } 29 | 30 | 31 | /** 32 | * @test 33 | */ 34 | public function the_logger_table_can_be_rolled_back() { 35 | 36 | include_once(__DIR__ . '/../database/migrations/create_request_response_loggers_table.php.stub'); 37 | 38 | (new \CreateRequestResponseLoggersTable)->up(); 39 | 40 | $this->assertNull((new \CreateRequestResponseLoggersTable)->down()); 41 | 42 | $this->assertFalse(Schema::hasTable(config('request-response-logger.table'))); 43 | } 44 | 45 | 46 | /** 47 | * @test 48 | */ 49 | public function the_migration_will_throw_exception_if_defined_config_table_already_exists() { 50 | 51 | include_once(__DIR__ . '/../database/migrations/create_request_response_loggers_table.php.stub'); 52 | 53 | (new \CreateRequestResponseLoggersTable)->up(); 54 | 55 | $this->expectException(Exception::class); 56 | 57 | (new \CreateRequestResponseLoggersTable)->up(); 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /tests/JobsTest.php: -------------------------------------------------------------------------------- 1 | $uuid]); 34 | 35 | Bus::assertDispatched(StoreRequestResponse::class, function ($job) use ($uuid) { 36 | return $job->data['uuid'] === $uuid; 37 | }); 38 | } 39 | 40 | 41 | /** 42 | * @test 43 | */ 44 | public function the_model_store_job_store_data_to_table() { 45 | 46 | $uuid = UuidGenerator::uuid4(); 47 | 48 | $dispatchMethod = $this->getDispatchMethod(false); 49 | 50 | StoreRequestResponse::{$dispatchMethod}(['uuid' => $uuid]); 51 | 52 | $this->assertDatabaseHas(config('request-response-logger.table'), [ 53 | 'uuid' => $uuid, 54 | ]); 55 | } 56 | 57 | 58 | /** 59 | * @test 60 | */ 61 | public function the_model_delete_job_will_run() { 62 | 63 | Bus::fake(); 64 | 65 | $dispatchMethod = $this->getDispatchMethod(false); 66 | 67 | DeleteRequestResponse::{$dispatchMethod}(); 68 | 69 | Bus::assertDispatched(DeleteRequestResponse::class, function ($job) { 70 | return true; 71 | }); 72 | } 73 | 74 | 75 | /** 76 | * @test 77 | */ 78 | public function the_model_delete_job_will_delete_records_from_table() { 79 | 80 | $modelClass = config('request-response-logger.model'); 81 | 82 | DB::table(config('request-response-logger.table')) 83 | ->insert([ 84 | ['response_status_code' => '200', 'marked' => false], 85 | ['response_status_code' => '201', 'marked' => false], 86 | ['response_status_code' => '401', 'marked' => true], 87 | ['response_status_code' => '404', 'marked' => true], 88 | ]); 89 | 90 | $this->assertEquals($modelClass::count(), 4); 91 | 92 | $dispatchMethod = $this->getDispatchMethod(false); 93 | 94 | DeleteRequestResponse::{$dispatchMethod}(); 95 | 96 | $this->assertEquals($modelClass::count(), 0); 97 | } 98 | 99 | } -------------------------------------------------------------------------------- /tests/MiddlewareTest.php: -------------------------------------------------------------------------------- 1 | json([ 33 | 'status' => true, 34 | 'data' => request()->all(), 35 | ]); 36 | 37 | })->middleware(['web', LogRequestResponse::class]); 38 | } 39 | 40 | 41 | /** 42 | * @test 43 | */ 44 | public function middleware_saves_the_data_in_table() { 45 | 46 | $request = $this->get('/request-response-logger-test'); 47 | $log = RequestResponseLogger::first(); 48 | 49 | $this->assertSame($log->request_method, 'GET'); 50 | 51 | $request = $this->post('/request-response-logger-test'); 52 | $log = RequestResponseLogger::orderBy('id', 'desc')->first(); 53 | 54 | $this->assertSame($log->request_method, 'POST'); 55 | } 56 | 57 | 58 | /** 59 | * @test 60 | */ 61 | public function middleware_will_save_data_along_with_auth_user() { 62 | 63 | $user = User::create(['email' => 'testuser@test.com', 'password' => Hash::make('123456')]); 64 | 65 | $this->be($user); 66 | 67 | $this->post('/request-response-logger-test'); 68 | $log = RequestResponseLogger::first(); 69 | 70 | $this->assertNotNull($log->request_auth_user_id); 71 | $this->assertEquals($log->request_auth_user_id, $user->id); 72 | } 73 | 74 | 75 | } -------------------------------------------------------------------------------- /tests/ModelTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(class_exists(config('request-response-logger.model'))); 27 | } 28 | 29 | 30 | /** 31 | * @test 32 | */ 33 | public function it_is_a_proper_model_class() { 34 | 35 | $modelClass = config('request-response-logger.model'); 36 | 37 | $this->assertIsObject(new $modelClass); 38 | $this->assertInstanceOf(Model::class, new $modelClass); 39 | } 40 | 41 | 42 | /** 43 | * @test 44 | */ 45 | public function it_will_store_data_in_table() { 46 | 47 | $modelClass = config('request-response-logger.model'); 48 | 49 | $record = $modelClass::create([]); 50 | 51 | $this->assertDatabaseHas(config('request-response-logger.table'), [ 52 | 'id' => $record->id, 53 | ]); 54 | } 55 | 56 | 57 | /** 58 | * @test 59 | */ 60 | public function it_can_filter_records_by_defined_scopes() { 61 | 62 | $modelClass = config('request-response-logger.model'); 63 | 64 | DB::table(config('request-response-logger.table')) 65 | ->insert([ 66 | ['response_status_code' => '200', 'marked' => false], 67 | ['response_status_code' => '201', 'marked' => false], 68 | ['response_status_code' => '401', 'marked' => true], 69 | ['response_status_code' => '404', 'marked' => true], 70 | ]); 71 | 72 | $this->assertEquals($modelClass::count(), 4); 73 | $this->assertEquals($modelClass::successful()->count(), 2); 74 | $this->assertEquals($modelClass::failed()->count(), 2); 75 | $this->assertEquals($modelClass::marked()->count(), 2); 76 | } 77 | 78 | 79 | /** 80 | * @test 81 | */ 82 | public function it_can_handle_related_to_auth_user() { 83 | 84 | $user = User::create(['email' => 'testuser@test.com', 'password' => Hash::make('123456')]); 85 | 86 | $this->be($user); 87 | 88 | $modelClass = config('request-response-logger.model'); 89 | 90 | $log = $modelClass::create([ 91 | 'request_auth_user_id' => $user->id 92 | ]); 93 | 94 | $this->assertEquals($log->user->id, $user->id); 95 | } 96 | 97 | /** 98 | * @test 99 | */ 100 | public function it_can_cast_columns_on_store_and_retrival_as_defined_in_config() { 101 | 102 | $modelClass = config('request-response-logger.model'); 103 | 104 | $log = $modelClass::create([ 105 | 'request_headers' => ['x-header-value' => 'some_value'], 106 | 'request_body' => ['value' => 'some_value'], 107 | 'response_headers' => ['x-header-value' => 'some_value'], 108 | 'response_body' => ['value' => 'some_value'], 109 | ]); 110 | 111 | $this->assertJson($log->getRawOriginal('request_headers')); 112 | $this->assertJson($log->getRawOriginal('request_body')); 113 | 114 | $this->assertIsArray($log->response_headers); 115 | $this->assertIsArray($log->response_body); 116 | 117 | Config::set('request-response-logger.json_to_array_on_retrieve', false); 118 | 119 | $this->assertJson($log->response_headers); 120 | $this->assertJson($log->response_body); 121 | } 122 | 123 | } -------------------------------------------------------------------------------- /tests/RedisTest.php: -------------------------------------------------------------------------------- 1 | 28 | */ 29 | protected $redisImportCommand; 30 | 31 | 32 | /** 33 | * Setup the test environment. 34 | * 35 | * @return void 36 | */ 37 | protected function setUp(): void { 38 | 39 | parent::setUp(); 40 | 41 | // $app = new Container(); 42 | 43 | $this->app->singleton('redis', function ($app) { 44 | 45 | $config = $app->make('config')->get('request-response-logger.redis_configs', []); 46 | 47 | return new RedisFaker($config); 48 | }); 49 | 50 | $this->app->bind('redis.connection', function ($app) { 51 | 52 | return $app['redis']->connection(); 53 | }); 54 | 55 | $this->redisImportCommand = $this->configureTestCommand(RequestResponseLoggerRedisImport::class); 56 | 57 | Config::set('request-response-logger.store_on_redis', true); 58 | Config::set('request-response-logger.fallback_on_redis_failure', false); 59 | } 60 | 61 | 62 | /** 63 | * Define routes setup. 64 | * 65 | * @param \Illuminate\Routing\Router $router 66 | * @return void 67 | */ 68 | protected function defineRoutes($router) { 69 | 70 | Route::any('/request-response-logger-test', function() { 71 | 72 | return response()->json([ 73 | 'status' => true, 74 | 'data' => request()->all(), 75 | ]); 76 | 77 | })->middleware(['web', LogRequestResponse::class]); 78 | } 79 | 80 | 81 | /** 82 | * Configure the testable command as dummy command 83 | * 84 | * @param string $commandClass 85 | * @return object<\Symfony\Component\Console\Tester\CommandTester> 86 | */ 87 | protected function configureTestCommand(string $commandClass) { 88 | 89 | $command = new $commandClass; 90 | $command->setLaravel($this->app); 91 | 92 | return new CommandTester($command); 93 | } 94 | 95 | 96 | /** 97 | * @test 98 | */ 99 | public function middleware_saves_the_data_redis_storage() { 100 | 101 | $request = $this->get('/request-response-logger-test'); 102 | 103 | $this->assertEquals(1, Redis::llen(config('request-response-logger.redis_key_name'))); 104 | 105 | $request = $this->get('/request-response-logger-test'); 106 | 107 | $this->assertEquals(2, Redis::llen(config('request-response-logger.redis_key_name'))); 108 | } 109 | 110 | 111 | /** 112 | * @test 113 | */ 114 | public function middleware_will_not_saves_the_data_in_model_table_first() { 115 | 116 | $request = $this->get('/request-response-logger-test'); 117 | 118 | $this->assertEquals(1, Redis::llen(config('request-response-logger.redis_key_name'))); 119 | $this->assertEquals(RequestResponseLogger::count(), 0); 120 | 121 | $request = $this->get('/request-response-logger-test'); 122 | 123 | $this->assertEquals(2, Redis::llen(config('request-response-logger.redis_key_name'))); 124 | $this->assertEquals(RequestResponseLogger::count(), 0); 125 | } 126 | 127 | 128 | /** 129 | * @test 130 | */ 131 | public function middleware_will_push_the_data_in_model_table_from_redis_storage_when_hits_max_store_limit() { 132 | 133 | for ($i=0; $i < config('request-response-logger.max_redis_count') ; $i++) { 134 | 135 | $this->get('/request-response-logger-test'); 136 | } 137 | 138 | $this->assertEquals(0, Redis::llen(config('request-response-logger.redis_key_name'))); 139 | 140 | $this->assertEquals(RequestResponseLogger::count(), config('request-response-logger.max_redis_count')); 141 | } 142 | 143 | 144 | /** 145 | * @test 146 | */ 147 | public function the_redis_import_command_will_run() { 148 | 149 | $this->assertEquals($this->redisImportCommand->execute([]), 0); 150 | } 151 | 152 | 153 | /** 154 | * @test 155 | */ 156 | public function the_redis_import_command_will_import_data_from_redis_storage_to_db() { 157 | 158 | $this->get('/request-response-logger-test'); 159 | $this->get('/request-response-logger-test'); 160 | 161 | $this->assertEquals(RequestResponseLogger::count(), 0); 162 | $this->assertEquals(2, Redis::llen(config('request-response-logger.redis_key_name'))); 163 | 164 | $this->redisImportCommand->execute([]); 165 | 166 | $this->assertEquals(0, Redis::llen(config('request-response-logger.redis_key_name'))); 167 | $this->assertEquals(RequestResponseLogger::count(), 2); 168 | } 169 | 170 | } -------------------------------------------------------------------------------- /tests/Traits/LaravelTestBootstrapping.php: -------------------------------------------------------------------------------- 1 | RequestResponseLogger::class, 35 | ]; 36 | } 37 | 38 | 39 | /** 40 | * Define environment setup. 41 | * 42 | * @param Illuminate\Foundation\Application $app 43 | * @return void 44 | */ 45 | protected function defineEnvironment($app) { 46 | 47 | // Setup default database to use sqlite :memory: 48 | $app['config']->set('database.default', 'testbench'); 49 | $app['config']->set('database.connections.testbench', [ 50 | 'driver' => 'sqlite', 51 | 'database' => ':memory:', 52 | 'prefix' => '', 53 | ]); 54 | 55 | $app['config']->set('app.url', 'http://localhost/'); 56 | $app['config']->set('app.debug', false); 57 | $app['config']->set('app.key', env('APP_KEY', 'base64:4vaRSWQcoaHh8mA8qIRxL1Ei+UNIj9Wst9rY+ne2rE4=')); 58 | $app['config']->set('app.cipher', 'AES-256-CBC'); 59 | 60 | $app['config']->set('request-response-logger', [ 61 | 'table' => 'request_response_loggers', 62 | 'model' => RequestResponseLoggerModel::class, 63 | 'log_on_queue' => false, 64 | 'jobs' => [ 65 | 'log' => \Touhidurabir\RequestResponseLogger\Jobs\StoreRequestResponse::class, 66 | 'clear' => \Touhidurabir\RequestResponseLogger\Jobs\DeleteRequestResponse::class, 67 | ], 68 | 'json_to_array_on_retrieve' => true, 69 | 'store_on_redis' => false, 70 | 'max_redis_count' => 4, 71 | 'redis_store_in_segment_count' => 2, 72 | 'redis_key_name' => 'request_response_log', 73 | 'redis_configs' => [ 74 | 'url' => env('REDIS_URL'), 75 | 'host' => env('REDIS_HOST', '127.0.0.1'), 76 | 'password' => env('REDIS_PASSWORD', null), 77 | 'port' => env('REDIS_PORT', '6379'), 78 | 'database' => env('REDIS_DB', '0'), 79 | ], 80 | 'fallback_on_redis_failure' => true, 81 | 'delete_in_segment_count' => 2, 82 | ]); 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /tests/Traits/TestDatabaseBootstraping.php: -------------------------------------------------------------------------------- 1 | loadMigrationsFrom(__DIR__ . '/../App/database/migrations'); 17 | 18 | $this->artisan('migrate', ['--database' => 'testbench'])->run(); 19 | 20 | (new \CreateRequestResponseLoggersTable)->up(); 21 | 22 | $this->beforeApplicationDestroyed(function () { 23 | $this->artisan('migrate:rollback', ['--database' => 'testbench'])->run(); 24 | }); 25 | } 26 | } --------------------------------------------------------------------------------