Soal 1Dengan logging.basicConfig(level=logging.INFO), level mana yang ditekan?
logging — Catat alih-alih print
Pelajari 5 level log dan format basicConfig, kode format %(levelname)s, plus output file FileHandler untuk lulus dari debugging print lewat contoh.
logging adalah modul standar untuk mencatat perilaku aplikasimu secara bertahap. Menulis output debug dengan print mengalami masalah: kamu lupa menghapusnya di production, kamu tidak bisa membedakan apa yang penting, dan beralih ke output file nanti berarti menulis ulang semuanya. Dengan logging, kamu mendapatkan level pentingnya (DEBUG / INFO / WARNING / ERROR / CRITICAL) untuk memfilter output, Formatter untuk formatting terpadu, dan Handler untuk berganti tujuan (stdout / file / remote).
logger.info(...) dari kodemu, alurnya adalah Logger memfilter berdasarkan level → Handler memutuskan tujuan → Formatter merakit format. Karena ketiga tanggung jawab dipisahkan, mengubah format atau tujuan di satu tempat mengubah seluruh perilaku.Level log — 5 tingkat penting
logging memiliki 5 level, dan hanya pesan di atau di atas level yang diset pada Logger yang dipancarkan. Praktik standar adalah DEBUG selama development dan INFO atau WARNING di production — dan bisa mengubah volume output dari satu setting tanpa menyentuh kode adalah perbedaan terbesar dari print.
Memilih di antara WARNING / ERROR / DEBUG
Dengan logger dikonfigurasi, panggil level selain INFO dan amati output. Karena level diset ke INFO, DEBUG tidak menghasilkan output sama sekali — konfirmasi itu juga. Ini fondasi perilaku logging "ubah volume output dari satu setting".
Pancarkan log ke file
Proyek nyata hampir selalu butuh log dipersistensi ke file, bukan hanya di layar. Kamu nanti akan ingin grep untuk investigasi postmortem atau menariknya ke alat monitoring. Teruskan filename="app.log" ke basicConfig dan tujuan root logger beralih ke file — melakukan hal yang sama dengan print akan berarti menulis ulang setiap panggilan print, tetapi dengan logging kamu mengubah satu baris config untuk mengalihkan.
logging, kamu beralih di antaranya dalam satu baris config.filemode adalah "a" (default, append) atau "w" (overwrite). Append adalah pilihan production — simpan histori. Overwrite sering lebih nyaman selama development — mulai dari awal setiap kali. Untuk memancarkan ke layar dan file, kamu melewati basicConfig dan menggabungkan StreamHandler + FileHandler secara eksplisit (dibahas nanti).
StreamHandler dan FileHandler secara terpisah dan addHandler masing-masing.import logging
import os
# Buat folder induk dulu (FileHandler tidak auto-membuatnya)
os.makedirs("logs", exist_ok=True)
# Teruskan filename ke basicConfig untuk beralih tujuan ke file
logging.basicConfig(
level=logging.INFO,
format="[%(levelname)s] %(message)s",
filename="logs/app.log", # Output file
filemode="w", # "a" (append) atau "w" (overwrite)
force=True,
)
logger = logging.getLogger("app")
logger.info("Dimulai")
logger.warning("Config kedaluwarsa")
logger.error("Koneksi DB gagal")
# Baca file kembali untuk konfirmasi konten
with open("logs/app.log") as f:
print(f.read(), end="")
Definisikan handler di file terpisah
Saat aplikasimu tumbuh, pisahkan config logger (format, level, tujuan) ke modul khusus dan minta setiap modul import darinya. Config logger tidak lagi terduplikasi, dan kamu mengubah format/tujuan di satu tempat. Polanya: bangun StreamHandler langsung, lampirkan Formatter, daftarkan dengan logger.addHandler(...) — semuanya hidup di file terpisah.
main.py / orders.py / users.py) cukup memanggil setup_logger(__name__) untuk mendapatkan logger yang terkonfigurasi. Untuk mengubah format atau tujuan, edit log_setup.py saja.# log_setup.py — modul khusus untuk config logger
import logging
def setup_logger(name):
logger = logging.getLogger(name)
logger.setLevel(logging.INFO)
if not logger.handlers: # Mencegah pendaftaran ganda
handler = logging.StreamHandler()
handler.setFormatter(
logging.Formatter("[%(name)s] [%(levelname)s] %(message)s")
)
logger.addHandler(handler)
logger.propagate = False # Jangan rambatkan ke root
return logger
# main.py — mengimpor log_setup dan memakainya
from log_setup import setup_logger
logger = setup_logger("orders")
logger.info("Pesanan diterima")
Format lebih rinci — timestamp / modul / baris
String format Formatter menerima kode penggantian gaya %(...)s, membiarkanmu mengemas info yang relevan secara operasional ke dalam satu baris. Berikut enam kode yang paling sering dipakai dengan contoh. Nama logger bisa hierarkis lewat bentuk dipisahkan titik seperti parent.child.grandchild — memanggil getLogger(__name__) dari setiap modul otomatis mencatat "modul mana yang memancarkan log ini".
| Kode | Apa yang ditampilkan | Contoh |
|---|---|---|
| %(asctime)s | Timestamp | 2024-12-01 10:30:45 |
| %(name)s | Nama logger (dipisah titik untuk hierarki) | app.orders |
| %(levelname)s | Level log | INFO / WARNING / ERROR |
| %(funcName)s | Nama fungsi pemanggil | process_order |
| %(lineno)d | Nomor baris pemanggil | 42 |
| %(message)s | Tubuh pesan | Memproses pesanan |
import logging
# Format rinci untuk production
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(name)s] [%(levelname)s] %(funcName)s:%(lineno)d - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
force=True,
)
logger = logging.getLogger("app.orders")
def process_order(order_id):
logger.info(f"Memproses pesanan {order_id}")
process_order(1234)
# Contoh output:
# 2024-12-01 10:30:45 [app.orders] [INFO] process_order:13 - Memproses pesanan 1234
Pisahkan config ke file yaml
Begitu konfigurasi logger menjadi kompleks, alih-alih hard-code format / handler / level di Python, pisahkan ke file config yaml. logging.config.dictConfig(...) menerima config dict, jadi cukup parse yaml dan teruskan untuk mengonfigurasi seluruh setup logger. Codebase-mu tiba-tiba menangani "file yaml berbeda untuk production / staging / development" tanpa perubahan.
# logging.yml — sumber tunggal untuk config logger
version: 1
disable_existing_loggers: false
formatters:
default:
format: "[%(name)s] [%(levelname)s] %(message)s"
handlers:
console:
class: logging.StreamHandler
formatter: default
level: INFO
loggers:
app:
level: INFO
handlers: [console]
propagate: false
| Key | Peran | Catatan |
|---|---|---|
| version | Versi skema dictConfig | Saat ini hanya 1. Wajib. |
| disable_existing_loggers | Apakah menonaktifkan logger yang sudah ada | false direkomendasikan (true membungkam logger seperti app.orders dari sebelumnya) |
| formatters | List bernama dari Formatter | Definisikan dengan nama apa pun (misal default), dirujuk dari handler |
| formatters.<name>.format | String format (kode %(...)s) | Sama dengan argumen Formatter di kode |
| handlers | List bernama dari Handler (tujuan) | Definisikan console / file / mail dan seterusnya |
| handlers.<name>.class | Nama lengkap class Handler | logging.StreamHandler / logging.FileHandler / logging.handlers.RotatingFileHandler dll. |
| handlers.<name>.formatter | Nama Formatter yang diterapkan | Pakai key yang didefinisikan di formatters |
| handlers.<name>.level | Level per Handler | Filter output lebih halus daripada level Logger |
| loggers | List bernama dari Logger | loggers.app diambil via logging.getLogger("app") |
| loggers.<name>.handlers | List nama Handler yang dilampirkan ke logger ini | Beberapa OK, seperti [console, file] |
| loggers.<name>.propagate | Apakah merambat ke logger induk | false menghentikan perambatan ke root logger (menghindari output ganda) |
Rotasi log
Jika log terus tumbuh, file membengkak tanpa batas, jadi production butuh rotasi — rename file lama ke nama terpisah dan akhirnya hapus. logging.handlers dilengkapi dengan dua varian — berbasis ukuran dan berbasis waktu — dan kamu memilih berdasarkan kasus penggunaan.
RotatingFileHandler — berbasis ukuran
RotatingFileHandler(filename, maxBytes, backupCount) adalah Handler yang beralih ke file baru ketika maxBytes akan dilampaui. File lama di-rename dengan suffix angka (app.log.1 / app.log.2), dan begitu backupCount dilampaui, yang tertua dihapus. Dengan maxBytes=10_000_000 (10 MB) + backupCount=5, log-mu bersirkulasi dalam plafon 60 MB.
maxBytes, ia di-rename app.log → app.log.1, dan app.log segar dibuat untuk terus menulis. Jika app.log.N sudah ada, ia naik ke app.log.N → app.log.N+1, dan file tertua di luar backupCount dihapus.import logging
from logging.handlers import RotatingFileHandler
# Berbasis ukuran: rotasi ketika maxBytes terlampaui
size_handler = RotatingFileHandler(
"app.log",
maxBytes=10_000_000, # Rotasi ketika ini akan terlampaui (10 MB)
backupCount=5, # Mempertahankan app.log.1 sampai app.log.5 (maks 60 MB total)
)
size_handler.setFormatter(logging.Formatter("[%(levelname)s] %(message)s"))
TimedRotatingFileHandler — berbasis waktu
TimedRotatingFileHandler(filename, when, interval, backupCount) rotasi pada batas waktu yang ditentukan oleh when. Pakai when="midnight" (setiap hari pada 0:00), "H" (per jam), "M" (menit), "S" (detik), "D" (harian), dan seterusnya. Backup membawa suffix timestamp seperti app.log.2024-12-01_00-00-00, jadi nama file memberitahumu "kapan log ini ditulis" langsung.
app.log baru terbuka. File ber-timestamp lebih lama melewati backupCount auto-dihapus.import logging
from logging.handlers import TimedRotatingFileHandler
# Berbasis waktu: rotasi setiap hari tengah malam
day_handler = TimedRotatingFileHandler(
"app.log",
when="midnight", # "S" detik / "M" menit / "H" jam / "D" hari / "midnight" dll.
interval=1, # Setiap N unit (when="H", interval=6 berarti setiap 6 jam)
backupCount=30, # Mempertahankan histori 30 hari
)
day_handler.setFormatter(
logging.Formatter("%(asctime)s [%(levelname)s] %(message)s")
)
Cek Pemahaman
Jawab setiap pertanyaan satu per satu.
Soal 2Ketika kamu meneruskan filename="app.log" ke logging.basicConfig, ke mana log pergi?
Soal 3Manfaat utama memisahkan config logger (format / level / handler) ke modul terpisah adalah apa?