Belajar dengan membaca secara berurutan

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).

Bagaimana logging memproses panggilan
Kodelogger.info(...)LoggerFilter berdasarkan levelHandlerPilih tujuanFormatterRakit format
Ketika kamu memanggil logger.info(...) dari kodemu, alurnya adalah Logger memfilter berdasarkan levelHandler memutuskan tujuanFormatter 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.

5 level logging
DEBUGDetailINFOKemajuanWARNINGPerhatianERRORKegagalanCRITICALFatal
DEBUG (detail) / INFO (kemajuan normal) / WARNING (perhatian) / ERROR (kegagalan) / CRITICAL (bencana). Hanya pesan di atau di atas level Logger yang dipancarkan — DEBUG di development, INFO/WARNING di production adalah pola standar.

Pertama, konfigurasi format dan level dengan basicConfig dan pancarkan satu log INFO.

① Impor logging dan konfigurasi basicConfig dengan level=logging.INFO, format="[%(levelname)s] %(message)s", force=True

② Dapatkan logger via logger = logging.getLogger("app")

③ Pancarkan satu log via logger.info("App dimulai")

(Jika kode kamu jalan dengan benar, penjelasannya akan muncul.)

Python Editor

Jalankan kode untuk melihat output

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".

Pakai ulang config logger dari Latihan 1 dan pancarkan di tiga level. Catatan: di INFO, DEBUG tidak muncul.

① Pancarkan WARNING via logger.warning("Config file is outdated")

② Pancarkan ERROR via logger.error("DB connection failed")

③ Panggil logger.debug("Detailed trace") — di level INFO, tidak boleh ada yang dicetak

Python Editor

Jalankan kode untuk melihat output

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.

Output layar vs. output file
logger.info(...)(layar saja)Terminal ditutup→ Log hilanglogger.info(...)filename=app.loggrep nanti / tarik kealat monitoring
Output layar menghilang ketika terminal ditutup, tetapi output file bertahan untuk grep postmortem dan ingestion alat monitoring. Dengan 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).

Kombinasi filemode dan Handler
filemode="a" append→ Simpan histori (prod)filemode="w" overwrite→ Slate bersih (dev)StreamHandler→ Output layarFileHandler→ Output file
filemode="a" menambahkan dan menyimpan histori (production), sementara filemode="w" menimpa untuk slate bersih setiap run (development). Untuk mengirim ke layar dan file, bangun 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="")

Teruskan filename ke basicConfig untuk beralih log ke file, lalu baca konten kembali dari disk.

① Pakai os.makedirs("logs", exist_ok=True) untuk membuat folder induk dulu (FileHandler tidak auto-membuatnya)

② Impor logging dan panggil basicConfig dengan level=logging.INFO, format="[%(levelname)s] %(message)s", filename="logs/app.log", filemode="w", force=True

③ Dapatkan logger dengan logger = logging.getLogger("app")

④ Pancarkan 3 log di INFO / WARNING / ERROR dengan pesan "Dimulai", "Config kedaluwarsa", "Koneksi DB gagal"

⑤ Buka logs/app.log untuk dibaca, lalu di bawah judul --- isi logs/app.log --- cetak konten tanpa newline akhir (print(content, end=""))

Python Editor

Jalankan kode untuk melihat output

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.

