Membangun visibilitas aplikasi: konfigurasi LOGGING Django dan structured logging, debugging dengan Django Debug Toolbar, lalu pengujian dengan TestCase dan client test, pytest-django, serta coverage untuk memastikan kode teruji.

Sampai episode 14 aplikasi sudah kaya fitur — dan semakin kaya, semakin banyak yang bisa salah. Di episode ini kita membangun tiga hal yang menentukan kepercayaan diri di produksi: logging (mengetahui apa yang terjadi), debugging (menemukan di mana kesalahannya), dan testing (memastikan kesalahan tidak kembali).
Mengapa topik ini penting? Karena bug yang tidak terlihat adalah bom waktu. Logging yang baik memberi jejak saat insiden terjadi, debug toolbar mempercepat analisis saat development, dan test yang rapi membuat perubahan kode jadi berani tanpa takut merusak fitur lain. Tiga keterampilan ini memisahkan "developer" dari "engineer".
Django memakai modul logging Python standar dengan hierarki: Logger → Handler → Formatter. Tambahkan konfigurasi di settings.py:
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"verbose": {
"format": "{asctime} {levelname} {name} {message}",
"style": "{",
},
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "verbose",
},
"file": {
"class": "logging.handlers.RotatingFileHandler",
"filename": BASE_DIR / "logs/django.log",
"maxBytes": 10 * 1024 * 1024,
"backupCount": 5,
"formatter": "verbose",
},
},
"loggers": {
"django": {"handlers": ["console", "file"], "level": "INFO"},
"blog": {"handlers": ["console"], "level": "DEBUG", "propagate": False},
},
}RotatingFileHandler memotong file saat mencapai maxBytes dan menyimpan backupCount file lama — mencegah log memakan disk. Logger django menangkap request/error framework; logger blog untuk logika aplikasi sendiri.
Log teks bebas sulit diparsing. Structured logging menghasilkan JSON per baris — standar untuk dikirim ke observability (episode 26 memakai OpenTelemetry). Tanpa library tambahan, format JSON bisa dibuat via formatter custom:
import json
import logging
from datetime import datetime
class JsonFormatter(logging.Formatter):
def format(self, record):
data = {
"ts": datetime.utcnow().isoformat() + "Z",
"level": record.levelname,
"logger": record.name,
"message": record.getMessage(),
}
if record.exc_info:
data["exc"] = self.formatException(record.exc_info)
return json.dumps(data)Pakai di production:
LOGGING = {
...
"formatters": {
"json": {"()": "devblog.logging_formatter.JsonFormatter"},
},
"handlers": {
"console": {"class": "logging.StreamHandler", "formatter": "json"},
},
}Di kode aplikasi:
import logging
logger = logging.getLogger(__name__)
def publish_post(request, pk):
...
logger.info("post published", extra={"post_id": pk, "user": request.user.username})Setiap log jadi satu objek JSON yang bisa di-query — ts, level, logger, plus field ekstra. Ini fondasi debugging di produksi.
Tip
Prinsip level log: DEBUG = detail untuk development, INFO = event penting (login, publish), WARNING = kondisi abnormal yang belum fatal, ERROR = operasi gagal, CRITICAL = sistem tidak bisa lanjut. Jangan log data sensitif (password, token, PII) — log yang bocor adalah insiden keamanan tersendiri (episode 18).
Toolbar yang paling sering dipakai untuk debugging request di development:
pip install django-debug-toolbarif DEBUG:
INSTALLED_APPS += ["debug_toolbar"]
MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"]
INTERNAL_IPS = ["127.0.0.1"]Panelnya menampilkan per-request: SQL queries (jumlah + durasi), cache, template, headers, dan settings. Jangan pernah mengaktifkan ini di produksi — ia menambah overhead besar dan membocorkan query SQL ke siapa pun.
Warning
Debug Toolbar hanya boleh aktif saat DEBUG = True. Kalau DEBUG tidak sengaja True di produksi, dua hal langsung bocor: halaman error menampilkan traceback penuh + settings (termasuk SECRET_KEY di konteks template), dan toolbar mengekspos detail internal. Episode 18 akan meng-hardening ini, dan manage.py check --deploy mendeteksinya.
Test Django dibangun di atas unittest. django.test.TestCase membuat database test terisolasi yang di-rollback tiap test. Client test mensimulasikan request:
from django.test import TestCase
from django.urls import reverse
from .models import Post
class PostListViewTests(TestCase):
def setUp(self):
self.author = User.objects.create_user(username="alice", password="x")
self.post = Post.objects.create(
title="Halo Dunia",
slug="halo-dunia",
body="Isi post",
status="published",
author=self.author,
)
def test_list_only_shows_published(self):
Post.objects.create(
title="Draft", slug="draft", body="x",
status="draft", author=self.author,
)
response = self.client.get(reverse("blog:home"))
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Halo Dunia")
self.assertNotContains(response, "Draft")
def test_detail_returns_404_for_unknown(self):
response = self.client.get(reverse("blog:post_detail", args=[999]))
self.assertEqual(response.status_code, 404)setUp dijalankan sebelum tiap test. self.client mensimulasikan browser: get, post, login(). assertContains memeriksa konten HTML; assertEqual(status_code, ...) memeriksa status.
pytest lebih ringkas untuk suite besar:
pip install pytest pytest-django pytest-cov[pytest]
DJANGO_SETTINGS_MODULE = devblog.settings
python_files = tests.py test_*.py *_tests.pyTest dengan fixture-style:
import pytest
@pytest.mark.django_db
def test_home_returns_200(client):
response = client.get("/")
assert response.status_code == 200Jalankan dengan coverage:
pytest --cov=blog --cov-report=term-missingOutput menampilkan persentase coverage per file dan baris yang belum ter-cover. @pytest.mark.django_db memberi akses database test hanya pada test yang butuh.
Note
Coverage 100% bukan jaminan bug-free — yang penting adalah baris-baris kritis (auth, payment, permission) ter-cover penuh. Target realistis produksi: 80%+ dengan prioritas pada view yang menangani uang dan data pengguna. Kombinasi TestCase (unit/integration) dan client/pytest (request-level) menutupi dua level sekaligus.
Sebelum masuk pipeline CI (episode 23), pastikan ketiganya jalan bersama:
python manage.py check
pytest --cov=blog
python manage.py check --deploy # preview produksi (episode 18)Inti yang harus dibawa pulang:
RotatingFileHandler mencegah disk penuh.DEBUG=True dilarang di produksi.assertContains untuk cek konten.Di episode 16 selanjutnya kita merapikan fondasi deployment: Settings & Environments Management — pemisahan konfigurasi per environment, django-environ/pydantic-settings, pengelolaan secrets, dan prinsip 12-factor untuk project yang siap produksi. Sampai jumpa di episode 16!