ibrahim-kaya / visit-tracker
A Laravel package to track page visits including IP, browser, device, and referrer information.
Requires
- php: ^8.0
- guzzlehttp/guzzle: ^7.9
- hisorange/browser-detect: ^5.0
- illuminate/auth: ^9.0|^10.0|^11.0|^12.0|^13.0
- illuminate/queue: ^9.0|^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^9.0|^10.0|^11.0|^12.0|^13.0
Requires (Dev)
- orchestra/testbench: ^11.3
- phpunit/phpunit: ^11.5.3
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
A Laravel package to automatically track page visits including IP, browser, device, referrer, and more. Perfect for analytics and monitoring.
Features
- Automatic tracking of all web requests.
- Terminable middleware — UA parsing and DB writes run after the response is sent (does not delay TTFB).
- Optional queue-based processing when you prefer a worker.
- Logs detailed visitor information:
- IP address (with optional geolocation from http://ip-api.com)
- Browser name, platform/OS, device type
- Referrer URL, full URL, normalized path
- User agent, HTTP method, optional payload
- Authenticated user ID (if logged in)
- Country code (when detailed IP info is enabled)
- Attribute anonymous visits after login/register (visitor cookie + session)
- Exclude paths/methods; skip AJAX and prefetch by default
- Optional sampling and per-visitor/page dedupe
- Optional bot logging (cheap bot pre-check when disabled)
- Configurable IP info cache (positive + negative) and statistics cache
- Optional retention pruning via
model:prune - Middleware auto-registered for all web routes
Installation
1. Require the package via Composer
composer require ibrahim-kaya/visit-tracker
2. Publish the configuration
php artisan vendor:publish --provider="IbrahimKaya\VisitTracker\VisitTrackerServiceProvider" --tag=visit-tracker-config
3. Run migrations
php artisan migrate
Creates / updates the page_visit_logs table (including performance indexes, path, and country_code).
Note: On very large existing tables, adding indexes may take time. Prefer a maintenance window.
4. Queue (optional)
By default use_queue is false. Tracking still runs after the HTTP response is sent (terminate), so a single INSERT is usually enough and no worker is required.
If you prefer queues:
QUEUE_CONNECTION=redis
# or database / sync
Prefer redis over the database queue driver (database queues add INSERT jobs + DELETE jobs per visit).
php artisan queue:work
Performance recommendations
| Topic | Recommendation |
|---|---|
| Response time | Tracking runs in terminate() after the response is sent — keep it that way. |
| Queues | Default use_queue => false is fine for most sites. If you enable queues, use redis. |
| Browser detect cache | Publish hisorange/browser-detect config and raise browser-detect.cache.interval to 7–30 days (UA → result is deterministic). Prefer redis or apcu as the app cache store instead of file. |
| IP geolocation | Keep detailed_ip_info => false unless needed. Free ip-api.com allows 45 req/min, HTTP only. Failures are negatively cached. |
| Noise | Defaults skip Livewire/Debugbar/Telescope/Horizon/health/Sanctum/broadcasting, AJAX, and prefetch. Tune sample_rate / dedupe_seconds under heavy traffic. |
| Retention | Set retention_days and schedule $schedule->command('model:prune')->daily(); |
Configuration
config/visit-tracker.php (key options):
return [ 'excluded_paths' => [ 'livewire/*', '_debugbar/*', 'telescope/*', 'horizon/*', 'up', 'sanctum/csrf-cookie', 'broadcasting/auth', ], 'excluded_methods' => [], 'skip_ajax' => true, 'skip_prefetch' => true, 'sample_rate' => 1.0, // 0.0–1.0 'dedupe_seconds' => 0, // 0 = disabled 'log_bots' => false, 'detailed_ip_info' => false, 'ip_info_cache_duration' => 86400, 'ip_info_negative_cache_duration' => 900, // After-response INSERT (no worker). Set true only if you want a queue worker. 'use_queue' => false, 'queue_connection' => env('VISIT_TRACKER_QUEUE_CONNECTION'), 'queue_name' => env('VISIT_TRACKER_QUEUE'), 'log_payload' => false, 'excluded_payload_fields' => [ 'password', 'password_confirmation', 'token', '_token', ], 'attribute_on_auth' => true, 'visitor_cookie' => 'visit_tracker_vid', 'visitor_cookie_minutes' => 60 * 24 * 365 * 2, 'retention_days' => null, // e.g. 90 'statistics_cache_ttl' => 60, // seconds; 0 disables ];
- excluded_paths — Wildcards supported. Defaults cover common framework noise.
- skip_ajax / skip_prefetch — Avoid logging XHR/JSON and browser prefetch/prerender.
- sample_rate — Log only a fraction of visits (e.g.
0.1≈ 10%). - dedupe_seconds — Skip repeats of the same visitor +
page_urlwithin N seconds (cache-backed). - log_bots — When
false, a cheap CrawlerDetect check skips bots before the heavy UA pipeline. - detailed_ip_info — Optional ip-api.com lookup (after response / in job).
- use_queue —
false= write interminate;true= dispatchProcessVisitLog. - retention_days — Enable
MassPrunablecleanup viamodel:prune. - statistics_cache_ttl — Short TTL cache around
PageVisitLogstatistic helpers.
Attribute anonymous visits after login/register
When attribute_on_auth is enabled (default), the package:
- Stores a persistent
visitor_idcookie while the guest browses (queued only when missing) - Saves that
visitor_id(andsession_id) on each visit log - On
Login/Registered, updates matching rows whereuser_idis null
use IbrahimKaya\VisitTracker\Models\PageVisitLog; PageVisitLog::attributeToUser( auth()->id(), request()->cookie(config('visit-tracker.visitor_cookie')), session()->getId() );
Retention pruning
// config/visit-tracker.php 'retention_days' => 90, // app/Console/Kernel.php or routes/console.php $schedule->command('model:prune', [ '--model' => [\IbrahimKaya\VisitTracker\Models\PageVisitLog::class], ])->daily();
Usage
No extra code is required. Visit any web page and the visit is logged automatically after the response is sent.
use IbrahimKaya\VisitTracker\Models\PageVisitLog; $recentVisits = PageVisitLog::latest()->take(5)->get(); foreach ($recentVisits as $visit) { echo $visit->ip_address; echo $visit->browser; echo $visit->device_type; echo $visit->path; echo $visit->country_code; echo $visit->method; echo $visit->payload; }
Optional manual middleware registration:
protected $middleware = [ \IbrahimKaya\VisitTracker\Middleware\VisitTracker::class, ];
Statistics
The PageVisitLog model provides static helpers (results are cached for statistics_cache_ttl seconds).
For custom reporting, query the model with Eloquent directly. Aggregations prefer the indexed
path/country_codecolumns when present.
Basic Statistics
use IbrahimKaya\VisitTracker\Models\PageVisitLog; $total = PageVisitLog::totalVisits(); $total = PageVisitLog::totalVisits(true); // exclude bots $unique = PageVisitLog::uniqueVisitors(); $uniqueIps = PageVisitLog::uniqueIpAddresses(true);
Page Statistics
$topPages = PageVisitLog::mostVisitedPages(10); $topPages = PageVisitLog::mostVisitedPages(5, true); foreach ($topPages as $page) { echo $page->page_url . ': ' . $page->visit_count . ' visits'; } $visits = PageVisitLog::visitsByDateRange('2024-01-01', '2024-01-31', true);
Device & Browser Statistics
$deviceStats = PageVisitLog::statisticsByDeviceType(true); $browserStats = PageVisitLog::statisticsByBrowser(); $platformStats = PageVisitLog::statisticsByPlatform();
Referrer & Time-based
$referrers = PageVisitLog::statisticsByReferrer(10, true); $daily = PageVisitLog::dailyStatistics(30, true);
Geographic Statistics
// Prefer country_code (filled when detailed_ip_info is enabled) $countryStats = PageVisitLog::statisticsByCountry(true); foreach ($countryStats as $stat) { echo $stat['country'] . ': ' . $stat['count'] . ' visits'; }
Summary
$summary = PageVisitLog::summaryStatistics(30, true); // total_visits, unique_visitors, unique_ips, top_pages, by_device, by_browser, by_platform
Query Scopes
$visits = PageVisitLog::excludeBots() ->dateRange('2024-01-01', '2024-01-31') ->get();
License
MIT License © İbrahim Kaya