Search by

bangsamu / visitor-counter

BangsaMU

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)

Package info

github.com/BangsaMU/visitor-counter

pkg:composer/bangsamu/visitor-counter

Statistics

Installs: 145

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.2.0 2026-10-31 10:54 UTC

This package is auto-updated.

Last update: 2026-10-31 10:55:36 UTC


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 RecordVisit masuk ke grup web. Baris ditulis di terminate(), setelah respons terkirim ke browser.
  • Tabel visitors dibuat otomatis bila belum ada. Bila sudah ada (buatan app atau 0.0.x), hanya kolom/index yang kurang yang ditambahkan, tanpa error dan tanpa menyentuh data lama. Ini dijalankan oleh php 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 (route visitor-counter.index) dengan filter tanggal, user, halaman/route, IP, method, dan status.
  • Baris lebih tua dari 180 hari dihapus model:prune setiap 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:

  1. Halaman HTML yang dibuka disisipi skrip kecil sebelum </body> (hanya selama IP belum diketahui atau sudah lebih dari 30 menit).
  2. Browser bertanya IP publiknya ke https://api.ipify.org?format=json, lalu mengirimnya ke POST /visitor-counter/client-ip (route visitor-counter.client-ip, CSRF, throttle 10/menit). Nilainya disimpan di session, tidak ada tulis ke database.
  3. Request berikutnya mencatat client_ip = IP itu dengan client_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:prune 02: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 / RecordVisitor diganti RecordVisit (otomatis di grup web, 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_daily dan 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-counter ke visitor-counter.
  • Dashboard pindah ke /visitor-counter dan butuh gate viewVisitorCounter (dulu cukup login).
  • Tabel lama otomatis ditambah kolom session_hash, method, query_string, route_name, status, duration_ms, is_ajax, client_ip, client_ip_source dan index baru; data lama tetap.

Test

composer install
vendor/bin/phpunit