Python Logging: Tutorial Mendalam
Diterbitkan: 2022-03-11Saat aplikasi menjadi lebih kompleks, memiliki log yang baik bisa sangat berguna, tidak hanya saat debugging tetapi juga untuk memberikan wawasan tentang masalah/kinerja aplikasi.
Pustaka standar Python dilengkapi dengan modul logging yang menyediakan sebagian besar fitur logging dasar. Dengan mengaturnya dengan benar, pesan log dapat membawa banyak informasi berguna tentang kapan dan di mana log diaktifkan serta konteks log, seperti proses/utas yang sedang berjalan.
Terlepas dari kelebihannya, modul logging sering diabaikan karena membutuhkan waktu untuk menyiapkan dengan benar dan, menurut pendapat saya, lengkap dengan dokumen logging resmi di https://docs.python.org/3/library/logging.html tidak benar-benar memberikan praktik terbaik logging atau menyoroti beberapa kejutan logging.
Tutorial logging Python ini tidak dimaksudkan sebagai dokumen lengkap tentang modul logging melainkan panduan "memulai" yang memperkenalkan beberapa konsep logging serta beberapa "gotchas" yang harus diperhatikan. Postingan akan diakhiri dengan praktik terbaik dan berisi beberapa petunjuk ke topik logging yang lebih lanjut.
Harap dicatat bahwa semua potongan kode di pos menganggap bahwa Anda telah mengimpor modul logging:
import logging
Konsep untuk Pencatatan Python
Bagian ini memberikan gambaran tentang beberapa konsep yang sering ditemui dalam modul logging.
Level Pencatatan Python
Level log sesuai dengan "kepentingan" log yang diberikan: log "kesalahan" harus lebih mendesak daripada log "peringatkan", sedangkan log "debug" harus berguna hanya saat men-debug aplikasi.
Ada enam level log di Python; setiap level dikaitkan dengan bilangan bulat yang menunjukkan tingkat keparahan log: NOTSET=0, DEBUG=10, INFO=20, WARN=30, ERROR=40, dan CRITICAL=50.
Semua level cukup mudah (DEBUG < INFO < WARN ) kecuali NOTSET, yang kekhususannya akan dibahas selanjutnya.
Pemformatan Pencatatan Python
Pemformat log pada dasarnya memperkaya pesan log dengan menambahkan informasi konteks ke dalamnya. Akan berguna untuk mengetahui kapan log dikirim, di mana (file Python, nomor baris, metode, dll.), dan konteks tambahan seperti utas dan proses (bisa sangat berguna saat men-debug aplikasi multithread).
Misalnya, ketika log "hello world" dikirim melalui pemformat log:
"%(asctime)s — %(name)s — %(levelname)s — %(funcName)s:%(lineno)d — %(message)s"
itu akan menjadi
2018-02-07 19:47:41,864 - abc - WARNING - <module>:1 - hello world
Penangan Pencatatan Python
Handler log adalah komponen yang secara efektif menulis/menampilkan log: Menampilkannya di konsol (melalui StreamHandler), dalam file (melalui FileHandler), atau bahkan dengan mengirimi Anda email melalui SMTPHandler, dll.
Setiap penangan log memiliki 2 bidang penting:
- Pemformat yang menambahkan informasi konteks ke log.
- Level log yang menyaring log yang levelnya lebih rendah. Jadi penangan log dengan level INFO tidak akan menangani log DEBUG.
Pustaka standar menyediakan beberapa penangan yang seharusnya cukup untuk kasus penggunaan umum: https://docs.python.org/3/library/logging.handlers.html#module-logging.handlers. Yang paling umum adalah StreamHandler dan FileHandler:
console_handler = logging.StreamHandler() file_handler = logging.FileHandler("filename")
Pencatat Python
Logger mungkin yang paling sering digunakan secara langsung dalam kode dan juga yang paling rumit. Logger baru dapat diperoleh dengan:
toto_logger = logging.getLogger("toto")
Logger memiliki tiga bidang utama:
- Propagate: Memutuskan apakah log harus disebarkan ke induk logger. Secara default, nilainya adalah True.
- Level A: Seperti level pengendali log, level logger digunakan untuk menyaring log yang “kurang penting”. Kecuali, tidak seperti pengendali log, level hanya diperiksa di logger "anak"; setelah log disebarkan ke induknya, levelnya tidak akan diperiksa. Ini lebih merupakan perilaku yang tidak intuitif.
- Handlers: Daftar handler yang akan dikirimi log ketika sampai ke logger. Hal ini memungkinkan penanganan log yang fleksibel—misalnya, Anda dapat memiliki penangan log file yang mencatat semua log DEBUG dan penangan log email yang hanya akan digunakan untuk log CRITICAL. Dalam hal ini, hubungan logger-handler mirip dengan hubungan penerbit-konsumen: Sebuah log akan disiarkan ke semua penangan setelah melewati pemeriksaan tingkat logger.
Logger unik berdasarkan namanya, artinya jika logger dengan nama "toto" telah dibuat, panggilan berikutnya dari logging.getLogger("toto")
akan mengembalikan objek yang sama:
assert id(logging.getLogger("toto")) == id(logging.getLogger("toto"))
Seperti yang mungkin sudah Anda duga, penebang memiliki hierarki. Di atas hierarki adalah logger root, yang dapat diakses melalui logging.root. Logger ini dipanggil saat metode seperti logging.debug()
digunakan. Secara default, level log root adalah WARN, jadi setiap log dengan level yang lebih rendah (misalnya melalui logging.info("info")
) akan diabaikan. Keistimewaan lain dari root logger adalah bahwa handler defaultnya akan dibuat saat pertama kali log dengan level lebih besar dari WARN dicatat. Menggunakan logger root secara langsung atau tidak langsung melalui metode seperti logging.debug()
umumnya tidak disarankan.

