Perjalanan kita sebelumnya ditutup Strategi Versioning Image: image toko kini terdistribusi dengan tag semver yang terukur, digest yang terkunci, dan kebijakan mutasi yang disepakati tim. Namun mendistribusikan image hanyalah separuh kehidupan sebuah sistem — separuh lainnya berlangsung setelah container menyala, dan di situlah masalah jarang bersedia mengumumkan dirinya: backend mendadak lambat, cache diam, atau proxy mengembalikan galat tanpa penjelasan. Section ini membekali kita dengan empat perangkat diagnosis Docker — docker logs, docker inspect, docker stats, dan docker events — lalu merangkainya menjadi kebiasaan penyelidikan yang sistematis. Kita mulai dari jendela paling banyak dipakai: docker logs, yang di section ini kita naikkan satu tingkat dari sekadar membaca menjadi memahami ke mana log itu benar-benar pergi.

Recap Empat Flag Penyaring #

Chapter Melihat Log Container di Section Dasar Docker CLI telah memperkenalkan docker logs beserta empat flag penyaringnya; stack toko kini memberi bendera-bendera itu lalu lintas yang layak disaring. Berikut recapnya:

FlagBentuk pendekFungsi
--follow-fterus mengikuti keluaran baru, seperti tail -f
--tail N—batasi N baris terakhir, atau --tail all
--since—hanya baris sejak durasi atau timestamp tertentu
--timestamps-ttambahkan stempel waktu pengumpulan RFC3339Nano di tiap baris

Kelima bendera itu bebas dirangkai dalam satu perintah. Kombinasi favorit untuk memotret lalu lintas terbaru lewat proxy — jendela satu jam, maksimal lima baris, lengkap dengan stempel waktu:

bash
$ docker logs --since 1h --tail 5 --timestamps toko-proxy-1

Output yang Dihasilkan:

text
2026-10-13T00:41:03.481302753Z 172.21.0.1 - - [13/Oct/2026:00:41:03 +0000] "GET / HTTP/1.1" 200 1284 "-" "curl/8.14.1"
2026-10-13T00:41:16.604812496Z 172.21.0.1 - - [13/Oct/2026:00:41:16 +0000] "GET /api/produk.json HTTP/1.1" 200 62 "-" "curl/8.14.1"
2026-10-13T00:41:29.913027441Z 172.21.0.1 - - [13/Oct/2026:00:41:29 +0000] "GET /api/produk.json HTTP/1.1" 200 62 "-" "curl/8.14.1"
2026-10-13T01:12:44.104512312Z 172.21.0.1 - - [13/Oct/2026:01:12:44 +0000] "GET / HTTP/1.1" 200 1284 "-" "curl/8.14.1"
2026-10-13T01:38:07.812401337Z 172.21.0.1 - - [13/Oct/2026:01:38:07 +0000] "GET /api/produk.json HTTP/1.1" 200 62 "-" "curl/8.14.1"

Mari kita telaah hasil kombinasi tersebut:

  1. --since 1h memotong jendela waktu — seluruh baris berumur lebih dari satu jam lenyap dari tampilan, termasuk blok startup nginx dari saat stack dinyalakan pagi tadi.
  2. --tail 5 menjaga keluaran tetap pendek — pada proxy yang sibuk, --since menentukan jendela waktu dan --tail membatasi jumlah; keduanya bekerja sama seperti keran dan gelas.
  3. --timestamps menambahkan stempel waktu daemon — format RFC3339Nano dengan akhiran Z (UTC) di sisi kiri setiap baris, menempel tepat di depan waktu versi nginx sendiri di dalam kurung siku.
  4. 172.21.0.1 adalah gateway network — permintaan dari host masuk lewat gateway network Compose toko_frontend-net, sehingga dari sudut pandang nginx pelayan permintaannya selalu gateway itu — perilaku yang kita kenal sejak Chapter Melihat Log Container.
  5. -f tetap rekan terbaik terminal kedua — docker logs -f --tail 20 toko-proxy-1 menampilkan dua puluh baris terakhir lalu terus mengikuti; Ctrl-C hanya memutus klien kita, bukan container-nya.

