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():

python
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):

text
INFO root: Aplikasi kasir dimulai
WARNING root: Stok kopi tinggal 2
ERROR root: Koneksi database gagal

Mari kita telaah peran setiap bagian:

  1. Baris 3 (logging.basicConfig(...)): memasang handler dan formatter pada root logger sekali saja; pemanggilan basicConfig kedua di program yang sama diabaikan bila root sudah memiliki handler.
  2. 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.
  3. Parameter format=...: template isi baris log. Placeholder %(levelname)s diisi nama level, %(name)s diisi nama logger, dan %(message)s diisi pesan kita.
  4. Baris 5-7 (logging.info() dst.): panggilan level tanpa logger eksplisit ditujukan ke root logger, itulah sebabnya %(name)s tercetak root.

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:

Aspekprint()logging
Tingkat kepentingantidak adalima level bawaan, bisa disaring per level
Stempel waktuharus dihitung manualtersedia lewat %(asctime)s
Asal pesantidak tercatat%(name)s merekam logger pengirim
Tujuan outputstdoutstdout, stderr, berkas, jaringan — via handler
Mematikan pesanhapus atau komen kodenyanaikkan level di satu titik konfigurasi
Traceback errorcetak manual dengan traceback moduleotomatis 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__:

python
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:

python
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):

text
kasir.pembayaran INFO: Memproses pembayaran
kasir.pembayaran DEBUG: Saldo pengguna dicek

Nama 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:

python
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:

text
root
toko.kasir
toko
True

Mari kita telaah hasilnya:

  1. Baris 5-6 (getLogger("toko"), getLogger("toko.kasir")): logger induk dibuat lebih dulu, sehingga logger anak otomatis menautkan diri ke logger "toko" terdekat.
  2. Baris 9 (anak.parent is induk): operator is mengonfirmasi bahwa parent milik 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.