Takip et

Python 3’te Logging Kullanımı: Kapsamlı Bir Rehber

Python 3’te Logging Kullanımı: Kapsamlı Bir Rehber Giriş: Neden Logging Önemlidir? Yazılım geliştirme sürecinde, bir uygulamanın davranış

Python 3’te Logging Kullanımı: Kapsamlı Bir Rehber

Giriş: Neden Logging Önemlidir?

Yazılım geliştirme sürecinde, bir uygulamanın davranışını anlamak, hataları ayıklamak ve sistemin genel sağlığını izlemek kritik öneme sahiptir. Geliştiriciler genellikle bu amaçla print() fonksiyonunu kullanmaya alışkın olsa da, büyük ve karmaşık uygulamalarda print()‘in sınırlılıkları hızla ortaya çıkar. İşte bu noktada “logging” devreye girer. Logging, uygulamanızın çalışma zamanında ürettiği olayları, mesajları ve hata bilgilerini yapılandırılmış bir şekilde kaydetme sürecidir. Bu kayıtlar, sistem yöneticilerine, destek ekiplerine ve geliştiricilere, bir sorun ortaya çıktığında ne olduğunu anlamaları için değerli bilgiler sağlar.

Python’ın standart kütüphanesinde yer alan logging modülü, bu ihtiyacı karşılamak için güçlü ve esnek bir çerçeve sunar. print()‘in aksine, logging modülü, mesajların önem derecesine göre filtrelenmesine, farklı hedeflere (konsol, dosya, ağ, veritabanı vb.) yönlendirilmesine ve özelleştirilebilir formatlarda kaydedilmesine olanak tanır. Bu sayede, geliştirme aşamasında detaylı hata ayıklama mesajları alırken, üretim ortamında yalnızca kritik hataları ve uyarıları kaydetmek gibi farklı senaryoları kolayca yönetebilirsiniz. Güvenlik denetimleri, performans izleme ve kullanıcı davranışlarını analiz etme gibi alanlarda da logging vazgeçilmez bir araçtır.

Python’da Temel Logging Kullanımı

Python’da logging’e başlamak oldukça basittir. logging modülü, varsayılan olarak kök (root) bir logger ile gelir ve bu logger’ı temel konfigürasyonlarla hemen kullanmaya başlayabilirsiniz.

Log Seviyeleri ve Anlamları

logging modülü, mesajların önem derecesini belirtmek için standart log seviyeleri tanımlar. Bu seviyeler, mesajları filtrelemek ve farklı bağlamlarda farklı detay seviyeleriyle çalışmak için kullanılır:

* DEBUG (10): Detaylı bilgi, genellikle bir sorunu teşhis ederken ilgi çekici olanlar. Yalnızca geliştirme aşamasında veya çok spesifik hata ayıklama senaryolarında kullanılır.
* INFO (20): Uygulamanın beklendiği gibi çalıştığını doğrulayan genel bilgiler. Kullanıcı eylemleri, sistem başlangıç/durdurma gibi olaylar.
* WARNING (30): Beklenmedik bir şeyin meydana geldiğini veya yakın gelecekte bir sorunun ortaya çıkabileceğini gösteren bir uyarı. Yazılım hala beklendiği gibi çalışıyor.
* ERROR (40): Ciddi bir sorun nedeniyle yazılımın bazı işlevleri yerine getiremediğini gösterir. Ancak uygulama tamamen çökmemiş olabilir.
* CRITICAL (50): Uygulamanın kendisinin veya kritik bir bileşeninin tamamen çöktüğünü veya çalışmayı durdurduğunu gösteren çok ciddi bir hata.

Bu seviyeler, varsayılan olarak logging.WARNING seviyesinden itibaren mesajları işler. Yani, bir konfigürasyon yapmadığınızda, yalnızca WARNING, ERROR ve CRITICAL seviyesindeki mesajlar konsola yazdırılır.

Basit Bir Log Mesajı Örneği

En basit haliyle, logging modülünü aşağıdaki gibi kullanabilirsiniz:

import logging

Varsayılan konfigürasyon ile loglama

logging.warning("Bu bir uyarı mesajıdır.") logging.info("Bu bir bilgi mesajıdır.") # Varsayılan olarak görünmez logging.error("Bu bir hata mesajıdır.")

Yukarıdaki kodu çalıştırdığınızda, konsolda şuna benzer bir çıktı göreceksiniz:

WARNING:root:Bu bir uyarı mesajıdır.
ERROR:root:Bu bir hata mesajıdır.

INFO mesajının neden görünmediğini merak ediyorsanız, bunun nedeni varsayılan log seviyesinin WARNING olmasıdır. basicConfig() fonksiyonu ile bu seviyeyi değiştirebilir ve daha fazla kontrol sağlayabilirsiniz.

