6 Cara Mudah Dan Efektif Menggunakan Python Logging - CRUDPRO

6 Cara Mudah Dan Efektif Menggunakan Python Logging

6 Cara Mudah Dan Efektif Menggunakan Python Logging

Pada artikel ini, saya akan menunjukkan enam alasan praktis mengapa "logging" Python lebih fleksibel dan kuat dan mengapa Anda benar-benar harus menggunakannya jika Anda belum memulai sebelumnya.

Code

Agar lebih praktis, mari pertimbangkan contoh mainan. Saya membuat aplikasi kecil yang menghitung regresi linier untuk dua daftar Python:

import numpy as np
from sklearn.linear_model import LinearRegression
from typing import List, Optional


def do_regression(arr_x: List, arr_y: List) -> Optional[List]:
    """ LinearRegression for X and Y lists """
    try:
        x_in = np.array(arr_x).reshape(-1, 1)
        y_in = np.array(arr_y).reshape(-1, 1)
        print(f"X: {x_in}")
        print(f"y: {y_in}")

        reg = LinearRegression().fit(x_in, y_in)
        out = reg.predict(x_in)
        print(f"Out: {out}")
        print(f"Score: {reg.score(x_in, arr_y)}")
        print(f"Coef: {reg.coef_}")
        return out.reshape(-1).tolist()
    except ValueError as err:
        print(f"ValueError: {err}")
    return None


if __name__ == "__main__":
    print("App started")
    ret = do_regression([1,2,3,4], [5,6,7,8])
    print(f"LinearRegression result: {ret}")

Code ini berfungsi, tetapi bisakah kita melakukannya dengan lebih baik? Kami jelas bisa. Mari kita lihat lima keuntungan menggunakan "logging" daripada "print" dalam code ini.

1. Logging levels

Mari ubah sedikit code kita:

import logging


def do_regression(arr_x: List, arr_y: List) -> Optional[List]:
    """LinearRegression for X and Y Python lists"""
    try:
        x_in = np.array(arr_x).reshape(-1, 1)
        y_in = np.array(arr_y).reshape(-1, 1)
        logging.debug(f"X: {x_in}")
        ...

    except ValueError as err:
        logging.error(f"ValueError: {err}")
    return None


if __name__ == "__main__":     
    logging.basicConfig(level=logging.DEBUG, format='%(message)s')

    logging.info("App started")
    ret = do_regression([1,2,3,4], [5,6,7,8])
    logging.info(f"LinearRegression result: {ret}")

Di sini saya mengganti panggilan "print" dengan panggilan "logging". Kami membuat perubahan kecil, tapi hasilnya lebih fleksibel. Dengan menggunakan parameter "level", kita sekarang bisa mengatur level logging yang berbeda. Misalkan, jika kita menggunakan "level=logging.DEBUG", maka semua output akan terlihat. Saat kami yakin bahwa code kami siap untuk memproduksi, kami bisa mengubah level menjadi "logging.INFO", dan pesan debug tidak ditampilkan lagi:

6 Cara Mudah Dan Efektif Menggunakan Python Logging

Dan yang terpenting adalah tidak diperlukan perubahan code kecuali inisialisasi logging tersebut!

Omong-omong, semua konstanta yang tersedia bisa ditemukan di file logging/__init__.py :

ERROR = 40
WARNING = 30
INFO = 20
DEBUG = 10
NOTSET = 0

Seperti yang bisa kita lihat, level "ERROR" ialah yang tertinggi; dengan mengaktifkan level log "ERROR", kami dapat menyembunyikan semua pesan lain, dan hanya kesalahan yang hendak ditampilkan.

2. Formatting

Seperti yang dapat kita lihat dari tangkapan monitor terakhir, mudah untuk mengontrol output logging. Tetapi kita dapat berbuat lebih banyak untuk meningkatkan itu. Kami bisa menyesuaikan output dengan memberikan string "format". Misalkan, saya bisa menentukan pemformatan seperti ini:

logging.basicConfig(level=logging.DEBUG,
                    format='[%(asctime)s] %(filename)s:%(lineno)d: %(message)s')

Tanpa perubahan code yang lain, saya akan bisa melihat stempel waktu, nama file, dan bahkan nomor baris di output:

6 Cara Mudah Dan Efektif Menggunakan Python Logging

Ada sekitar 20 parameter berbeda yang tersedia, yang bisa ditemukan di paragraf "LogRecord attributes" dari manual.

3. Saving logs to a file

Logging Python ialah modul yang sangat fleksibel, dan fungsinya dapat dengan mudah diperluas. Katakanlah kami ingin menyimpan semua log kami ke file untuk analisis di masa mendatang. Untuk melakukan ini, kita perlu menambahkan dua baris code:

logging.basicConfig(level=logging.DEBUG,
                    format='[%(asctime)s] %(filename)s:%(lineno)d: %(message)s')

