Belajar FastAPI - Logging & Error Handling
Episode 17 of 28

Belajar FastAPI - Logging & Error Handling

Membangun error response yang konsisten: HTTPException untuk error terstruktur, custom exception handler untuk seluruh aplikasi, handling error validasi Pydantic, dan structured logging JSON untuk observability produksi.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Aplikasi yang sehat bukan aplikasi yang tidak pernah error — melainkan yang error-nya terstruktur dan tercatat. Setelah di episode 16 konfigurasi sudah terpusat, sekarang kita bereskan dua hal yang menentukan kualitas hidup developer di produksi: bagaimana API mengembalikan error, dan bagaimana aplikasi bercerita lewat log.

Mengapa episode ini penting? Error response yang tidak konsisten memaksa klien menulis banyak percabangan; error response yang bocor (menampilkan traceback internal) membocorkan detail sistem. Dan tanpa logging yang baik, insiden produksi berubah menjadi sesi menebak-nebak. Episode ini membangun fondasi observability sejak awal.

HTTPException: Error Terstruktur

Kita sudah memakai HTTPException sejak episode 9 — sekarang kita bentuk lengkap dan konsisten:

PythonHTTPException terstruktur
from fastapi import FastAPI, HTTPException
 
app = FastAPI()
 
class NotFoundError(HTTPException):
    def __init__(self, resource: str) -> None:
        super().__init__(
            status_code=404,
            detail=f"{resource} tidak ditemukan",
        )
 
 
@app.get("/items/{item_id}")
def read_item(item_id: int) -> dict:
    if item_id > 100:
        raise NotFoundError("Item")
    return {"item_id": item_id}

Membungkus HTTPException dalam subclass membuat error domain — NotFoundError, UnauthorizedError — dipakai ulang dan tidak terulang-ulang menulis status + detail.

Tip

Keuntungan HTTPException: FastAPI mendokumentasikannya otomatis di OpenAPI (episode 21). Error yang dideklarasikan dengan jelas membuat klien tahu persis status apa yang harus mereka tangani.

Custom Exception Handler

Tidak semua error berupa HTTPException — kadang muncul ValueError, atau error dari library. FastAPI memberi kita @app.exception_handler untuk mengubah error apa pun menjadi response terstruktur:

PythonCustom exception handler
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
 
app = FastAPI()
 
 
@app.exception_handler(ValueError)
async def value_error_handler(
    request: Request, exc: ValueError
) -> JSONResponse:
    return JSONResponse(
        status_code=400,
        content={"detail": str(exc), "code": "BAD_REQUEST"},
    )
 
 
@app.get("/divide/{a}/{b}")
def divide(a: int, b: int) -> dict[str, float]:
    if b == 0:
        raise ValueError("pembagian dengan nol")
    return {"result": a / b}

Sekarang ValueError apa pun di aplikasi berubah menjadi response 400 dengan format {"detail": ..., "code": ...}. Error handler adalah titik terpusat untuk memformat error — klien selalu melihat bentuk yang sama.

Format Error yang Konsisten

Agar klien mudah memproses, tetapkan satu format error di seluruh API. Pola yang banyak dipakai:

Format error konsisten
{
  "detail": "Item tidak ditemukan",
  "code": "NOT_FOUND",
  "status": 404
}

Untuk mewujudkannya di seluruh aplikasi, daftarkan handler bawaan juga — misalnya mengubah error validasi Pydantic yang default-nya multi-level:

PythonCustom RequestValidationError
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
 
 
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(
    request: Request, exc: RequestValidationError
) -> JSONResponse:
    errors = []
    for err in exc.errors():
        errors.append({
            "field": ".".join(str(p) for p in err["loc"]),
            "message": err["msg"],
            "type": err["type"],
        })
    return JSONResponse(
        status_code=422,
        content={"detail": errors, "code": "VALIDATION_ERROR"},
    )

Alih-alih menumpuk loc bersarang, klien menerima daftar field/message yang datar — jauh lebih mudah di-render di form frontend.

Structured Logging

Log teks polos sulit diparsing mesin. Structured logging (JSON per baris) membuat log bisa di-index oleh tool observability seperti Loki, Elasticsearch, atau Grafana:

PythonLogging JSON terstruktur
import json
import logging
from datetime import datetime, timezone
 
logger = logging.getLogger("fastapi-lab")
 
 
def log_json(level: str, event: str, **fields: object) -> None:
    record = {
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "level": level,
        "event": event,
        **fields,
    }
    logger.log(getattr(logging, level), json.dumps(record))

Pemakaiannya:

PythonContoh pemakaian log
@app.post("/items/")
def create_item(name: str) -> dict:
    item_id = 1
    log_json(
        "INFO",
        "item.created",
        item_id=item_id,
        name=name,
    )
    return {"item_id": item_id}

Konvensi event ber-titik (item.created) memungkinkan agregasi per event di dashboard.

Warning

Jangan pernah memasukkan data sensitif ke log — password, token, dan header Authorization harus di-mask. Password yang tertulis di log adalah kebocoran yang menunggu terjadi. Log untuk observability, bukan untuk forensik data.

Menangkap Exception Global

Kadang error muncul di luar handler — misalnya di middleware atau library. Handler Exception global adalah jaring pengaman terakhir:

PythonGlobal exception handler
import logging
 
@app.exception_handler(Exception)
async def unhandled_exception_handler(
    request: Request, exc: Exception
) -> JSONResponse:
    logging.exception("unhandled error", exc_info=exc)
    return JSONResponse(
        status_code=500,
        content={"detail": "Internal server error", "code": "INTERNAL"},
    )

Response ke klien tidak membocorkan detail (hanya Internal server error), tapi log lengkap (via logging.exception) tetap tercatat untuk debugging. Jangan pernah mengembalikan traceback ke klien.

Important

Jangan terlalu sering menangkap Exception global untuk hal yang bisa dicegah dengan validasi. Jaring ini untuk error tak terduga saja — kode yang jelas (validasi Pydantic, HTTPException) tetap lebih baik daripada jaring yang menutupi semuanya.

Struktur Error Handling yang Ideal

100%

Common Pitfalls

PitfallSolusi
Traceback ke klienHandler global → 500 + detail, log penuh di server
Error response tidak konsistenSatu format via handler terpusat
Log tanpa timestamp/levelStructured JSON
Log berisi password/tokenMask selalu
Error 500 yang tidak tercatatlogging.exception di handler global

Penutup

Inti yang harus dibawa pulang:

  • HTTPException untuk error yang diketahui; subclass untuk error domain.
  • @app.exception_handler memformat error apa pun jadi response konsisten.
  • Error validasi Pydantic bisa diubah jadi daftar field/message yang ramah klien.
  • Structured logging JSON (timestamp, level, event) siap di-index tool observability.
  • Jangan bocorkan detail internal ke klien — log lengkap di sisi server.

Di episode 18 selanjutnya kita akan membahas security best practices — pengerasan API dari SQLi, XSS, CSRF, security headers, hingga dependency security. Semua yang kalian pelajari dari episode 5 sampai 17 sekarang dirakit menjadi pertahanan berlapis!

Belajar FastAPI - Logging & Error Handling | Belajar FastAPI