bangsamu / visitor-counter
Pencatat semua request web Laravel 13: siapa membuka apa, kapan, berapa lama, dan dari IP mana (termasuk IP dari browser bila server di belakang NAT)
Requires
- php: ^8.3
- illuminate/console: ^13.0
- illuminate/database: ^13.0
- illuminate/http: ^13.0
- illuminate/routing: ^13.0
- illuminate/support: ^13.0
- illuminate/view: ^13.0
Requires (Dev)
- orchestra/testbench: ^11.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Mencatat semua request web aplikasi Laravel: siapa (user/tamu, sesi) membuka apa (method, path, query, nama route), kapan, hasilnya (status), berapa lama (durasi), dan dari IP mana. Pelengkap activity log (perubahan data): paket ini mencatat jejak navigasi, termasuk halaman yang hanya dibaca.
| Versi | Laravel | PHP |
|---|---|---|
^1.0 |
13 | 8.3+ |
0.0.x |
8–12 | 8.0+ (versi lama, tidak dikembangkan lagi) |
App di bawah Laravel 13 tetap memakai 0.0.x; composer menolak memasang 1.x di sana.
Pasang
composer require bangsamu/visitor-counter:^1.0
Selesai. Tanpa langkah lain:
- Middleware
RecordVisitmasuk ke grupweb. Baris ditulis diterminate(), setelah respons terkirim ke browser. - Tabel
visitorsdibuat otomatis bila belum ada. Bila sudah ada (buatan app atau0.0.x), hanya kolom/index yang kurang yang ditambahkan, tanpa error dan tanpa menyentuh data lama. Ini dijalankan olehphp artisan migrate(migration paket dimuat otomatis) dan, bila migrate belum sempat dijalankan, sekali sebelum insert pertama (hasilnya ditandai di cache per versi skema, jadi tidak ada cek skema di setiap request). Manual:php artisan visitor-counter:sync-schema. - Dashboard di
/visitor-counter(routevisitor-counter.index) dengan filter tanggal, user, halaman/route, IP, method, dan status. - Baris lebih tua dari 180 hari dihapus
model:prunesetiap hari pukul 02:30 (butuh scheduler berjalan); jadwal pencatatan dan lama penyimpanan bisa diubah dari dashboard (lihat Pengaturan pencatatan).
IP klien (server di belakang NAT)
Kolom ip selalu IP yang dilihat server ($request->ip(), mengikuti TrustProxies). Bila server hanya melihat IP
private (router/NAT tanpa header X-Forwarded-For), IP asli diambil dari browser:
- Halaman HTML yang dibuka disisipi skrip kecil sebelum
</body>(hanya selama IP belum diketahui atau sudah lebih dari 30 menit). - Browser bertanya IP publiknya ke
https://api.ipify.org?format=json, lalu mengirimnya kePOST /visitor-counter/client-ip(routevisitor-counter.client-ip, CSRF, throttle 10/menit). Nilainya disimpan di session, tidak ada tulis ke database. - Request berikutnya mencatat
client_ip= IP itu denganclient_ip_source=browser.
IP dari browser tidak terverifikasi (bisa dipalsukan klien): pakai sebagai petunjuk, bukan bukti. User di satu LAN
kantor akan terlihat dengan IP publik kantor yang sama; pembeda orangnya adalah user_id dan session_hash.
Bila app sudah punya pelapor IP sendiri yang menyimpan ['ip' => ..., 'at' => timestamp] di session, cantumkan key-nya
di client_ip.fallback_session_keys (bawaan: reported_client_ip). Layout yang tidak ingin disisipi otomatis: set
VISITOR_COUNTER_INJECT_SCRIPT=false lalu pasang <x-visitor-counter::client-ip /> sebelum </body>.
Pengaturan pencatatan (jadwal & retensi)
Kartu Pengaturan pencatatan di dashboard (gate yang sama) mengatur, tanpa ubah .env:
-
User login dan Tamu (belum login), masing-masing: Selalu dicatat, Hanya di luar jam kerja, atau Tidak dicatat. Contoh pemantauan anomali: user login Hanya di luar jam kerja (perilaku user di jam kerja sudah tercatat activity log/audit trail app), tamu Selalu dicatat.
-
Hari kerja (Sen–Min) dan jam kerja (mulai sampai sebelum selesai, zona waktu app). Hari libur dihitung di luar jam kerja bila app mengikat kalender liburnya:
$this->app->singleton(\Bangsamu\VisitorCounter\Contracts\HolidayCalendar::class, MyHolidayCalendar::class);
(
isHoliday(CarbonInterface $date): bool, sebaiknya di-cache karena bisa dipanggil di setiap request jam kerja). -
Simpan (hari): data lebih lama dihapus otomatis setiap malam (
model:prune02:30, butuh scheduler);0= simpan selamanya.
Bawaannya dari config schedule dan retention_days (selalu dicatat, Sen–Jum 08:00–17:00, 180 hari). Pengaturan
dari dashboard disimpan di storage/framework/visitor-counter-{APP_ENV}-settings.json (dibaca dari file, tanpa query per
request) dan memicu event Bangsamu\VisitorCounter\Events\SettingsUpdated (before, after, user).
Backup database harian ikut membengkak bila tabel log ikut di-dump: kecualikan isinya (struktur tetap ikut), mis.
spatie/laravel-backup + MariaDB di config/database.php koneksi → dump →
'add_extra_option' => '--ignore-table-data='.env('DB_DATABASE').'.visitors'. Setelah restore, tabel kosong terisi
lagi otomatis.
Refresh berulang
Request GET (termasuk AJAX) dengan alamat + query, status, dan user yang sama dari sesi yang sama hanya dicatat
sekali per repeat_window_seconds (bawaan 60 detik, dihitung dari saat terakhir dicatat): refresh 5 kali dalam 10 detik
= 1 baris; refresh terus-menerus = 1 baris per menit, jadi jejaknya tetap terlihat. Halaman/query lain tetap dicatat, dan
POST/PUT/PATCH/DELETE (aksi) selalu dicatat. Penanda disimpan di session (tanpa query/cache tambahan).
VISITOR_COUNTER_REPEAT_WINDOW=0 = setiap request dicatat.
Menyalakan / mematikan pencatatan
Dashboard punya tombol Nonaktifkan pencatatan / Aktifkan pencatatan (gate dashboard yang sama), atau lewat artisan:
php artisan visitor-counter:disable php artisan visitor-counter:enable
Saat nonaktif, request tidak menulis apa pun ke database (tidak ada insert, cek skema, maupun query lain dari paket)
dan skrip pelapor IP tidak disisipkan. Dashboard tetap bisa dibuka untuk melihat data lama. Status disimpan sebagai file
penanda storage/framework/visitor-counter-{APP_ENV}-disabled.json (seperti php artisan down): bertahan setelah
cache:clear/deploy, tanpa query per request. Lebih dari satu server aplikasi: arahkan VISITOR_COUNTER_SWITCH_PATH ke
folder bersama. VISITOR_COUNTER_ENABLED=false tetap mematikan pencatatan apa pun status saklarnya.
Setiap perubahan memicu event Bangsamu\VisitorCounter\Events\RecordingToggled (enabled, user, source =
dashboard/console) untuk dicatat app ke audit log-nya.
Akses dashboard
Gate viewVisitorCounter. Bawaan paket: user yang punya salah satu ability di dashboard.abilities (bawaan is_root;
Gate::before milik app, mis. untuk ROOT, tetap berlaku). Aturan lain cukup didefinisikan di app:
Gate::define('viewVisitorCounter', fn (User $user) => $user->can('read_report_audit'));
Layout auto: tema tabler (app.themes = _tabler) → adminlte::page → layout paket (Bootstrap 5). Tampilan bisa
diganti dengan php artisan vendor:publish --tag=visitor-counter-views.
Konfigurasi .env (semua opsional)
| Variabel | Bawaan | Arti |
|---|---|---|
VISITOR_COUNTER_ENABLED |
true |
Matikan pencatatan (mengalahkan saklar dashboard) |
VISITOR_COUNTER_SWITCH_PATH |
storage/framework | File penanda saklar dashboard/artisan |
VISITOR_COUNTER_RETENTION_DAYS |
180 |
Umur data; 0 = simpan selamanya |
VISITOR_COUNTER_RECORD_AJAX |
true |
false = request AJAX/JSON tidak dicatat |
VISITOR_COUNTER_REPEAT_WINDOW |
60 |
Detik; GET sama dari sesi sama dicatat sekali per jendela, 0 = mati |
VISITOR_COUNTER_CLIENT_IP |
auto |
auto / server / browser |
VISITOR_COUNTER_INJECT_SCRIPT |
true |
Sisipkan skrip pelapor IP otomatis |
VISITOR_COUNTER_IP_LOOKUP_URL |
ipify | Layanan IP publik untuk browser (JSON {"ip": ...}) |
VISITOR_COUNTER_PATH |
visitor-counter |
Path dashboard |
VISITOR_COUNTER_LAYOUT |
auto |
Nama view layout dashboard |
VISITOR_COUNTER_TABLE |
visitors |
Nama tabel |
VISITOR_COUNTER_DB_CONNECTION |
default | Koneksi database lain untuk log |
VISITOR_COUNTER_AUTO_SCHEMA |
true |
false = skema hanya lewat migrate / sync-schema |
Pengecualian path/route, method, parameter query yang disembunyikan (password, token, ...), gate, dan lainnya:
php artisan vendor:publish --tag=visitor-counter-config. Bawaan sudah melewatkan polling sesi app bangsamu
(session.status, session.keep-alive, session.client-ip), Telescope, Debugbar, dan robots.txt.
Angka ringkas
use Bangsamu\VisitorCounter\Facades\VisitorCounter; VisitorCounter::total(); // semua request VisitorCounter::today(); // request hari ini VisitorCounter::monthly(); // request bulan ini VisitorCounter::unique(); // IP unik (IP klien, atau IP server bila kosong) VisitorCounter::uniqueToday(); VisitorCounter::daily($from, $to); // per hari: hits, unique_ips, unique_users
JSON untuk widget: GET /visitor-counter/stats?from=Y-m-d&to=Y-m-d dan GET /visitor-counter/today (gate sama).
Performa
Satu INSERT per request, dijalankan setelah respons terkirim (PHP-FPM), jadi user tidak menunggu. Tidak ada panggilan
HTTP dari server (IP publik diminta browser) dan tidak ada cek skema per request. Volume perkiraan: 100 user aktif ×
300 request/hari ≈ 30 ribu baris/hari; retensi 180 hari menjaga tabel tetap terbatas. Index: created_at,
(user_id, created_at), (visit_date, ip).
Upgrade dari 0.0.x
- Butuh Laravel 13 dan PHP 8.3.
- Middleware
CountVisitor/RecordVisitordigantiRecordVisit(otomatis di grupweb, bukan middleware global). - Tidak ada lagi pengambilan IP publik server dari Cloudflare/ipify:
client_ip= IP user (server publik atau laporan browser), kosong bila belum diketahui. - Mode
unique_dailydan jeda 10 menit per path (cache) dihapus; sejak v1.2 refresh GET berulang disaring per sesi (repeat_window_seconds, lihat Refresh berulang), aksi POST/PUT/PATCH/DELETE selalu dicatat. - Config dipindah dari key
Visitor-counterkevisitor-counter. - Dashboard pindah ke
/visitor-counterdan butuh gateviewVisitorCounter(dulu cukup login). - Tabel lama otomatis ditambah kolom
session_hash,method,query_string,route_name,status,duration_ms,is_ajax,client_ip,client_ip_sourcedan index baru; data lama tetap.
Test
composer install vendor/bin/phpunit