Mesin kita berada di kondisi paling rapi sepanjang hari: hanya web dan cache yang hidup, tanpa satu bekas container menumpuk. Tetapi ada satu wilayah yang selama ini gelap — sepanjang section ini kita menjalankan, menghentikan, menyalakan, bahkan membekukan web berkali-kali tanpa pernah tahu apa yang tercatat di dalamnya. Container berjalan di latar belakang tanpa layar yang bisa diintip, sehingga stdout dan stderr satu-satunya jendela resmi ke dalam rumahnya; kebiasaan membaca jendela itu adalah keterampilan diagnosis paling fundamental bagi siapa pun yang mengelola container. Chapter ini membuka jendela tersebut dengan docker logs — dari membaca seluruh riwayat, menyaringnya dengan flag, memahami batas kerasnya, sampai membaca log startup Redis dan log pendek hello-world.

Membaca Log dengan docker logs #

Log yang menarik adalah log yang lahir dari lalu lintas nyata, jadi sebelum membaca, kita hasilkan dulu peristiwa yang layak dicatat. Dua permintaan beruntun ke halaman utama, satu permintaan ke halaman yang tidak ada, lalu dua permintaan penutup:

bash
$ curl localhost:8080

Output yang Dihasilkan:

text
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

Halaman yang sama seperti Chapter Menjalankan Container Pertama — sisa halaman dipotong. Ulangi perintah tersebut sekali lagi, lalu kita kirim permintaan yang sengaja salah alamat:

bash
$ curl localhost:8080/tidak-ada

Output yang Dihasilkan:

text
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.29.4</center>
</body>
</html>

Dua permintaan curl penutup ke halaman utama melengkapi lalu lintas kita. Sekarang saatnya membaca seluruh riwayat web sejak pertama kali dinyalakan:

bash
$ docker logs web

Output yang Dihasilkan:

