Setelah berlatih debugging interaktif, kita menghadapi kebutuhan yang lebih luas: merekam kejadian dalam program secara sistematis — kapan aplikasi dimulai, stok mana yang menipis, operasi mana yang gagal. Fungsi print() mampu mencetak teks, tetapi tidak mengenal tingkat kepentingan pesan, tidak mencatat asal pesan, dan tidak punya mekanisme mematikan pesan diagnosis tanpa menghapus kodenya. Python membawa solusi jadi di standard library bernama module logging. Pada Chapter ini kita akan mempelajari konfigurasi dasarnya, perbandingannya dengan print(), dan konvensi logger per module yang dipakai industri.
Log Pertama dengan logging #
Cara tercepat memakai module ini adalah mengimpor logging, mengonfigurasi root logger lewat logging.basicConfig(), lalu memanggil fungsi level seperti logging.info():
import logging
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s: %(message)s")
logging.info("Aplikasi kasir dimulai")
logging.warning("Stok kopi tinggal 2")
logging.error("Koneksi database gagal")Output yang Dihasilkan (dicetak module logging ke stderr):
INFO root: Aplikasi kasir dimulai
WARNING root: Stok kopi tinggal 2
ERROR root: Koneksi database gagalMari kita telaah peran setiap bagian:
- Baris 3 (
logging.basicConfig(...)): memasang handler dan formatter pada root logger sekali saja; pemanggilanbasicConfigkedua di program yang sama diabaikan bila root sudah memiliki handler. - Parameter
level=logging.INFO: pesan dengan level INFO ke atas akan diproses; level DEBUG yang lebih rendah tersaring. Kita akan bedah mekanisme level ini pada Chapter Logging Levels. - Parameter
format=...: template isi baris log. Placeholder%(levelname)sdiisi nama level,%(name)sdiisi nama logger, dan%(message)sdiisi pesan kita. - Baris 5-7 (
logging.info()dst.): panggilan level tanpa logger eksplisit ditujukan ke root logger, itulah sebabnya%(name)stercetakroot.
Bila basicConfig() dipanggil tanpa parameter format, format bawaannya adalah %(levelname)s:%(name)s:%(message)s — menghasilkan baris seperti WARNING:root:Stok kopi tinggal 2.
logging versus print() #
Perpindahan dari print() ke logging bukan sekadar gaya. Tabel berikut merangkum perbedaan yang benar-benar terasa saat program tumbuh:
| Aspek | print() | logging |
|---|---|---|
| Tingkat kepentingan | tidak ada | lima level bawaan, bisa disaring per level |
| Stempel waktu | harus dihitung manual | tersedia lewat %(asctime)s |
| Asal pesan | tidak tercatat | %(name)s merekam logger pengirim |
| Tujuan output | stdout | stdout, stderr, berkas, jaringan — via handler |
| Mematikan pesan | hapus atau komen kodenya | naikkan level di satu titik konfigurasi |
| Traceback error | cetak manual dengan traceback module | otomatis lewat logging.exception() |
Satu baris logging.basicConfig(level=logging.WARNING) di titik masuk program bisa melenyapkan seluruh pesan INFO dan DEBUG tanpa menyentuh ratusan baris pemanggilan log yang tersebar di banyak module.
Logger per Module: getLogger(name) #
Konvensi industri Python: setiap module membuat logger sendiri di tingkat atas dengan nama dari variabel khusus __name__:
import logging
logger = logging.getLogger(__name__)Ketika module itu diimpor sebagai bagian dari package kasir, nilai __name__ adalah "kasir.pembayaran", sehingga setiap baris log dari module tersebut membawa asal-usulnya. Untuk melihat efeknya tanpa membangun package, kita bisa membuat logger bernama eksplisit:
import logging
logging.basicConfig(level=logging.DEBUG, format="%(name)s %(levelname)s: %(message)s")
logger = logging.getLogger("kasir.pembayaran")
logger.info("Memproses pembayaran")
logger.debug("Saldo pengguna dicek")Output yang Dihasilkan (dicetak module logging ke stderr):
kasir.pembayaran INFO: Memproses pembayaran
kasir.pembayaran DEBUG: Saldo pengguna dicekNama logger bukan sekadar label. Nama dengan titik membentuk hierarki induk-anak: logger "toko.kasir" menempel di bawah logger "toko", dan logger "toko" menempel di bawah logger root. Perhatikan urutan pembuatannya:
import logging
root = logging.getLogger()
induk = logging.getLogger("toko")
anak = logging.getLogger("toko.kasir")
print(root.name)
print(anak.name)
print(anak.parent.name)
print(anak.parent is induk)Output yang Dihasilkan:
root
toko.kasir
toko
TrueMari kita telaah hasilnya:
- Baris 5-6 (
getLogger("toko"),getLogger("toko.kasir")): logger induk dibuat lebih dulu, sehingga logger anak otomatis menautkan diri ke logger"toko"terdekat. - Baris 9 (
anak.parent is induk): operatorismengonfirmasi bahwaparentmilik logger"toko.kasir"memang object logger"toko"yang sama, bukan salinan baru.
Logger "toko.kasir" adalah anak dari logger "toko", yang merupakan anak dari logger root. Sifat penting hierarki ini: pesan yang dikirim ke logger anak akan diteruskan naik ke seluruh leluhurnya (perilaku yang dikendalikan oleh flag propagate) sampai diambil oleh handler yang terpasang. Karena itulah konvensi aplikasi biasanya memasang handler cukup sekali di root logger — seluruh logger __name__ di module mana pun otomatis menyalurkan pesannya ke sana.
Dengan log dasar berjalan, pertanyaan alami berikutnya adalah: kapan sebuah pesan pantas disebut DEBUG, dan kapan ia harus ERROR? Kita atur tuntas pada Chapter Logging Levels.