Data toko kini punya rumah yang benar: volume berlabel yang tak tersentuh down -v, data-root di disk terpisah, dan ambang waspada 80% yang dijaga pemantauan. Namun di bagian penutup chapter sebelumnya kita sudah mencium penghuni berikutnya yang jauh lebih rakus — log. Sebuah stack lima service yang sehat pun mencetak baris pada setiap permintaan, setiap healthcheck, setiap koneksi database, dan di driver bawaan json-file seluruh baris itu menumpuk tanpa satu pun batas ukuran. Kisah klasiknya berakhir sama: server berjalan normal berbulan-bulan, lalu suatu Minggu pagi seluruh endpoint menjawab galat tulis dan df -h menunjukkan partisi data penuh oleh satu berkas log berukuran belasan gigabyte. Chapter ini memutus nasib log sebelum nasib itu memutus server: memilih driver yang tepat untuk produksi, memasang rotasi sebagai bawaan daemon, menimpanya per-container saat dibutuhkan, lalu menaikkan kualitas isi log — terstruktur satu baris satu event — sampai teknik mengorelasikan satu permintaan lintas proxy dan backend.
Empat Driver yang Layak Dipertimbangkan #
Chapter Docker Logs di Section Observability dan Troubleshooting telah membuka tujuh driver; di produksi, pilihan yang sungguh-sungguh bersaing tinggal empat:
| Driver | Rotasi | Dapat dibaca docker logs | Peran di produksi |
|---|---|---|---|
local | ya — warga asli driver (max-size, max-file) | ya | bawaan standar server tunggal: ramping, terukur, tanpa konfigurasi tambahan |
json-file | ya — lewat log-opts | ya | kompatibel dengan tool yang membaca berkas JSON per baris; bisa membawa label dan env ke baris log |
syslog | dikelola server syslog penerima | tidak | mengarahkan log ke pusat syslog yang sudah dimiliki organisasi |
fluentd / gelf | dikelola agregator penerima | tidak | agregasi lintas host — log seluruh armada terkumpul di satu tempat yang bisa dicari |
Batas terpenting dari tabel itu bukan fitur, melainkan kolom ketiga:
Bawaan Daemon: Rotasi untuk Seluruh Armada #
Keputusan paling berdampak diambil di satu berkas yang sudah kita tanam sejak Chapter Konfigurasi Docker Engine — daemon.json di /etc/docker. Di server toko, blok logging melengkapi data-root yang dipasang chapter sebelumnya:
{
"data-root": "/data/docker",
"log-driver": "local",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}$ sudo dockerd --validate --config-file /etc/docker/daemon.json
$ sudo systemctl restart dockerOutput yang Dihasilkan:
configuration OKMari kita telaah tiga kunci logging tersebut:
log-driver: local— seluruh container baru lahir dengan driver local: format biner yang lebih hemat disk daripada envelope JSON per baris, dan rotasi yang menjadi warga asli driver, bukan tambalan konfigurasi.max-size: 10m— berkas log terpotong begitu menyentuh 10 megabyte; potongan yang sedang aktif berganti nama menjadi riwayat dan berkas segar dibuka.max-file: 3— maksimal tiga generasi berkas disimpan sebelum yang tertua dihapus; anggaran tertinggi log per container kini terkunci sekitar 30 megabyte — angka yang bisa dimasukkan ke perencanaan kapasitas, bukan ditakuti.
Satu perilaku yang wajib diingat ulang dari Chapter Docker Logs: pengaturan daemon hanya berlaku bagi container yang diciptakan setelah daemon menyala ulang. Container lama tetap memakai driver yang melekat sejak kelahirannya — di server toko, lima container stack diperbarui sekaligus dengan docker compose up -d --force-recreate pada jendela pemeliharaan yang sama dengan bukti down -v chapter sebelumnya.
Mengapa persoalan ini mendapat satu WARNING penuh dan bukan sekadar catatan kaki:
Tim yang memilih json-file sebagai bawaan — biasanya karena tool eksternal membaca berkas -json.log langsung — memakai bentuk varian yang sama ditambah satu kunci khas driver itu:
{
"data-root": "/data/docker",
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3",
"labels": "env"
}
}Kunci "labels": "env" memerintahkan daemon menyeret nilai label env milik container ke setiap baris log yang dikumpulkan — label env=prod pada container toko-backend ikut tercatat sebagai atribut dan terlihat saat docker logs --details. Dua mesin dengan driver berbeda kini bisa dibedakan baris lognya tanpa membuka inspect: satu atribut kecil yang menyelamatkan banyak waktu saat menyisir log lintas environment.
Prioritas per Container #
Bawaan daemon menaungi seluruh container — tetapi container tertentu kadang menuntut perlakuan khusus: satu service yang sangat cerewet butuh anggaran lebih besar, atau satu service debug justru butuh batas lebih ketat. Flag --log-driver dan --log-opt pada saat penciptaan container menimpa bawaan daemon, dan timpaan itu berlaku penuh:
$ docker run -d --name uji-log \
--log-driver json-file \
--log-opt max-size=5m \
--log-opt max-file=2 \
--label env=prod \
alpine:3.20 sh -c 'while true; do echo denyut; sleep 5; done'
$ docker logs --tail 2 uji-logOutput yang Dihasilkan:
denyut
denyutContainer uji itu mencetak satu baris setiap lima detik dengan anggaran maksimal 10 megabyte; label env=prod yang disertakan akan menempel pada baris-barisnya bila driver dipasang di bawah daemon.json varian json-file di atas. Selesai menguji, bekasnya kita bereskan:
$ docker rm -f uji-logOutput yang Dihasilkan:
uji-logDalam kosakata Compose, timpaan per-service hidup di bawah kunci logging:
services:
backend:
image: registry.example.com/toko/backend:1.4.0
labels:
env: prod
logging:
driver: json-file
options:
max-size: "5m"
max-file: "2"
labels: "env"Mari kita telaah bentuk deklarasinya — dan satu jebakan YAML yang menunggu di dalamnya:
driver: json-file— service backend satu-satunya yang kita timpa: aplikasi Node-nya paling cerewet, dan envelope JSON per baris memudahkan penyelidikan--details; empat service lain tetap menikuti bawaanlocaldari daemon.optionsbeserta seluruh nilainya ditulis sebagai string berkutip — inilah jebakannya:max-file: "2"yang kehilangan kutipnya diurai YAML sebagai bilangan bulat2, dan Compose menolak nilai numerik pada options dengan galat bahwa nilai bukan string; kebiasaan berkutip seluruh opsi log adalah vaksin satu karakter termurah di seluruh compose.yaml.labels: "env"pada opsi driver — pasangan label di service dan opsi di logging bekerja berpasangan: label menyediakan nilainya, opsilabelsmemerintahkan driver json-file menyeretnya ke baris log.
Log Terstruktur: Satu Event Satu Baris #
Rotasi mengurus nasib berkas; kualitas isi tetap urusan aplikasi. Recap satu paragraf dari Chapter Docker Logs: aplikasi toko kini mencetak setiap kejadian sebagai satu baris JSON dengan field tetap — satu event, satu baris, lengkap atributnya. Produksi menambah satu disiplin: nama field distandarkan lintas service menjadi time, level, msg, dan trace_id, sehingga penyelidik tidak perlu menerjemahkan ulang kosakata tiap service:
$ docker logs --tail 1 toko-produksi-backend-1Output yang Dihasilkan:
{"time":"2026-10-23T09:41:30.104+07:00","level":"info","msg":"pesanan diterima","rute":"POST /api/pesanan","status":201,"durasi_ms":87,"trace_id":"b7d4a3f8c2e19d4f6a2b8c0d1e3f4a5b"}Flag --timestamps menambahkan satu kejutan yang membingungkan pemula: baris log kini memuat DUA stempel waktu yang berbeda wujud.
$ docker logs --timestamps --tail 1 toko-produksi-backend-1Output yang Dihasilkan:
2026-10-23T02:41:30.107812496Z {"time":"2026-10-23T09:41:30.104+07:00","level":"info","msg":"pesanan diterima","rute":"POST /api/pesanan","status":201,"durasi_ms":87,"trace_id":"b7d4a3f8c2e19d4f6a2b8c0d1e3f4a5b"}Mari kita bedah kedua lapis waktu tersebut:
- Lapis kiri
2026-10-23T02:41:30.107812496Z— stempel milik Docker: momen daemon mengumpulkan baris dari stdout container, ditulis RFC3339Nano dengan akhiranZkarena UTC; inilah lapisan envelope yang kita bedah format internalnya pada Chapter *Docker Logs`. - Lapis kanan
"time":"2026-10-23T09:41:30.104+07:00"— stempel milik aplikasi: momen kejadian sebenarnya versi backend, ditulis aplikasi sendiri dengan zona waktu WIB. - Keduanya adalah momen yang hampir sama — 09:41:30+07:00 adalah 02:41:30Z ditambah tujuh jam; selisih tiga milidetik di antara keduanya adalah perjalanan baris dari proses aplikasi ke tangan daemon.
- Aturan praktisnya — saat menyusun kronologi kejadian, percayai waktu aplikasi; saat mengurutkan baris pengumpulan lintas container, stempel daemon-lapis yang konsisten satu format. Menyadari dua lapis ini ada mencegah kesimpulan salah bahwa “backend mencatat di masa depan”.
Korelasi Antar Service #
Satu permintaan POST dari pelanggan menempuh proxy lalu backend; bila hasilnya 500, penyelidik harus bisa menjahit jejak kedua pihak menjadi satu kronologi. Tiga kunci jahitan itu:
Kunci pertama: nama container. Compose memberi prefiks nama service pada setiap baris docker compose logs — toko-produksi-backend-1 | dan toko-produksi-proxy-1 | — sehingga arus log campuran tetap terbaca siapa pembicaranya. Nama service yang stabil (proxy, frontend, backend, db, cache) adalah kontrak penamaan yang kita jaga sejak Section Multi-Container Application.
Kunci kedua: timestamp yang terkalibrasi. Kronologi lintas service hanya jujur bila jam host akurat — dua host yang bergeser 40 detik akan menempatkan jawaban sebelum pertanyaannya. Server toko memeriksa dirinya dengan satu perintah:
$ timedatectlOutput yang Dihasilkan (dipotong):
Local time: Fri 2026-10-23 09:41:45 WIB
Universal time: Fri 2026-10-23 02:41:45 UTC
Time zone: Asia/Jakarta (WIB, +0700)
System clock synchronized: yes
NTP service: activeBaris System clock synchronized: yes dengan NTP service: active adalah syarat kunci kedua; sinkronisasi NTP adalah urusan host, bukan container — semua container mewarisi jam kernel yang sama.
Kunci ketiga: trace id. Nama memberitahu siapa, waktu memberitahu kapan; kunci ketiga memberitahu permintaan yang mana. nginx membangkitkan pengenal unik untuk setiap permintaan lewat variabel $request_id, menyuntikkannya ke backend sebagai header, dan mencatatnya di log aksesnya sendiri:
log_format toko '$remote_addr [$time_local] "$request" '
'$status $body_bytes_sent trace_id=$request_id';
server {
listen 443 ssl;
http2 on;
access_log /dev/stdout toko;
location /api/ {
set $backend_host backend;
proxy_set_header X-Request-ID $request_id;
proxy_pass http://$backend_host:3000;
}
}Mari kita telaah empat baris kunci konfigurasi tersebut:
log_format toko ...— format bernamatokoyang menutup tiap baris akses dengantrace_id=$request_id; pengenal 32 karakter heksadesimal yang unik per permintaan.access_log /dev/stdout toko;— log akses nginx diarahkan ke stdout, bukan berkas di dalam container; barisnya mengalir ke driver log Docker dan terbacadocker compose logs proxy— kebiasaan satu-event-satu-baris yang sama dengan aplikasi.proxy_set_header X-Request-ID $request_id;— proxy menyuntikkan pengenal itu sebagai header ke backend; backend toko membaca header ini dan menuangkannya sebagai fieldtrace_idpada JSON log-nya, sehingga satu permintaan membawa satu pengenal dari pintu sampai jawaban.set $backend_host backenddanproxy_passberbasis variabel — pola resolver127.0.0.11dari Chapter Reverse Proxy dan TLS yang membuat proxy tetap menemukan backend baru setelah redeploy; kita memakai kembali fondasi yang sama tanpa mengubahnya.
Bukti bahwa tiga kunci bekerja: satu grep menyatukan dua pihak yang berbeda format log:
$ docker compose logs backend proxy | grep trace_id | tail -n 2Output yang Dihasilkan:
toko-produksi-backend-1 | {"time":"2026-10-23T09:41:30.104+07:00","level":"info","msg":"pesanan diterima","rute":"POST /api/pesanan","status":201,"durasi_ms":87,"trace_id":"b7d4a3f8c2e19d4f6a2b8c0d1e3f4a5b"}
toko-produksi-proxy-1 | 192.168.1.50 [23/Oct/2026:09:41:30 +0700] "POST /api/pesanan HTTP/2.0" 201 92 trace_id=b7d4a3f8c2e19d4f6a2b8c0d1e3f4a5bDua baris itu adalah satu permintaan utuh: proxy menerima POST dari klien 192.168.1.50, menjawab 201 dengan 92 byte; backend mencatat penerimaan pesanan dalam 87 milidetik — kronologi lengkap dari pintu sampai jawaban, dijahit oleh satu pengenal yang sama. Resep korelasi dirangkum dalam satu tabel:
| Kebutuhan penyelidikan | Kunci yang dipakai | Prasyaratnya |
|---|---|---|
| Urutan kejadian lintas service | timestamp terkalibrasi | host sinkron NTP; zona waktu dipahami (WIB vs UTC) |
| Siapa yang sedang bicara | prefiks nama container | nama service Compose yang stabil lintas rilis |
| Satu permintaan ujung ke ujung | trace id dari proxy | header X-Request-ID di-inject dan dicatat semua pihak |
| Luka cepat saat insiden | grep trace_id | log terstruktur satu baris satu event |
Log kini hidup dalam anggaran yang terukur, terbaca tanpa ritual, dan bisa dijahit lintas service. Namun sebaik apa pun log menjelaskan kejadian, log tetap hanya saksi — data yang hilang tetap harus bisa lahir kembali dari salinan yang aman. Bagaimana tiga salinan, dua media, dan satu lokasi luar dirakit dalam kosakata Docker, lengkap dengan skrip yang berjalan tiap malam dan runbook pemulihan yang teruji, adalah materi Chapter Backup dan Recovery.
Referensi #
- Configure logging drivers
— daftar driver log, dukungan
docker logs, dan opsi rotasi. - local logging driver
— perilaku rotasi
max-sizedanmax-filepada driver local. - json-file logging driver
— opsi rotasi dan
labelspada driver json-file. - Compose Specification
— skema kunci
loggingbeserta tipe nilai options pada services.