Sentralisasi log_setup; modul mengimpor darinya
log_setup.py(format, tujuan, level)main.pyorders.pyusers.pyimportimportimport
log_setup.py adalah sumber tunggal untuk config logger (format, tujuan, level). Setiap modul (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")

log_setup.py disediakan (buka panel file 📂 di kiri konsol untuk memeriksanya). Dari main.py, impor setup_logger dan pancarkan 3 log dengan logger bernama "orders".

① Impor fungsi via from log_setup import setup_logger

② Dapatkan logger dengan logger = setup_logger("orders")

③ Pancarkan 3 log di INFO / WARNING / ERROR: "Pesanan diterima", "Stok rendah", "API pembayaran mengembalikan error"

Python Editor

Jalankan kode untuk melihat output

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".

KodeApa yang ditampilkanContoh
%(asctime)sTimestamp2024-12-01 10:30:45
%(name)sNama logger (dipisah titik untuk hierarki)app.orders
%(levelname)sLevel logINFO / WARNING / ERROR
%(funcName)sNama fungsi pemanggilprocess_order
%(lineno)dNomor baris pemanggil42
%(message)sTubuh pesanMemproses 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

Pakai 3 nama logger hierarkis — app / app.orders / app.orders.payment — dan tampilkan dari modul mana setiap log datang dengan %(name)s. Di production kamu akan menambahkan %(asctime)s untuk timestamp, tetapi di sini kita tetap pada hierarki saja agar output tetap deterministik.

① Impor logging dan konfigurasi basicConfig dengan level=logging.INFO, format="[%(name)s] [%(levelname)s] %(message)s", force=True

② Dapatkan 3 logger: logging.getLogger("app"), logging.getLogger("app.orders"), logging.getLogger("app.orders.payment")

③ Pancarkan satu log dari masing-masing: app memanggil info("App dimulai"), app.orders memanggil info("Pesanan diterima"), app.orders.payment memanggil error("Error API pembayaran")

Python Editor

Jalankan kode untuk melihat output

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.

Alur file config yaml → dictConfig
logging.ymlformat / handler / levelyaml.safe_load()+ dictConfig()Logger terkonfigurasilogger.info(...)
Tulis format / handler / level ke logging.yml, kemudian konversi ke dict via yaml.safe_load() dan teruskan ke logging.config.dictConfig(...). Sisi Python cukup memuat yaml dan memanggil dictConfig, dan logger ter-set up.
# 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
KeyPeranCatatan
versionVersi skema dictConfigSaat ini hanya 1. Wajib.
disable_existing_loggersApakah menonaktifkan logger yang sudah adafalse direkomendasikan (true membungkam logger seperti app.orders dari sebelumnya)
formattersList bernama dari FormatterDefinisikan dengan nama apa pun (misal default), dirujuk dari handler
formatters.<name>.formatString format (kode %(...)s)Sama dengan argumen Formatter di kode
handlersList bernama dari Handler (tujuan)Definisikan console / file / mail dan seterusnya
handlers.<name>.classNama lengkap class Handlerlogging.StreamHandler / logging.FileHandler / logging.handlers.RotatingFileHandler dll.
handlers.<name>.formatterNama Formatter yang diterapkanPakai key yang didefinisikan di formatters
handlers.<name>.levelLevel per HandlerFilter output lebih halus daripada level Logger
loggersList bernama dari Loggerloggers.app diambil via logging.getLogger("app")
loggers.<name>.handlersList nama Handler yang dilampirkan ke logger iniBeberapa OK, seperti [console, file]
loggers.<name>.propagateApakah merambat ke logger indukfalse menghentikan perambatan ke root logger (menghindari output ganda)

logging.yml disediakan (buka panel file 📂 untuk memeriksanya). Parse ke dict via yaml.safe_load, kemudian teruskan ke logging.config.dictConfig untuk mengonfigurasi logger.

① Impor logging, logging.config, dan yaml

② Buka dengan with open("logging.yml") as f: dan konversi ke dict via yaml.safe_load(f)

③ Teruskan dict ke logging.config.dictConfig(...) untuk menerapkan

④ Dapatkan logger via logger = logging.getLogger("app") dan pancarkan 1 INFO dan 1 WARNING: "Dimulai dengan config yaml" dan "WARNING via yaml"

Python Editor

Jalankan kode untuk melihat output

Rotasi log

Jika log terus tumbuh, file membengkak tanpa batas, jadi production butuh rotasirename 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.

Dua jenis: berbasis ukuran dan berbasis waktu
RotatingFileHandlerBerbasis ukuran(rotasi saat maxBytes terlampaui)TimedRotatingFileHandlerBerbasis waktu(rotasi pada batas when)
Berbasis ukuran cocok untuk log debug di mana kamu ingin batas keras pada penggunaan disk. Berbasis waktu cocok untuk log akses yang ingin kamu iris berdasarkan tanggal untuk analisis atau alat monitoring.

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.

Cara kerja RotatingFileHandler
app.logMenulismaxBytes terlampaui→ rotasiapp.log.1app.log.2 ...(backup)Lewat backupCount→ Tertua dihapus
Ketika file aktif app.log mendekati 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"))

rotate_logging.yml disediakan (buka panel file 📂 untuk memeriksa). Config RotatingFileHandler (maxBytes=60 / backupCount=2) hidup di yaml, dan kode hanya memuat dan memakainya.

① Impor os / shutil, hapus dan buat ulang folder rotate/. Juga bersihkan semua handler pada logger rotate_demo dengan close + removeHandler (menghindari interferensi pada rerun)

② Buka yaml dengan with open("rotate_logging.yml"), konversi via yaml.safe_load(f), dan teruskan ke logging.config.dictConfig(...) untuk mengonfigurasi handler

③ Dapatkan logger = logging.getLogger("rotate_demo") dan pancarkan 15 log dalam loop: for i in range(15): logger.info(f"event {i:02d}")

④ Dari os.listdir("rotate"), ambil hanya file yang dimulai dengan app.log, sortir, dan di bawah judul File setelah rotasi: ◯ cetak setiap nama file sebagai - filename di barisnya sendiri

Python Editor

Jalankan kode untuk melihat output

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.

Cara kerja TimedRotatingFileHandler
app.logMenuliswhen berikutnya (waktu)→ rotasiapp.log.2024-12-01app.log.2024-12-02(suffix timestamp)Lewat backupCount→ Tertua dihapus
Pada waktu yang ditentukan oleh when (setiap hari pada 0:00, dll.), app.log aktif di-rename dengan nama bersuffix timestamp (misalnya, app.log.2024-12-01_00-00-00) dan 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")
)

time_logging.yml disediakan. Config TimedRotatingFileHandler (when: S (per detik) / interval: 1 / backupCount: 3) hidup di yaml, dan kode Python memakai time.sleep untuk menyimulasikan berlalunya waktu untuk memicu rotasi.

① Impor logging / os / shutil / time. Bersihkan semua handler pada logger trotate_demo dengan close + removeHandler, kemudian hapus dan buat ulang folder trotate/

② Buka time_logging.yml, konversi ke dict via yaml.safe_load(f), dan teruskan ke logging.config.dictConfig(...) untuk mengonfigurasi

③ Dapatkan logger dan pancarkan 3 log dengan sleep di antaranya: logger.info(...)time.sleep(1.2)logger.info(...)time.sleep(1.2)logger.info(...), memicu 2 rotasi

④ Dari os.listdir("trotate"), pisahkan app.log (log saat ini) dari semua yang lain (backup ber-timestamp), kemudian cetak Log saat ini ada: True/False dan Jumlah backup: ◯

Python Editor

Jalankan kode untuk melihat output
QUIZ

Cek Pemahaman

Jawab setiap pertanyaan satu per satu.

Soal 1Dengan logging.basicConfig(level=logging.INFO), level mana yang ditekan?

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?