1. Pengenalan Logging
Logging adalah proses mencatat peristiwa (event) yang terjadi saat program berjalan. Modul logging di Python adalah library standar yang menyediakan sistem logging yang fleksibel dan powerful β jauh lebih baik daripada menggunakan print() untuk debugging.
Logging digunakan di semua aplikasi production-grade β mulai dari web server, API, script automasi, hingga aplikasi desktop. Log membantu developer memahami apa yang terjadi di dalam sistem, mendeteksi masalah, dan melakukan audit.
Ekosistem Logging di Python
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β ARSITEKTUR LOGGING β β β β ββββββββββββ ββββββββββββ ββββββββββββββββββββββββ β β β Kode ββββββΆβ Logger ββββββΆβ Handler(s) β β β β Anda β β (entry β β β β β β β β point) β β ββββββββββββββββββββ β β β ββββββββββββ ββββββββββββ β β StreamHandler β β β β β β (console output) β β β β β ββββββββββββββββββββ€ β β β β β FileHandler β β β β β β (write to file) β β β β β ββββββββββββββββββββ€ β β β β β RotatingFileH. β β β β β β (auto-rotate) β β β β β ββββββββββββββββββββ€ β β β β β SMTPHandler β β β β β β (send email) β β β β β ββββββββββββββββββββ β β β ββββββββββββ¬ββββββββββββ β β β β β ββββββββββββΌββββββββββββ β β β Formatter β β β β (format pesan log) β β β β β β β β 2024-06-26 10:30:15 β β β β app.module - INFO β β β β - Pesan log... β β β ββββββββββββββββββββββββ β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
2. Mengapa Logging?
print() vs logging
| Aspek | print() | logging |
|---|---|---|
| Level | Tidak ada | DEBUG, INFO, WARNING, ERROR, CRITICAL |
| Output | Hanya console | Console, file, email, HTTP, socket |
| Format | Manual | Otomatis (timestamp, level, module) |
| Rotasi | Tidak ada | Otomatis rotate file per ukuran/waktu |
| Kontrol | Sulit di-disable | Bisa di-filter per level/module |
| Thread-safe | Tidak | Ya |
| Production | β Tidak cocok | β Standar industri |
# β Menggunakan print() β tidak profesional
print("Database connected") # Kapan? Dari mana? Level apa?
print("User Budi logged in") # Tidak ada timestamp
print("Error: connection failed") # Tidak bisa di-filter
# β
Menggunakan logging β profesional
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
logger.info("Database connected")
# INFO:__main__:Database connected
logger.info("User Budi logged in")
# INFO:__main__:User Budi logged in
logger.error("Error: connection failed")
# ERROR:__main__:Error: connection failed
3. Log Levels
Python logging memiliki 5 level standar yang menunjukkan tingkat keparahan pesan. Setiap level memiliki angka (severity) β semakin tinggi angkanya, semakin kritis.
| Level | Angka | Kapan Digunakan | Contoh |
|---|---|---|---|
DEBUG | 10 | Info detail untuk debugging | Variabel x=42, query SQL |
INFO | 20 | Konfirmasi bahwa program berjalan normal | Server started, user login |
WARNING | 30 | Sesuatu yang tidak diharapkan tapi program masih jalan | Disk space low, deprecated API |
ERROR | 40 | Error yang menyebabkan fungsi gagal | DB connection failed, file not found |
CRITICAL | 50 | Error fatal yang menyebabkan program berhenti | Out of memory, system crash |
βββββββββββββββββββββββββββββββββββββββββββββββββββββ β LOG LEVEL HIERARCHY β β β β Level Angka Keparahan β β βββββββββββββββββββββββββββββββββββββ β β CRITICAL βββββββββ 50 π΄ Paling kritis β β ERROR ββββββββ 40 π΄ Error β β WARNING ββββββ 30 π‘ Peringatan β β INFO ββββ 20 π’ Informasi β β DEBUG ββ 10 π΅ Detail debug β β β β Jika level set ke WARNING: β β β CRITICAL, ERROR, WARNING β tercatat β β β INFO, DEBUG β diabaikan β β β βββββββββββββββββββββββββββββββββββββββββββββββββββββ
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
# Menggunakan setiap level
logger.debug("Memproses data: x=42, y=100")
# DEBUG:__main__:Memproses data: x=42, y=100
logger.info("Server berhasil dimulai pada port 8000")
# INFO:__main__:Server berhasil dimulai pada port 8000
logger.warning("Penggunaan memori mencapai 85%")
# WARNING:__main__:Penggunaan memori mencapai 85%
logger.error("Gagal terhubung ke database PostgreSQL")
# ERROR:__main__:Gagal terhubung ke database PostgreSQL
logger.critical("Sistem kehabisan memori! Shutdown darurat!")
# CRITICAL:__main__:Sistem kehabisan memori! Shutdown darurat!
# Menggunakan f-string untuk formatting
user = "Budi"
action = "login"
logger.info(f"User {user} melakukan {action}")
# INFO:__main__:User Budi melakukan login
# Cara yang lebih efisien: lazy formatting
logger.info("User %s melakukan %s", user, action)
# String hanya di-format jika level aktif (hemat performa)
4. Basic Logging
Konfigurasi Dasar dengan basicConfig
import logging
# ===== Konfigurasi paling sederhana =====
logging.basicConfig(level=logging.INFO)
logging.info("Pesan info") # Akan muncul di console
# ===== Konfigurasi dengan format lengkap =====
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# Output: 2024-06-26 10:30:15 - __main__ - INFO - Pesan info
# ===== Konfigurasi output ke file =====
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
filename='app.log', # Tulis ke file
filemode='w' # 'w' = overwrite, 'a' = append
)
logging.debug("Debug message")
logging.info("Info message")
logging.warning("Warning message")
# Semua tertulis ke app.log
# ===== Penting: basicConfig hanya bisa dipanggil SEKALI =====
# Panggilan kedua akan diabaikan!
# Gunakan Handler untuk konfigurasi yang lebih fleksibel.
Variabel yang Tersedia di Format
| Variabel | Keterangan | Contoh |
|---|---|---|
%(asctime)s | Timestamp | 2024-06-26 10:30:15 |
%(name)s | Nama logger | __main__ |
%(levelname)s | Nama level | INFO |
%(levelno)s | Nomor level | 20 |
%(message)s | Pesan log | User logged in |
%(filename)s | Nama file | app.py |
%(funcName)s | Nama fungsi | process_data |
%(lineno)d | Nomor baris | 42 |
%(module)s | Nama modul | app |
%(process)d | Process ID | 12345 |
%(thread)d | Thread ID | 67890 |
5. Formatters
Formatter menentukan format output pesan log β timestamp, level, nama module, dan isi pesan.
import logging
# Buat logger
logger = logging.getLogger('myapp')
logger.setLevel(logging.DEBUG)
# Buat formatter
formatter_simple = logging.Formatter('%(levelname)s - %(message)s')
formatter_detail = logging.Formatter(
'%(asctime)s | %(name)s | %(levelname)-8s | %(filename)s:%(lineno)d | %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
formatter_json = logging.Formatter(
'{"time": "%(asctime)s", "level": "%(levelname)s", '
'"module": "%(module)s", "message": "%(message)s"}',
datefmt='%Y-%m-%dT%H:%M:%S'
)
# Buat handler dan pasang formatter
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
console_handler.setFormatter(formatter_simple)
file_handler = logging.FileHandler('app_detail.log', encoding='utf-8')
file_handler.setLevel(logging.INFO)
file_handler.setFormatter(formatter_detail)
json_handler = logging.FileHandler('app.json', encoding='utf-8')
json_handler.setLevel(logging.WARNING)
json_handler.setFormatter(formatter_json)
# Pasang handler ke logger
logger.addHandler(console_handler)
logger.addHandler(file_handler)
logger.addHandler(json_handler)
# Test
logger.debug("Pesan debug") # Hanya console
logger.info("Pesan info") # Console + file_detail
logger.warning("Pesan warning") # Console + file_detail + json
logger.error("Pesan error") # Console + file_detail + json
6. Handlers
Handler menentukan ke mana pesan log dikirim β console, file, email, atau HTTP. Satu logger bisa memiliki beberapa handler sekaligus.
Jenis Handler
| Handler | Fungsi | Kegunaan |
|---|---|---|
StreamHandler | Output ke console (stdout/stderr) | Debugging lokal |
FileHandler | Tulis ke file | Log permanen |
RotatingFileHandler | File dengan rotasi berdasarkan ukuran | Production |
TimedRotatingFileHandler | File dengan rotasi berdasarkan waktu | Log harian |
SMTPHandler | Kirim email saat error | Alerting |
HTTPHandler | Kirim log via HTTP | Centralized logging |
SysLogHandler | Kirim ke syslog | System logging |
RotatingFileHandler
import logging
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
logger = logging.getLogger('myapp')
logger.setLevel(logging.DEBUG)
# ===== RotatingFileHandler β rotate per ukuran =====
# File rotate saat mencapai 5MB, simpan 5 backup
rotating_handler = RotatingFileHandler(
'app.log',
maxBytes=5 * 1024 * 1024, # 5 MB
backupCount=5, # Simpan 5 file backup
encoding='utf-8'
)
rotating_handler.setLevel(logging.INFO)
# File yang dihasilkan:
# app.log (aktif)
# app.log.1 (backup terbaru)
# app.log.2
# app.log.3
# app.log.4
# app.log.5 (backup terlama, akan dihapus)
# ===== TimedRotatingFileHandler β rotate per waktu =====
# Rotate setiap tengah malam, simpan 30 hari
timed_handler = TimedRotatingFileHandler(
'daily.log',
when='midnight', # Rotate jam 00:00
interval=1, # Setiap 1 interval
backupCount=30, # Simpan 30 file backup
encoding='utf-8'
)
# Opsi 'when':
# 'S' β setiap detik
# 'M' β setiap menit
# 'H' β setiap jam
# 'D' β setiap hari
# 'midnight' β setiap tengah malam
# 'W0'-'W6' β setiap hari dalam minggu (0=Senin)
# Pasang handler
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
rotating_handler.setFormatter(formatter)
timed_handler.setFormatter(formatter)
logger.addHandler(rotating_handler)
logger.addHandler(timed_handler)
7. Loggers dan Hierarchy
Logger di Python memiliki hierarki berdasarkan nama, dipisahkan oleh titik (.). Logger anak mewarisi konfigurasi dari logger parent.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββ β LOGGER HIERARCHY β β β β root logger β β βββ myapp (parent untuk semua) β β β βββ myapp.database (DB operations) β β β βββ myapp.api (API requests) β β β βββ myapp.auth (authentication) β β βββ another_app β β β β Logging flow: β β 1. Pesan dicatat ke logger β β 2. Jika logger punya handler β proses β β 3. Pesan propagate ke parent β ulangi β β 4. Sampai ke root logger β β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
import logging
# ===== Membuat logger dengan nama =====
# Logger dengan hierarki (dipisahkan titik)
logger_db = logging.getLogger('myapp.database')
logger_api = logging.getLogger('myapp.api')
logger_auth = logging.getLogger('myapp.auth')
# Set level pada masing-masing logger
logger_db.setLevel(logging.DEBUG)
logger_api.setLevel(logging.INFO)
logger_auth.setLevel(logging.WARNING)
# ===== Propagasi =====
# Logger anak mewarisi handler dari parent
# myapp.database β myapp β root
# Nonaktifkan propagasi jika ingin handler sendiri
# logger_db.propagate = False
# ===== Praktik terbaik: gunakan __name__ =====
# __name__ otomatis memberi nama berdasarkan modul
# Jika file di: myapp/database.py β logger nama "myapp.database"
logger = logging.getLogger(__name__)
# Di production, biasanya setup di main app:
def setup_logging():
"""Setup logging untuk seluruh aplikasi."""
root = logging.getLogger()
root.setLevel(logging.DEBUG)
# Console handler untuk development
console = logging.StreamHandler()
console.setLevel(logging.INFO)
console.setFormatter(logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
))
root.addHandler(console)
# File handler untuk production
from logging.handlers import RotatingFileHandler
file_h = RotatingFileHandler(
'app.log', maxBytes=10*1024*1024, backupCount=5
)
file_h.setLevel(logging.DEBUG)
file_h.setFormatter(logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
))
root.addHandler(file_h)
# Panggil di awal aplikasi
setup_logging()
8. Exception Logging
Logging exception dengan benar sangat penting untuk debugging di production. Modul logging memiliki fitur khusus untuk mencatat traceback.
import logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# ===== exc_info=True β catat traceback lengkap =====
def proses_data(data):
try:
hasil = 100 / data
return hasil
except ZeroDivisionError:
logger.error("Gagal memproses data: pembagian dengan nol!", exc_info=True)
# exc_info=True menambahkan traceback lengkap di log
#
# ERROR:__main__:Gagal memproses data: pembagian dengan nol!
# Traceback (most recent call last):
# File "app.py", line 6, in proses_data
# hasil = 100 / data
# ZeroDivisionError: division by zero
# ===== logger.exception() β shortcut untuk exc_info=True =====
def baca_file(path):
try:
with open(path, 'r') as f:
return f.read()
except FileNotFoundError:
logger.exception(f"File tidak ditemukan: {path}")
# logger.exception() otomatis set exc_info=True
# Hanya gunakan di dalam blok except!
# ===== Stack info β tambah info stack tanpa exception =====
def fungsi_dalam():
logger.warning("Ada masalah di sini", stack_info=True)
# Tambahkan stack trace meskipun tidak ada exception
# ===== Contoh: API error handler =====
def handle_api_request(request):
logger.info(f"Menerima request: {request.method} {request.url}")
try:
result = proses_request(request)
logger.info(f"Request berhasil: {result.status}")
return result
except TimeoutError:
logger.error("API timeout", exc_info=True)
return {"error": "Timeout"}
except Exception as e:
logger.critical(f"Error tidak terduga: {e}", exc_info=True)
return {"error": "Internal server error"}
9. Konfigurasi Logging
Python menyediakan beberapa cara untuk mengkonfigurasi logging β dari basicConfig, dictConfig, hingga file konfigurasi.
Dictionary Config
import logging
import logging.config
# ===== Dictionary-based configuration =====
LOGGING_CONFIG = {
'version': 1,
'disable_existing_loggers': False,
# Formatters
'formatters': {
'standard': {
'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
'datefmt': '%Y-%m-%d %H:%M:%S'
},
'simple': {
'format': '%(levelname)s - %(message)s'
},
'detailed': {
'format': '%(asctime)s | %(name)s | %(levelname)-8s | '
'%(filename)s:%(lineno)d | %(funcName)s() | %(message)s',
'datefmt': '%Y-%m-%d %H:%M:%S'
}
},
# Handlers
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'level': 'INFO',
'formatter': 'simple',
'stream': 'ext://sys.stdout'
},
'file': {
'class': 'logging.handlers.RotatingFileHandler',
'level': 'DEBUG',
'formatter': 'detailed',
'filename': 'app.log',
'maxBytes': 10485760, # 10 MB
'backupCount': 5,
'encoding': 'utf-8'
},
'error_file': {
'class': 'logging.FileHandler',
'level': 'ERROR',
'formatter': 'detailed',
'filename': 'errors.log',
'encoding': 'utf-8'
}
},
# Loggers
'loggers': {
'myapp': {
'level': 'DEBUG',
'handlers': ['console', 'file', 'error_file'],
'propagate': False
},
'myapp.database': {
'level': 'WARNING',
'handlers': ['file', 'error_file'],
'propagate': False
}
},
# Root logger
'root': {
'level': 'WARNING',
'handlers': ['console', 'file']
}
}
# Terapkan konfigurasi
logging.config.dictConfig(LOGGING_CONFIG)
# Gunakan logger
logger = logging.getLogger('myapp')
logger.debug("Debug message") # ke file
logger.info("Info message") # ke console + file
logger.warning("Warning message") # ke console + file
logger.error("Error message") # ke console + file + error_file
File Config
# File: logging.conf
[loggers]
keys=root,myapp
[handlers]
keys=consoleHandler,fileHandler
[formatters]
keys=simpleFormatter,detailFormatter
[logger_root]
level=WARNING
handlers=consoleHandler
[logger_myapp]
level=DEBUG
handlers=consoleHandler,fileHandler
qualname=myapp
propagate=0
[handler_consoleHandler]
class=StreamHandler
level=INFO
formatter=simpleFormatter
args=(sys.stdout,)
[handler_fileHandler]
class=FileHandler
level=DEBUG
formatter=detailFormatter
args=('app.log', 'a', 'utf-8')
[formatter_simpleFormatter]
format=%(levelname)s - %(message)s
[formatter_detailFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt=%Y-%m-%d %H:%M:%S
# === Menggunakan dari Python ===
# import logging.config
# logging.config.fileConfig('logging.conf')
# logger = logging.getLogger('myapp')
10. Praktik Terbaik
1. Gunakan __name__ sebagai nama logger. Otomatis sesuai hierarki modul.
2. Jangan gunakan root logger langsung. Buat logger tersendiri per modul.
3. Set level di handler, bukan hanya di logger. Lebih fleksibel.
4. Gunakan lazy formatting. logger.info("x=%s", x) bukan logger.info(f"x={x}")
5. Selalu gunakan exc_info=True saat logging exception.
6. Gunakan RotatingFileHandler di production agar file tidak membengkak.
7. Hindari logging data sensitif (password, token, data pribadi).
import logging
# β
1. Gunakan __name__
logger = logging.getLogger(__name__)
# β
2. Lazy formatting (hanya format jika level aktif)
user_id = 12345
logger.info("Processing user %s", user_id) # Bukan f"..."
# β
3. Jangan log data sensitif
# β logger.info(f"Password: {password}")
# β logger.info(f"Token: {api_token}")
# β
logger.info("User %s authenticated successfully", username)
# β
4. Gunakan level yang tepat
logger.debug("Variable x=%d, y=%d", x, y) # Debug detail
logger.info("Request processed in %dms", time) # Info normal
logger.warning("Cache miss for key: %s", key) # Perhatian
logger.error("Failed to connect: %s", err) # Error
logger.critical("System out of memory!") # Kritis
# β
5. Structured logging untuk production
def log_request(logger, method, path, status, duration):
"""Log HTTP request dengan format terstruktur."""
log_data = {
'method': method,
'path': path,
'status': status,
'duration_ms': duration
}
if status >= 500:
logger.error("Request failed: %s", log_data)
elif status >= 400:
logger.warning("Client error: %s", log_data)
else:
logger.info("Request OK: %s", log_data)
# β
6. Filter sensitive data
class SensitiveFilter(logging.Filter):
"""Filter untuk menyensor data sensitif."""
SENSITIVE_PATTERNS = ['password', 'token', 'secret', 'api_key']
def filter(self, record):
msg = record.getMessage().lower()
for pattern in self.SENSITIVE_PATTERNS:
if pattern in msg:
record.msg = "[SENSITIVE DATA REDACTED]"
record.args = ()
break
return True
logger.addFilter(SensitiveFilter())
11. Proyek Praktik: Logging untuk Aplikasi Web
"""
Sistem logging lengkap untuk aplikasi web.
File: logger_setup.py
"""
import logging
import logging.config
from logging.handlers import RotatingFileHandler
from datetime import datetime
from pathlib import Path
def setup_app_logging(
app_name="myapp",
log_dir="logs",
console_level=logging.INFO,
file_level=logging.DEBUG,
max_bytes=10*1024*1024, # 10 MB
backup_count=10
):
"""Setup logging profesional untuk aplikasi."""
# Buat direktori log
log_path = Path(log_dir)
log_path.mkdir(parents=True, exist_ok=True)
# Root logger
root = logging.getLogger()
root.setLevel(logging.DEBUG)
# Format
detail_fmt = logging.Formatter(
'%(asctime)s | %(name)-20s | %(levelname)-8s | '
'%(filename)s:%(lineno)d | %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
console_fmt = logging.Formatter(
'%(asctime)s | %(levelname)-8s | %(message)s',
datefmt='%H:%M:%S'
)
# Console handler
console = logging.StreamHandler()
console.setLevel(console_level)
console.setFormatter(console_fmt)
root.addHandler(console)
# File handler β semua log
all_handler = RotatingFileHandler(
log_path / "app.log",
maxBytes=max_bytes,
backupCount=backup_count,
encoding='utf-8'
)
all_handler.setLevel(file_level)
all_handler.setFormatter(detail_fmt)
root.addHandler(all_handler)
# File handler β error saja
error_handler = RotatingFileHandler(
log_path / "error.log",
maxBytes=max_bytes,
backupCount=backup_count,
encoding='utf-8'
)
error_handler.setLevel(logging.ERROR)
error_handler.setFormatter(detail_fmt)
root.addHandler(error_handler)
# Logger untuk aplikasi
logger = logging.getLogger(app_name)
logger.info("=" * 60)
logger.info("Aplikasi %s dimulai pada %s", app_name, datetime.now())
logger.info("=" * 60)
return logger
# ===== Contoh penggunaan =====
if __name__ == "__main__":
logger = setup_app_logging("beebane_api")
# Simulasi aktivitas aplikasi
logger.debug("Memuat konfigurasi database...")
logger.info("Server berjalan pada port 8000")
logger.info("Menerima request GET /api/users")
logger.warning("Rate limit mendekati batas: 95/100")
logger.error("Database connection timeout setelah 30s")
logger.critical("Memory usage mencapai 95%! Restart diperlukan")
# Simulasi exception
try:
result = 1 / 0
except ZeroDivisionError:
logger.exception("Error saat menghitung rata-rata")
logger.info("Aplikasi selesai")
12. Quiz: Uji Pemahamanmu!
Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang Python Logging: