├── .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 | }
--------------------------------------------------------------------------------