Debugging

Membaca log task dan melakukan forensik kegagalan umum, dari kehabisan memori hingga deadlock.

Daftar isi

Log task adalah rekaman eksekusi satu task instance, dan kemampuan membacanya dengan cepat menentukan berapa lama pipeline berhenti. Topik ini membahas asal log task pada Airflow, forensik atas tiga kegagalan nyata yang jenisnya berbeda, dan status perintah baca log pada CLI Airflow 3. Bagian akhir menyajikan pola kegagalan yang tidak menyisakan traceback, sehingga diagnosisnya berbeda. Semua kegagalan pada topik ini benar-benar dieksekusi dan keluarannya diambil dari log eksekusi tersebut.

Asal log task#

Setiap task instance menulis log melalui logger Airflow, dan lokasi penyimpanannya dikendalikan oleh konfigurasi base_log_folder dengan nilai bawaan di bawah AIRFLOW_HOME/logs. Pada deployment yang berjalan penuh dengan scheduler dan worker, Airflow menyusun log task dalam struktur folder bernama dag_id=<nama dag>/run_id=<nama run>/task_id=<nama task>/attempt=<nomor percobaan>. Struktur tersebut membuat satu berkas log menggambarkan tepat satu percobaan eksekusi satu task. Antarmuka web menampilkan isi berkas yang sama pada tab Log panel task instance, sehingga membaca log melalui UI dan membaca berkas log secara langsung memberikan hasil yang identik.

Eksekusi lokal tidak membentuk berkas log per task. Perintah dags test mengeksekusi task di dalam satu proses Python dan seluruh log task menyatu pada keluaran perintahnya, sebagaimana digunakan pada seluruh latihan jalur ini. Pengetahuan yang sama tetap berlaku pada kedua bentuk log, yaitu blok traceback muncul tepat setelah pesan Task failed with exception, satu blok untuk setiap task yang gagal.

Forensik traceback pada log eksekusi#

Bukti forensik paling kaya berasal dari task yang gagal dengan jenis error yang berbeda-beda. DAG berikut memuat tiga task tanpa dependensi, dan ketiganya sengaja dibuat gagal, yaitu pembagian dengan nol, pencarian kunci kamus yang tidak ada, serta task yang melampaui batas waktunya.

python dag_laporan_suhu.py
"""DAG laporan suhu ruang ujian untuk latihan membaca log kegagalan.

Tiga task berjalan tanpa dependensi dan masing-masing sengaja gagal
dengan jenis error yang berbeda: ZeroDivisionError, KeyError, dan
pelampauan batas execution_timeout.
"""
import time
from datetime import timedelta

import pendulum
from airflow.sdk import dag, task

CATATAN = {
    "R-101": [25.1, 26.3, 24.8],
    "R-102": [24.8, 25.6],
}


@dag(
    dag_id="laporan_suhu",
    schedule=None,
    start_date=pendulum.datetime(2026, 11, 1, tz="UTC"),
    description="Tiga task yang sengaja gagal untuk latihan forensik log.",
)
def laporan_suhu():
    @task
    def hitung_rata() -> None:
        """Menghitung rata-rata suhu; membagi nol ketika data kosong."""
        suhu = CATATAN.get("R-103", [])
        rata = sum(suhu) / len(suhu)
        print(f"rata-rata R-103: {rata}")

    @task
    def catat_rekap() -> None:
        """Menuliskan rekap; mencari kunci ruang yang tidak ada."""
        maksimum = max(CATATAN["R-105"])
        print(f"suhu maksimum: {maksimum}")

    @task(execution_timeout=timedelta(seconds=5))
    def kumpulkan_lama() -> None:
        """Menunggu sensor perangkat yang tidak pernah selesai."""
        print("mulai mengumpulkan catatan perangkat")
        time.sleep(60)
        print("selesai")

    hitung_rata()
    catat_rekap()
    kumpulkan_lama()


laporan_suhu()

Argumen execution_timeout pada task ketiga memasang batas durasi lima detik, dan pelampauan batas itu memunculkan exception AirflowTaskTimeout. Lingkungan latihan disiapkan pada folder terpisah, berkas DAG dipastikan dapat diparse, lalu DAG dijalankan sekali. Kegagalan task membuat dags test berakhir dengan status gagal, sehingga perintahnya diakhiri || echo agar kejadian itu tercatat tanpa menghentikan urutan langkah.

