Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Route Statistics Laravel Package

bilfeldt/laravel-route-statistics

Log Laravel requests and responses for route usage statistics, grouped by route with optional hourly, daily, or monthly aggregation to keep database storage minimal. Track per-user and unauthenticated usage.

View on GitHub
Deep Wiki
Context7

Getting Started

The package logs each request and stores it (optionally aggregated) in a single route_statistics table, so you get "who hit which route how many times" without an analytics stack.

  1. Install — requires Laravel 10–13 / PHP 8.2+ for the current v4 line:

    composer require bilfeldt/laravel-route-statistics
    
  2. Publish and run the migration (this is the step people forget — the service provider registers the middleware alias and request macros, but not the schema):

    php artisan vendor:publish --provider="Bilfeldt\LaravelRouteStatistics\LaravelRouteStatisticsServiceProvider" --tag="migrations"
    php artisan migrate
    
  3. Publish the config so you can tune aggregation, exclusions and the user model used by the user() relation:

    php artisan vendor:publish --provider="Bilfeldt\LaravelRouteStatistics\LaravelRouteStatisticsServiceProvider" --tag="config"
    
  4. Enable logging — start scoped, not global. In bootstrap/app.php the provider already registers the routestatistics alias, so your first use case should be the authenticated groups you actually care about:

    Route::middleware(['auth', 'routestatistics'])->group(function () {
        // dashboard, reports, API resources...
    });
    

    Reserve the global approach (prepending RouteStatisticsMiddleware in withMiddleware()) for when you genuinely want site-wide coverage — see the gotchas below.

  5. Look here first:

    • The published migration — it defines exactly what you can report on: user_id, team_id, method, route, parameters, status, ip, date, counter.
    • The published config — aggregation levels (second/minute/hour/day/month), exclusions, queue toggle and the model class behind RouteStatistic::user().
    • The two shipped commands, which are the fastest sanity check that logging works.
  6. First real use case — hit a few pages, then:

    php artisan route:stats      # traffic per logged route
    php artisan route:unused     # routes your app declares but never logs
    

    If route:stats is empty, logging isn't enabled on those routes; if route:unused lists everything, the middleware isn't applied broadly enough.

Implementation Patterns

Three enablement styles, use all three together.

  • Global (prepend RouteStatisticsMiddleware in withMiddleware()) for full coverage and a trustworthy route:unused report.

  • Route group (Route::middleware(['routestatistics'])) for the common case where you only care about authenticated app/API traffic.

  • Ad-hoc macro ($request->routeStatistics() inside a controller) for conditional logging — e.g. log only successful, non-cached, non-export requests:

    public function show(Request $request, Report $report)
    {
        $request->routeStatistics();
    
        return view('reports.show', compact('report'));
    }
    

Enrich with context for per-tenant reporting. The default logger captures user_id and a team_id, but the real leverage is pushing your own dimensions (tenant, plan, API client, feature flag) into the context so you can slice usage later. Do it in a small middleware that runs after tenant resolution and before the response completes:

$request->routeStatistics();                       // tag
// then attach context, e.g. team_id / custom columns

This is the pattern that turns the table from "raw request log" into a product-analytics table: groupBy('team_id')->sum('counter') becomes "which customers actually use feature X".

Always aggregate with SUM(counter), never COUNT(*). Aggregation collapses N requests into one row with counter = N. Every dashboard, report and export must be written as:

RouteStatistic::query()
    ->whereWeb()                 // or whereApi()
    ->where('date', '>=', now()->subDays(30))
    ->where('status', '<', 400)  // ignore error traffic when measuring adoption
    ->groupBy('route', 'user_id')
    ->selectRaw('route, user_id, SUM(counter) as hits')
    ->orderByDesc('hits')
    ->get();

The README hints at the intended dashboard: use the user() relation to render "top users", and group by route for "top routes". Both are one query away once you internalise sum(counter).

Retention is a scheduled job, not a feature. The whole point of aggregation is that the table stays small. Wire a nightly purge so old raw/aggregated rows are dropped:

RouteStatistic::where('date', '<', now()->subMonths(6))->delete();

Pair it with the aggregation config so recent data is granular (minute/hour) and old data is coarse (day/month). If you skip this, you've built a write-amplified request log.

Queue the writes for hot paths. Enable queued logging in the config when the middleware sits on high-traffic API routes — a synchronous insert per request is a real latency cost. Make sure a worker is running in every environment, and confirm the job isn't silently dropped on deploy.

Use route:unused as a maintenance signal, not a hard truth. Schedule it weekly (or run it in CI against a staging environment with production-like traffic) to find endpoints that can be deleted or that lack coverage, before refactoring controllers.

Testing. The package ships a RouteStatisticFactory, so feature tests can assert logging behaviour directly:

$this->get('/dashboard')->assertOk();
$this->assertDatabaseHas('route_statistics', ['route' => 'dashboard', 'status' => 200]);

And set the enabled flag to false in phpunit.xml so unrelated tests don't write rows.

Gotchas and Tips

  • Put the middleware first. When enabling globally, prepend it (as the README shows). Middleware that short-circuits earlier — auth redirects, throttling, maintenance mode — can prevent the request from ever being tagged, so route:stats and route:unused won't reflect real traffic.
  • counter is the number, not the row count. Any query using count() will under-report once aggregation kicks in. This is the single most common reporting bug.
  • Unnamed routes are grouped by URI. That's fine (/posts/{post}), but renaming a path silently starts a new group and orphans historical rows. Name your routes.
  • Route parameters land in their own column (v4 change). In v3 they lived in a JSON blob; if you're upgrading and querying parameters, write a backfill or adjust your queries. Parameters are serialised as JSON and can contain sensitive values (emails, tokens, filters) — keep parameter logging disabled or scrub them, and treat the column as PII.
  • user_id + ip is personal data. Decide on a retention window, document it, and consider not storing the IP at all if you only need usage counts. Anonymous traffic is exactly what the package is good at surfacing, so don't just drop those rows — hash or aggregate them.
  • Error traffic pollutes adoption metrics. 401/404/500 responses get logged like anything else. Filter on status < 400 for usage reporting, and use the error rows deliberately for "who is hitting 403s".
  • Aggregated timestamps are truncated. Since v3.3.0 the date is rounded (microseconds zeroed) according to the aggregation level, and date is cast to Carbon. Don't build exact-timestamp forensics on aggregated rows — keep raw logging on for the window where you need precision.
  • Queued logging hides failures. With the queue enabled, writes are asynchronous: tests pass with a Queue::fake() and nothing in the DB, and a dead worker means a silent data gap. Assert on the job in tests and monitor queue depth.
  • route:unused produces false positives by design. It only knows routes that would be logged. If the middleware covers just your auth group, every public route shows up as "unused". Either accept that, or apply the middleware globally when auditing.
  • Disable in local/CI. Set ROUTE_STATISTICS_ENABLED=false in .env/phpunit.xml unless you're actively debugging logging — otherwise every local pageview and every test run pollutes your dev database.
  • Bots inflate counts. Crawler hits are indistinguishable from users by default; push a user_agent/bot flag through the logger context (or exclude those paths) if your numbers look inflated.
  • Extension points: the actual write goes through Bilfeldt\RequestLogger\Contracts\RequestLoggerInterface from bilfeldt/laravel-request-logger — bind your own implementation to change what gets written, and extend RouteStatistic (or point the config's user model at your own) for custom relations and accessors. Check the underlying request-logger README when you need context/grouping APIs beyond what the route-statistics docs show.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo