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.
✅ 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
# 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:workAdd 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-01Architecture: 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
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
],
];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.
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();# 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-runOne 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 |
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
->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->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)#[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
'exclude' => [
'health', // Suffix match: */health
'api/*/internal', // Wildcard: api/v1/internal, api/v2/internal
'regex:/^(ping|pong)/', // Regex: starts with ping or pong
]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!
# Schedule in app/Console/Kernel.php
protected function schedule(Schedule $schedule) {
$schedule->command('tracker:cleanup --days=90')->daily();
}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')]
- PHP >= 8.0
- Laravel 8, 9, 10, or 11
- Queue worker running (for background processing)
MIT License
- GitHub: https://github.com/our-edu/laravel-request-tracker
- Issues: https://github.com/our-edu/laravel-request-tracker/issues
Made with ❤️ by Our Education