Uygulamalarınızın derinliklerinde neler olup bittiğini merak mı ediyorsunuz? Bir hata meydana geldiğinde nerede ve neden olduğunu anlamakta zorlanıyor musunuz? İşte bu noktada Python’da loglama devreye giriyor ve kodunuzun kalbine inmenizi sağlayan güçlü bir araç sunuyor. Bu makalede, loglamanın temel prensiplerinden en gelişmiş tekniklerine kadar her şeyi adım adım öğreneceğiz.
Modern yazılım geliştirme dünyasında, herhangi bir uygulamanın kararlılığını ve performansını sağlamak, geliştiriciler için kritik bir görevdir. Bir uygulamanın “gözü” ve “kulağı” niteliğindeki loglama, geliştirme sürecinden üretim ortamına kadar her aşamada vazgeçilmez bir araç haline gelmiştir. Peki, loglama tam olarak nedir ve neden bu kadar önemlidir? Loglama, uygulamanızın belirli olayları (hatalar, uyarılar, bilgi mesajları vb.) kaydetme sürecidir. Bu kayıtlar, sorun giderme, performans analizi, güvenlik denetimi ve hatta kullanıcı davranışlarını anlama gibi birçok farklı amaç için kullanılabilir.
Loglama olmadan, uygulamanızın davranışını anlamak adeta karanlıkta yol almaya benzerdi. Bir hata oluştuğunda, hatanın kaynağını bulmak için saatler harcamanız gerekebilir, çünkü sistemde neler olup bittiğine dair hiçbir kaydınız olmaz. Loglar sayesinde, uygulamanızın belirli bir anda hangi adımları izlediğini, hangi verilerle çalıştığını ve nerede takıldığını net bir şekilde görebilirsiniz. Bu durum, özellikle dağıtık sistemlerde veya karmaşık iş akışlarında hayati önem taşır. Öyleyse, Python’ın zengin
logging
modülünü kullanarak bu süreci nasıl yöneteceğimizi inceleyelim.
Python'ın standart kütüphanesinde yer alan
logging
modülü, oldukça esnek ve güçlü bir loglama altyapısı sunar. Bu modülün temelinde dört ana bileşen bulunur:
- Loggerlar (Kaydediciler): Log mesajlarını oluşturan ana nesnelerdir. Uygulamanızın farklı bölümleri için farklı loggerlar tanımlayabilirsiniz. Örneğin, veritabanı işlemleri için ayrı, web istekleri için ayrı bir logger kullanmak mümkündür. Her loggerın bir seviyesi vardır ve yalnızca o seviyedeki veya daha yüksek seviyedeki mesajları işler.
- Handlerlar (İşleyiciler): Loggerlar tarafından oluşturulan log mesajlarını belirli hedeflere yönlendiren nesnelerdir. Bu hedefler bir dosya, konsol, veritabanı, e-posta veya uzak bir sunucu olabilir. Bir loggera birden fazla handler ekleyerek aynı log mesajını farklı yerlere gönderebilirsiniz.
- Formatlayıcılar (Formatters): Log mesajlarının nasıl görüneceğini belirleyen nesnelerdir. Zaman damgası, log seviyesi, dosya adı, satır numarası gibi bilgileri mesaj metniyle birleştirerek okunabilir bir çıktı oluştururlar.
- Filtreler (Filters): Log mesajlarını daha fazla kontrol etmek için kullanılırlar. Belirli kriterlere uyan mesajların işlenip işlenmeyeceğine karar verirler. Örneğin, yalnızca belirli bir kelimeyi içeren mesajları loglamak için bir filtre kullanabilirsiniz.
Bu bileşenlerin yanı sıra, log mesajlarının önem derecesini belirten log seviyeleri de kritik bir rol oynar. Python'da varsayılan olarak tanımlanmış beş standart log seviyesi bulunmaktadır:
| Seviye | Açıklama |
|---|---|
|
Detaylı hata ayıklama bilgileri, yalnızca bir problemi teşhis ederken ilgilenilir. |
|
Uygulamanın beklendiği gibi çalıştığını doğrulayan genel bilgiler. |
|
Beklenmeyen bir olayın veya gelecekte bir soruna yol açabilecek bir durumun göstergesi. Yazılım halen doğru çalışıyor. |
|
Ciddi bir problem nedeniyle yazılımın bazı fonksiyonları gerçekleştiremediği durumlar. |
|
Uygulamanın kendisinin veya kritik bir bileşeninin tamamen durmasına neden olan ciddi hatalar. |
Bu seviyeler, log mesajlarının önem sırasına göre filtrelenmesini ve işlenmesini sağlar. Örneğin, üretim ortamında yalnızca
INFO
ve üzeri seviyedeki mesajları loglarken, geliştirme ortamında
DEBUG
seviyesini kullanarak daha detaylı bilgi edinebiliriz. Bu esneklik, Python loglama mekanizmasını son derece kullanışlı kılmaktadır.
Python'da Temel Loglama Nasıl Yapılır? Uygulamalı Adımlar
Python'da loglama işlemine başlamak oldukça kolaydır ve standart
logging
modülü ile gerçekleştirilir. Öncelikle, en temel düzeyde bir loglama kurulumunu nasıl yapacağımızı ele alalım. Varsayılan olarak,
logging
modülü sadece
WARNING
veya daha yüksek seviyedeki mesajları konsola yazdırır. Ancak bu davranışı kolayca değiştirebilir ve ihtiyaçlarımıza göre özelleştirebiliriz. İşte adım adım temel loglama kurulumu ve kullanımı:
Basit Bir Logger Oluşturma ve Kullanma
İlk olarak, Python'ın
logging
modülünü içe aktarıp temel log mesajları göndermeyi deneyelim. Aşağıdaki kod bloğu, çeşitli log seviyelerindeki mesajları nasıl oluşturacağınızı göstermektedir:
import logging
# Varsayılan logger'ı kullanarak log mesajları gönderelim
logging.debug("Bu bir hata ayıklama mesajıdır.")
logging.info("Bu bir bilgi mesajıdır.")
logging.warning("Bu bir uyarı mesajıdır. Dikkat edin!")
logging.error("Bu bir hata mesajıdır. Bir şeyler ters gitti.")
logging.critical("Bu kritik bir hata mesajıdır! Uygulama çökebilir.")
Yukarıdaki kodu çalıştırdığınızda, konsolda muhtemelen sadece
WARNING
,
ERROR
ve
CRITICAL
mesajlarını göreceksiniz. Bunun nedeni, Python'ın varsayılan loglama seviyesinin
WARNING
olarak ayarlanmış olmasıdır.
DEBUG
ve
INFO
mesajları varsayılan olarak göz ardı edilir.
Loglama Seviyesini ve Formatını Yapılandırma
Loglama çıktısını tamamen kontrol etmek için
basicConfig()
fonksiyonunu kullanırız. Bu fonksiyon, temel loglama ayarlarını tek bir çağrıyla yapmanızı sağlar. Genellikle betiğinizin veya uygulamanızın başlangıcında bir kez çağrılmalıdır.
import logging
# Temel loglama yapılandırması
# level: Loglanacak en düşük seviye
# format: Log mesajlarının çıktı formatı
# filename: Logların yazılacağı dosya (isteğe bağlı)
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s')
logging.debug("Artık DEBUG mesajlarını da göreceksiniz.")
logging.info("Bilgi mesajları artık loglanıyor.")
logging.warning("Hala uyarı mesajları alıyoruz.")
logging.error("Hatalar her zaman önemlidir.")
logging.critical("Kritik durumlar alarm verir.")
Bu kodda,
level=logging.DEBUG
diyerek en düşük loglama seviyesini
DEBUG
olarak ayarladık. Bu,
DEBUG
ve daha yüksek tüm seviyelerdeki mesajların işleneceği anlamına gelir.
format
parametresi ise log çıktısının yapısını belirler. Burada kullanılan bazı format belirleyiciler şunlardır:
-
%(asctime)s: Log mesajının oluşturulduğu zaman.
-
%(levelname)s: Log mesajının seviyesi (DEBUG, INFO, WARNING vb.).
-
%(message)s: Asıl log mesajı.
-
%(name)s: Logger'ın adı (varsayılan olarak
root).
-
%(filename)s: Loglama çağrısının yapıldığı dosya adı.
-
%(lineno)d: Loglama çağrısının yapıldığı satır numarası.
Logları Bir Dosyaya Yazdırma
Sadece konsola log yazmak çoğu zaman yeterli değildir. Kalıcı kayıtlar için logları bir dosyaya yazmamız gerekir.
basicConfig()
fonksiyonuna
filename
parametresini ekleyerek bunu kolayca yapabiliriz:
import logging
# Logları bir dosyaya yönlendirme
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
filename='uygulama.log',
filemode='a') # 'a' ekleme modudur, 'w' ise her seferinde dosyayı siler
logger = logging.getLogger('ana_uygulama') # Özel bir logger oluşturma
logger.info("Uygulama başlatılıyor...")
logger.debug("Bu mesaj dosyaya yazılmayacak çünkü seviye INFO.") # Bu mesaj loglanmayacak
logger.warning("Veritabanı bağlantısı kurulurken bir sorun yaşandı.")
try:
1 / 0
except ZeroDivisionError:
logger.error("Sıfıra bölme hatası meydana geldi!", exc_info=True)
Bu örnekte,
filename='uygulama.log'
ile logların
uygulama.log
adlı bir dosyaya yazılmasını sağladık.
filemode='a'
ise, her uygulama çalıştığında logların dosyanın sonuna eklenmesini sağlar. Eğer
'w'
(write) kullanırsak, her seferinde dosya silinip yeniden oluşturulur. Ayrıca,
logging.getLogger('ana_uygulama')
ile özel bir logger oluşturduk. Bu, daha modüler ve yönetilebilir bir loglama yapısı kurmamızı sağlar. Hata durumlarında
exc_info=True
parametresini kullanarak istisna bilgilerini (traceback) loga dahil etmek, sorun giderme açısından son derece değerlidir.
Farklı Handler'ları Birlikte Kullanma
Bazen log mesajlarını hem konsola hem de bir dosyaya yazmak isteyebilirsiniz. Python'ın loglama modülü bunu kolayca yapmamızı sağlar. Farklı
Handler
nesneleri oluşturup bunları aynı
Logger
nesnesine ekleyerek bu işlevi yerine getirebiliriz.
import logging
# Logger oluşturma
logger = logging.getLogger('cift_cikis_logger')
logger.setLevel(logging.DEBUG) # En düşük seviyeyi ayarla
# Konsol handler'ı oluşturma
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO) # Konsola sadece INFO ve üzeri mesajları gönder
# Dosya handler'ı oluşturma
file_handler = logging.FileHandler('cift_cikis.log')
file_handler.setLevel(logging.DEBUG) # Dosyaya tüm DEBUG ve üzeri mesajları gönder
# Formatlayıcı oluşturma
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# Handler'lara formatlayıcıyı ekleme
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)
# Logger'a handler'ları ekleme
logger.addHandler(console_handler)
logger.addHandler(file_handler)
logger.debug("Bu bir hata ayıklama mesajıdır. Sadece dosyaya gider.")
logger.info("Bu bir bilgi mesajıdır. Hem konsola hem dosyaya gider.")
logger.warning("Bu bir uyarı mesajıdır. Hem konsola hem dosyaya gider.")
Bu örnekte, aynı logger'a hem bir
StreamHandler
(konsol için) hem de bir
FileHandler
(dosya için) ekledik. Her handler'ın kendi log seviyesi olabilir, bu da çıktıyı daha da özelleştirmemize olanak tanır.
DEBUG
mesajı yalnızca dosyaya yazılırken,
INFO
ve
WARNING
mesajları hem konsola hem de dosyaya yazılacaktır. Bu yapılandırma, uygulamanızın farklı ortamlar veya farklı ihtiyaçlar için esnek loglama stratejileri geliştirmesine imkan tanır.
Gelişmiş Loglama Teknikleri: Performans ve Güvenlik İçin Ne Yapmalıyız?
Temel loglama kurulumu birçok senaryo için yeterli olsa da, büyük ölçekli uygulamalar, yüksek trafikli web servisleri veya uzun süre çalışan arka plan görevleri için daha gelişmiş tekniklere ihtiyaç duyarız. Bu bölümde, loglama performansını artıran, log güvenliğini sağlayan ve log yönetimini kolaylaştıran ileri düzey Python loglama yaklaşımlarını inceleyeceğiz.
Log Dosyası Yönetimi: RotatingFileHandler ve TimedRotatingFileHandler
Tek bir log dosyasına sürekli yazmak, zamanla dosyanın boyutunun aşırı büyümesine neden olabilir. Bu durum, disk alanı sorunlarına yol açabileceği gibi, log dosyasının açılıp okunmasını da zorlaştırabilir. Python'ın
logging.handlers
modülü, bu sorunu çözmek için
RotatingFileHandler
ve
TimedRotatingFileHandler
gibi özel handler'lar sunar.
RotatingFileHandler ile Boyuta Göre Döndürme
RotatingFileHandler
, log dosyasının belirli bir boyuta ulaştığında otomatik olarak yeni bir dosyaya dönmesini sağlar. Eski dosyalar genellikle yeniden adlandırılarak yedeklenir. Bu sayede, log dosyalarınızın kontrol altında kalmasını sağlarsınız.
import logging
from logging.handlers import RotatingFileHandler
import time
import os
LOG_FILE = 'uygulama_dondur.log'
# Önceki log dosyalarını temizleyelim (test amaçlı)
if os.path.exists(LOG_FILE):
os.remove(LOG_FILE)
for i in range(5):
if os.path.exists(f"{LOG_FILE}.{i}"):
os.remove(f"{LOG_FILE}.{i}")
logger = logging.getLogger('rotating_logger')
logger.setLevel(logging.INFO)
# RotatingFileHandler: maxBytes=1024 (1KB), backupCount=5 (5 yedek dosya)
handler = RotatingFileHandler(LOG_FILE, maxBytes=1024, backupCount=5)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
print(f"Loglar '{LOG_FILE}' dosyasına yazılacak. Her 1KB'da bir dönecek ve 5 yedek tutulacak.")
# Çok sayıda log mesajı göndererek dosyaların dönmesini sağlayalım
for i in range(1000):
logger.info(f"Bu {i}. log mesajıdır. Dosya boyutu büyüyor...")
time.sleep(0.001) # Çok hızlı yazmamak için küçük bir bekleme
print("Loglama tamamlandı. Dosyaları kontrol edin.")
Bu örnekte,
uygulama_dondur.log
dosyası her 1 KB'a ulaştığında, mevcut dosya
uygulama_dondur.log.1
olarak yeniden adlandırılır ve yeni bir
uygulama_dondur.log
dosyası oluşturulur.
backupCount=5
sayesinde en fazla 5 eski log dosyası saklanır.
TimedRotatingFileHandler ile Zamana Göre Döndürme
TimedRotatingFileHandler
ise log dosyalarını belirli bir zaman dilimine (örneğin, günlük, haftalık) göre döndürür. Bu, özellikle günlük veya aylık raporlamalar için logları ayrı tutmak istediğinizde çok kullanışlıdır.
import logging
from logging.handlers import TimedRotatingFileHandler
import time
import os
LOG_FILE_TIMED = 'uygulama_zamanli.log'
# Test amaçlı temizlik
if os.path.exists(LOG_FILE_TIMED):
os.remove(LOG_FILE_TIMED)
# Diğer zamanlanmış dosyaları da temizleyebiliriz, ancak formatı değişkendir.
logger_timed = logging.getLogger('timed_logger')
logger_timed.setLevel(logging.INFO)
# TimedRotatingFileHandler: when='s' (saniye), interval=5 (her 5 saniyede bir), backupCount=3
# 'midnight', 'h', 'd', 'w0'-'w6', 's', 'm' değerleri kullanılabilir.
handler_timed = TimedRotatingFileHandler(LOG_FILE_TIMED,
when='s',
interval=5,
backupCount=3)
formatter_timed = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
handler_timed.setFormatter(formatter_timed)
logger_timed.addHandler(handler_timed)
print(f"Loglar '{LOG_FILE_TIMED}' dosyasına yazılacak. Her 5 saniyede bir dönecek ve 3 yedek tutulacak.")
for i in range(15): # 15 saniye boyunca log gönder (3 döndürme göreceğiz)
logger_timed.info(f"Bu {i}. zamanlanmış log mesajıdır. Bekleyelim...")
time.sleep(1)
print("Zamanlanmış loglama tamamlandı. Dosyaları kontrol edin.")
Bu örnekte, log dosyası her 5 saniyede bir dönecektir.
backupCount=3
sayesinde 3 eski log dosyası zaman damgasıyla birlikte (
uygulama_zamanli.log.YYYY-MM-DD_HH-MM-SS
formatında) saklanacaktır. Bu, log dosyalarının manuel müdahaleye gerek kalmadan düzenli kalmasını sağlar.
Yapılandırma Dosyası Kullanarak Loglama Ayarları
Uygulamanın kodunda loglama ayarlarını yapmak, karmaşıklaştıkça yönetimi zorlaştırabilir. Daha esnek ve dışarıdan yönetilebilir bir çözüm için loglama yapılandırmasını bir dosyada tutmak tercih edilir. Python'ın
logging.config
modülü, JSON, YAML veya INI formatındaki dosyaları kullanarak loglama ayarlarını yüklemenize olanak tanır.
Örnek bir
logging.ini
dosyası:
[loggers]
keys=root,my_app_logger
[handlers]
keys=consoleHandler,fileHandler
[formatters]
keys=simpleFormatter,detailedFormatter
[logger_root]
level=INFO
handlers=consoleHandler
[logger_my_app_logger]
level=DEBUG
handlers=consoleHandler,fileHandler
qualname=my_app_logger
propagate=0
[handler_consoleHandler]
class=StreamHandler
level=INFO
formatter=simpleFormatter
args=(sys.stdout,)
[handler_fileHandler]
class=FileHandler
level=DEBUG
formatter=detailedFormatter
args=('app_config.log','a')
[formatter_simpleFormatter]
format=%(levelname)s - %(message)s
[formatter_detailedFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(module)s:%(lineno)d - %(message)s
Python kodu ile bu dosyayı yükleme:
import logging.config
import os
import sys
# logging.ini dosyasını yükleyelim
# Dosyanın varlığını kontrol etmek önemlidir
config_file = 'logging.ini'
if os.path.exists(config_file):
logging.config.fileConfig(config_file, disable_existing_loggers=False)
print(f"Loglama yapılandırması '{config_file}' dosyasından yüklendi.")
else:
print(f"Hata: '{config_file}' bulunamadı. Varsayılan loglama kullanılıyor.")
logging.basicConfig(level=logging.INFO) # Yedek olarak basicConfig
# Loggers'ı alalım ve kullanalım
root_logger = logging.getLogger()
app_logger = logging.getLogger('my_app_logger')
root_logger.info("Bu root logger'dan bir bilgi mesajıdır.")
root_logger.debug("Root logger'ın varsayılan seviyesi INFO olduğu için bu görünmez.")
app_logger.debug("Bu özel app_logger'dan bir hata ayıklama mesajıdır. Dosyaya yazılmalı.")
app_logger.info("Bu app_logger'dan bir bilgi mesajıdır. Hem konsola hem dosyaya yazılmalı.")
app_logger.error("Bir hata meydana geldi, app_logger ile loglandı.", exc_info=True)
Yapılandırma dosyaları, uygulamanızı dağıtırken veya farklı ortamlar için loglama ayarlarını değiştirmek istediğinizde büyük kolaylık sağlar. Kodda değişiklik yapmadan sadece yapılandırma dosyasını güncelleyerek loglama davranışını değiştirebilirsiniz.
Loglama Güvenliği: Hassas Verilerin Gizlenmesi
Loglama yaparken en önemli konulardan biri de güvenliktir. Kullanıcı şifreleri, kredi kartı numaraları, kişisel kimlik bilgileri gibi hassas verilerin loglara yazılmaması gerekir. Bu tür veriler, log dosyaları ele geçirildiğinde ciddi güvenlik açıkları yaratabilir. Hassas verileri loglardan uzak tutmak için birkaç yöntem mevcuttur:
- Filtreler Kullanma:
logging.Filtersınıfını kullanarak log mesajlarını filtreleyebilirsiniz. Belirli anahtar kelimeleri veya desenleri içeren mesajları algılayıp sansürleyebilir veya tamamen atabilirsiniz.
- Veri Maskeleme: Loga yazmadan önce hassas verileri maskelemek (örneğin, şifreleri
ile değiştirmek) yaygın bir uygulamadır.
- Doğru Log Seviyeleri: Hassas verileri sadece
DEBUGseviyesinde loglamak ve üretim ortamında bu seviyenin altındaki logları kapatmak bir çözüm olabilir, ancak yine de risk taşır. En güvenli yöntem, bu verileri asla loglamamaktır.
import logging
class CustomFilter(logging.Filter):
def filter(self, record):
message = record.getMessage()
if "şifre" in message or "token" in message:
record.msg = self.mask_sensitive_data(record.msg)
if record.args:
record.args = tuple(self.mask_sensitive_data(str(arg)) if isinstance(arg, str) else arg for arg in record.args)
return True
def mask_sensitive_data(self, data):
# Basit bir maskeleme örneği
return data.replace("şifre123", "[MASKED_PASSWORD]").replace("my_secret_token", "[MASKED_TOKEN]")
logger = logging.getLogger('security_logger')
logger.setLevel(logging.DEBUG)
# Konsol handler'ı
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
console_handler.setFormatter(formatter)
# Filtreyi handler'a ekleme
console_handler.addFilter(CustomFilter())
logger.addHandler(console_handler)
logger.info("Kullanıcı login oldu: username=testuser, şifre=şifre123")
logger.debug("API çağrısı: endpoint=/api/data, token=my_secret_token")
logger.warning("Genel bir uyarı mesajı.")
Bu
CustomFilter
örneği, log mesajında "şifre" veya "token" kelimeleri geçtiğinde ilgili hassas verileri maskeler. Bu tür filtreler, uygulamanızın loglama stratejisini daha güvenli hale getirmenize yardımcı olur. Loglama işlemi sırasında, özellikle kullanıcı girişi veya dış sistemlerle etkileşim içeren kısımlarda dikkatli olmak ve hassas verileri asla açık metin olarak loglamamak temel güvenlik prensibidir.
Gerçek Dünya Senaryoları: Python Loglama Vaka Analizleri
Teorik bilgiler ve basit örnekler loglamanın temellerini anlamak için harikadır, ancak gerçek dünya uygulamalarında loglamanın nasıl kullanıldığını görmek, bu beceriyi derinleştirmek için kritik öneme sahiptir. İşte farklı senaryolarda Python loglamayı nasıl kullanabileceğimize dair iki vaka analizi.
Vaka Analizi 1: E-ticaret Uygulamasında Sipariş İşleme Akışı İzleme
Bir e-ticaret uygulamasında, kullanıcıların sipariş verme, ödeme yapma ve siparişin sevkiyatını takip etme gibi karmaşık bir akış vardır. Bu akışın her adımını izlemek ve olası hataları tespit etmek, müşteri memnuniyeti ve iş sürekliliği için hayati öneme sahiptir. Python loglama modülü bu senaryoda güçlü bir araç olarak karşımıza çıkar.
Senaryo Detayları:
Kullanıcı bir ürün seçer, sepete ekler, ödeme bilgilerini girer ve siparişi tamamlar. Bu süreçte aşağıdaki adımlar gerçekleşir:
- Ürün sepete eklenir.
- Ödeme işlemi başlatılır.
- Ödeme başarılı veya başarısız olur.
- Sipariş veritabanına kaydedilir.
- Stok güncellemesi yapılır.
- Sipariş onay e-postası gönderilir.
import logging
import random
import sys
import os
# Logger'ı yapılandırma
LOG_FILE = 'ecommerce_orders.log'
if os.path.exists(LOG_FILE):
os.remove(LOG_FILE) # Her çalıştırmada temizle
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(LOG_FILE),
logging.StreamHandler(sys.stdout) # Konsola da yaz
]
)
order_logger = logging.getLogger('OrderProcessor')
def add_to_cart(user_id, product_id, quantity):
order_logger.info(f"Kullanıcı {user_id}: Ürün {product_id} (adet: {quantity}) sepete eklendi.")
# Gerçek sepete ekleme mantığı burada olur
return True
def process_payment(user_id, order_id, amount):
order_logger.info(f"Kullanıcı {user_id}: Sipariş {order_id} için {amount} TL ödeme işlemi başlatıldı.")
if random.random() < 0.9: # %90 başarı oranı
order_logger.info(f"Kullanıcı {user_id}: Sipariş {order_id} için ödeme başarılı.")
return True
else:
order_logger.error(f"Kullanıcı {user_id}: Sipariş {order_id} için ödeme başarısız oldu!")
return False
def save_order_to_db(order_details):
order_logger.info(f"Sipariş {order_details['order_id']} veritabanına kaydediliyor.")
if random.random() < 0.95: # %95 başarı oranı
order_logger.info(f"Sipariş {order_details['order_id']} başarıyla kaydedildi.")
return True
else:
order_logger.critical(f"Sipariş {order_details['order_id']} veritabanına kaydedilemedi! Kritik hata.", exc_info=True)
return False
def update_stock(product_id, quantity):
order_logger.info(f"Ürün {product_id} için stok güncelleniyor, {quantity} adet azaltılıyor.")
# Stok güncelleme mantığı
return True
def send_confirmation_email(user_id, order_id):
order_logger.info(f"Kullanıcı {user_id} için sipariş {order_id} onay e-postası gönderiliyor.")
# E-posta gönderme mantığı
return True
def complete_order_flow(user_id, product_id, quantity, amount):
order_id = f"ORD-{random.randint(1000, 9999)}"
order_logger.info(f"[{order_id}] Yeni sipariş akışı başlatıldı: Kullanıcı={user_id}, Ürün={product_id}, Adet={quantity}, Tutar={amount}")
if not add_to_cart(user_id, product_id, quantity):
order_logger.error(f"[{order_id}] Sepete eklenemedi. Akış durduruldu.")
return
if not process_payment(user_id, order_id, amount):
order_logger.error(f"[{order_id}] Ödeme başarısız. Akış durduruldu.")
return
order_details = {
'order_id': order_id,
'user_id': user_id,
'product_id': product_id,
'quantity': quantity,
'amount': amount,
'status': 'completed'
}
if not save_order_to_db(order_details):
order_logger.critical(f"[{order_id}] Veritabanı kaydı başarısız. Akış kritik hata ile durduruldu.")
return
update_stock(product_id, quantity)
send_confirmation_email(user_id, order_id)
order_logger.info(f"[{order_id}] Sipariş başarıyla tamamlandı.")
# Birkaç sipariş akışını simüle edelim
print("\n--- Sipariş Akışı 1 ---")
complete_order_flow("user_alice", "PROD-101", 2, 150.00)
print("\n--- Sipariş Akışı 2 ---")
complete_order_flow("user_bob", "PROD-203", 1, 75.50)
print("\n--- Sipariş Akışı 3 (Hata Bekleniyor) ---")
complete_order_flow("user_charlie", "PROD-404", 3, 220.00)
Bu örnekte, her bir sipariş akışı adımı için
INFO
seviyesinde loglar oluşturulur. Olası hatalar (
payment_failed
,
db_save_failed
)
ERROR
veya
CRITICAL
seviyesinde loglanarak hemen fark edilmeleri sağlanır.
exc_info=True
kullanımı, istisna ayrıntılarını da loga ekleyerek hata ayıklamayı kolaylaştırır. Bu sayede, operasyon ekipleri anormallikleri hızlıca tespit edebilir ve müşteri deneyimi olumsuz etkilenmeden müdahale edebilir.
Vaka Analizi 2: Veri İşleme Hattında Performans ve Anomali Tespiti
Büyük veri işleme hatlarında (data pipelines), verilerin farklı aşamalardan geçerek dönüştürüldüğü ve analiz edildiği karmaşık süreçler bulunur. Bu hatların performansı ve doğru çalışması, nihai analitik sonuçların güvenilirliği için kritik öneme sahiptir. Loglama, bu hatlardaki darboğazları ve veri anormalliklerini tespit etmek için mükemmel bir araçtır.
Senaryo Detayları:
Bir veri hattı, ham verileri alır, temizler, dönüştürür ve bir veri ambarına yükler. Her adımın ne kadar sürdüğünü ve potansiyel veri kalitesi sorunlarını izlemek istiyoruz.
import logging
import time
import random
import sys
import os
# Logger'ı yapılandırma
LOG_FILE_DATA_PIPELINE = 'data_pipeline.log'
if os.path.exists(LOG_FILE_DATA_PIPELINE):
os.remove(LOG_FILE_DATA_PIPELINE)
logging.basicConfig(
level=logging.DEBUG, # Gelişme ortamında daha detaylı loglar için DEBUG
format='%(asctime)s - %(name)s - %(levelname)s - %(process)d - %(message)s',
handlers=[
logging.FileHandler(LOG_FILE_DATA_PIPELINE),
logging.StreamHandler(sys.stdout)
]
)
pipeline_logger = logging.getLogger('DataPipeline')
def fetch_data(source_id):
start_time = time.time()
pipeline_logger.debug(f"[{source_id}] Veri kaynağı {source_id}'dan veri çekiliyor...")
time.sleep(random.uniform(0.1, 0.5)) # Simüle edilmiş gecikme
data_records = random.randint(100, 1000)
end_time = time.time()
pipeline_logger.info(f"[{source_id}] Veri çekme tamamlandı ({data_records} kayıt). Süre: {end_time - start_time:.2f}s")
return {"source": source_id, "records": data_records, "raw_data": [random.randint(1, 100) for _ in range(data_records)]}
def clean_data(data):
start_time = time.time()
source_id = data['source']
total_records = data['records']
corrupted_records = random.randint(0, int(total_records * 0.05)) # %0-5 arası bozuk kayıt
cleaned_records = total_records - corrupted_records
if corrupted_records > 0:
pipeline_logger.warning(f"[{source_id}] {corrupted_records} adet bozuk kayıt tespit edildi ve temizlendi.")
time.sleep(random.uniform(0.05, 0.3))
end_time = time.time()
pipeline_logger.info(f"[{source_id}] Veri temizleme tamamlandı ({cleaned_records} geçerli kayıt). Süre: {end_time - start_time:.2f}s")
data["cleaned_records"] = cleaned_records
return data
def transform_data(data):
start_time = time.time()
source_id = data['source']
pipeline_logger.debug(f"[{source_id}] Veriler dönüştürülüyor...")
# Dönüşüm mantığı (örneğin,聚合, zenginleştirme)
if random.random() < 0.02: # %2 hata olasılığı
raise ValueError("Veri dönüşüm hatası!")
time.sleep(random.uniform(0.1, 0.6))
end_time = time.time()
pipeline_logger.info(f"[{source_id}] Veri dönüşümü tamamlandı. Süre: {end_time - start_time:.2f}s")
data["transformed"] = True
return data
def load_data_to_warehouse(data):
start_time = time.time()
source_id = data['source']
pipeline_logger.debug(f"[{source_id}] Veri ambarına yükleniyor...")
time.sleep(random.uniform(0.2, 0.8))
end_time = time.time()
pipeline_logger.info(f"[{source_id}] Veri ambarına yükleme tamamlandı. Yüklenen kayıt: {data.get('cleaned_records', 'N/A')}. Süre: {end_time - start_time:.2f}s")
return True
def run_data_pipeline(source_id):
pipeline_logger.info(f"--- Veri hattı başlatıldı: Kaynak {source_id} ---")
try:
raw_data = fetch_data(source_id)
cleaned_data = clean_data(raw_data)
transformed_data = transform_data(cleaned_data)
load_data_to_warehouse(transformed_data)
pipeline_logger.info(f"--- Veri hattı başarıyla tamamlandı: Kaynak {source_id} ---")
except ValueError as e:
pipeline_logger.error(f"--- Veri hattı hatası: Kaynak {source_id}. Hata: {e} ---", exc_info=True)
except Exception as e:
pipeline_logger.critical(f"--- Veri hattı kritik hatası: Kaynak {source_id}. Beklenmeyen hata: {e} ---", exc_info=True)
# Çeşitli veri kaynakları için hattı çalıştıralım
for i in range(1, 4):
run_data_pipeline(f"SRC-{i}")
time.sleep(1) # Arka arkaya çok hızlı çalıştırmamak için
Bu senaryoda, her bir işlem adımının başlangıç ve bitiş zamanlarını
INFO
seviyesinde loglayarak performans izlemesi yaparız.
DEBUG
mesajları, geliştirme aşamasında daha detaylı içgörüler sağlar. Veri temizleme adımında tespit edilen bozuk kayıtlar
WARNING
olarak işaretlenir.
transform_data
fonksiyonundaki olası hatalar
ERROR
olarak yakalanır ve
exc_info=True
ile detaylı hata bilgisi loglanır. Bu sayede, hem hattın genel performansını gözlemleyebilir hem de olası veri kalitesi sorunlarını veya işlem hatalarını proaktif olarak tespit edebiliriz. Zaman damgaları ve süreç ID'leri sayesinde, logları merkezi bir log toplama sisteminde (ELK Stack gibi) analiz ederken belirli bir kaynak veya sürecin davranışını kolayca izleyebiliriz.
Loglama İpuçları ve En İyi Uygulamalar
Python'da loglama sadece temel fonksiyonları kullanmaktan ibaret değildir. Uygulamanız büyüdükçe ve karmaşıklaştıkça, loglama stratejinizin de gelişmesi gerekir. İşte daha verimli, okunabilir ve yönetilebilir loglar oluşturmanıza yardımcı olacak bazı ipuçları ve en iyi uygulamalar:
1. Anlamlı Logger İsimleri Kullanın
Her modül veya önemli uygulama bileşeni için ayrı bir logger oluşturmak, logların izlenebilirliğini artırır. Örneğin, bir web uygulamasında
auth
modülü için
logging.getLogger('my_app.auth')
, veritabanı işlemleri için
logging.getLogger('my_app.database')
gibi hiyerarşik isimler kullanın. Bu, logları filtrelerken veya analiz ederken hangi mesajın nereden geldiğini hızlıca anlamanızı sağlar.
import logging
# Hiyerarşik logger örnekleri
logger_auth = logging.getLogger('my_app.auth')
logger_db = logging.getLogger('my_app.database')
# Her logger kendi içinde yapılandırılabilir
logger_auth.setLevel(logging.INFO)
logger_db.setLevel(logging.DEBUG) # Veritabanı için daha detaylı log
# ... handler ve formatter ekleme işlemleri ...
logger_auth.info("Kullanıcı 'admin' başarıyla giriş yaptı.")
logger_db.debug("Veritabanı bağlantısı kontrol ediliyor...")
2. İstisnaları Doğru Şekilde Loglayın (
exc_info=True
exc_info=True)
Bir hata (istisna) meydana geldiğinde, yalnızca hata mesajını değil, tüm izleme bilgisini (traceback) loglamak çok önemlidir.
logger.error()
veya
logger.exception()
yöntemleriyle
exc_info=True
parametresini kullanarak bunu yapabilirsiniz.
logger.exception()
özel bir durumdur, her zaman
ERROR
seviyesinde loglar ve otomatik olarak
exc_info=True
olarak ayarlanmıştır.
import logging
logging.basicConfig(level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger('error_logger')
try:
result = 10 / 0
except ZeroDivisionError as e:
logger.error("Sıfıra bölme hatası meydana geldi!", exc_info=True)
# veya daha kısa olarak:
# logger.exception("Sıfıra bölme hatası meydana geldi!")
3. Dinamik ve Bağlamsal Loglama (Contextual Logging)
Log mesajlarına, uygulamanın o anki durumu hakkında daha fazla bilgi eklemek, sorun gidermeyi kolaylaştırır. Örneğin, bir web isteği işlenirken, isteğin benzersiz ID'sini, kullanıcının IP adresini veya kullanıcının ID'sini her log mesajına dahil etmek faydalı olabilir. Bunu yapmanın bir yolu, log mesajlarını formatlarken veya
LoggerAdapter
kullanarak ek bağlam bilgileri eklemektir.
import logging
# LoggerAdapter ile bağlamsal bilgi ekleme
class ContextualAdapter(logging.LoggerAdapter):
def process(self, msg, kwargs):
return f"[{self.extra['request_id']}] {msg}", kwargs
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
base_logger = logging.getLogger('my_api')
request_id = "req-12345"
adapter = ContextualAdapter(base_logger, {'request_id': request_id})
adapter.info("API isteği başlatıldı.")
# ... işleme ...
adapter.warning("Bazı veriler eksik.")
adapter.info("API isteği tamamlandı.")
Bu yaklaşım sayesinde, farklı isteklerin loglarını kolayca ayırt edebilir ve izleyebiliriz. Ayrıca her log çağrısında tekrar tekrar aynı bilgileri yazmak zorunda kalmayız.
4. Log Seviyelerini Akıllıca Kullanın
Her log mesajının uygun bir seviyede olması gerekir. Gereksiz yere yüksek seviyelerde loglama (örneğin her şeyi
ERROR
olarak işaretlemek) gerçek sorunların gözden kaçmasına neden olabilir. Aşırı
DEBUG
loglama ise performans düşüşüne ve log dosyalarının aşırı büyümesine yol açar.
-
DEBUG: Geliştirme ve hata ayıklama için çok detaylı bilgiler.
-
INFO: Uygulamanın normal işleyişini gösteren genel bilgiler (örneğin, "Kullanıcı giriş yaptı", "Veri işleme başladı").
-
WARNING: Potansiyel sorunlar veya beklenen ama istenmeyen durumlar (örneğin, "Yapılandırma dosyası bulunamadı, varsayılanlar kullanılıyor").
-
ERROR: Uygulamanın belirli bir fonksiyonunun düzgün çalışmasını engelleyen hatalar.
-
CRITICAL: Uygulamanın veya sistemin kararlılığını etkileyen ciddi hatalar.
5. Harici Kütüphaneler ve Merkezi Loglama Sistemleri ile Entegrasyon
Büyük ölçekli uygulamalarda, logları merkezi bir sistemde toplamak ve analiz etmek esastır. Popüler seçenekler arasında ELK Stack (Elasticsearch, Logstash, Kibana), Splunk, Graylog ve Datadog bulunur. Python'da, bu sistemlere log göndermek için çeşitli kütüphaneler (örneğin,
logstash-formatter
veya
fluent-logger
) veya doğrudan TCP/UDP socket handler'ları kullanılabilir.
Merkezi loglama, tüm uygulama ve servislerinizin loglarını tek bir yerden izlemenizi, aramanızı ve görselleştirmenizi sağlar. Bu, mikroservis mimarilerinde veya birden fazla sunucuya dağılmış uygulamalarda sorun gidermeyi ve operasyonel görünürlüğü önemli ölçüde artırır.
Bu ipuçları ve en iyi uygulamaları takip ederek, Python uygulamalarınızda daha sağlam, izlenebilir ve yönetilebilir bir loglama stratejisi oluşturabilirsiniz. Loglama, sadece hata ayıklama aracı değil, aynı zamanda uygulamanızın sağlığını ve performansını sürekli izlemek için güçlü bir operasyonel araçtır.
Sonuç ve Sıkça Sorulan Sorular
Python'da loglama, uygulamanızın davranışını anlamak, hataları tespit etmek, performansı izlemek ve güvenlik açıklarını saptamak için hayati bir araçtır. Bu makalede, loglamanın temel kavramlarından başlayarak, farklı log seviyeleri, handler'lar, formatlayıcılar ve filtreler gibi yapı taşlarını detaylı bir şekilde inceledik. Ayrıca, log dosyası yönetimi için
RotatingFileHandler
ve
TimedRotatingFileHandler
gibi gelişmiş tekniklere değindik ve yapılandırma dosyaları kullanarak loglama ayarlarını nasıl yöneteceğimizi öğrendik. Gerçek dünya vaka analizleri sayesinde, e-ticaret sipariş işleme ve veri işleme hatları gibi karmaşık senaryolarda loglamanın pratik uygulamalarını gördük.
Unutmayın ki etkili bir loglama stratejisi, sadece kodunuzdaki hataları bulmaktan çok daha fazlasını sunar. Uygulamanızın yaşam döngüsü boyunca bir "kara kutu" olmasını engelleyerek, size sürekli bir geri bildirim mekanizması sağlar. İyi yapılandırılmış loglar, geliştirme süreçlerini hızlandırır, operasyonel yükü azaltır ve nihayetinde daha güvenilir ve bakımı kolay yazılımlar oluşturmanıza yardımcı olur. Artık Python'daki bu güçlü loglama yeteneklerini kendi projelerinizde kullanarak uygulamalarınızın derinliklerine inmeye hazırsınız.
Sıkça Sorulan Sorular (SSS)
1. Python'da loglama yapmak performansımı etkiler mi?
Evet, her log mesajı bir miktar CPU ve I/O kaynağı tüketir. Özellikle çok detaylı (
DEBUG
seviyesi) loglama veya yüksek yoğunluklu I/O işlemleri gerektiren handler'lar (örneğin ağ üzerinden log gönderme) performans üzerinde gözle görülür bir etki yaratabilir. Bu nedenle üretim ortamlarında log seviyelerini dikkatli ayarlamak ve gerektiğinde asenkron loglama (
QueueHandler
) kullanmak önemlidir.
2. Hangi log seviyesini ne zaman kullanmalıyım?
-
DEBUG: Geliştirme ve hata ayıklama aşamasında, her detay adımını izlemek istediğinizde.
-
INFO: Uygulamanın normal akışını, önemli olayları (kullanıcı girişi, işlem başlatma) kaydetmek için.
-
WARNING: Beklenmeyen ama uygulamanın çalışmasını engellemeyen durumlar (yapılandırma hatası, deprecated özellik kullanımı).
-
ERROR: Uygulamanın bir kısmının işlevini yerine getiremediği durumlar (veritabanı bağlantı hatası, dosya okuma hatası).
-
CRITICAL: Uygulamanın veya sistemin tamamen çökmesine neden olan çok ciddi, kurtarılamaz hatalar.
3. Python loglama modülü hassas verileri otomatik olarak gizler mi?
Hayır, Python'ın standart
logging
modülü hassas verileri otomatik olarak gizlemez. Bu, geliştiricinin sorumluluğundadır. Makalede bahsedilen filtreler veya manuel maskeleme yöntemleri kullanılarak hassas verilerin loglara sızması önlenmelidir. Güvenlik ve gizlilik her zaman öncelikli olmalıdır.
4. Log dosyalarını manuel olarak silmek yerine daha iyi bir yöntem var mı?
Kesinlikle! Makalede de değindiğimiz gibi,
RotatingFileHandler
ve
TimedRotatingFileHandler
gibi handler'lar, log dosyalarını belirli boyut veya zamana göre otomatik olarak döndürür ve eski dosyaları siler. Bu, log dosyalarınızın disk alanını doldurmasını engeller ve manuel müdahaleye gerek kalmadan düzenli kalmasını sağlar. Ayrıca, merkezi loglama sistemleri genellikle logları belirli bir süre sonra otomatik olarak arşivler veya siler.
5. Birden fazla Python uygulaması aynı log dosyasına yazabilir mi?
Evet, yazabilirler ancak dikkatli olunmalıdır. Farklı uygulamalar aynı
FileHandler
ile aynı dosyaya yazarsa, eşzamanlı erişim sorunları yaşanabilir ve log mesajları karışabilir. Çözüm olarak, her uygulama için ayrı bir log dosyası kullanmak veya
logging.handlers.WatchedFileHandler
gibi kilit mekanizmaları olan handler'lar kullanmak ya da en iyisi, logları merkezi bir log toplama sistemine (örneğin Syslog veya ELK Stack) göndermek daha güvenli ve yönetilebilirdir.