Secara default, ketika logger baru dibuat, induknya akan disetel ke logger root:
lab = logging.getLogger("ab") assert lab.parent == logging.root # lab's parent is indeed the root logger
Namun, logger menggunakan "notasi titik", yang berarti bahwa logger dengan nama "ab" akan menjadi anak dari logger "a." Namun, ini hanya berlaku jika logger "a" telah dibuat, jika tidak, induk "ab" masih menjadi root.
la = logging.getLogger("a") assert lab.parent == la # lab's parent is now la instead of root
Ketika logger memutuskan apakah log harus lulus sesuai dengan pemeriksaan level (misalnya, jika level log lebih rendah dari level logger, log akan diabaikan), ia menggunakan "level efektif" alih-alih level sebenarnya. Level efektif sama dengan level logger jika levelnya bukan NOTSET, yaitu semua nilai dari DEBUG sampai CRITICAL; namun, jika level logger NOTSET, maka level efektif akan menjadi level ancestor pertama yang memiliki level non-NOTSET.
Secara default, logger baru memiliki level NOTSET, dan karena root logger memiliki level WARN, level efektif logger akan menjadi WARN. Jadi, bahkan jika logger baru memiliki beberapa penangan yang terpasang, penangan ini tidak akan dipanggil kecuali jika tingkat log melebihi PERINGATAN:
toto_logger = logging.getLogger("toto") assert toto_logger.level == logging.NOTSET # new logger has NOTSET level assert toto_logger.getEffectiveLevel() == logging.WARN # and its effective level is the root logger level, ie WARN # attach a console handler to toto_logger console_handler = logging.StreamHandler() toto_logger.addHandler(console_handler) toto_logger.debug("debug") # nothing is displayed as the log level DEBUG is smaller than toto effective level toto_logger.setLevel(logging.DEBUG) toto_logger.debug("debug message") # now you should see "debug message" on screen
Secara default, level logger akan digunakan untuk memutuskan lintasan log: Jika level log lebih rendah dari level logger, log akan diabaikan.
Praktik Terbaik Pencatatan Python
Modul logging memang sangat berguna, tetapi berisi beberapa kebiasaan yang dapat menyebabkan sakit kepala berjam-jam bahkan untuk pengembang Python terbaik. Berikut adalah praktik terbaik untuk menggunakan modul ini menurut saya:
- Konfigurasikan logger root tetapi jangan pernah menggunakannya dalam kode Anda—misalnya, jangan pernah memanggil fungsi seperti
logging.info()
, yang sebenarnya memanggil root logger di belakang layar. Jika Anda ingin menangkap pesan kesalahan dari pustaka yang Anda gunakan, pastikan untuk mengonfigurasi logger root untuk menulis ke file, misalnya, untuk mempermudah proses debug. Secara default, logger root hanya menghasilkanstderr
, sehingga log bisa hilang dengan mudah. - Untuk menggunakan logging, pastikan untuk membuat logger baru dengan menggunakan
logging.getLogger(logger name)
. Saya biasanya menggunakan__name__
sebagai nama logger, tetapi apa pun bisa digunakan, asalkan konsisten. Untuk menambahkan lebih banyak penangan, saya biasanya memiliki metode yang mengembalikan logger (Anda dapat menemukan intinya di https://Gist.github.com/nguyenkims/e92df0f8bd49973f0c94bddf36ed7fd0).
import logging import sys from logging.handlers import TimedRotatingFileHandler FORMATTER = logging.Formatter("%(asctime)s — %(name)s — %(levelname)s — %(message)s") LOG_FILE = "my_app.log" def get_console_handler(): console_handler = logging.StreamHandler(sys.stdout) console_handler.setFormatter(FORMATTER) return console_handler def get_file_handler(): file_handler = TimedRotatingFileHandler(LOG_FILE, when='midnight') file_handler.setFormatter(FORMATTER) return file_handler def get_logger(logger_name): logger = logging.getLogger(logger_name) logger.setLevel(logging.DEBUG) # better to have too much log than not enough logger.addHandler(get_console_handler()) logger.addHandler(get_file_handler()) # with this pattern, it's rarely necessary to propagate the error up to parent logger.propagate = False return logger
Setelah Anda dapat membuat logger baru dan menggunakannya:
my_logger = get_logger("my module name") my_logger.debug("a debug message")
- Gunakan kelas RotatingFileHandler, seperti TimedRotatingFileHandler yang digunakan dalam contoh alih-alih FileHandler, karena ini akan memutar file untuk Anda secara otomatis ketika file mencapai batas ukuran atau melakukannya setiap hari.
- Gunakan alat seperti Sentry, Airbrake, Raygun, dll., untuk menangkap log kesalahan secara otomatis untuk Anda. Ini sangat berguna dalam konteks aplikasi web, di mana log bisa sangat bertele-tele dan log kesalahan bisa hilang dengan mudah. Keuntungan lain menggunakan alat ini adalah Anda bisa mendapatkan detail tentang nilai variabel dalam kesalahan sehingga Anda dapat mengetahui URL apa yang memicu kesalahan, pengguna mana yang bersangkutan, dll.
Jika Anda tertarik dengan praktik terbaik lainnya, baca 10 Kesalahan Paling Umum yang Dilakukan Pengembang Python oleh sesama Toptaler Martin Chikilian.