Seperti yang dapat kita lihat, saya menambahkan parameter baru "handlers". StreamHandler menampilkan log di konsol, dan FileHandler, seperti yang dapat kita tebak dari namanya, menyimpan output yang sama ke file.

Sistem ini sangat fleksibel. Banyak object "handlers" yang berbeda tersedia di Python, dan saya mendorong pembaca untuk memeriksa manualnya sendiri. Dan sama seperti yang telah kita ketahui, logging bekerja hampir secara otomatis; tidak diperlukan perubahan code lebih lanjut.

4. Rotating log files

Menyimpan log ke file ialah pilihan yang bagus, tapi sayangnya, ruang disk tak terbatas. Kami dapat dengan mudah mengatasi permasalahan ini dengan menggunakan rotating log files:

from logging.handlers import TimedRotatingFileHandler

...

if __name__ == "__main__":
    file_handler = TimedRotatingFileHandler(
            filename="debug.log",
            when="midnight",
            interval=1,
            backupCount=3,
        )
    logging.basicConfig(level=logging.DEBUG, 
                        format='[%(asctime)s] %(message)s',
                        handlers=[file_handler, logging.StreamHandler()])

Semua parameter cukup jelas. Object TimedRotatingFileHandler akan membuat file log, yang akan diubah tiap tengah malam, dan hanya tiga file log terakhir yang akan disimpan. File sebelumnya akan secara otomatis diganti namanya jadi sesuatu seperti "debug.log.2023.03.03", dan sesudah interval tiga hari, file itu akan dihapus.

5. Sending logs via socket

Pencatatan Python ternyata sangat fleksibel. Bila kita tidak ingin menyimpan log ke file lokal, kita cukup menambahkan socket handler, yang akan mengirimkan log ke service lain menggunakan IP dan port tertentu:

from logging.handlers import SocketHandler


logging.basicConfig(level=logging.DEBUG, format='[%(asctime)s] %(message)s',
                    handlers=[SocketHandler(host="127.0.0.1", port=15001), 
                              logging.StreamHandler()])

Itu dia; tidak perlu lagi mengubah code!

Kami bisa membuat aplikasi lain yang akan mendengarkan port yang sama:

import socket
import logging
import pickle
import struct
from logging import LogRecord


port = 15001
stream_handler = logging.StreamHandler()


def create_socket() -> socket.socket:
    """Create the socket"""
    sock = socket.socket(socket.AF_INET)
    sock.settimeout(30.0)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    return sock


def read_socket_data(conn_in: socket.socket):
    """Read data from socket"""
    while True:
        data = conn_in.recv(4)  # Data: 4 bytes length + body
        if len(data) > 0:
            body_len = struct.unpack(">L", data)[0]
            data = conn_in.recv(body_len)
            record: LogRecord = logging.makeLogRecord(pickle.loads(data))
            stream_handler.emit(record)
        else:
            logging.debug("Socket connection lost")
            return


if __name__ == "__main__":
    logging.basicConfig(level=logging.DEBUG, format='[%(asctime)s] %(message)s',
                        handlers=[stream_handler])

    sock = create_socket()
    sock.bind(("127.0.0.1", port))  # Local connections only
    sock.listen(1)  # One client can be connected
    logging.debug("Logs listening thread started")
    while True:
        try:
            conn, _ = sock.accept()
            logging.debug("Socket connection established")
            read_socket_data(conn)
        except socket.timeout:
            logging.debug("Socket listening: no data")

Bagian yang sulit di sini yaitu menggunakan sistem emit, yang menambahkan semua data jarak jauh yang diterima oleh socket ke StreamHandler yang aktif.

6. Filter log

Untuk menambahkan filter khusus ke log. Katakanlah kita hanya ingin memasukkan nilai X dan Y ke file untuk analisis di masa mendatang. Sangat mudah untuk membuat kelas Filter baru, yang hanya akan menyimpan log string yang berisi catatan "x:" atau "y:":

from logging import LogRecord, Filter


class DataFilter(Filter):
    """Filter for logging messages"""

    def filter(self, record: LogRecord) -> bool:
        """Save only filtered data"""
        return "x:" in record.msg.lower() or "y:" in record.msg.lower()

Kemudian kita dapat dengan mudah menambahkan filter ini ke file log. Output konsol kami akan tetap utuh, tapi file hanya akan memiliki nilai "x:" dan "y:".

file_handler = logging.FileHandler("debug.log")
file_handler.addFilter(DataFilter())

logging.basicConfig(level=logging.DEBUG, 
                    format='[%(asctime)s] %(message)s',
                    handlers=[file_handler, logging.StreamHandler()])

Kesimpulan

Dalam artikel singkat ini, kita mempelajari beberapa cara mudah untuk memasukkan log ke program Python. Masuk dengan Python ialah kerangka kerja yang fleksibel, dan pasti layak menghabiskan waktu untuk menyelidiki cara kerjanya.