text
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to process configuration files
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Sourcing /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Sourcing /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Sourcing /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
172.17.0.1 - - [13/Oct/2026:09:47:12 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.14.1"
...
172.17.0.1 - - [13/Oct/2026:10:22:31 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.14.1"
172.17.0.1 - - [13/Oct/2026:10:22:47 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.14.1"
172.17.0.1 - - [13/Oct/2026:10:22:59 +0000] "GET /tidak-ada HTTP/1.1" 404 153 "-" "curl/8.14.1"
2026/10/13 10:22:59 [error] 31#31: *9 open() "/usr/share/nginx/html/tidak-ada" failed (2: No such file or directory), client: 172.17.0.1, server: localhost, request: "GET /tidak-ada HTTP/1.1", host: "localhost:8080"
172.17.0.1 - - [13/Oct/2026:10:23:12 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.14.1"
172.17.0.1 - - [13/Oct/2026:10:23:26 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.14.1"

Di antara baris akses pertama dan kelompok baris terakhir dipotong: empat blok pesan startup yang sama — dari setiap kali kita men-start ulang web pada Chapter Menghentikan dan Menjalankan Container — beserta beberapa baris akses lain dari chapter-chapter sebelumnya. Mari kita telaah isi output di atas:

  1. Delapan baris /docker-entrypoint.sh di awal — keluaran stdout skrip entrypoint nginx saat web pertama kali dinyalakan pukul 09:46; seluruh proses persiapan konfigurasi ini terekam sebagai bagian dari log container.
  2. Baris akses pertama mencatat waktu 09:47:12 — permintaan curl dari Chapter *Menjalankan Container Pertama`; log bertahan melewati segala siklus stop, start, kill, dan pause yang kita jalankan kemarin, karena penyimpanannya dikelola daemon di luar proses container.
  3. 172.17.0.1 sebagai alamat klien — permintaan dari mesin kita sendiri masuk ke container lewat gateway jaringan bridge bawaan Docker, sehingga dari sudut pandang nginx, pelayan permintaan adalah gateway 172.17.0.1 — subnet 172.17.0.0/16 yang kita kenal dari arsitektur jaringan Docker.
  4. Format combined log nginx — alamat klien, dua tanda minus untuk identitas dan pengguna, waktu dalam kurung siku, permintaan HTTP lengkap, kode status, jumlah byte respons, referer, dan terakhir User-Agent curl/8.14.1 — persis versi curl di mesin latihan kita.
  5. 200 615 berbanding 404 153 — setiap baris mencatat keberhasilan dengan 615 byte halaman selamat datang, sementara permintaan ke /tidak-ada dijawab 404 dengan 153 byte halaman error.
  6. Baris [error] di tengah barisan akses — ini stderr, bukan stdout: log error nginx yang menyusul tepat di bawah permintaan 404. docker logs menggabungkan stdout dan stderr secara kronologis dalam satu tampilan, dan satu baris ini buktinya.
  7. Tanda +0000 pada setiap waktu — nginx mencatat waktu dalam UTC, zona waktu bawaan container; pukul 10:22 di log adalah pukul 10:22 UTC pada hari yang sama dengan seluruh latihan kita.

Menyaring Keluaran dengan Flag #

Log yang kita baca barusan masih pendek, tetapi container produksi bisa menimbun ratusan ribu baris dalam hitungan jam. Empat flag berikut mengubah docker logs dari selang air mancur menjadi selang dengan keran:

FlagBentuk pendekFungsi
--follow-fTerus mengikuti keluaran baru, seperti tail -f pada Unix
--tail N—Membatasi N baris terakhir; misalnya --tail 20 atau --tail all
--since—Hanya menampilkan baris sejak durasi atau timestamp, misalnya --since 10m
--timestamps-tMenambahkan waktu pengumpulan berformat RFC3339Nano di tiap baris

Flag-flag tersebut bebas dirangkai dalam satu perintah. Kombinasi paling akrab sehari-hari adalah --since dan --tail — “lima menit terakhir, maksimal dua puluh baris”:

bash
$ docker logs --since 5m --tail 20 web

Output yang Dihasilkan:

text
172.17.0.1 - - [13/Oct/2026:10:22:31 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.14.1"
172.17.0.1 - - [13/Oct/2026:10:22:47 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.14.1"
172.17.0.1 - - [13/Oct/2026:10:22:59 +0000] "GET /tidak-ada HTTP/1.1" 404 153 "-" "curl/8.14.1"
2026/10/13 10:22:59 [error] 31#31: *9 open() "/usr/share/nginx/html/tidak-ada" failed (2: No such file or directory), client: 172.17.0.1, server: localhost, request: "GET /tidak-ada HTTP/1.1", host: "localhost:8080"
172.17.0.1 - - [13/Oct/2026:10:23:12 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.14.1"
172.17.0.1 - - [13/Oct/2026:10:23:26 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.14.1"

Mari kita telaah hasil penyaringan di atas:

  1. --since 5m memotong berdasarkan waktu — seluruh baris berumur lebih dari lima menit lenyap: blok startup pukul 09:46, baris akses pertama pukul 09:47, semuanya tersaring keluar; nilai lain yang sah berupa durasi seperti 30s atau timestamp mutlak.
  2. --tail 20 membatasi jumlah — sisa hasil --since hanya enam baris sehingga batas dua puluh belum terasa; pada container sibuk, keduanya bekerja sama — --since menentukan jendela waktu, --tail menjaga keluaran tetap pendek.
  3. Format nilai durasi mengikuti konvensi Go — 5m berarti lima menit; 30s detik, 1h jam, dan kombinasi seperti 1h30m juga sah.

Untuk kronologi lintas container, flag -t menambahkan stempel waktu pengumpulan pada setiap baris:

bash
$ docker logs -t --tail 3 web

Output yang Dihasilkan:

text
2026-10-13T10:22:59.481302753Z 2026/10/13 10:22:59 [error] 31#31: *9 open() "/usr/share/nginx/html/tidak-ada" failed (2: No such file or directory), client: 172.17.0.1, server: localhost, request: "GET /tidak-ada HTTP/1.1", host: "localhost:8080"
2026-10-13T10:23:12.604812496Z 172.17.0.1 - - [13/Oct/2026:10:23:12 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.14.1"
2026-10-13T10:23:26.913027441Z 172.17.0.1 - - [13/Oct/2026:10:23:26 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.14.1"

Mari kita telaah format barunya:

  1. Prefiks waktu berformat RFC3339Nano — tanggal, huruf T, jam dengan pecahan nanodetik, diakhiri Z yang berarti UTC; setiap baris log kini membawa momen persis saat daemon mengumpulkannya.
  2. Waktu daemon berdampingan dengan waktu aplikasi — baris pertama memuat dua waktu: 10:22:59.481... saat baris dikumpulkan dan 10:22:59 versi nginx sendiri; keduanya nyaris berimpit pada container sehat dan saling menjauh saat penulisan log tersendat.

Log Hanya Membaca stdout dan stderr #

Batas terpenting docker logs perlu kita pahami sejak awal: perintah ini hanya membaca stdout dan stderr yang dikumpulkan daemon sejak container dinyalakan. docker logs tidak membaca berkas di dalam filesystem container — aplikasi yang menulis lognya ke /var/log/aplikasi.log akan tampak senyap di docker logs karena tulisannya tidak pernah mengalir ke kedua saluran tersebut. Lalu mengapa log akses nginx tetap terbaca padahal nginx secara aslinya menulis ke berkas? Image nginx sengaja menautkan access.log ke /dev/stdout dan error.log ke /dev/stderr lewat tautan simbolik, sehingga tulisan ke berkas itu dialihkan menjadi aliran yang bisa dikumpulkan daemon — keputusan desain image yang kita tiru kelak pada Dockerfile aplikasi sendiri.

Log Startup cache dan hello-world #

nginx adalah tipe container yang terus menulis log selama hidupnya; container lain justru paling berbicara di momen kelahirannya. Mari kita baca riwayat cache, sang Redis yang dinyalakan pada Chapter Melihat Container dan sejak itu diam sempurna:

bash
$ docker logs cache

Output yang Dihasilkan:

text
1:C 13 Oct 2026:09:52:11.233 # oO0O oO0O Redis is starting oO0O oO0O
...
1:M 13 Oct 2026:09:52:11.236 * Running mode=standalone, port: 6379.
1:M 13 Oct 2026:09:52:11.237 * Server initialized
1:M 13 Oct 2026:09:52:11.238 * Ready to accept connections tcp

Satu baris yang memuat versi Redis beserta peringatan konfigurasi dipotong agar ringkas. Mari kita telaah empat baris yang tampil:

  1. Prefiks 1:C dan 1:M — Redis menandai setiap barisnya dengan nomor proses dan perannya: 1:C dicatat PID 1 saat memeriksa diri di awal startup, lalu 1:M saat bertransformasi menjadi proses master yang siap bekerja.
  2. Tanda # dan * — # menandai baris penting seperti peringatan, sementara * menandai informasi biasa; redis menjadwalkan seluruhnya ke stdout.
  3. Ready to accept connections tcp — kalimat kunci setiap log startup: sejak baris ini, cache siap menerima koneksi di port 6379. Setelahnya Redis membisu total — sunyi justru tanda sehat untuk server yang tidak menerima permintaan apa pun.
  4. Waktu 09:52:11 — momen cache dinyalakan; log startup ini tetap terbaca berbulan-bulan kemudian selama container-nya belum dihapus.

hello-world menutup spektrum yang sama dari sisi sebaliknya: stdout-nya pendek dan seluruh hidupnya berlangsung sekejap. Kita sengaja menjalankannya tanpa flag --rm kali ini — container bekasnya harus tetap ada dulu supaya lognya bisa dibaca:

bash
$ docker run --name latihan-log hello-world

Output yang Dihasilkan:

text
Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
...
bash
$ docker logs latihan-log

Output yang Dihasilkan:

text
Hello from Docker!
This message shows that your installation appears to be working correctly.
...

Sisa pesan dipotong — isinya identik dengan yang tercetak saat container berjalan. Mari kita telaah pasangan output di atas:

  1. stdout pendek tetap terbaca utuh — seluruh ucapan hello-world tersimpan sebagai log meski prosesnya sudah lama selesai; docker logs mengulangnya kata demi kata.
  2. Log bertahan setelah container berhenti — latihan-log berstatus Exited (0) saat kita membacanya; kemampuan membaca log container berhenti inilah yang membuat diagnosis setelah kejadian tetap mungkin.
  3. Mengapa tanpa --rm — flag itu menghapus container begitu proses selesai, dan penghapusan container ikut membuang lognya; quirky_einstein dulu menyimpan log yang sama persis sampai kita menghapusnya pada Chapter Menghapus Container.

Sesuai kebiasaan membersihkan bekas latihan, kita bereskan latihan-log sekarang juga:

bash
$ docker rm latihan-log

Output yang Dihasilkan:

text
latihan-log

Kini kita bisa membaca seluruh ucapan container — dari log akses yang riuh sampai log startup yang sesaat. Tetapi membaca tetaplah aktivitas satu arah: kita mengamati keluaran yang dikirim ke luar, tanpa pernah meminta container mengerjakan sesuatu yang baru. Bagaimana caranya memeriksa versi program di dalam web, menjelajah isi direktori konfigurasinya, atau berbicara langsung dengan Redis di dalam cache? Jawabannya adalah docker exec — pintu dua arah ke dalam container yang sedang hidup — dan pintu itu kita buka pada Chapter Menjalankan Perintah dalam Container.

Referensi #