Baris log ERROR: Gagal menyimpan order menjawab “apa yang terjadi”, tetapi tidak menjawab pertanyaan investigasi yang sebenarnya: order siapa, di request mana, siapa penggunanya? Tanpa konteks, kita harus menebak-nebak korelasi antar baris log dari timestamp yang hampir sama. Laravel menyediakan dua alat untuk memperkaya log: konteks per-baris yang dikirim bersama pesan, dan withContext() yang menautkan sekumpulan data ke semua baris berikutnya. Pada chapter ini kita gunakan keduanya, menambahkan request id lewat middleware, lalu menetapkan batas tegas soal data apa yang boleh masuk log.

Konteks per Baris Log #

Bentuk paling sederhana adalah array kedua pada setiap pemanggilan Log::*:

php
Log::warning('Sisa stok buku tinggal 2.', ['produk' => 'Buku "Belajar Laravel"', 'gudang' => 'Bandung']);

Output yang Dihasilkan:

text
[2026-10-13 19:53:13] local.WARNING: Sisa stok buku tinggal 2. {"produk":"Buku \"Belajar Laravel\"","gudang":"Bandung"} 

Konteks dirender sebagai JSON di ujung baris, jadi kunci dan nilai bebas kita tentukan. Konsekuensinya: kunci yang konsisten (order_id, user_id, produk) membuat log bisa dicari dan diagregasi — kunci yang asal tempel membuat log hanya bisa dibaca manusia.

Log::withContext untuk Konteks Menetap #

Menuliskan ['request_id' => ...] di setiap pemanggilan log itu repetitif dan mudah lupa. Method withContext() mendaftarkan konteks sekali, dan semua baris berikutnya di channel tersebut membawanya otomatis:

php
Log::info('Sebelum withContext.');

Log::withContext([
    'request_id' => 'req-8f3a12',
    'user_id' => 7,
]);

Log::info('Sesudah withContext.');
Log::warning('Stok menipis dengan konteks menempel.');

Output yang Dihasilkan:

text
[2026-10-13 19:53:47] local.INFO: Sebelum withContext.  
[2026-10-13 19:53:47] local.INFO: Sesudah withContext. {"request_id":"req-8f3a12","user_id":7} 
[2026-10-13 19:53:47] local.WARNING: Stok menipis dengan konteks menempel. {"request_id":"req-8f3a12","user_id":7} 

Baris pertama lahir sebelum konteks terdaftar sehingga polos; dua baris berikutnya otomatis membawa request_id dan user_id tanpa disebut lagi. Method withoutContext() menghapus konteks yang sudah terpasang.

Middleware: Request Id untuk Setiap Baris #

Pola produksi yang lazim adalah middleware yang menempelkan identitas request di awal pipeline, sehingga seluruh log selama request itu — dari controller sampai exception handler — memakai identitas yang sama:

php
namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\Response;

class TandaiRequest
{
    public function handle(Request $request, Closure $next): Response
    {
        Log::withContext([
            'request_id' => $request->header('X-Request-Id', 'req-tanpa-id'),
            'user_id' => $request->user()?->id,
        ]);

        return $next($request);
    }
}

Middleware ini kita daftarkan ke grup web di bootstrap/app.php lewat $middleware->web(TandaiRequest::class). Route di bawahnya cukup memanggil Log::info() polos:

php
Route::get('/halaman-order', function () {
    Log::info('Budi membuka halaman order.');

    return 'Daftar order Budi';
});

Feature test berikut masuk sebagai Budi dengan header X-Request-Id, lalu memeriksa log yang tertulis:

php
$budi = User::factory()->create(['name' => 'Budi', 'email' => '[email protected]']);

$response = $this->actingAs($budi)
    ->get('/halaman-order', ['X-Request-Id' => 'req-8f3a12']);

Output yang Dihasilkan:

text
status: 200
log:
[2026-10-13 19:54:15] testing.INFO: Budi membuka halaman order. {"request_id":"req-8f3a12","user_id":1}

Satu baris log kini menjawab siapa (user_id: 1), kapan, dan dari request mana — dan exception yang terjadi belakangan di request yang sama akan membawa konteks identik karena exception handler menulis log setelah middleware berjalan. Menyaring dengan konteks pun tinggal grep:

bash
grep 'req-8f3a12' storage/logs/laravel-2026-10-13.log

shareContext Antar Channel #

Satu catatan penting: withContext() hanya menaut ke channel tempatnya dipanggil. Untuk konteks yang harus hadir di semua channel — versi aplikasi, nama environment — gunakan Log::shareContext():

php
Log::shareContext(['app_versi' => '1.2.0']);
Log::channel('daily')->info('Baris di channel daily.');
Log::info('Baris di channel default.');

Output yang Dihasilkan:

text
--- laravel.log ---
[2026-10-13 19:53:47] local.INFO: Baris di channel default. {"request_id":"req-8f3a12","user_id":7,"app_versi":"1.2.0"} 

--- laravel-2026-10-13.log ---
[2026-10-13 19:53:47] local.INFO: Baris di channel daily. {"app_versi":"1.2.0"} 

Konteks app_versi muncul di kedua channel — termasuk yang baru di-resolve setelah shareContext() dipanggil. Perlu diketahui juga bahwa Laravel tidak otomatis menambahkan request id ke log; slot yang kita lihat kosong hanyalah bagian extra dari Monolog. Jika kita ingin konteks yang bertahan sampai ke job queue, Laravel menyediakan facade Context terpisah yang didokumentasikan di dokumentasi Context .

Dengan konteks yang rapi, log kita siap menjadi sumber kebenaran saat investigasi. Namun log hanyalah salah satu alat — saat bug harus ditemukan sekarang, kita perlu teknik debugging yang lebih interaktif: itu topik chapter Debugging Laravel .