Mencetak pesan ke layar hanya membantu saat kita sedang mengawasi terminal sendiri. Aplikasi produksi berjalan berhari-hari tanpa ada yang menatap layarnya, dan kegagalan yang muncul tengah malam akan hilang begitu layar bergulur. Yang dibutuhkan adalah jejak permanen: catatan kejadian yang bisa dibaca ulang kapan pun, siapa pun yang mencari. Aktifitas mencatat ini disebut logging, dan PHP menyediakannya mulai dari function error_log() yang sederhana sampai directive konfigurasi yang mengatur log PHP sendiri. Chapter ini membahas keduanya, lalu menutup dengan format pesan terstruktur dan library Monolog yang menjadi standar industri.

Mencatat ke Berkas dengan error_log() #

Function error_log() menerima pesan, tipe tujuan, dan lokasi tujuan. Tipe 3 adalah yang paling sering dipakai aplikasi: menambahkan pesan ke sebuah berkas tanpa pernah menghapus isinya — setiap panggilan menumpuk satu baris baru di akhir berkas:

php
<?php
$berkasLog = sys_get_temp_dir() . "/aplikasi-kasir.log";

@unlink($berkasLog);

error_log("Pengguna Budi berhasil login\n", 3, $berkasLog);
error_log("Stok buku tinggal 3 buah\n", 3, $berkasLog);

echo "Isi berkas log sekarang:\n";
echo file_get_contents($berkasLog);

unlink($berkasLog);

Output yang Dihasilkan:

text
Isi berkas log sekarang:
Pengguna Budi berhasil login
Stok buku tinggal 3 buah

Mari kita telaah baris-baris kode di atas:

  1. Baris 2: berkas log diletakkan di direktori sementara sistem lewat sys_get_temp_dir() — cukup untuk latihan tanpa mengotori folder proyek.
  2. Baris 4: @unlink() menghapus bekas latihan sebelumnya supaya isi log selalu dimulai dari kosong; penekan @ menyembunyikan laporan ketika berkasnya memang belum ada.
  3. Baris 6-7: dua panggilan error_log() dengan tipe 3 menambahkan dua pesan berturut-turut — karakter \n di akhir setiap pesan adalah pemisah baris yang kita tulis sendiri, karena error_log() tidak menambahkannya secara otomatis.
  4. Baris 10-11: isi berkas dibaca kembali untuk membuktikan kedua pesan tersimpan, lalu berkas dibersihkan.

Perilaku menumpuk tanpa menghapus inilah yang membuat log menjadi jejak sejarah: pesan dari Senin tetap utuh ketika pesan Selasa ditulis.

Empat Tujuan Pesan yang Didukung #

error_log() mengenal empat tipe tujuan, dan memilihnya dengan tepat menentukan ke mana pesan Anda berakhir:

TipeTujuanCatatan
0System logger PHPMasuk ke mekanisme log sistem sesuai directive error_log; perilakunya bergantung SAPI dan sistem operasi
1Email ke alamat tertentuAlamat tujuan dikirim sebagai parameter keempat; membutuhkan mail server yang terpasang dan kini jarang dipakai
3Berkas yang kita tentukanMenambahkan pesan apa adanya; pilihan paling umum dan paling mudah dikendalikan
4Log SAPIMenambahkan pesan ke log web server (misalnya error log nginx); berguna bila Anda tidak punya akses penuh ke sistem berkas

Tipe 3 menjadi favorit karena perilakunya dapat diprediksi persis: satu panggilan, satu tambahan baris, di berkas pilihan kita sendiri. Tipe 0 dan 4 menyerahkan format dan lokasi kepada lingkungan server, sehingga hasilnya berbeda antar instalasi. Tipe 1 terdengar praktis untuk peringatan mendesak, tetapi mengirim email langsung dari dalam kode membuat pengujian sulit dan berisiko membanjiri kotak masuk saat error terjadi berulang.

Directive error_log di php.ini #

Selain function, PHP juga bisa mencatat error miliknya sendiri secara otomatis melalui dua directive yang telah kita kenal pada chapter error-reporting . Ketika log_errors aktif, setiap error yang dilaporkan PHP — warning, fatal, atau notice — ikut tercatat ke tujuan yang ditentukan directive error_log:

ini
log_errors = On
error_log = /var/log/aplikasi/php-error.log

Dua baris ini menjadikan PHP pencatat setia yang bekerja bahkan untuk kegagalan yang tidak tersentuh kode kita, misalnya ekstensi yang gagal dimuat atau skrip yang mati karena kehabisan memori. Pastikan direktori tujuannya dapat ditulis oleh pengguna yang menjalankan PHP, dan letakkan di luar folder yang bisa diakses pengunjung web agar isinya tidak bisa dibaca umum. Tanpa directive error_log, PHP menyerahkan pencatatan kepada log bawaan SAPI — pada web server biasanya berakhir di error log-nya.

Pada lingkungan produksi, kombinasi yang benar adalah display_errors = Off dan log_errors = On: pengunjung tidak melihat pesan mentah, sementara developer tetap mendapat catatan lengkap. Strategi lengkapnya dirangkum pada chapter production-error-handling .

Format Pesan yang Mudah Ditelusuri #

Log hanya berguna bila barisnya mudah disaring dan diurutkan. Baris log yang baik memuat empat unsur: waktu kejadian, tingkat keseriusan (level), pesan inti, dan konteks tambahan berbentuk pasangan kunci-nilai. Mari kita bungkus aturan itu menjadi sebuah function tulisLog():