logging.basicConfig() ile Temel Konfigürasyon

logging.basicConfig() fonksiyonu, kök logger için hızlı ve kolay bir şekilde temel konfigürasyon yapmanızı sağlar. Bu fonksiyon genellikle uygulamanızın başlangıcında bir kez çağrılır ve loglama davranışını global olarak ayarlar.

basicConfig()‘in bazı önemli parametreleri şunlardır:

* level: Log seviyesini ayarlar (örn. logging.INFO, logging.DEBUG). Bu seviyenin altındaki hiçbir mesaj işlenmez.
* format: Log mesajlarının nasıl görüneceğini belirten bir dize.
* filename: Log mesajlarının yazılacağı dosyanın adı.
* filemode: Dosya açma modu (‘w’ yazma, ‘a’ ekleme). Varsayılan ‘a’.
* stream: Log mesajlarının yazılacağı bir akış (örn. sys.stdout, sys.stderr). Genellikle filename ile birlikte kullanılmaz.

Şimdi basicConfig() kullanarak hem seviyeyi hem de formatı ayarlayalım ve logları bir dosyaya yazdıralım:

import logging

Logları bir dosyaya yazacak ve DEBUG seviyesinden itibaren tüm mesajları gösterecek şekilde konfigüre et

logging.basicConfig( level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s', filename='app.log', filemode='w' # Her çalıştırmada dosyayı yeniden oluştur ) logging.debug("Bu bir hata ayıklama mesajıdır.") logging.info("Kullanıcı 'admin' sisteme giriş yaptı.") logging.warning("Yapılandırma dosyasında eksik bir anahtar bulundu.") logging.error("Veritabanı bağlantısı başarısız oldu.") logging.critical("Sistem kritik bir hata nedeniyle kapatılıyor!") print("Loglar 'app.log' dosyasına yazıldı.")

Bu kodu çalıştırdığınızda, konsolda hiçbir çıktı görmeyeceksiniz (çünkü filename belirtildiğinde varsayılan olarak konsola yazmayı durdurur). Ancak app.log adında bir dosya oluşacak ve içeriği şöyle olacaktır:

2023-10-27 10:30:00,123 - DEBUG - Bu bir hata ayıklama mesajıdır.
2023-10-27 10:30:00,124 - INFO - Kullanıcı 'admin' sisteme giriş yaptı.
2023-10-27 10:30:00,125 - WARNING - Yapılandırma dosyasında eksik bir anahtar bulundu.
2023-10-27 10:30:00,126 - ERROR - Veritabanı bağlantısı başarısız oldu.
2023-10-27 10:30:00,127 - CRITICAL - Sistem kritik bir hata nedeniyle kapatılıyor!

%(asctime)s, %(levelname)s, %(message)s gibi ifadeler, log formatı için kullanılan özel özniteliklerdir. Bunları Formatter’lar bölümünde daha detaylı inceleyeceğiz.

Logging Bileşenleri: Derinlemesine Bakış

logging modülü, loglama sürecini yönetmek için dört ana bileşen etrafında döner: Logger’lar, Handler’lar, Formatter’lar ve Filter’lar. Bu bileşenlerin her biri, loglama akışının belirli bir yönünü kontrol eder.

Logger’lar: Mesajların Kaynağı

Logger’lar, bir uygulamanın mesajları gönderdiği ana arayüzdür. Her logger’ın bir adı vardır ve bu adlar genellikle hiyerarşik bir yapı oluşturur (noktalarla ayrılmış isimler). Örneğin, my_app.module_a adlı bir logger, my_app adlı logger’ın altındadır.

Bir logger nesnesi oluşturmak için logging.getLogger() fonksiyonu kullanılır:

import logging

Kök logger'ı alır

root_logger = logging.getLogger()

Özel bir logger oluşturur

app_logger = logging.getLogger("my_application") db_logger = logging.getLogger("my_application.database")

Logger’lar kendi seviyelerini ayarlayabilirler. Bir logger’ın seviyesi, o logger’dan geçen mesajların işlenip işlenmeyeceğini belirler.

import logging

Varsayılan kök logger seviyesi WARNING'dir.

Kök logger için basicConfig yapalım ki çıktıyı görelim.

logging.basicConfig(level=logging.INFO, format='%(name)s - %(levelname)s - %(message)s') my_logger = logging.getLogger("my_module") my_logger.setLevel(logging.DEBUG) # Bu logger'dan geçen DEBUG mesajları da işlenecek. another_logger = logging.getLogger("another_module") another_logger.setLevel(logging.ERROR) # Sadece ERROR ve CRITICAL mesajları işlenecek. my_logger.debug("Bu my_module'dan gelen bir debug mesajıdır.") my_logger.info("Bu my_module'dan gelen bir bilgi mesajıdır.") another_logger.warning("Bu another_module'dan gelen bir uyarı mesajıdır.") # İşlenmeyecek another_logger.error("Bu another_module'dan gelen bir hata mesajıdır.")

Çıktı:

my_module - DEBUG - Bu my_module'dan gelen bir debug mesajıdır.
my_module - INFO - Bu my_module'dan gelen bir bilgi mesajıdır.
another_module - ERROR - Bu another_module'dan gelen bir hata mesajıdır.

Logger Hiyerarşisi ve Yayılma (Propagation):
Logger’lar hiyerarşik bir yapıya sahiptir. Bir logger, mesajları kendi seviyesinin üzerindeyse işler ve ardından bu mesajları üst logger’ına (parent logger) iletir. Bu sürece “yayılma” (propagation) denir. Kök logger, tüm logger’ların en üstündedir.

import logging

Kök logger'ı INFO seviyesine ayarla ve konsola yaz.

logging.basicConfig(level=logging.INFO, format='%(name)s - %(levelname)s - %(message)s') parent_logger = logging.getLogger("parent") parent_logger.setLevel(logging.WARNING) # Parent logger WARNING seviyesinde. child_logger = logging.getLogger("parent.child") child_logger.setLevel(logging.DEBUG) # Child logger DEBUG seviyesinde. child_logger.propagate = True # Varsayılan olarak True'dur. child_logger.debug("Çocuktan gelen DEBUG mesajı.") # Child logger işler, parent'a yayılır, parent seviyesi WARNING olduğu için parent işleyemez. child_logger.info("Çocuktan gelen INFO mesajı.") # Child logger işler, parent'a yayılır, parent seviyesi WARNING olduğu için parent işleyemez. child_logger.warning("Çocuktan gelen WARNING mesajı.") # Child logger işler, parent'a yayılır, parent seviyesi WARNING olduğu için parent da işler.

Eğer child_logger.propagate = False yapsaydık, child'dan gelen mesajlar parent'a yayılmazdı.

Çıktı:

parent.child - DEBUG - Çocuktan gelen DEBUG mesajı.
parent.child - INFO - Çocuktan gelen INFO mesajı.
parent.child - WARNING - Çocuktan gelen WARNING mesajı.
parent - WARNING - Çocuktan gelen WARNING mesajı.

Gördüğünüz gibi, child_logger‘dan gelen WARNING mesajı hem kendi handler’ı (varsayılan olarak kök logger’ın handler’ı) tarafından hem de parent_logger‘ın handler’ı tarafından işlendi.

Handler’lar: Mesajların Hedefi

Handler’lar, logger’lar tarafından üretilen log kayıtlarını (LogRecords) alır ve bunları belirli hedeflere (konsol, dosya, e-posta, veritabanı vb.) gönderir. Bir logger’a birden fazla handler ekleyebilirsiniz, böylece aynı log mesajı farklı yerlere farklı şekillerde gönderilebilir.

Bazı yaygın Handler türleri:

* StreamHandler: Log mesajlarını konsol veya herhangi bir akışa (stream) gönderir.
* FileHandler: Log mesajlarını belirtilen bir dosyaya yazar.
* RotatingFileHandler: Dosyaya yazar, ancak dosya belirli bir boyuta ulaştığında otomatik olarak yeni bir dosyaya geçer (dosya rotasyonu). Eski dosyaları belirli bir sayıda tutar.
* TimedRotatingFileHandler: Dosyaya yazar, ancak dosya belirli bir zaman aralığına (günlük, saatlik vb.) göre rotasyona uğrar.
* SMTPHandler: Log mesajlarını e-posta ile gönderir.
* HTTPHandler: Log mesajlarını HTTP POST isteği olarak bir web sunucusuna gönderir.
* NullHandler: Hiçbir şey yapmaz. Kütüphane yazarları tarafından, kütüphanelerinin loglama çağrıları yapmasını sağlamak ama aynı zamanda bir uygulamanın loglama konfigürasyonu yapmaması durumunda hiçbir çıktı üretmemesini sağlamak için kullanılır.

Birden Fazla Handler Kullanımı:
Aşağıdaki örnek, hem konsola hem de bir dosyaya log yazan özel bir logger’ın nasıl yapılandırılacağını gösterir:

import logging
import sys

Özel bir logger oluştur

my_app_logger = logging.getLogger("my_application") my_app_logger.setLevel(logging.DEBUG) # En düşük seviyeden itibaren tüm mesajları yakala

1. StreamHandler: Konsola INFO seviyesinden itibaren logları yaz

console_handler = logging.StreamHandler(sys.stdout) console_handler.setLevel(logging.INFO) console_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') console_handler.setFormatter(console_formatter) my_app_logger.addHandler(console_handler)

2. FileHandler: Dosyaya DEBUG seviyesinden itibaren logları yaz

file_handler = logging.FileHandler("debug.log") file_handler.setLevel(logging.DEBUG) file_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(module)s:%(lineno)d - %(message)s') file_handler.setFormatter(file_formatter) my_app_logger.addHandler(file_handler) my_app_logger.debug("Bu sadece dosyada görünen bir debug mesajıdır.") my_app_logger.info("Bu hem konsolda hem de dosyada görünen bir bilgi mesajıdır.") my_app_logger.warning("Bu bir uyarıdır.") my_app_logger.error("Bir hata meydana geldi.")

Bu örnekte, my_app_logger‘a iki farklı handler ekledik: biri konsola INFO seviyesinden itibaren yazarken, diğeri debug.log dosyasına DEBUG seviyesinden itibaren yazıyor. Her handler’ın kendi formatlayıcısı da var.

Konsol çıktısı:

2023-10-27 10:30:00,123 - my_application - INFO - Bu hem konsolda hem de dosyada görünen bir bilgi mesajıdır.
2023-10-27 10:30:00,124 - my_application - WARNING - Bu bir uyarıdır.
2023-10-27 10:30:00,125 - my_application - ERROR - Bir hata meydana geldi.

debug.log dosyasının içeriği:

2023-10-27 10:30:00,122 - my_application - DEBUG - :30 - Bu sadece dosyada görünen bir debug mesajıdır.
2023-10-27 10:30:00,123 - my_application - INFO - :31 - Bu hem konsolda hem de dosyada görünen bir bilgi mesajıdır.
2023-10-27 10:30:00,124 - my_application - WARNING - :32 - Bu bir uyarıdır.
2023-10-27 10:30:00,125 - my_application - ERROR - :33 - Bir hata meydana geldi.

RotatingFileHandler ve TimedRotatingFileHandler:
Üretim ortamlarında, log dosyalarının sınırsız büyümesi disk alanını tüketebilir. Bu durumu önlemek için rotasyonlu dosya handler’ları kullanılır.

from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
import time

RotatingFileHandler örneği

rot_file_handler = RotatingFileHandler( 'rotating_app.log', maxBytes=1024 * 10, # 10 KB backupCount=5 # En fazla 5 eski log dosyası tut ) rot_file_handler.setLevel(logging.INFO) rot_file_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')) my_app_logger.addHandler(rot_file_handler)

TimedRotatingFileHandler örneği (günlük rotasyon)

timed_rot_file_handler = TimedRotatingFileHandler( 'timed_app.log', when='midnight', # Her gece yarısı rotasyon yap interval=1, backupCount=7 # En fazla 7 günlük log dosyası tut ) timed_rot_file_handler.setLevel(logging.INFO) timed_rot_file_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')) my_app_logger.addHandler(timed_rot_file_handler)

Bir süre boyunca log mesajları gönderelim

for i in range(200): my_app_logger.info(f"Test mesajı {i}") time.sleep(0.01) # Biraz bekle ki dosya boyutu artsın

Bu örnekler, log dosyalarınızın yönetilebilir boyutlarda kalmasını sağlar. RotatingFileHandler belirli bir boyuta ulaştığında, TimedRotatingFileHandler ise belirli bir zaman aralığı geçtiğinde yeni bir log dosyası oluşturur ve eski dosyaları adlandırarak saklar.

Formatter’lar: Mesajların Biçimi

Formatter’lar, log kayıtlarının çıktı formatını belirler. Bir LogRecord nesnesi, log mesajının kendisi, seviyesi, zaman damgası, logger adı, kaynak dosya adı ve satır numarası gibi birçok öznitelik içerir. Formatter’lar, bu öznitelikleri kullanarak çıktıyı istediğiniz şekilde yapılandırmanıza olanak tanır.

Bir Formatter nesnesi oluştururken bir biçim dizesi (format string) belirtilir. Bu dize, log kayıtlarındaki özniteliklere karşılık gelen özel yüzde işaretli ifadeler (%) içerir.

Yaygın kullanılan biçim öznitelikleri:

* %(name)s: Logger’ın adı.
* %(levelname)s: Log seviyesi (DEBUG, INFO vb.).
* %(asctime)s: İnsan tarafından okunabilir zaman damgası.
* %(filename)s: Log çağrısının yapıldığı dosyanın adı.
* %(lineno)d: Log çağrısının yapıldığı satır numarası.
* %(module)s: Log çağrısının yapıldığı modül adı.
* %(funcName)s: Log çağrısının yapıldığı fonksiyon adı.
* %(message)s: Log mesajının kendisi.
* %(process)d: İşlem ID’si.
* %(thread)d: İş parçacığı ID’si.
* %(threadName)s: İş parçacığı adı.

Tarih/Saat Biçimlendirmesi:
Formatter‘a datefmt parametresi ile özel bir tarih/saat formatı da sağlayabilirsiniz. Bu, strftime() format kodlarını kullanır.

import logging

Logger oluştur

my_logger = logging.getLogger("custom_format_logger") my_logger.setLevel(logging.DEBUG)

StreamHandler oluştur

handler = logging.StreamHandler()

Özel formatlayıcı oluştur

formatter = logging.Formatter( '%(asctime)s - [%(levelname)s] - (%(filename)s:%(lineno)d) - %(message)s', datefmt='%Y-%m-%d %H:%M:%S' # YYYY-MM-DD HH:MM:SS formatı )

Handler'a formatlayıcıyı ata

handler.setFormatter(formatter)

Logger'a handler'ı ekle

my_logger.addHandler(handler) my_logger.debug("Bu bir hata ayıklama mesajıdır.") my_logger.info("Uygulama başarıyla başlatıldı.") my_logger.error("Veri işleme sırasında bir hata oluştu.")

Çıktı:

2023-10-27 10:30:00 - [DEBUG] - (example.py:19) - Bu bir hata ayıklama mesajıdır.
2023-10-27 10:30:00 - [INFO] - (example.py:20) - Uygulama başarıyla başlatıldı.
2023-10-27 10:30:00 - [ERROR] - (example.py:21) - Veri işleme sırasında bir hata oluştu.

Filter’lar: Mesajların Süzülmesi

Filter’lar, log kayıtlarını işlenmeden önce dinamik olarak süzmek için kullanılır. Bir logger’a veya bir handler’a filtreler ekleyebilirsiniz. Bir log kaydı, tüm filtrelerden geçtikten sonra işlenir. Eğer herhangi bir filtre kaydı reddederse, o kayıt işlenmez.

Filter’lar, logging.Filter sınıfından türetilerek veya doğrudan bir logging.Filter nesnesi oluşturularak kullanılabilir. Filter‘ın filter() metodu, bir LogRecord nesnesini alır ve True (kabul) veya False (reddet) döndürür.

import logging

Sadece belirli bir logger adından gelen mesajları kabul eden özel bir filtre

class MyCustomFilter(logging.Filter): def __init__(self, name_starts_with): super().__init__() self.name_starts_with = name_starts_with def filter(self, record): return record.name.startswith(self.name_starts_with)

Logger oluştur

main_logger = logging.getLogger("main_app") main_logger.setLevel(logging.DEBUG)

Handler oluştur ve formatlayıcı ata

handler = logging.StreamHandler() formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s') handler.setFormatter(formatter)

Filtreyi oluştur ve handler'a ekle

custom_filter = MyCustomFilter("main_app.submodule") handler.addFilter(custom_filter) # Handler'a filtre ekliyoruz main_logger.addHandler(handler)

Alt logger'lar

submodule_logger = logging.getLogger("main_app.submodule_a") submodule_logger.info("Submodule A'dan gelen bilgi mesajı.") another_submodule_logger = logging.getLogger("main_app.submodule_b") another_submodule_logger.warning("Submodule B'den gelen uyarı mesajı.")

Ana logger'dan gelen mesaj

main_logger.error("Ana uygulamadan gelen hata mesajı.")

Yukarıdaki örnekte, MyCustomFilter sadece main_app.submodule ile başlayan logger adlarından gelen mesajları kabul eder. Bu nedenle, main_app.submodule_a ve main_app.submodule_b‘den gelen mesajlar filtreyi geçecek, ancak main_app‘ten gelen mesaj geçemeyecektir.

Çıktı:

main_app.submodule_a - INFO - Submodule A'dan gelen bilgi mesajı.
main_app.submodule_b - WARNING - Submodule B'den gelen uyarı mesajı.

main_app logger’ından gelen ERROR mesajı, main_app.submodule ile başlamadığı için filtreden geçemedi ve konsola yazdırılmadı.

Gelişmiş Logging Konfigürasyonları

logging.basicConfig() ile Detaylı Konfigürasyon

Daha önce de belirtildiği gibi, basicConfig() basit senaryolar için harikadır. Ancak, karmaşık uygulamalarda farklı handler’lar, filtreler ve logger hiyerarşileri kurmanız gerektiğinde yetersiz kalabilir. basicConfig() yalnızca kök logger’ı yapılandırır ve yalnızca ilk çağrıldığında etkilidir. Birden fazla handler veya farklı logger’lar için özel ayarlar yapmak istiyorsanız, diğer konfigürasyon yöntemlerine başvurmanız gerekir.

Dosya Tabanlı Konfigürasyon (INI, YAML, JSON)

Büyük uygulamalarda, loglama konfigürasyonunu kodun içine gömmek yerine ayrı bir dosyada tutmak daha esnektir. Bu, uygulamanızı yeniden dağıtmadan loglama davranışını değiştirmenize olanak tanır. Python’ın logging.config modülü, sözlük tabanlı konfigürasyonu (dictionary-based configuration) destekler ve bu da INI, YAML veya JSON gibi dosya formatlarından kolayca yüklenebilir.

INI Dosyası ile Konfigürasyon Örneği:
logging.conf adında bir dosya oluşturalım:

[loggers]
keys=root,my_app

[handlers]
keys=consoleHandler,fileHandler

[formatters]
keys=simpleFormatter,detailedFormatter

[logger_root]
level=INFO
handlers=consoleHandler

[logger_my_app]
level=DEBUG
handlers=fileHandler
qualname=my_app
propagate=0 # Kök logger'a yayılmayı kapat

[handler_consoleHandler]
class=StreamHandler
level=INFO
formatter=simpleFormatter
args=(sys.stdout,)

[handler_fileHandler]
class=FileHandler
level=DEBUG
formatter=detailedFormatter
args=('app.log', 'a')

[formatter_simpleFormatter]
format=%(name)s - %(levelname)s - %(message)s
datefmt=

[formatter_detailedFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(module)s:%(lineno)d - %(message)s
datefmt=%Y-%m-%d %H:%M:%S

Şimdi bu dosyayı Python kodumuzdan yükleyelim:

import logging.config
import sys # sys.stdout için gerekli

logging.conf dosyasını yükle

try: logging.config.fileConfig('logging.conf') except Exception as e: print(f"Logging konfigürasyonu yüklenirken hata oluştu: {e}") sys.exit(1)

Logger'ları al

root_logger = logging.getLogger() my_app_logger = logging.getLogger("my_app") root_logger.info("Bu kök logger'dan gelen bir bilgi mesajıdır.") root_logger.debug("Bu kök logger'dan gelen bir debug mesajıdır. (Görünmez)") my_app_logger.debug("Bu my_app logger'dan gelen bir debug mesajıdır.") my_app_logger.info("Bu my_app logger'dan gelen bir bilgi mesajıdır.") my_app_logger.error("my_app'te bir hata oluştu.")

Bu örnek, INI dosyasının nasıl kullanıldığını gösterir. logging.config.dictConfig fonksiyonu ile YAML veya JSON dosyalarını da yükleyebilirsiniz, bu da daha karmaşık veri yapıları için daha uygun olabilir.

Modüller Arası Logging

Büyük bir uygulamada, farklı Python modüllerinin kendi logger’larına sahip olması iyi bir pratiktir. Bu, log mesajlarının hangi modülden geldiğini kolayca takip etmenizi sağlar ve loglama hiyerarşisinin avantajlarından yararlanmanıza yardımcı olur. Her modülde, logger’ı logging.getLogger(__name__) ile almak standart bir yaklaşımdır. __name__ özel değişkeni, o modülün adını içerir.

my_module.py:

import logging

logger = logging.getLogger(__name__) # Logger adı 'my_module' olacak

def do_something():
    logger.debug("do_something fonksiyonu çalışıyor.")
    # ... bazı işlemler ...
    logger.info("do_something fonksiyonu tamamlandı.")

main_app.py:

import logging
import my_module

Temel konfigürasyon

logging.basicConfig(level=logging.DEBUG, format='%(name)s - %(levelname)s - %(message)s') main_logger = logging.getLogger(__name__) # Logger adı 'main_app' olacak main_logger.info("Uygulama başlatılıyor.") my_module.do_something() main_logger.info("Uygulama sona erdi.")

Çıktı:

main_app - INFO - Uygulama başlatılıyor.
my_module - DEBUG - do_something fonksiyonu çalışıyor.
my_module - INFO - do_something fonksiyonu tamamlandı.
main_app - INFO - Uygulama sona erdi.

Bu yaklaşım, log çıktısında mesajın kaynağını net bir şekilde görmenizi sağlar ve her modülün kendi loglama seviyesini ve handler’larını yapılandırma esnekliği sunar.

İstisna (Exception) Logging

Hataların loglanması, herhangi bir uygulamanın kararlılığı için hayati öneme sahiptir. logging modülü, istisnaları (exceptions) ve onların izleme bilgilerini (traceback) kolayca loglamak için özel yöntemler sunar.

logger.exception(msg, args, kwargs): Bu yöntem, yalnızca ERROR seviyesindeki mesajlar için geçerlidir ve çağrıldığı anda mevcut istisna bilgisini (traceback dahil) otomatik olarak log mesajına ekler. Genellikle bir except bloğu içinde kullanılır.
* logger.error(msg, exc_info=True): Bu yöntem, exc_info=True parametresi ile kullanıldığında, exception() ile aynı etkiyi yaratır ve istisna bilgisini loglar.

import logging

logging.basicConfig(level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger("exception_logger")

def divide(a, b):
    try:
        result = a / b
        logger.info(f"Bölme sonucu: {result}")
    except ZeroDivisionError:
        logger.exception("Sıfıra bölme hatası oluştu!") # Traceback'i otomatik olarak loglar
        # veya
        # logger.error("Sıfıra bölme hatası oluştu!", exc_info=True)
    except TypeError:
        logger.error("Geçersiz türde giriş!", exc_info=True) # exc_info=True ile traceback'i loglar
    except Exception as e:
        logger.error(f"Beklenmedik bir hata oluştu: {e}", exc_info=True)

divide(10, 2)
divide(10, 0)
divide("a", 2)

Çıktı (konsol ve/veya dosya):

2023-10-27 10:30:00,123 - ERROR - Sıfıra bölme hatası oluştu!
Traceback (most recent call last):
  File "", line 13, in divide
ZeroDivisionError: division by zero
2023-10-27 10:30:00,124 - ERROR - Geçersiz türde giriş!
Traceback (most recent call last):
  File "", line 12, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'int'

logger.exception() ve exc_info=True kullanımı, hata ayıklama sırasında çok değerli olan ayrıntılı hata izleme bilgilerini otomatik olarak yakalar.

Performans ve Logging

Loglama, uygulamanızın performansını etkileyebilir, özellikle yüksek hacimli veya disk G/Ç işlemlerinin yoğun olduğu senaryolarda. Performansı optimize etmek için dikkat edilmesi gerekenler:

* Log Seviyelerini Akıllıca Kullanın: Üretim ortamında DEBUG seviyesinde loglama yapmak gereksiz yere disk alanı tüketir ve G/Ç yükünü artırır. Yalnızca gerekli seviyeleri (örn. INFO, WARNING, ERROR) kullanın.
* Gereksiz Loglama Çağrılarından Kaçının: Özellikle döngüler içinde veya sıkça çağrılan fonksiyonlarda, yalnızca gerekli olduğunda loglama yapın.

# Kötü örnek: mesaj her zaman oluşturulur
    logger.debug(f"Karmaşık hesaplama sonucu: {very_complex_calculation()}")

    # İyi örnek: mesaj sadece DEBUG seviyesi etkinse oluşturulur
    if logger.isEnabledFor(logging.DEBUG):
        logger.debug(f"Karmaşık hesaplama sonucu: {very_complex_calculation()}")

* Asenkron Logging (QueueHandler): Performans kritik uygulamalarda, log mesajlarını ayrı bir iş parçacığında veya işlemde işlemek için QueueHandler kullanılabilir. Bu, ana uygulama iş parçacığının loglama G/Ç işlemlerini beklemesini engeller.
* logging.disable(): Belirli bir log seviyesinin altındaki tüm loglama çağrılarını geçici olarak devre dışı bırakmak için kullanılabilir.

logging.disable(logging.CRITICAL) # CRITICAL altındaki tüm logları devre dışı bırak
    logger.error("Bu mesaj görünmez.")
    logging.disable(logging.NOTSET) # Loglamayı tekrar etkinleştir
    logger.error("Bu mesaj şimdi görünür.")

Logging’e Alternatifler ve En İyi Uygulamalar

print() vs. logging: Neden logging tercih edilmeli?

| Özellik | print() | logging |
| :————— | :——————————————- | :———————————————— |
| Kontrol | Yok. Her zaman konsola yazar. | Seviye, hedef, format üzerinde tam kontrol. |
| Esneklik | Yok. | Birden fazla hedef, rotasyon, filtreleme. |
| Performans | Basit senaryolarda hızlı. | Daha fazla overhead, ancak optimize edilebilir. |
| Standartlaşma| Yok. | Endüstri standardı seviyeler ve formatlar. |
| Kullanım | Hızlı hata ayıklama, küçük script’ler. | Üretim uygulamaları, hata ayıklama, izleme. |
| İstisna Bilgisi| Manuel olarak eklenmeli. | Otomatik olarak traceback ekleyebilir (exc_info=True). |

print() sadece geliştirme aşamasında hızlı denemeler için uygunken, logging modülü gerçek dünya uygulamalarında sağlamlık, esneklik ve sürdürülebilirlik sağlar.

Yapılandırılmış Logging (Structured Logging)

Geleneksel log mesajları genellikle serbest metin formatındadır, bu da makine tarafından analiz edilmelerini zorlaştırır. Yapılandırılmış logging, log kayıtlarını JSON veya diğer makine tarafından okunabilir formatlarda üretme yaklaşımıdır. Bu, logları merkezi bir log yönetimi sistemine (örn. ELK Stack, Splunk) gönderirken çok faydalıdır, çünkü alanlara göre kolayca sorgulanabilir ve analiz edilebilirler.

import logging
import json

class JsonFormatter(logging.Formatter):
    def format(self, record):
        log_record = {
            "timestamp": self.formatTime(record, self.datefmt),
            "level": record.levelname,
            "logger_name": record.name,
            "message": record.getMessage(),
            "module": record.module,
            "funcName": record.funcName,
            "lineno": record.lineno,
            # Ekstra verileri burada ekleyebilirsiniz
            getattr(record, 'extra_data', {})
        }
        return json.dumps(log_record)

logger = logging.getLogger("structured_logger")
logger.setLevel(logging.INFO)

handler = logging.StreamHandler()
handler.setFormatter(JsonFormatter(datefmt='%Y-%m-%dT%H:%M:%S%z'))
logger.addHandler(handler)

Ekstra veri ile loglama

logger.info("Kullanıcı girişi başarılı.", extra={'extra_data': {'user_id': 123, 'ip_address': '192.168.1.1'}}) logger.error("Veritabanı bağlantısı kesildi.", extra={'extra_data': {'db_host': 'localhost', 'port': 5432}})

Bu örnek, log mesajlarını JSON formatında çıktılama yeteneğini gösterir. extra parametresi ile log kaydına özel veriler ekleyebilirsiniz.

Asenkron Logging (Async Logging)

Yukarıda bahsedildiği gibi, loglama G/Ç işlemleri (diske yazma, ağ üzerinden gönderme) uygulamanızın ana iş parçacığını bloke edebilir. Asenkron logging, bu G/Ç işlemlerini ayrı bir iş parçacığına veya sürece devrederek uygulamanızın yanıt verme hızını artırır. logging.handlers.QueueHandler ve logging.handlers.QueueListener bu senaryo için tasarlanmıştır.

import logging
import logging.handlers
import queue
import time
import threading

Ana logger'ı oluştur

logger = logging.getLogger("async_logger") logger.setLevel(logging.DEBUG)

Bir kuyruk oluştur

log_queue = queue.Queue(-1) # Sınırsız boyutlu kuyruk

Kuyruk handler'ı oluştur ve logger'a ekle

queue_handler = logging.handlers.QueueHandler(log_queue) logger.addHandler(queue_handler)

Kuyruk dinleyicisi için hedef handler'lar

file_handler = logging.FileHandler("async_app.log") file_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')) console_handler = logging.StreamHandler() console_handler.setFormatter(logging.Formatter('ASYNC: %(levelname)s - %(message)s'))

Kuyruk dinleyicisini oluştur ve başlat

queue_listener = logging.handlers.QueueListener(log_queue, file_handler, console_handler) queue_listener.start() # Ayrı bir thread'de logları işlemeye başlar

Ana uygulama iş parçacığında log mesajları üret

for i in range(10): logger.info(f"Ana iş parçacığından mesaj {i}") time.sleep(0.1) logger.debug("Hata ayıklama mesajı.") logger.error("Kritik bir hata meydana geldi.")

Uygulama kapanırken dinleyiciyi durdurmak önemlidir

Aksi takdirde kuyruktaki son mesajlar işlenmeyebilir.

time.sleep(0.5) # Kuyruktaki mesajların işlenmesi için biraz bekle queue_listener.stop() print("Asenkron loglama tamamlandı.")

Bu kurulum, log mesajlarını bir kuyruğa atar ve bu kuyruktaki mesajlar ayrı bir iş parçacığında çalışan QueueListener tarafından alınarak gerçek handler’lara (bu örnekte FileHandler ve StreamHandler) iletilir. Bu, loglama işleminin ana uygulamanın performansını doğrudan etkilemesini engeller.

Sonuç

Python’ın logging modülü, uygulamalarınız için güçlü, esnek ve ölçeklenebilir bir loglama çözümü sunar. print() fonksiyonunun basitliğini aşarak, log seviyeleri, handler’lar, formatlayıcılar ve filtreler gibi bileşenlerle loglama davranışını tamamen kontrol etmenizi sağlar. İster basit bir script için konsola bilgi mesajları yazın, ister büyük ölçekli bir kurumsal uygulama için merkezi log yönetimi sistemlerine yapılandırılmış loglar gönderin, logging modülü her ihtiyaca cevap verebilecek kapasitededir.

Doğru loglama stratejileri, geliştirme sürecini hızlandırır, üretim ortamındaki sorunları teşhis etmeyi kolaylaştırır ve genel sistem sağlığını izlemek için paha biçilmez bir araç haline gelir. Bu rehberde ele alınan temel ve gelişmiş kavramları anlayarak, Python uygulamalarınızda etkili ve verimli bir loglama altyapısı kurabilirsiniz. Logging’i uygulamanızın vazgeçilmez bir parçası haline getirerek, daha şeffaf, yönetilebilir ve güvenilir yazılımlar geliştireceksiniz.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Gönder

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.
Exit mobile version