diff --git a/.gitignore b/.gitignore index 0ba21f1..21dabd5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .php_cs .php_cs.cache .phpunit.result.cache +.phpunit.cache build composer.lock coverage diff --git a/README.md b/README.md index 5e2ca41..c3d6d12 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,7 @@ This package works as follows: - `team_id`: The team id associated with the request (if available) - `method`: The HTTP method (`GET/POST/...`) - `route`: The route name (if available) or the route URI (eg `/posts/{post}`) + - `parameters`: The route parameters passed (if enabled else `null`) - `status`: The HTTP status (eg `202`) - `ip`: The request ip - `date`: The date of the request as datetime (can be aggregated) diff --git a/config/route-statistics.php b/config/route-statistics.php index 6c7d5d3..dc80025 100644 --- a/config/route-statistics.php +++ b/config/route-statistics.php @@ -12,6 +12,16 @@ */ 'enabled' => env('ROUTE_STATISTICS_ENABLED', true), + /* + |-------------------------------------------------------------------------- + | Store parameters + |-------------------------------------------------------------------------- + | + | If this setting is set to true the route parameters will also be logged. + | + */ + 'store_route_parameters' => env('ROUTE_STATISTICS_STORE_ROUTE_PARAMETERS', false), + /* |-------------------------------------------------------------------------- | Aggregation diff --git a/database/factories/RouteStatisticFactory.php b/database/factories/RouteStatisticFactory.php index e64e413..68c54f0 100644 --- a/database/factories/RouteStatisticFactory.php +++ b/database/factories/RouteStatisticFactory.php @@ -15,6 +15,7 @@ public function definition() 'method' => $this->faker->randomElement(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']), 'route' => $this->faker->domainWord().'.'.$this->faker->randomElement(['index', 'create', 'store', 'show', 'edit', 'update', 'destroy']), 'status' => $this->faker->randomElement([200, 201, 202, 204, 300, 301, 302, 303, 304, 400, 401, 402, 403, 404, 405, 406, 422, 429, 500, 501, 502, 503, 504]), + 'parameters' => $this->faker->json(), 'ip' => $this->faker->ipv4(), 'date' => $this->faker->dateTime(), 'counter' => $this->faker->randomNumber(4), diff --git a/database/migrations/add_parameters_to_route_statistics_table.php.stub b/database/migrations/add_parameters_to_route_statistics_table.php.stub new file mode 100644 index 0000000..5879ad9 --- /dev/null +++ b/database/migrations/add_parameters_to_route_statistics_table.php.stub @@ -0,0 +1,22 @@ +json('parameters')->after('route')->nullable(); + }); + } + + public function down() + { + Schema::table('route_statistics', function (Blueprint $table) { + $table->dropColumn('parameters'); + }); + } +}; diff --git a/src/Commands/LaravelRouteStatisticsCommand.php b/src/Commands/LaravelRouteStatisticsCommand.php index c3738ff..43f13e4 100644 --- a/src/Commands/LaravelRouteStatisticsCommand.php +++ b/src/Commands/LaravelRouteStatisticsCommand.php @@ -2,6 +2,7 @@ namespace Bilfeldt\LaravelRouteStatistics\Commands; +use Bilfeldt\LaravelRouteStatistics\Models\RouteStatistic; use Illuminate\Console\Command; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Facades\DB; @@ -40,11 +41,16 @@ public function handle() $this->applyGrouping($query); $this->applySorting($query); - $results = $query->limit($this->option('limit'))->get(); + $fields = $this->getFields(); + + $results = $query + ->limit($this->option('limit')) + ->get() + ->map(fn (RouteStatistic $model): array => $this->toTableRow($model, $fields)); $this->table( - $this->getFields(), - $results->toArray() + $fields, + $results ); return Command::SUCCESS; @@ -111,16 +117,30 @@ protected function getFields(): array return array_merge($this->option('group'), ['last_used', 'counter']); } - return [ + return array_filter([ 'id', 'user_id', 'team_id', 'method', 'route', 'status', + config('route-statistics.store_route_parameters') === true ? 'parameters' : null, 'ip', 'date', 'counter', - ]; + ]); + } + + /** + * @param RouteStatistic $model + * @param $fields array + * @return array + */ + protected function toTableRow(RouteStatistic $model, array $fields): array + { + return array_map( + fn (mixed $item): mixed => is_array($item) ? json_encode($item) : $item, + $model->only($fields) + ); } } diff --git a/src/LaravelRouteStatisticsServiceProvider.php b/src/LaravelRouteStatisticsServiceProvider.php index e74dc1f..5730063 100644 --- a/src/LaravelRouteStatisticsServiceProvider.php +++ b/src/LaravelRouteStatisticsServiceProvider.php @@ -60,6 +60,7 @@ private function publishMigrations() { $this->publishes([ __DIR__.'/../database/migrations/create_route_statistics_table.php.stub' => database_path('migrations/'.date('Y_m_d_His', time()).'_create_route_statistics_table.php'), + __DIR__.'/../database/migrations/add_parameters_to_route_statistics_table.php.stub' => database_path('migrations/'.date('Y_m_d_His', time()).'_add_parameters_to_route_statistics_table.php'), // you can add any number of migrations here ], 'migrations'); } diff --git a/src/Models/RouteStatistic.php b/src/Models/RouteStatistic.php index 632bc05..a2f7642 100644 --- a/src/Models/RouteStatistic.php +++ b/src/Models/RouteStatistic.php @@ -23,6 +23,7 @@ class RouteStatistic extends Model implements RequestLoggerInterface protected $casts = [ 'date' => 'datetime', + 'parameters' => 'array', ]; //====================================================================== @@ -85,6 +86,7 @@ public function getLogAttributes(Request $request, $response, ?int $time = null, 'team_id' => $this->getRequestTeam($request)?->getKey(), 'method' => $request->getMethod(), 'route' => $request->route()?->getName() ?? $request->route()?->uri(), + 'parameters' => config('route-statistics.store_route_parameters') ? $request->route()->originalParameters() : null, 'status' => $response->getStatusCode(), 'ip' => $request->ip(), 'date' => $this->getDate(), diff --git a/tests/TestCase.php b/tests/TestCase.php index cd04265..593fb1f 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -35,7 +35,17 @@ public function getEnvironmentSetUp($app) 'prefix' => '', ]); - $migration = include __DIR__.'/../database/migrations/create_route_statistics_table.php.stub'; - $migration->up(); + $this->runMigrations([ + 'create_route_statistics_table', + 'add_parameters_to_route_statistics_table', + ]); + } + + private function runMigrations(array $fileNames): void + { + foreach ($fileNames as $fileName) { + $class = require __DIR__.'/../database/migrations/'.$fileName.'.php.stub'; + $class->up(); + } } } diff --git a/tests/Unit/RouteStatisticModelTest.php b/tests/Unit/RouteStatisticModelTest.php index 2af737b..83bcd2a 100644 --- a/tests/Unit/RouteStatisticModelTest.php +++ b/tests/Unit/RouteStatisticModelTest.php @@ -46,4 +46,52 @@ public function test_get_log_attributes(): void { $this->markTestIncomplete('Mock the request and response to ensture the correct attributes are returned'); } + + public function test_logs_parameters_if_config_enabled(): void + { + Config::set('route-statistics.store_route_parameters', true); + + $route = 'home'; + $params = [ + 'param1' => 'one', + 'param2' => 'two', + ]; + $request = \Illuminate\Http\Request::create($route.'/'.implode('/', $this->get_route_parameters($params)), 'GET'); + $this->app['router']->get($route.'/'.implode('/', $this->get_route_keys($params)), fn () => 'Test route response'); + $response = $this->app['router']->dispatch($request); + + (new RouteStatistic)->log($request, $response, 1, 2); + + $log = RouteStatistic::first(); + $this->assertEquals($params, $log->parameters); + } + + public function test_logs_parameters_if_config_disabled(): void + { + Config::set('route-statistics.store_route_parameters', false); + + $route = 'home'; + $params = [ + 'param1' => 'one', + 'param2' => 'two', + ]; + $request = \Illuminate\Http\Request::create($route.'/'.implode('/', $this->get_route_parameters($params)), 'GET'); + $this->app['router']->get($route.'/'.implode('/', $this->get_route_keys($params)), fn () => 'Test route response'); + $response = $this->app['router']->dispatch($request); + + (new RouteStatistic)->log($request, $response, 1, 2); + + $log = RouteStatistic::first(); + $this->assertNull($log->parameters); + } + + private function get_route_parameters(array $parameters): array + { + return array_values($parameters); + } + + private function get_route_keys(array $parameters): array + { + return array_map(fn ($parameter) => '{'.$parameter.'}', array_keys($parameters)); + } }