php
<?php
function tulisLog(string $level, string $pesan, array $konteks = []): void
{
    $waktu = new DateTimeImmutable(
        "2026-01-15 08:30:00",
        new DateTimeZone("Asia/Jakarta")
    );

    $baris = sprintf(
        "[%s] %s: %s",
        $waktu->format("Y-m-d H:i:s"),
        strtoupper($level),
        $pesan
    );

    if ($konteks !== []) {
        $baris .= " " . json_encode($konteks, JSON_UNESCAPED_UNICODE);
    }

    error_log($baris . "\n", 3, sys_get_temp_dir() . "/kasir-terstruktur.log");
}

$berkasLog = sys_get_temp_dir() . "/kasir-terstruktur.log";
@unlink($berkasLog);

tulisLog("info", "Pengguna Budi berhasil login", ["user_id" => 42, "ip" => "192.168.1.10"]);
tulisLog("warning", "Stok buku menipis", ["produk" => "B001", "sisa" => 3]);

echo file_get_contents($berkasLog);
unlink($berkasLog);

Output yang Dihasilkan:

text
[2026-01-15 08:30:00] INFO: Pengguna Budi berhasil login {"user_id":42,"ip":"192.168.1.10"}
[2026-01-15 08:30:00] WARNING: Stok buku menipis {"produk":"B001","sisa":3}

Mari kita telaah bagian-bagian utamanya:

  1. Baris 3-8: waktu kejadian dibekukan pada nilai tetap agar output latihan ini konsisten setiap dijalankan — pada aplikasi nyata, gunakan new DateTimeImmutable("now", new DateTimeZone("Asia/Jakarta")) agar tercatat waktu sebenarnya.
  2. Baris 10-15: sprintf() merangkai waktu, level yang dihurufbesarkan, dan pesan ke dalam satu format tetap "[waktu] LEVEL: pesan".
  3. Baris 17-19: konteks opsional dirangkai sebagai JSON lewat json_encode() — format terstruktur yang nantinya mudah disaring mesin, dan JSON_UNESCAPED_UNICODE menjaga teks Indonesia tetap terbaca manusia.
  4. Baris 21: baris jadi dikirim ke berkas log dengan tipe 3, diakhiri \n.

Tingkat keseriusan yang lazim dipakai industri cukup sedikit dan mudah disaring:

LevelMaknaContoh kejadian
DEBUGRincian teknis untuk penyelidikanIsi variabel saat menelusuri bug
INFOKejadian normal yang pentingPengguna berhasil login
WARNINGKondisi mencurigakan tapi tak fatalStok menipis, request lambat
ERRORKegagalan yang mengganggu fiturGagal menyimpan pesanan ke database

Membaca Log di Terminal #

Berkas log paling sering dibaca dari terminal dengan dua senjata andalan. Perintah tail -f menampilkan baris terakhir sebuah berkas lalu terus mengikuti baris baru yang masuk secara langsung:

bash
tail -f /var/log/aplikasi/php-error.log

Perintah ini adalah cara klasik mengawasi aplikasi sambil dites: setiap error baru muncul di terminal Anda hanya beberapa detik setelah terjadi. Untuk mencari kejadian masa lalu, grep menyaring baris yang memuat teks tertentu, lalu tail membatasi hasilnya ke yang terbaru:

bash
grep "ERROR" /var/log/aplikasi/php-error.log | tail -20

Monolog untuk Kebutuhan yang Serius #

Semakin besar aplikasi, kebutuhan logging juga tumbuh: berkas log perlu dirotasi agar tidak membengkak, satu pesan kadang perlu dikirim ke beberapa tujuan sekaligus, dan tiap bagian aplikasi ingin punya saluran (channel) tersendiri. Library Monolog menjawab semua itu dan menjadi standar de facto di ekosistem PHP — framework Symfony dan Laravel memakainya di balik layar:

bash
composer require monolog/monolog
php
<?php
require "vendor/autoload.php";

use Monolog\Level;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

$log = new Logger("aplikasi-kasir");
$log->pushHandler(new StreamHandler(__DIR__ . "/app.log", Level::Warning));

$log->warning("Stok buku menipis", ["produk" => "B001", "sisa" => 3]);
$log->info("Pengguna Budi berhasil login");

Contoh di atas memerlukan pemasangan Composer sehingga tidak bisa dijalankan langsung oleh alat verifikasi buku ini, tetapi idenya lurus: sebuah Logger bernama aplikasi-kasir dibuat, lalu sebuah StreamHandler dipasang untuk menulis ke app.log dengan ambang Level::Warning. Panggilan warning() tercatat karena levelnya sama atau lebih serius dari ambang, sedangkan info() dilewati. Handler bertugas ke tujuan mana pesan mengalir — Monolog menyediakan handler untuk berkas, syslog, Slack, email, dan banyak lagi — sehingga arsitektur “level + konteks + tujuan” yang kita bangun manual di awal chapter terwujud rapi dalam bentuk siap pakai.

Ringkasan #

Logging mengubah kegagalan sesaat menjadi jejak permanen yang bisa ditelusuri mundur. Function error_log() dengan tipe 3 menambahkan pesan ke berkas pilihan kita, directive log_errors dan error_log membuat PHP mencatat kegagalannya sendiri, dan format terstruktur — waktu, level, pesan, konteks — menjadikan setiap baris mudah disaring dan diurutkan. Untuk kebutuhan besar, Monolog menyediakan rotasi, banyak tujuan, dan channel dalam satu paket mapan. Dengan catatan yang lengkap, kini kita siap menyusun strategi menyeluruh: apa yang boleh dilihat pengunjung, dan apa yang disimpan untuk developer — topik penutup section ini.

Referensi #