Python Logging Modülü Derinlemesine
Kodun çalışıyor, her şey yolunda. Sonra bir gün production'da bir hata oluşuyor. Kullanıcı "çalışmıyor" diyor ama sen ne olduğunu bilmiyorsun. print() satırlarıyla debug etmeye çalışıyorsun — ama production'da print çıktılarını kim görüyor ki? İşte bu noktada logging hayat kurtarır.
Logging, programının çalışma anında ne yaptığını, neyle karşılaştığını ve neyin ters gittiğini kayıt altına almaktır. Python'un yerleşik logging modülü bu işi profesyonel düzeyde yapmanı sağlar. Bu derste logging'i sıfırdan derinlemesine öğreneceksin.
1. print() vs logging
🏥 Analoji: Hasta Takip Sistemi
Bir hastanede doktor, hastanın durumunu iki şekilde takip edebilir:
Yapışkan not: Hastanın yatağına bir post-it yapıştır — "Ateşi var." Kim yazdı, ne zaman yazdı, ne kadar ciddi? Belli değil. Bir süre sonra notlar karışır, kaybolur.
Hasta dosyası: Her kayıt tarih, saat, doktor adı, departman ve ciddiyet seviyesiyle birlikte yazılır. Geçmişe dönük inceleme yapılabilir, departmana göre filtrelenebilir, kritik kayıtlar otomatik alarm tetikler.
print() yapışkan not gibidir — hızlı, basit ama ciddi işlerde yetersiz. logging ise hasta dosyası gibidir — yapılandırılmış, filtrelenebilir, yönlendirilebilir.
Somut Karşılaştırma
# ❌ print ile "loglama"
def process_order(order_id):
print(f"Sipariş işleniyor: {order_id}")
try:
# ... işlem ...
print(f"Sipariş tamamlandı: {order_id}")
except Exception as e:
print(f"HATA! Sipariş {order_id}: {e}")# ✅ logging ile profesyonel loglama
import logging
logger = logging.getLogger(__name__)
def process_order(order_id):
logger.info("Sipariş işleniyor: %s", order_id)
try:
# ... işlem ...
logger.info("Sipariş tamamlandı: %s", order_id)
except Exception as e:
logger.error("Sipariş işlenemedi: %s", order_id, exc_info=True)İkinci versiyonda exc_info=True stack trace'i otomatik ekler. Seviye bilgisi (INFO, ERROR) var. Çıktı dosyaya, konsola veya uzak sunucuya yönlendirilebilir. Ve en önemlisi: production'da DEBUG mesajlarını kapatıp sadece ERROR'ları görebilirsin — print ile bu mümkün değil.
print'in Sorunları
| Özellik | print() | logging |
|---|---|---|
| Seviye kontrolü | ❌ | ✅ DEBUG→CRITICAL |
| Çıktı yönlendirme | Sadece stdout | Dosya, konsol, ağ, e-posta... |
| Format kontrolü | Elle | Otomatik timestamp, modül adı... |
| Filtreleme | ❌ | ✅ Modül, seviye, özel filtre |
| Production'da kapatma | Satırları silmen lazım | Seviye ayarla, bitti |
| Thread-safe | ❌ | ✅ |
2. Logging Temelleri
getLogger() ve Seviyeler
Logging modülünün kalbi Logger nesnesidir. Her modül kendi logger'ını oluşturur:
import logging
# Logger oluştur — modül adıyla
logger = logging.getLogger(__name__)
# Root logger'ı yapılandır (en basit yol)
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
# Farklı seviyelerde log
logger.debug("Detaylı debug bilgisi — geliştirme sırasında")
logger.info("Genel bilgi — normal işlem akışı")
logger.warning("Uyarı — dikkat edilmesi gereken durum")
logger.error("Hata — bir şeyler yanlış gitti")
logger.critical("Kritik — sistem çökebilir")Çıktı:
2024-01-15 10:30:00,123 - __main__ - DEBUG - Detaylı debug bilgisi — geliştirme sırasında
2024-01-15 10:30:00,123 - __main__ - INFO - Genel bilgi — normal işlem akışı
2024-01-15 10:30:00,124 - __main__ - WARNING - Uyarı — dikkat edilmesi gereken durum
2024-01-15 10:30:00,124 - __main__ - ERROR - Hata — bir şeyler yanlış gitti
2024-01-15 10:30:00,124 - __main__ - CRITICAL - Kritik — sistem çökebilirSeviye Hiyerarşisi
Seviyeler sayısal değerlerle temsil edilir. Bir seviye ayarladığında, o seviye ve üstü loglanır:
DEBUG (10) → INFO (20) → WARNING (30) → ERROR (40) → CRITICAL (50)import logging
logging.basicConfig(level=logging.WARNING)
logger = logging.getLogger("app")
logger.debug("Bu GÖRÜNMEZ") # 10 < 30
logger.info("Bu da GÖRÜNMEZ") # 20 < 30
logger.warning("Bu görünür") # 30 >= 30 ✅
logger.error("Bu da görünür") # 40 >= 30 ✅Development'ta DEBUG, production'da WARNING veya INFO kullanmak yaygın bir pratiktir. Böylece geliştirme sırasında her detayı görürsün, production'da ise sadece önemli olayları loglarsın.
Log Mesajı Formatlama
String formatlama konusunda logging'in özel bir yaklaşımı var:
import logging
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.DEBUG)
user_id = 42
action = "login"
# ✅ Doğru — lazy formatting (% stili)
logger.info("Kullanıcı %s %s yaptı", user_id, action)
# ✅ Kabul edilebilir — f-string
logger.info(f"Kullanıcı {user_id} {action} yaptı")
# ✅ Neden % stili öneriliyor?
# Eğer log seviyesi nedeniyle mesaj YAZILMAYACAKSA,
# % stili formatlama YAPILMAZ — performans kazancı
# f-string ise her zaman formatlanır% stili formatlama, mesaj loglanmayacaksa string oluşturma maliyetinden kaçınır. Yüksek frekanslı debug loglarında bu fark hissedilir. Ama pratikte f-string de yaygın olarak kullanılır — okunabilirlik açısından daha iyi olduğu durumlar var.
3. Handler'lar: Log Nereye Gider?
Handler'lar log mesajlarının nereye yazılacağını belirler. Bir logger'a birden fazla handler ekleyebilirsin — aynı mesaj hem konsola hem dosyaya gidebilir.
StreamHandler — Konsola Yazma
import logging
logger = logging.getLogger("app")
logger.setLevel(logging.DEBUG)
# Konsol handler
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO) # Konsolda sadece INFO+
formatter = logging.Formatter(
"%(asctime)s - %(levelname)s - %(message)s"
)
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
logger.debug("Bu konsolda görünMEZ") # DEBUG < INFO
logger.info("Bu konsolda görünür")
logger.error("Bu da konsolda görünür")Dikkat: logger seviyesi DEBUG ama handler seviyesi INFO. Mesaj önce logger filtresinden geçer, sonra handler filtresinden. Her iki filtreyi de geçen mesajlar yazılır.
FileHandler — Dosyaya Yazma
import logging
logger = logging.getLogger("app")
logger.setLevel(logging.DEBUG)
# Dosya handler
file_handler = logging.FileHandler(
"app.log",
encoding="utf-8" # Türkçe karakterler için önemli
)
file_handler.setLevel(logging.DEBUG)
formatter = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(funcName)s:%(lineno)d - %(message)s"
)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)RotatingFileHandler — Boyut Bazlı Döndürme
Log dosyaları büyür. Gigabyte'larca log dosyası disk alanını tüketir. RotatingFileHandler dosya belirli bir boyuta ulaşınca yeni dosyaya geçer:
import logging
from logging.handlers import RotatingFileHandler
logger = logging.getLogger("app")
logger.setLevel(logging.DEBUG)
# 5MB'a ulaşınca döndür, en fazla 3 yedek tut
handler = RotatingFileHandler(
"app.log",
maxBytes=5 * 1024 * 1024, # 5 MB
backupCount=3,
encoding="utf-8"
)
formatter = logging.Formatter(
"%(asctime)s - %(levelname)s - %(message)s"
)
handler.setFormatter(formatter)
logger.addHandler(handler)
# Dosyalar: app.log → app.log.1 → app.log.2 → app.log.3 (sonra silinir)Dosya 5MB'a ulaştığında app.log → app.log.1 olur, yeni app.log oluşturulur. backupCount=3 ile en fazla 3 eski dosya tutulur — yani toplam disk kullanımı yaklaşık 20MB ile sınırlı kalır.
TimedRotatingFileHandler — Zaman Bazlı Döndürme
Bazı durumlarda boyut yerine zamana göre döndürmek istersin — her gün yeni bir log dosyası gibi:
import logging
from logging.handlers import TimedRotatingFileHandler
logger = logging.getLogger("app")
logger.setLevel(logging.DEBUG)
# Her gece yarısı döndür, 30 gün tut
handler = TimedRotatingFileHandler(
"app.log",
when="midnight", # Her gece yarısı
interval=1, # 1 günde bir
backupCount=30, # 30 günlük log tut
encoding="utf-8"
)
handler.suffix = "%Y-%m-%d" # app.log.2024-01-15
formatter = logging.Formatter(
"%(asctime)s - %(levelname)s - %(message)s"
)
handler.setFormatter(formatter)
logger.addHandler(handler)when parametresinin seçenekleri: "S" (saniye), "M" (dakika), "H" (saat), "D" (gün), "midnight" (gece yarısı), "W0"-"W6" (haftanın günleri).
Çoklu Handler: Konsol + Dosya
Production'da en yaygın senaryo: konsolda kısa bilgi, dosyada detaylı log:
import logging
from logging.handlers import RotatingFileHandler
def setup_logging():
"""Production-grade logging kurulumu."""
logger = logging.getLogger("myapp")
logger.setLevel(logging.DEBUG)
# Konsol — sadece WARNING ve üstü
console = logging.StreamHandler()
console.setLevel(logging.WARNING)
console.setFormatter(logging.Formatter(
"%(levelname)s: %(message)s"
))
# Dosya — tüm detaylar
file_handler = RotatingFileHandler(
"myapp.log",
maxBytes=10 * 1024 * 1024,
backupCount=5,
encoding="utf-8"
)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - "
"%(funcName)s:%(lineno)d - %(message)s"
))
logger.addHandler(console)
logger.addHandler(file_handler)
return logger
logger = setup_logging()
logger.debug("Debug — sadece dosyada")
logger.warning("Warning — hem konsolda hem dosyada")4. Formatter: Log Nasıl Görünür?
Formatter log mesajının formatını belirler. Hangi bilgiler dahil edilecek, nasıl sıralanacak, tarih formatı ne olacak — hepsini formatter kontrol eder.
Format String ve Kullanılabilir Alanlar
import logging
# Detaylı format
formatter = logging.Formatter(
"%(asctime)s | %(name)-20s | %(levelname)-8s | "
"%(filename)s:%(lineno)d | %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)Sık kullanılan format alanları:
| Alan | Açıklama | Örnek |
|---|---|---|
%(asctime)s | Zaman damgası | 2024-01-15 10:30:00 |
%(name)s | Logger adı | myapp.database |
%(levelname)s | Seviye | WARNING |
%(filename)s | Dosya adı | app.py |
%(funcName)s | Fonksiyon adı | process_order |
%(lineno)d | Satır numarası | 42 |
%(message)s | Log mesajı | Sipariş işlendi |
%(module)s | Modül adı | app |
%(process)d | Process ID | 12345 |
%(thread)d | Thread ID | 140735 |
Custom Formatter
Standart formatter yetmediğinde kendi formatter'ını yazabilirsin:
import logging
import json
from datetime import datetime, timezone
class ColorFormatter(logging.Formatter):
"""Konsol için renkli log formatter."""
COLORS = {
logging.DEBUG: "\033[36m", # Cyan
logging.INFO: "\033[32m", # Yeşil
logging.WARNING: "\033[33m", # Sarı
logging.ERROR: "\033[31m", # Kırmızı
logging.CRITICAL: "\033[41m", # Kırmızı arka plan
}
RESET = "\033[0m"
def format(self, record):
color = self.COLORS.get(record.levelno, self.RESET)
record.levelname = f"{color}{record.levelname}{self.RESET}"
return super().format(record)
# Kullanım
logger = logging.getLogger("app")
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
handler.setFormatter(ColorFormatter(
"%(asctime)s %(levelname)-18s %(message)s"
))
logger.addHandler(handler)
logger.debug("Debug mesajı")
logger.info("Bilgi mesajı")
logger.warning("Uyarı mesajı")
logger.error("Hata mesajı")
logger.critical("Kritik mesaj")Renkli formatter geliştirme ortamında çok faydalı — loglar arasında hataları gözle taramak çok kolaylaşır. Ama dosyaya yazarken ANSI renk kodları gereksiz olur — dosya handler'ına normal formatter ata.
5. Logger Hiyerarşisi ve Propagation
Python'da logger'lar bir ağaç yapısı oluşturur. Bu yapıyı anlamak, karmaşık projelerde logging'i doğru yapılandırmanın anahtarıdır.
Hiyerarşi Nasıl Çalışır?
Logger adları nokta ile ayrılan hiyerarşi oluşturur:
root
├── myapp # logging.getLogger("myapp")
│ ├── myapp.database # logging.getLogger("myapp.database")
│ ├── myapp.api # logging.getLogger("myapp.api")
│ │ └── myapp.api.auth # logging.getLogger("myapp.api.auth")
│ └── myapp.utils # logging.getLogger("myapp.utils")__name__ kullanmak bu hiyerarşiyi otomatik oluşturur. myapp/database/models.py dosyasında logging.getLogger(__name__) yazdığında logger adı myapp.database.models olur.
Propagation
Bir logger'a gelen mesaj, üst logger'lara da iletilir (propagate edilir). Bu varsayılan davranıştır:
import logging
# Root logger'ı yapılandır
logging.basicConfig(
level=logging.DEBUG,
format="%(name)s - %(levelname)s - %(message)s"
)
# Alt logger oluştur
parent_logger = logging.getLogger("myapp")
child_logger = logging.getLogger("myapp.database")
# Child'dan log yaz
child_logger.warning("Veritabanı bağlantısı yavaş")
# Bu mesaj:
# 1. child_logger (myapp.database) tarafından işlenir
# 2. parent_logger (myapp) tarafından işlenir (propagation)
# 3. root logger tarafından işlenir (propagation)
# → Tek handler varsa tek kez yazılır, sorun yokSorun şu: eğer hem child hem parent logger'da handler varsa, mesaj iki kez yazılır:
import logging
# Root handler
logging.basicConfig(level=logging.DEBUG, format="ROOT: %(message)s")
# myapp logger'a ayrı handler ekle
app_logger = logging.getLogger("myapp")
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter("APP: %(message)s"))
app_logger.addHandler(handler)
# Alt logger
db_logger = logging.getLogger("myapp.database")
db_logger.warning("Yavaş sorgu tespit edildi")
# Çıktı (mesaj İKİ kez yazılır):
# APP: Yavaş sorgu tespit edildi
# ROOT: Yavaş sorgu tespit edildiBunu önlemek için propagation'ı kapatabilirsn:
app_logger.propagate = False # Üst logger'a iletme⚠️ Dikkat: Mükerrer log mesajları görüyorsan, sebebi neredeyse her zaman propagation'dır. Ya handler'ı sadece root logger'a ekle, ya da alt logger'larda
propagate = Falseyap.
6. dictConfig ile Yapılandırma
Büyük projelerde logging'i kod içinde yapılandırmak karmaşıklaşır. dictConfig tüm yapılandırmayı tek bir dict (veya YAML/JSON dosyası) ile yapmana olanak tanır.
Python Dict ile
import logging
import logging.config
LOGGING_CONFIG = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"simple": {
"format": "%(levelname)s - %(message)s"
},
"detailed": {
"format": "%(asctime)s - %(name)s - %(levelname)s - "
"%(funcName)s:%(lineno)d - %(message)s",
"datefmt": "%Y-%m-%d %H:%M:%S"
},
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "WARNING",
"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"
},
},
"loggers": {
"myapp": {
"level": "DEBUG",
"handlers": ["console", "file"],
"propagate": False
},
"myapp.database": {
"level": "WARNING", # DB logları sadece WARNING+
"handlers": ["file"],
"propagate": False
},
},
"root": {
"level": "WARNING",
"handlers": ["console"]
}
}
# Yapılandırmayı uygula
logging.config.dictConfig(LOGGING_CONFIG)
# Kullanım
logger = logging.getLogger("myapp")
logger.info("Uygulama başladı")
db_logger = logging.getLogger("myapp.database")
db_logger.debug("Bu GÖRÜNMEZ — seviye WARNING")
db_logger.warning("Yavaş sorgu — bu görünür")disable_existing_loggers: False önemli — bunu True yaparsan, yapılandırma öncesi oluşturulan logger'lar devre dışı kalır. Kütüphanelerin logger'ları sessizleşir ve hata ayıklama zorlaşır.
YAML Dosyasından Yükleme
YAML formatı dict'lerden çok daha okunabilir:
# logging_config.yaml
version: 1
disable_existing_loggers: false
formatters:
detailed:
format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
datefmt: "%Y-%m-%d %H:%M:%S"
handlers:
console:
class: logging.StreamHandler
level: INFO
formatter: detailed
stream: ext://sys.stdout
file:
class: logging.handlers.RotatingFileHandler
level: DEBUG
formatter: detailed
filename: app.log
maxBytes: 10485760
backupCount: 5
encoding: utf-8
loggers:
myapp:
level: DEBUG
handlers: [console, file]
propagate: false
root:
level: WARNING
handlers: [console]import logging.config
import yaml
with open("logging_config.yaml") as f:
config = yaml.safe_load(f)
logging.config.dictConfig(config)
logger = logging.getLogger("myapp")
logger.info("YAML konfigürasyondan yüklendi")YAML yaklaşımı yapılandırmayı koddan ayırır. Farklı ortamlar (dev, staging, production) için farklı YAML dosyaları kullanabilirsin — kod değişikliği gerekmez.
7. Filtreleme: Filter Sınıfı
Handler'lar seviyeye göre filtreler ama bazen daha ince ayar gerekir. Belirli bir modülden gelen logları engelleme, hassas veriyi maskeleme veya özel alanlar ekleme gibi işler için Filter sınıfı kullanılır.
Temel Filter
import logging
class ModuleFilter(logging.Filter):
"""Belirli modüllerden gelen logları engeller."""
def __init__(self, excluded_modules):
super().__init__()
self.excluded_modules = excluded_modules
def filter(self, record):
# True → logla, False → engelle
return record.name not in self.excluded_modules
# Kullanım
logger = logging.getLogger("myapp")
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter("%(name)s - %(message)s"))
# Gürültülü modülleri filtrele
handler.addFilter(ModuleFilter(["myapp.healthcheck", "myapp.metrics"]))
logger.addHandler(handler)Hassas Veri Maskeleme
Production loglarında kredi kartı numarası, şifre veya kişisel bilgi olmamalı:
import logging
import re
class SensitiveDataFilter(logging.Filter):
"""Log mesajlarındaki hassas veriyi maskeler."""
PATTERNS = [
# Kredi kartı numarası (basit pattern)
(re.compile(r"\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b"), "****-****-****-****"),
# E-posta
(re.compile(r"\b[\w.+-]+@[\w-]+\.[\w.-]+\b"), "***@***.***"),
# Şifre parametresi
(re.compile(r"password['\"]?\s*[:=]\s*['\"]?[\w!@#$%^&*]+", re.I), "password=***"),
]
def filter(self, record):
msg = record.getMessage()
for pattern, replacement in self.PATTERNS:
msg = pattern.sub(replacement, msg)
record.msg = msg
record.args = None # Formatlama tekrarını önle
return True
# Kullanım
logger = logging.getLogger("myapp")
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
handler.addFilter(SensitiveDataFilter())
handler.setFormatter(logging.Formatter("%(message)s"))
logger.addHandler(handler)
logger.info("Kullanıcı ali@example.com giriş yaptı")
# Çıktı: Kullanıcı ***@***.*** giriş yaptı
logger.info("Kart: 4532-1234-5678-9012 ile ödeme")
# Çıktı: Kart: ****-****-****-**** ile ödeme8. Yapılandırılmış Loglama (Structured Logging)
Geleneksel log satırları insanlar için okunabilir ama makineler için parse etmesi zordur. Modern sistemlerde loglar ELK Stack (Elasticsearch, Logstash, Kibana), Datadog veya Grafana Loki gibi araçlarla işlenir. Bu araçlar JSON formatında log bekler.
python-json-logger
pip install python-json-loggerimport logging
from pythonjsonlogger import jsonlogger
logger = logging.getLogger("myapp")
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
# JSON formatter
formatter = jsonlogger.JsonFormatter(
"%(asctime)s %(name)s %(levelname)s %(message)s",
rename_fields={
"asctime": "timestamp",
"levelname": "level",
}
)
handler.setFormatter(formatter)
logger.addHandler(handler)
# Standart log
logger.info("Kullanıcı giriş yaptı")
# {"timestamp": "2024-01-15 10:30:00", "name": "myapp", "level": "INFO", "message": "Kullanıcı giriş yaptı"}
# Extra alanlarla
logger.info(
"Sipariş oluşturuldu",
extra={
"order_id": "ORD-123",
"user_id": "USR-42",
"amount": 99.99,
"currency": "TRY"
}
)
# {"timestamp": "...", "level": "INFO", "message": "Sipariş oluşturuldu",
# "order_id": "ORD-123", "user_id": "USR-42", "amount": 99.99, "currency": "TRY"}JSON loglar makineler tarafından kolayca parse edilir. order_id'ye göre filtreleme, amount'a göre toplama gibi sorguları log toplama araçlarında doğrudan çalıştırabilirsin.
9. Production Logging Best Practices
Ortama Göre Yapılandırma
Production'da en iyi pratik: ortama göre (dev/prod) farklı logging yapılandırması kullanmaktır:
import logging
import logging.config
import os
def setup_logging():
"""Ortam değişkenine göre logging yapılandırır."""
env = os.getenv("APP_ENV", "development")
os.makedirs("logs", exist_ok=True)
if env == "development":
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s [%(levelname)s] %(name)s %(funcName)s:%(lineno)d — %(message)s",
datefmt="%H:%M:%S"
)
else:
logging.config.dictConfig({
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"detailed": {
"format": "%(asctime)s [%(levelname)s] %(name)s — %(message)s"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "WARNING",
"formatter": "detailed",
},
"file": {
"class": "logging.handlers.RotatingFileHandler",
"level": "INFO",
"formatter": "detailed",
"filename": "logs/app.log",
"maxBytes": 50_000_000,
"backupCount": 10,
"encoding": "utf-8",
},
},
"loggers": {
"urllib3": {"level": "WARNING"},
"sqlalchemy.engine": {"level": "WARNING"},
},
"root": {"level": "INFO", "handlers": ["console", "file"]},
})
logging.getLogger(__name__).info("Logging hazır — ortam: %s", env)
setup_logging()Gerçek Dünya Kullanım Örneği
"""
order_service.py — Logging'i doğru kullanan bir servis örneği.
"""
import logging
import time
from contextlib import contextmanager
logger = logging.getLogger(__name__)
@contextmanager
def log_operation(operation_name, **context):
"""İşlem süresi ve sonucunu loglar."""
start = time.perf_counter()
logger.info(
"%s başladı", operation_name,
extra=context
)
try:
yield
elapsed = time.perf_counter() - start
logger.info(
"%s tamamlandı (%.3fs)", operation_name, elapsed,
extra=context
)
except Exception:
elapsed = time.perf_counter() - start
logger.error(
"%s başarısız (%.3fs)", operation_name, elapsed,
extra=context,
exc_info=True # Stack trace dahil et
)
raise
class OrderService:
def __init__(self, db, payment_gateway):
self.db = db
self.payment_gateway = payment_gateway
self.logger = logging.getLogger(f"{__name__}.OrderService")
def create_order(self, user_id, items):
self.logger.info(
"Sipariş oluşturuluyor — kullanıcı: %s, ürün sayısı: %d",
user_id, len(items)
)
with log_operation("Stok kontrolü", user_id=user_id):
self._check_stock(items)
with log_operation("Ödeme işlemi", user_id=user_id):
total = sum(item["price"] * item["qty"] for item in items)
self._process_payment(user_id, total)
with log_operation("Sipariş kaydı", user_id=user_id):
order_id = self._save_order(user_id, items)
self.logger.info(
"Sipariş tamamlandı — order_id: %s, toplam: %.2f",
order_id, total
)
return order_id
def _check_stock(self, items):
for item in items:
self.logger.debug("Stok kontrolü: %s", item["name"])
def _process_payment(self, user_id, amount):
self.logger.debug("Ödeme: %.2f TL, kullanıcı: %s", amount, user_id)
def _save_order(self, user_id, items):
self.logger.debug("Veritabanına yazılıyor")
return "ORD-12345"Bu örnekte birkaç önemli pratik var:
Her sınıf kendi logger'ını oluşturuyor (
__name__+ sınıf adı)log_operationcontext manager'ı işlem süresini otomatik ölçüyorexc_info=Truehata durumunda stack trace'i dahil ediyorDebug logları detaylı, info logları anlamlı iş olaylarını kaydediyor
💡 İpucu: Log mesajlarını "5 yıl sonra bu sistemi debuglamaya çalışan bir geliştirici ne bilmek ister?" perspektifinden yaz. "İşlem yapıldı" yerine "Sipariş ORD-123 kullanıcı USR-42 için 99.99 TRY tutarında oluşturuldu" çok daha faydalı.
Sık Yapılan Hatalar
import logging
logger = logging.getLogger(__name__)
# ❌ YANLIŞ: Exception'ı string olarak logla
try:
result = 1 / 0
except Exception as e:
logger.error(f"Hata oluştu: {e}")
# Stack trace YOK — nerede olduğunu bilemezsin
# ✅ DOĞRU: exc_info ile stack trace dahil et
try:
result = 1 / 0
except Exception as e:
logger.error("Hata oluştu", exc_info=True)
# veya daha kısa:
logger.exception("Hata oluştu")
# logger.exception() otomatik olarak exc_info=True ekler
# ❌ YANLIŞ: Root logger'ı kirletme
logging.info("Bu root logger'a gider")
# ✅ DOĞRU: Modül logger'ı kullan
logger = logging.getLogger(__name__)
logger.info("Bu modüle özel logger'a gider")
# ❌ YANLIŞ: Her yerde basicConfig çağırma
# (Sadece bir kez, uygulama başlangıcında çağrılmalı)
logging.basicConfig(level=logging.DEBUG) # İlk çağrıdan sonrası etkisiz
# ✅ DOĞRU: dictConfig veya tek bir setup fonksiyonu kullanlogger.exception() metodu logger.error(..., exc_info=True) ile aynı şeyi yapar — daha kısa ve okunabilir. Sadece bir except bloğu içinde kullan.
Özet
print() yerine logging kullan — seviye kontrolü, filtreleme ve çıktı yönlendirme sağlar
getLogger(__name__) ile her modül kendi logger'ını oluşturur; bu hiyerarşik yapı modüle özgü yapılandırma imkânı tanır
Handler'lar log mesajlarını nereye gönderileceğini belirler;
RotatingFileHandlerile disk dolmasını,TimedRotatingFileHandlerile günlük döndürmeyi yönetirsindictConfig ile tüm yapılandırmayı tek bir yerden (dict, YAML, JSON) yönetmek en temiz yaklaşımdır
Structured logging (JSON format) modern log toplama araçlarıyla uyumlu çalışır ve arama/filtrelemeyi kolaylaştırır
exc_info=True veya
logger.exception()kullanarak stack trace'i her zaman dahil et — hata ayıklama için vazgeçilmez
AI Asistan
Sorularını yanıtlamaya hazır