Satu flag tambahan menutup recap: --details menyisipkan atribut ekstra seperti label environment ke tiap baris log — jarang dibutuhkan, cukup kita kenali wujudnya.

Driver Log Penentu Nasib stdout #

Pertanyaan yang belum pernah kita ajarkan: ke mana persisnya baris-baris itu pergi? Jawabannya bergantung pada logging driver — mekanisme yang dipilih daemon untuk mengumpulkan stdout dan stderr setiap container. Chapter Melihat Log Container sempat membocorkan satu fakta penting dalam catatan kaki: hanya driver json-file dan local yang bisa dibaca kembali oleh docker logs. Kini saatnya membedah mengapa, lewat tujuh driver yang paling sering dijumpai:

DriverKe mana log pergiDapat dibaca docker logs?
json-fileberkas JSON per container di host — bawaan Engineya
localberkas biner ramping per container, rotasi bawaanya
nonedibuang begitu saja, tidak dikumpulkantidak
journaldjurnal systemd milik hosttidak
syslogserver syslog melalui jaringantidak
fluentdagregator Fluentdtidak
gelfendpoint GELF, misalnya Graylogtidak

Dua driver terbaca karena lognya disimpan lokal oleh daemon dalam format yang bisa diputar ulang; lima lainnya mengalirkan log keluar dari host begitu diterima, sehingga docker logs menolak dengan pesan bahwa driver yang terpasang tidak mendukung pembacaan. Driver none adalah keadaan paling sunyi: container tetap bisa mencetak sebanyak apa pun, tetapi tidak ada yang menulisnya ke mana-mana.

Driver local dan Rotasi Terukur #

Driver bawaan json-file punya satu kelemahan yang sudah kita singgung di Chapter Konfigurasi Docker Engine: tanpa pengaturan, berkas lognya tumbuh selamanya sampai partisi habis. Kabar baiknya, pengaturan itu kini punya rumah yang lebih tepat — driver local, yang menyimpan log dalam format biner ramping dan mendukung rotasi sebagai warganya sendiri. Kita pasang sebagai bawaan seluruh container lewat daemon.json:

json
{
  "log-driver": "local",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

Mari kita telaah pasangan kunci tersebut:

  1. log-driver: local — menjadikan local sebagai bawaan untuk container baru; format internalnya memampatkan isi dan tidak menyimpan envelope JSON per baris, sehingga jauh lebih hemat disk daripada json-file.
  2. max-size: 10m — daemon memotong berkas log begitu menyentuh 10 megabyte; potongan lama menjadi riwayat.
  3. max-file: 3 — maksimal tiga berkas disimpan sebelum yang paling tua dihapus; anggaran log tertinggi per container kini terukur sekitar 30 megabyte, bisa direncanakan, bukan ditakuti.
  4. Perubahan hanya berlaku untuk container BARU — container yang sudah berjalan tetap memakai driver yang melekat sejak diciptakan; toko-proxy-1 yang lahir pagi tadi tetap ber-driver json-file sampai ia dibuat ulang, misalnya lewat docker compose up -d --force-recreate atau pergantian konfigurasi yang memicu recreate.

Untuk satu container tertentu tanpa menyentuh konfigurasi daemon, flag --log-driver dan --log-opt menimpa langsung saat penciptaan:

bash
$ docker run -d --name uji-rotasi --log-driver local --log-opt max-size=10m --log-opt max-file=2 alpine:3.20 sh -c 'while true; do echo baris uji-rotasi; sleep 1; done'

Output yang Dihasilkan:

text
f19c32e66187b683028abe259d039367e7f0646f7f70c0c382b2fb37d1ea0251

Container kecil itu mencetak satu baris tiap detik dengan driver local; mari kita buktikan driver tersebut tetap terbaca:

bash
$ docker logs --tail 3 uji-rotasi

Output yang Dihasilkan:

text
baris uji-rotasi
baris uji-rotasi
baris uji-rotasi

Bacaan yang normal itulah intinya: rotasi terjadi diam-diam di belakang, sementara pengalaman membaca log lewat docker logs tidak berubah sedikit pun. Kalau kita membiarkannya berjalan berjam-jam, berkas lognya akan terpotong rapi pada 10 megabyte dan hanya dua generasi yang bertahan. Karena uji coba ini sudah selesai, kita bereskan bekasnya:

bash
$ docker rm -f uji-rotasi

Output yang Dihasilkan:

text
uji-rotasi

Format Internal json-file #

Sebelum meninggalkan driver bawaan, mari kita bongkar satu tingkat lebih dalam: seperti apa sebenarnya berkas yang ditulis json-file? Setiap container mendapat satu berkas di host, di lokasi /var/lib/docker/containers/<id-penuh>/<id-penuh>-json.log. Dua baris pertama dari milik toko-proxy-1 berbentuk seperti ini:

json
{"log":"172.21.0.1 - - [13/Oct/2026:00:41:03 +0000] \"GET / HTTP/1.1\" 200 1284 \"-\" \"curl/8.14.1\"\n","stream":"stdout","time":"2026-10-13T00:41:03.481302753Z"}
{"log":"172.21.0.1 - - [13/Oct/2026:00:41:16 +0000] \"GET /api/produk.json HTTP/1.1\" 200 62 \"-\" \"curl/8.14.1\"\n","stream":"stdout","time":"2026-10-13T00:41:16.604812496Z"}

Mari kita telaah anatomi envelope tersebut:

  1. Field log — isi baris mentah persis seperti yang dicetak aplikasi, lengkap dengan tanda kutip yang lolos sebagai \" dan pemisah baris \n; inilah teks yang ditampilkan docker logs.
  2. Field stream — asal baris, stdout atau stderr; docker logs menggabungkan keduanya secara kronologis, tetapi asalnya tetap tercatat rapi di sini.
  3. Field time — momen daemon mengumpulkan baris, persis stempel yang ditambahkan flag --timestamps.

Log Terstruktur Satu Baris Satu Event #

Format envelope milik daemon hanyalah lapisan luar; kualitas log sesungguhnya ditentukan oleh aplikasi di dalam container. Siti kini membarui toko-api agar setiap kejadian dicetak sebagai satu baris JSON — satu event, satu baris, lengkap dengan atributnya:

bash
$ docker logs --tail 3 toko-backend-1

Output yang Dihasilkan:

text
{"waktu":"2026-10-13T01:40:02.104Z","level":"info","pesan":"permintaan selesai","rute":"/api/produk.json","status":200,"durasi_ms":12}
{"waktu":"2026-10-13T01:40:02.351Z","level":"info","pesan":"cache hit","kunci":"produk:semua","umur_ms":4200}
{"waktu":"2026-10-13T01:40:03.812Z","level":"error","pesan":"galat susunRekap","galat":"TypeError: Cannot read properties of undefined (reading 'total')"}

Satu baris seperti itu bisa langsung diurai mesin log mana pun: level untuk menyaring, rute untuk mengelompokkan, durasi_ms untuk mengukur. Bandingkan dengan kebiasaan lama yang masih membekas pada banyak aplikasi — kegagalan yang dicetak sebagai stack trace multi-baris. Inilah wujudnya ketika tertangkap flag --timestamps:

text
2026-10-13T01:37:44.104512312Z TypeError: Cannot read properties of undefined (reading 'total')
2026-10-13T01:37:44.104518441Z     at susunRekap (/app/src/rekap.js:118:22)
2026-10-13T01:37:44.104520933Z     at /app/src/server.js:64:9
2026-10-13T01:37:44.104522187Z     at /app/src/rute.js:31:7

Mari kita telaah mengapa empat baris itu menjadi masalah:

  1. Satu peristiwa pecah menjadi empat baris — padahal yang terjadi hanyalah satu kegagalan di dalam susunRekap; container tidak mati, prosesnya tetap hidup dan terus melayani.
  2. Setiap baris membawa stempel waktu sendiri — flag -t menstempel saat pengumpulan, sehingga satu stack trace tampak seperti empat event yang lahir beruntunan, bukan satu momen.
  3. Penyaringan berdasarkan baris menjadi bohong — --tail 3 bisa memotong stack trace di tengah, dan --since menghitung setiap baris sebagai kejadian terpisah.
  4. Agregator menderita paling parah — fluentd atau GELF yang menerima baris demi baris akan menyimpan empat entri terpisah untuk satu galat; kronologi insiden menjadi berlipat-lipat lebih ramai dari kenyataannya.

Solusinya sudah terlihat pada keluaran toko-api di atas: aplikasi sendirilah yang merangkai stack trace menjadi satu field dalam satu baris JSON — utuh, terukur, dan siap diurai. Kebiasaan “satu event satu baris” inilah mata uang bersama antara aplikasi, docker logs, dan seluruh pipeline log produksi kelak.

Menyimak Stack dengan compose logs #

Membaca log satu container sudah cukup untuk satu kecurigaan; diagnosis stack menuntut kronologi lintas service. Di sinilah docker compose logs mengambil alih — perintahnya mengenal service, bukan nama container:

bash
$ docker compose logs -f --tail=50 backend

Output yang Dihasilkan (dipotong):

text
toko-backend-1  | {"waktu":"2026-10-13T01:40:02.104Z","level":"info","pesan":"permintaan selesai","rute":"/api/produk.json","status":200,"durasi_ms":12}
toko-backend-1  | {"waktu":"2026-10-13T01:40:02.351Z","level":"info","pesan":"cache hit","kunci":"produk:semua","umur_ms":4200}
toko-backend-1  | {"waktu":"2026-10-13T01:40:03.812Z","level":"error","pesan":"galat susunRekap","galat":"TypeError: Cannot read properties of undefined (reading 'total')"}
...

Mari kita telaah tiga perilaku perintah tersebut:

  1. Prefiks toko-backend-1 | menempel pada setiap baris — identitas service menyatu dengan isinya; panggil beberapa service sekaligus seperti docker compose logs -f proxy backend — pola dari Chapter Local Development Stack — dan kronologi lintas service tersusun rapi dalam satu arus.
  2. --tail=50 memberi konteks awal sebelum mengikuti — lima puluh baris terakhir tampil seketika, lalu -f menempelkan baris-baris baru begitu lahir; kombinasi yang sama dengan docker logs, kini dalam kosakata service.
  3. --no-color membersihkan kode warna — terminal mendapat warna untuk memudahkan mata, tetapi saat keluaran disimpan ke berkas untuk laporan insiden, flag ini memastikan isinya murni teks.

Log yang Bertahan Setelah Container Mati #

Satu perilaku penutup yang sering menyelamatkan penyelidikan: log tidak ikut mati ketika container-nya berhenti. Stack toko menyimpan contoh permanennya — toko-migrate-1, job migrasi one-shot yang oleh desain berstatus Exited (0) sejak pagi:

bash
$ docker logs toko-migrate-1

Output yang Dihasilkan:

text
migrasi selesai

Kata-kata aplikasi kini kita baca dengan lancar: disaring, distempel, dirotasi, bahkan dirancang sejak ditulis. Namun log hanyalah kesaksian si terdakwa — ia bicara tentang apa yang aplikasi rasakan. Untuk memeriksa tempat kejadian itu sendiri, kita membutuhkan arsip keadaan yang dijaga daemon: status proses, exit code, jejak OOM killer, alamat IP, daftar mount, hingga environment yang tersuntik. Arsip itu terbuka lewat satu perintah bernama docker inspect, dan itulah agenda Chapter Docker Inspect.

Referensi #