bash
# lingkungan latihan terpisah untuk topik ini
export AIRFLOW_HOME=/tmp/ra-debugging
export AIRFLOW__CORE__LOAD_EXAMPLES=False
bash
# salin berkas DAG ke folder yang dipindai Airflow
export AIRFLOW_HOME=/tmp/ra-debugging
mkdir -p "$AIRFLOW_HOME/dags"
cp dag_laporan_suhu.py "$AIRFLOW_HOME/dags/"
bash
# siapkan skema basis data metadata
# log migrasi berisi stempel waktu, sehingga tidak ditampilkan
export AIRFLOW_HOME=/tmp/ra-debugging
airflow db migrate > /dev/null 2>&1
bash
# jalankan DAG sekali secara lokal, lognya dialihkan ke berkas
# perintah diakhiri echo karena task yang gagal membuatnya berakhir gagal
export AIRFLOW_HOME=/tmp/ra-debugging
airflow dags test laporan_suhu 2026-11-04 > "$AIRFLOW_HOME/dags-test.log" 2>&1 || echo "dags test berakhir dengan task gagal"
text Keluaran
dags test berakhir dengan task gagal

Lognya berisi tiga blok traceback, satu untuk setiap task yang gagal. Baris-baris inti dari ketiga blok itu diambil dengan satu pola pencarian per task, yaitu baris kode task yang bersangkutan beserta baris pesan exception-nya. Pola per task menjaga urutan keluaran tetap sama antar eksekusi, karena tiga task tanpa dependensi berjalan bersamaan dan urutan kegagalannya pada log bisa berubah.

bash
# ambil baris kode task dan baris exception dari setiap traceback
# satu pola per task agar urutan keluaran tidak bergantung pada log
export AIRFLOW_HOME=/tmp/ra-debugging
grep -E 'dag_laporan_suhu\.py", line [0-9]+, in catat_rekap|KeyError:' "$AIRFLOW_HOME/dags-test.log"
grep -E 'dag_laporan_suhu\.py", line [0-9]+, in kumpulkan_lama|raise AirflowTaskTimeout' "$AIRFLOW_HOME/dags-test.log"
grep -E 'dag_laporan_suhu\.py", line [0-9]+, in hitung_rata|ZeroDivisionError:' "$AIRFLOW_HOME/dags-test.log"
text Keluaran
  File "/tmp/ra-debugging/dags/dag_laporan_suhu.py", line 36, in catat_rekap
KeyError: 'R-105'
  File "/tmp/ra-debugging/dags/dag_laporan_suhu.py", line 43, in kumpulkan_lama
    raise AirflowTaskTimeout(self.error_message)
  File "/tmp/ra-debugging/dags/dag_laporan_suhu.py", line 30, in hitung_rata
ZeroDivisionError: division by zero

Traceback dibaca dari bawah ke atas. Baris terakhir selalu berisi jenis exception beserta pesannya, sedangkan baris di atasnya menyatakan lokasi kode yang memunculkan exception tersebut. Baris KeyError: 'R-105' menunjuk line 36, in catat_rekap, yaitu pencarian kunci R-105 pada kamus CATATAN yang memang tidak memuat ruang tersebut. Baris ZeroDivisionError: division by zero menunjuk line 30, in hitung_rata, yaitu pembagian dengan panjang list yang bernilai nol karena CATATAN.get("R-103", []) mengembalikan list kosong. Baris ketiga berasal dari kode internal Airflow, tetapi tetap mengarah ke task yang sama, yaitu kumpulkan_lama yang dihentikan oleh batas waktunya sebelum time.sleep(60) selesai.

Status akhir ketiga task dan dag run-nya memperkuat pembacaan tersebut. Tiga task berstatus gagal dan dag run ditutup dengan status yang sama.

bash
# hitung task yang berstatus gagal dan dag run yang berstatus gagal
export AIRFLOW_HOME=/tmp/ra-debugging
grep -c "new_state=failed" "$AIRFLOW_HOME/dags-test.log"
grep -c "state=failed, run_type=manual" "$AIRFLOW_HOME/dags-test.log"
text Keluaran
3
1

