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:
| Flag | Bentuk pendek | Fungsi |
|---|---|---|
--follow | -f | terus mengikuti keluaran baru, seperti tail -f |
--tail N | — | batasi N baris terakhir, atau --tail all |
--since | — | hanya baris sejak durasi atau timestamp tertentu |
--timestamps | -t | tambahkan 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:
$ docker logs --since 1h --tail 5 --timestamps toko-proxy-1Output yang Dihasilkan:
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:
--since 1hmemotong jendela waktu — seluruh baris berumur lebih dari satu jam lenyap dari tampilan, termasuk blok startup nginx dari saat stack dinyalakan pagi tadi.--tail 5menjaga keluaran tetap pendek — pada proxy yang sibuk,--sincemenentukan jendela waktu dan--tailmembatasi jumlah; keduanya bekerja sama seperti keran dan gelas.--timestampsmenambahkan stempel waktu daemon — format RFC3339Nano dengan akhiranZ(UTC) di sisi kiri setiap baris, menempel tepat di depan waktu versi nginx sendiri di dalam kurung siku.172.21.0.1adalah gateway network — permintaan dari host masuk lewat gateway network Composetoko_frontend-net, sehingga dari sudut pandang nginx pelayan permintaannya selalu gateway itu — perilaku yang kita kenal sejak Chapter Melihat Log Container.-ftetap rekan terbaik terminal kedua —docker logs -f --tail 20 toko-proxy-1menampilkan dua puluh baris terakhir lalu terus mengikuti;Ctrl-Chanya 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:
| Driver | Ke mana log pergi | Dapat dibaca docker logs? |
|---|---|---|
json-file | berkas JSON per container di host — bawaan Engine | ya |
local | berkas biner ramping per container, rotasi bawaan | ya |
none | dibuang begitu saja, tidak dikumpulkan | tidak |
journald | jurnal systemd milik host | tidak |
syslog | server syslog melalui jaringan | tidak |
fluentd | agregator Fluentd | tidak |
gelf | endpoint GELF, misalnya Graylog | tidak |
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:
{
"log-driver": "local",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}Mari kita telaah pasangan kunci tersebut:
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.max-size: 10m— daemon memotong berkas log begitu menyentuh 10 megabyte; potongan lama menjadi riwayat.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.- Perubahan hanya berlaku untuk container BARU — container yang sudah berjalan tetap memakai driver yang melekat sejak diciptakan;
toko-proxy-1yang lahir pagi tadi tetap ber-driver json-file sampai ia dibuat ulang, misalnya lewatdocker compose up -d --force-recreateatau pergantian konfigurasi yang memicu recreate.
Untuk satu container tertentu tanpa menyentuh konfigurasi daemon, flag --log-driver dan --log-opt menimpa langsung saat penciptaan:
$ 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:
f19c32e66187b683028abe259d039367e7f0646f7f70c0c382b2fb37d1ea0251Container kecil itu mencetak satu baris tiap detik dengan driver local; mari kita buktikan driver tersebut tetap terbaca:
$ docker logs --tail 3 uji-rotasiOutput yang Dihasilkan:
baris uji-rotasi
baris uji-rotasi
baris uji-rotasiBacaan 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:
$ docker rm -f uji-rotasiOutput yang Dihasilkan:
uji-rotasiFormat 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:
{"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:
- Field
log— isi baris mentah persis seperti yang dicetak aplikasi, lengkap dengan tanda kutip yang lolos sebagai\"dan pemisah baris\n; inilah teks yang ditampilkandocker logs. - Field
stream— asal baris,stdoutataustderr;docker logsmenggabungkan keduanya secara kronologis, tetapi asalnya tetap tercatat rapi di sini. - 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:
$ docker logs --tail 3 toko-backend-1Output yang Dihasilkan:
{"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:
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:7Mari kita telaah mengapa empat baris itu menjadi masalah:
- Satu peristiwa pecah menjadi empat baris — padahal yang terjadi hanyalah satu kegagalan di dalam
susunRekap; container tidak mati, prosesnya tetap hidup dan terus melayani. - Setiap baris membawa stempel waktu sendiri — flag
-tmenstempel saat pengumpulan, sehingga satu stack trace tampak seperti empat event yang lahir beruntunan, bukan satu momen. - Penyaringan berdasarkan baris menjadi bohong —
--tail 3bisa memotong stack trace di tengah, dan--sincemenghitung setiap baris sebagai kejadian terpisah. - 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:
$ docker compose logs -f --tail=50 backendOutput yang Dihasilkan (dipotong):
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:
- Prefiks
toko-backend-1 |menempel pada setiap baris — identitas service menyatu dengan isinya; panggil beberapa service sekaligus sepertidocker compose logs -f proxy backend— pola dari Chapter Local Development Stack — dan kronologi lintas service tersusun rapi dalam satu arus. --tail=50memberi konteks awal sebelum mengikuti — lima puluh baris terakhir tampil seketika, lalu-fmenempelkan baris-baris baru begitu lahir; kombinasi yang sama dengandocker logs, kini dalam kosakata service.--no-colormembersihkan 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:
$ docker logs toko-migrate-1Output yang Dihasilkan:
migrasi selesaiKata-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 #
- docker container logs
— referensi resmi seluruh flag penyaring beserta
--details. - Configure logging drivers
— daftar driver log dan batas
docker logsterhadap driver non-lokal. - local logging driver
— opsi
max-size,max-file, dan perilaku rotasi driver local. - docker compose logs
— flag
--tail,--follow, dan--no-colordalam kosakata service.