our-education / laravel-request-tracker
Event-driven Laravel package for tracking user access patterns with daily summaries, detailed journeys, and module-level analytics
Installs: 77
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 2
Forks: 0
Open Issues: 0
pkg:composer/our-education/laravel-request-tracker
Requires
- php: ^8.0
- illuminate/support: ^8.0|^9.0|^10.0|^11.0
Requires (Dev)
- orchestra/testbench: ^6.0|^7.0|^8.0|^9.0
README
Event-driven Laravel package for tracking user access patterns with daily summaries, detailed journeys, and module-level analytics. Automatically logs user activity in background jobs without slowing down your application.
Features
✅ Daily Activity Summary - One record per user+role+date with access count & timestamps
✅ Detailed User Journey - Track unique endpoints with module/submodule organization
✅ PHP 8 Attributes - Use #[TrackModule('module', 'submodule')] on controllers
✅ Auto Module Detection - Fallback to pattern matching when no attribute provided
✅ Queue-Based - All tracking via background jobs (respects QUEUE_CONNECTION)
✅ National ID Support - Commands filter by national_id for easier access
✅ Role Name Storage - Store role names from roles.name for filtering
✅ Silent Errors - Tracking failures won't break your application
✅ Data Retention - Automatic cleanup of old records
Quick Start
# Install composer require our-education/laravel-request-tracker # Publish & migrate php artisan vendor:publish --provider="OurEdu\RequestTracker\RequestTrackerServiceProvider" php artisan migrate # Enable in .env REQUEST_TRACKER_ENABLED=true QUEUE_CONNECTION=redis # Start queue worker php artisan queue:work
Add to controllers:
use OurEdu\RequestTracker\Attributes\TrackModule; #[TrackModule('orders')] class OrderController extends Controller { // All methods tracked as 'orders' module } #[TrackModule('users', 'profile')] class ProfileController extends Controller { // Tracked as 'users.profile' (module.submodule) }
View reports:
php artisan tracker:user-stats 1234567890 php artisan tracker:user-journey 1234567890 --date=2025-01-15 php artisan tracker:module-access orders --from=2025-01-01
How It Works
Architecture: Request → RequestHandled Event → EventsSubscriber → TrackUserAccessJob (queue) → Database
- After a request completes (post-auth middleware),
RequestHandledevent fires EventsSubscriberextracts user, role info and dispatches job to queueTrackUserAccessJobprocesses in background:- Creates/updates daily summary in
request_trackerstable - Checks for
#[TrackModule]attribute on controller (priority 1) - If no attribute, falls back to auto-detection via ModuleExtractor
- Creates detailed record in
user_access_detailstable if module detected
- Creates/updates daily summary in
- Your application responds immediately (no blocking)
What's Tracked:
- Daily Summary (all requests): User+role+date, access count, timestamps
- Detailed Journey (when module detected): Endpoint, module, submodule, action, visit count
Configuration
Environment Variables:
REQUEST_TRACKER_ENABLED=true # Enable/disable tracking REQUEST_TRACKER_SILENT_ERRORS=true # Don't break app on errors REQUEST_TRACKER_AUTO_CLEANUP=false # Auto-delete old records REQUEST_TRACKER_QUEUE_NAME=tracking # Queue name (null = default)
Config File (config/request-tracker.php):
return [ 'enabled' => env('REQUEST_TRACKER_ENABLED', false), 'silent_errors' => env('REQUEST_TRACKER_SILENT_ERRORS', true), // Exclude paths from tracking 'exclude' => [ 'parent/look-up', // Suffix match 'api/*/internal', // Wildcard 'regex:/^health/', // Regex ], // Auth guards to check 'auth_guards' => ['web', 'api'], // Queue configuration 'queue' => [ 'queue' => env('REQUEST_TRACKER_QUEUE_NAME', null), // null = default ], // Data retention 'retention' => [ 'auto_cleanup' => env('REQUEST_TRACKER_AUTO_CLEANUP', false), 'keep_summaries_days' => 90, 'keep_detailed_days' => 30, ], // Module auto-extraction from URLs 'module_mapping' => [ 'enabled' => true, // Pattern matching (checked first) 'patterns' => [ '/admission/' => 'admission', '/subject/' => 'subjects', '/certificate_manager/' => 'subjects', '/users/' => 'users', '/auth/' => 'authentication', ], // Auto-extract from path segments (fallback) // e.g., 'api/v1/en/subject/list' -> 'subject' (segment 3) 'auto_extract' => true, 'auto_extract_segment' => 3, // 0-based index after api/v1/locale ], ];
Usage
1. Add Attributes to Controllers
use OurEdu\RequestTracker\Attributes\TrackModule; // Simple module #[TrackModule('orders')] class OrderController extends Controller { public function index() { /* module='orders', action='index' */ } public function show($id) { /* module='orders', action='show' */ } } // Module + Submodule #[TrackModule('users', 'profile')] class ProfileController extends Controller { public function show($id) { /* module='users', submodule='profile' */ } } // Multiple controllers, same parent module #[TrackModule('reports', 'sales')] class SalesReportController { } #[TrackModule('reports', 'inventory')] class InventoryReportController { }
Action derivation: Route name last segment (e.g., users.profile.show → show) or method name.
Without attribute: Daily summary still tracked, but no detailed journey records.
2. Query the Data
use OurEdu\RequestTracker\Models\{RequestTracker, UserAccessDetail}; // Get today's summary $summary = RequestTracker::forUser($userUuid)->today()->first(); // Get user's journey $journey = UserAccessDetail::forUser($userUuid) ->forDate('2025-01-15') ->get(); // Most visited modules this month $modules = UserAccessDetail::forUser($userUuid) ->thisMonth() ->select('module', DB::raw('SUM(visit_count) as total')) ->groupBy('module') ->orderByDesc('total') ->get(); // Find who accessed a module $users = UserAccessDetail::whoAccessedModule('orders', '2025-01-01', '2025-01-31') ->get();
3. Use Artisan Commands
# User stats (by national_id) php artisan tracker:user-stats 1234567890 php artisan tracker:user-stats 1234567890 --from=2025-01-01 --to=2025-01-31 php artisan tracker:user-stats 1234567890 --role={role-uuid} # User journey php artisan tracker:user-journey 1234567890 --date=2025-01-15 php artisan tracker:user-journey 1234567890 --module=users # Module access report php artisan tracker:module-access orders --from=2025-01-01 --to=2025-01-31 php artisan tracker:module-access users --submodule=profile --role={role-uuid} # Cleanup old records php artisan tracker:cleanup --days=30 --dry-run
Database Schema
request_trackers - Daily Summary
One record per user_uuid + role_uuid + date
| Column | Type | Description |
|---|---|---|
uuid |
UUID | Primary key |
user_uuid |
UUID | User identifier |
role_uuid |
UUID | Role identifier |
role_name |
STRING | Role name (from roles.name) |
date |
DATE | Activity date |
access_count |
INTEGER | Total requests this day |
first_access |
DATETIME | First request timestamp |
last_access |
DATETIME | Last request timestamp |
user_session_uuid |
UUID | Session identifier |
user_access_details - Detailed Journey
One record per unique endpoint per user per date (only for attributed controllers)
| Column | Type | Description |
|---|---|---|
uuid |
UUID | Primary key |
tracker_uuid |
UUID | FK to request_trackers.uuid |
user_uuid |
UUID | User identifier |
role_uuid |
UUID | Role identifier |
role_name |
STRING | Role name (from roles.name) |
date |
DATE | Visit date |
method |
STRING | HTTP method (GET/POST/PUT/DELETE) |
endpoint |
TEXT | Full path (e.g., api/v1/users/123/profile) |
route_name |
STRING | Laravel route name |
controller_action |
STRING | Controller@method |
module |
STRING | Main module (from #[TrackModule] or auto-detected) |
submodule |
STRING | Submodule (from #[TrackModule] or auto-detected) |
action |
STRING | Action name (from route or method) |
visit_count |
INTEGER | Times visited today |
first_visit |
DATETIME | First visit timestamp |
last_visit |
DATETIME | Last visit timestamp |
Unique Constraint: tracker_uuid + endpoint + method
Query Scopes
RequestTracker Scopes
->forUser($uuid) // Filter by user ->forDate($date) // Specific date ->forDateRange($from, $to) // Date range ->today() // Today ->thisWeek() // This week ->thisMonth() // This month ->mostActive($limit) // Top active users
UserAccessDetail Scopes
->forUser($uuid) ->forModule($module) ->forSubmodule($submodule) ->forDate($date) ->forDateRange($from, $to) ->today() ->thisWeek() ->thisMonth() ->mostVisited($limit) ->groupByModule() ->whoAccessedModule($module, $from, $to) ->whoAccessedSubmodule($module, $submodule, $from, $to)
Module Detection Priority
#[TrackModule]Attribute (highest priority) - Class-level attribute- Custom Patterns - Defined in
config/request-tracker.php - Route Name - Dot notation (e.g.,
users.profile.show) - URL Path - Auto-extract from path segments
- Controller Name - Extract from controller class name
Advanced Features
Path Exclusion
'exclude' => [ 'health', // Suffix match: */health 'api/*/internal', // Wildcard: api/v1/internal, api/v2/internal 'regex:/^(ping|pong)/', // Regex: starts with ping or pong ]
Queue Integration
Package respects Laravel's QUEUE_CONNECTION:
sync- Immediate processing (dev only)redis- Background processing (recommended)database- Database queuesqs- AWS SQS
Important: Run php artisan queue:work in production!
Data Retention
# Schedule in app/Console/Kernel.php protected function schedule(Schedule $schedule) { $schedule->command('tracker:cleanup --days=90')->daily(); }
Troubleshooting
Tracking not working?
- Check
REQUEST_TRACKER_ENABLED=truein.env - Run
php artisan config:cache - Ensure queue worker is running:
php artisan queue:work - Check logs:
tail -f storage/logs/laravel.log
Jobs not processing?
- Verify
QUEUE_CONNECTIONis correct - Restart worker:
php artisan queue:restart - Check failed jobs:
php artisan queue:failed
Attribute not detected?
- PHP >= 8.0 required (attributes)
- Attribute must be at class level, not method level
- Syntax:
#[TrackModule('module', 'submodule')]
Requirements
- PHP >= 8.0
- Laravel 8, 9, 10, or 11
- Queue worker running (for background processing)
License
MIT License
Support
- GitHub: https://github.com/our-edu/laravel-request-tracker
- Issues: https://github.com/our-edu/laravel-request-tracker/issues
Made with ❤️ by Our Education