Pipeline yang berjalan sebulan sekali tidak selalu diawasi seseorang. Ketika hasilnya dicurigai, pertanyaan pertama selalu sama: apa yang sebenarnya terjadi saat dijalankan? Pada bagian ini kita memasang logging pada seluruh tahap dengan modul logging bawaan Python. Level yang tepat dipilih per kejadian, keluaran diarahkan ke dua tempat sekaligus, dan formatnya dibuat seragam.

Untuk pipeline produksi, print tidak memenuhi syarat:

Aspekprintlogging
Level keparahantidak adaDEBUG sampai ERROR
Waktu kejadianharus dihitung sendiriotomatis dari formatter
Tujuan keluaransatu tempatkonsol dan berkas sekaligus
Menyaring saat bermasalahmengubah kodeganti level saja

Satu baris log kita memuat tiga informasi: waktu kejadian, level, dan pesan. Baris WARNING yang sama tampil di layar operator dan tersimpan di data/processed/pipeline.log untuk ditelusuran minggu berikutnya.

setup_logger #

Satu fungsi di src/logging_setup.py menyiapkan semuanya:

python
FORMAT_LOG = "%(asctime)s | %(levelname)-8s | %(message)s"
FORMAT_WAKTU = "%Y-%m-%d %H:%M:%S"


def setup_logger(
    nama: str = "pipeline",
    berkas: str | None = None,
    level: int = logging.INFO,
) -> logging.Logger:
    """Siapkan logger dengan handler konsol dan (opsional) handler berkas.

    - nama   : nama logger; ambil logger yang sama dengan nama yang sama.
    - berkas : bila diisi, baris log juga ditulis ke berkas ini.
    - level  : batas level yang dicatat (default INFO).
    """
    logger = logging.getLogger(nama)
    logger.setLevel(level)
    logger.propagate = False
    logger.handlers.clear()

    pemformat = logging.Formatter(FORMAT_LOG, datefmt=FORMAT_WAKTU)

    konsol = logging.StreamHandler()
    konsol.setFormatter(pemformat)
    logger.addHandler(konsol)

    if berkas:
        Path(berkas).parent.mkdir(parents=True, exist_ok=True)
        ke_berkas = logging.FileHandler(berkas, encoding="utf-8")
        ke_berkas.setFormatter(pemformat)
        logger.addHandler(ke_berkas)

    return logger

Tiga keputusan pada fungsi ini layak diperhatikan:

  1. logger.handlers.clear(): panggilan setup_logger kedua kali mengganti handler lama. Tanpa baris ini, menjalankan pipeline dua kali dalam satu proses akan menggandakan setiap baris log.
  2. logger.propagate = False: logger tidak meneruskan barisnya ke logger induk, sehingga tidak muncul dua kali lewat jalur root.
  3. Dua handler, satu format: konsol untuk operator, berkas untuk arsip. Keduanya memakai pemformat yang sama sehingga isinya identik.

Log di Sepanjang Pipeline #

Modul src/pipeline.py mencatat setiap tahap. Ringkasannya sebagai berikut:

TahapLevelBaris log
Mulai prosesINFOjumlah berkas sumber
Baris ditolakWARNINGnama berkas, nomor baris, kode masalah, alasan
Ringkasan per berkasINFOdibaca, ditolak, lolos
PembersihanINFOjumlah baris bersih
Duplikat dibuangWARNINGjumlah persis dan konflik
PemuatanINFOjumlah baris dan tujuan
SelesaiINFOrekap akhir empat angka

Level dipilih menurut pertanyaan yang dijawab, bukan selera. INFO menjawab “tahapan mana yang selesai”; WARNING menjawab “data mana yang tidak masuk dan mengapa”. ERROR hanya untuk keadaan yang memang menghentikan pipeline, misalnya header berkas tidak dikenal.

Berkas Log yang Terus Bertambah #

Handler berkas menambahkan baris baru pada setiap eksekusi; satu eksekusi penuh menambah 31 baris. Untuk latihan ini ukurannya kecil, tetapi pipeline produksi perlu pembatasan. Kebiasaan yang umum adalah rotasi: memakai RotatingFileHandler yang memecah berkas log berdasarkan ukuran, lalu menyimpan beberapa generasi terakhir. Kita memilih FileHandler polos agar contoh tetap kecil; bila ingin mencoba rotasi, cukup ganti satu baris di setup_logger.

Log Hasil Eksekusi Nyata #

Berkas data/processed/pipeline.log berikut adalah hasil eksekusi sungguhan. Dua belas baris pertama diperlihatkan:

bash
head -12 data/processed/pipeline.log

Output yang Dihasilkan:

text
2026-10-17 07:42:21 | INFO     | Memulai pipeline: 2 berkas sumber
2026-10-17 07:42:21 | WARNING  | pesanan_2024-06.csv: baris 9 ditolak (email_tidak_valid): email harus berformat [email protected]
2026-10-17 07:42:21 | WARNING  | pesanan_2024-06.csv: baris 14 ditolak (jumlah_tidak_positif): jumlah tidak boleh negatif
2026-10-17 07:42:21 | WARNING  | pesanan_2024-06.csv: baris 20 ditolak (harga_kosong): harga tidak boleh kosong
2026-10-17 07:42:21 | WARNING  | pesanan_2024-06.csv: baris 25 ditolak (tanggal_tidak_valid): tanggal harus tanggal nyata berformat YYYY-MM-DD
2026-10-17 07:42:21 | WARNING  | pesanan_2024-06.csv: baris 32 ditolak (order_id_tidak_valid): pola order_id harus ORD-YYYY-NNNN
2026-10-17 07:42:21 | WARNING  | pesanan_2024-06.csv: baris 38 ditolak (kategori_tidak_dikenal): kategori harus salah satu dari: buku, elektronik, fashion, makanan, olahraga
2026-10-17 07:42:21 | WARNING  | pesanan_2024-06.csv: baris 43 ditolak (pelanggan_kosong): nama pelanggan tidak boleh kosong
2026-10-17 07:42:21 | WARNING  | pesanan_2024-06.csv: baris 49 ditolak (tanggal_tidak_valid): tanggal harus tanggal nyata berformat YYYY-MM-DD
2026-10-17 07:42:21 | WARNING  | pesanan_2024-06.csv: baris 54 ditolak (jumlah_tidak_positif): jumlah minimal 1
2026-10-17 07:42:21 | WARNING  | pesanan_2024-06.csv: baris 60 ditolak (email_kosong): email tidak boleh kosong
2026-10-17 07:42:21 | WARNING  | pesanan_2024-06.csv: baris 65 ditolak (produk_kosong): nama produk tidak boleh kosong

Satu eksekusi lengkap menghasilkan 31 baris: 21 baris WARNING untuk penolakan, dan 10 baris INFO untuk tahapan. Waktu pada kolom pertama adalah waktu nyata eksekusi; eksekusi berikutnya mencatat waktu yang berbeda. Nomor baris pada pesan penolakan merujuk nomor baris berkas CSV, sehingga operator dapat membuka berkas sumber tepat ke baris tersebut.

Materi lanjutan tentang level, handler, dan penelusuran kejadian ada di chapter Logging dan Pipeline Logging. Rujukan resmi: dokumentasi modul logging Python .

Seluruh tahap kini menghasilkan jejak yang dapat ditelusuri. Tahap berikutnya memastikan jejak itu sesuai janji: kita menguji setiap lapisan dengan pytest sebelum menjalankan pipeline di data asli. Lanjutkan ke Bagian 8: Menguji Pipeline.