Membaca log melalui CLI#

Airflow 2.x menyediakan airflow tasks logs untuk menampilkan log task dari terminal, dan perintah tersebut dihapus pada Airflow 3. Perintah berikut memperlihatkan respons CLI Airflow 3 atas perintah lama itu. Karena kegagalan itulah yang hendak ditunjukkan, perintahnya diakhiri || true.

bash
# perintah tasks logs tidak lagi tersedia pada Airflow 3
export AIRFLOW_HOME=/tmp/ra-debugging
airflow tasks logs laporan_suhu catat_rekap 2026-11-04 2>&1 | tail -n 1 || true
text Keluaran
airflow tasks command error: argument COMMAND: invalid choice: 'logs' (choose from clear, failed-deps, list, render, state, states-for-dag-run, test), see help above.

Kegagalan tanpa traceback#

Dua pola kegagalan yang paling sulit didiagnosis justru tidak menyisakan traceback sama sekali. Pola pertama adalah task yang dihentikan sistem operasi karena kehabisan memori. Kernel menghentikan proses worker yang kehabisan memori, sehingga log worker berhenti mendadak di tengah aktivitas tanpa baris Task failed with exception.

Task berstatus gagal tanpa satu pun pesan exception. Tanda konfirmasinya ada di log sistem operasi, misalnya dmesg atau journalctl yang memperlihatkan kernel menghentikan proses karena out of memory. Perbaikannya mengecilkan cakupan kerja per task, misalnya memproses data per partisi alih-alih seluruh rentang sekaligus.

Pola kedua adalah deadlock, yaitu dua proses saling menunggu kunci yang dipegang masing-masing sehingga tidak ada kemajuan. Task yang terjebak deadlock tetap berjalan, lognya terus menampilkan aktivitas menunggu, tetapi proses tidak pernah maju. Batas execution_timeout pada task yang membaca atau menulis basis data menjadi pengaman di sini. Pelampauan batas menghentikan task yang macet, sehingga kebuntuan tanpa batas waktu berubah menjadi kegagalan yang terlihat pada log.

Kesalahan umum#

  • Membaca traceback dari atas. Frame atas pada traceback berisi kode internal Airflow yang panjang dan berbeda antar versi, sehingga pembacaannya memakan waktu tanpa hasil. Bacalah dari baris paling bawah, yaitu jenis exception dan pesannya, lalu lihat satu baris di atasnya untuk menemukan berkas dan nomor baris kode task yang bersangkutan.
  • Mengandalkan airflow tasks logs pada Airflow 3. Perintah tersebut telah dihapus, sehingga alur kerja yang dibangun di atasnya berhenti. Gunakan tab Log pada antarmuka web, berkas log pada base_log_folder, atau keluaran dags test untuk eksekusi lokal.
  • Menjalankan task berat tanpa execution_timeout. Task yang macet karena deadlock atau koneksi yang tidak pernah selesai menempati slot worker tanpa batas waktu. Pasang batas durasi pada setiap task yang mengakses layanan luar agar kegagalan macet terlihat sebagai kegagalan, bukan sebagai pipeline yang berhenti tanpa penjelasan.

Ringkasan#

  • Log task per percobaan tersimpan di bawah base_log_folder dengan struktur dag_id/run_id/task_id/attempt, sedangkan dags test menyatukan seluruh log task pada keluaran perintahnya.
  • Traceback dibaca dari bawah, yaitu jenis exception di baris terakhir, lalu berkas dan nomor baris kode task pada baris di atasnya, sebagaimana ditunjukkan kegagalan KeyError, ZeroDivisionError, dan AirflowTaskTimeout.
  • Perintah airflow tasks logs dihapus pada Airflow 3, sehingga log task dibaca dari antarmuka web, berkas log, atau keluaran eksekusi lokal.
  • Task yang diakhiri kernel karena kehabisan memori dan task yang terjebak deadlock tidak menyisakan traceback. Keduanya didiagnosis melalui log sistem operasi serta dicegah dengan partisi data dan execution_timeout.

Referensi#