Python 3'te Günlük Kayıt (Logging) Kullanımı
Giriş
logging
modülü, standart Python kütüphanesinin bir parçasıdır ve yazılım çalışırken meydana gelen olayları izlemek için bir araç sağlar. Kodunuza günlük kayıt (log) çağrıları ekleyerek, hangi olayların gerçekleştiğini belirtebilirsiniz.
Bu modül, bir uygulamanın çalışmasıyla ilgili tanısal günlük kayıtlarını ve bir kullanıcının işlemlerinin analiz amacıyla kaydedildiği denetim günlüklerini (audit logging) destekler. Özellikle olayları bir dosyaya kaydetmek için yaygın olarak kullanılır.
Neden logging
Modülünü Kullanmalıyız?
logging
modülü, bir programda meydana gelen olayların kaydını tutar ve yazılım çalışırken gerçekleşen olaylarla ilgili çıktıları görmenizi sağlar.
Kodunuzda olayların gerçekleşip gerçekleşmediğini kontrol etmek için print()
ifadesini kullanmaya alışkın olabilirsiniz. print()
ifadesi, kodunuzu hata ayıklamak için temel bir yöntem sağlar. Ancak print()
ifadeleriyle kodu takip etmek ve hata ayıklamak uzun vadede logging modülüne göre daha az sürdürülebilirdir. İşte nedenleri:
Normal çıktı ile hata ayıklama çıktısını ayırt etmek zorlaşır çünkü ikisi de aynı kanaldan gelir.
Kodun farklı yerlerine dağılmış print()
ifadelerini devre dışı bırakmak için verimli bir yöntem yoktur.
Hata ayıklama tamamlandığında tüm print()
ifadelerini kaldırmak zahmetlidir.
Anlık olarak tanısal bilgi içeren bir günlük kaydı oluşturulamaz.
logging
modülünü kullanma alışkanlığı edinmek, özellikle küçük Python betiklerini aşan ve büyüyen uygulamalar için daha uygun bir yöntemdir. Ayrıca, logging
modülü uzun vadeli bir hata ayıklama yaklaşımı sağlar.
Günlükler, zaman içinde uygulamanızın davranışlarını ve hatalarını gösterebildiği için, geliştirme sürecinde neler olup bittiği hakkında daha iyi bir genel bakış sunar.
Konsola Hata Ayıklama Mesajları Yazdırma
Bilgi: Bu eğitimdeki örnek kodları takip etmek için, yerel sisteminizde bir Python etkileşimli kabuğunu python3
komutunu çalıştırarak açabilirsiniz. Ardından, >>>
istemcisine örnek kodları yapıştırabilir veya düzenleyebilirsiniz.
Eğer programda gerçekleşen olayları görmek için print()
ifadesini kullanmaya alışkınsanız, aşağıdaki gibi bir sınıf tanımlayıp nesneler oluşturan bir programla karşılaşabilirsiniz:
pizza.py
class Pizza():
def __init__(self, ad, fiyat):
self.ad = ad
self.fiyat = fiyat
print("Pizza oluşturuldu: {} ({} TL)".format(self.ad, self.fiyat))
def yap(self, miktar=1):
print("{} adet {} pizzası yapıldı.".format(miktar, self.ad))
def ye(self, miktar=1):
print("{} adet {} pizzası yendi.".format(miktar, self.ad))
pizza_01 = Pizza("enginar", 15)
pizza_01.yap()
pizza_01.ye()
pizza_02 = Pizza("margherita", 12)
pizza_02.yap(2)
pizza_02.ye()
Yukarıdaki kod, Pizza
sınıfının bir nesnesinin adını
ve fiyatını
tanımlamak için bir __init__
metodu içerir. Ayrıca, biri pizza yapmak (yap()
), diğeri pizza yemek (ye()
) olmak üzere iki metod barındırır. Bu iki metod, varsayılan olarak 1 değerine sahip bir miktar
parametresi alır.
Şimdi programı çalıştıralım:
$ python pizza.py
Çıktı şu şekilde olacaktır:
Pizza oluşturuldu: enginar (15 TL)
1 adet enginar pizzası yapıldı.
1 adet enginar pizzası yendi.
Pizza oluşturuldu: margherita (12 TL)
2 adet margherita pizzası yapıldı.
1 adet margherita pizzası yendi.
print()
ifadesi sayesinde kodun çalıştığını görebiliriz. Ancak bunun yerine logging
modülünü kullanabiliriz.
logging ile Kodun Güncellenmesi
Kodun tamamına dağılmış olan print()
ifadelerini kaldırabilir veya yorum satırına alabiliriz. Ardından, dosyanın en üstüne import logging
ekleyelim:
pizza.py
import logging
class Pizza():
def __init__(self, ad, fiyat):
self.ad = ad
self.fiyat = fiyat
# print("Pizza oluşturuldu: {} ({} TL)".format(self.ad, self.fiyat))
...
logging
modülü varsayılan olarak WARNING
seviyesine sahiptir. Bu seviye, DEBUG seviyesinin üzerindedir. Bu örnekte logging
modülünü hata ayıklama için kullanacağımızdan, yapılandırmayı değiştirerek logging.DEBUG
seviyesindeki bilgilerin konsola yazdırılmasını sağlayabiliriz. Bunu, import ifadesinin altına aşağıdaki satırı ekleyerek yapabiliriz:
pizza.py
import logging
logging.basicConfig(level=logging.DEBUG)
class Pizza():
...
Bu seviyedeki logging.DEBUG
, yukarıdaki kodda bir eşik belirlemek için başvurulan sabit bir tam sayı değerini ifade eder. DEBUG seviyesinin değeri 10'dur.
Şimdi, tüm print()
ifadelerini logging.debug()
ifadeleriyle değiştireceğiz. logging.DEBUG
sabit bir değer iken, logging.debug() ise logging modülünün bir yöntemidir. Bu yöntemi kullanırken, print()
ifadesine aktarılan aynı dizeyi kullanabiliriz. Aşağıdaki örnekte gösterildiği gibi:
import logging
logging.basicConfig(level=logging.DEBUG)
class Pizza():
def __init__(self, isim, fiyat):
self.isim = isim
self.fiyat = fiyat
logging.debug("Pizza oluşturuldu: {} (${})".format(self.isim, self.fiyat))
def yap(self, miktar=1):
logging.debug("{} adet {} pizzası yapıldı".format(miktar, self.isim))
def ye(self, miktar=1):
logging.debug("{} adet {} pizzası yendi".format(miktar, self.isim))
pizza_01 = Pizza("enginar", 15)
pizza_01.yap()
pizza_01.ye()
pizza_02 = Pizza("margherita", 12)
pizza_02.yap(2)
pizza_02.ye()
Bu noktada, programı python pizza.py komutuyla çalıştırdığımızda şu çıktıyı alacağız: Çıktı:
DEBUG:root:Pizza oluşturuldu: enginar ($15)
DEBUG:root:1 adet enginar pizzası yapıldı
DEBUG:root:1 adet enginar pizzası yendi
DEBUG:root:Pizza oluşturuldu: margherita ($12)
DEBUG:root:2 adet margherita pizzası yapıldı
DEBUG:root:1 adet margherita pizzası yendi
Günlük mesajlarında, kök seviyenizi ifade eden root
kelimesi ile birlikte ciddi düzey olarak DEBUG
seviyesi yer alır. logging modülü, farklı adlara sahip ve farklı çıktılara sahip olacak şekilde birden fazla modülünüz için farklı günlükçüler (loggers) kullanmanıza olanak tanır.
Örneğin, farklı isimlere ve çıktılara sahip iki günlükçü şu şekilde ayarlanabilir:
logger1 = logging.getLogger("modul_1")
logger2 = logging.getLogger("modul_2")
logger1.debug("Modül 1 hata ayıklayıcı")
logger2.debug("Modül 2 hata ayıklayıcı")
Çıktı:
DEBUG:modul_1:Modül 1 hata ayıklayıcı
DEBUG:modul_2:Modül 2 hata ayıklayıcı
Şimdi logging modülünü kullanarak mesajları bir dosyaya nasıl yazdıracağımızı öğrenelim.
Mesajları Bir Dosyaya Kaydetmek
logging
modülünün birincil amacı, mesajları bir dosyaya kaydetmektir. Mesajları bir dosyada tutmak, zamanla başvurabileceğiniz ve değerlendirilebilecek veriler sağlar; böylece kodunuzda ne gibi değişiklikler yapmanız gerektiğini görebilirsiniz.
Mesajları bir dosyaya kaydetmeye başlamak için, logging.basicConfig()
yöntemini bir filename
parametresi içerecek şekilde değiştirebiliriz. Bu durumda dosya adına test.log
diyelim:
import logging
logging.basicConfig(filename="test.log", level=logging.DEBUG)
class Pizza():
def __init__(self, isim, fiyat):
self.isim = isim
self.fiyat = fiyat
logging.debug("Pizza oluşturuldu: {} (${})".format(self.isim, self.fiyat))
def yap(self, miktar=1):
logging.debug("{} adet {} pizzası yapıldı".format(miktar, self.isim))
def ye(self, miktar=1):
logging.debug("{} adet {} pizzası yendi".format(miktar, self.isim))
pizza_01 = Pizza("enginar", 15)
pizza_01.yap()
pizza_01.ye()
pizza_02 = Pizza("margherita", 12)
pizza_02.yap(2)
pizza_02.ye()
Yukarıdaki kod, önceki bölümdekiyle aynıdır, ancak artık logların yazdırılacağı dosya adını ekledik. Kodları python pizza.py
komutuyla çalıştırdığımızda, dizinimizde test.log
adında yeni bir dosya oluşmuş olmalıdır.
Bu dosyayı nano (veya tercih ettiğiniz bir metin düzenleyici) ile açalım:
$ nano test.log
Dosya açıldığında aşağıdaki çıktıyı göreceğiz:
test.log
DEBUG:root:Pizza oluşturuldu: artichoke ($15)
DEBUG:root:1 adet artichoke pizza yapıldı
DEBUG:root:1 adet pizza yendi
DEBUG:root:Pizza oluşturuldu: margherita ($12)
DEBUG:root:2 adet margherita pizza yapıldı
DEBUG:root:1 adet pizza yendi
Bu, önceki bölümdeki konsol çıktısına benzemektedir, ancak şimdi test.log
dosyasına kaydedilmiştir. Dosyayı CTRL + x
ile kapatalım ve kodu düzenlemek için tekrar pizza.py
dosyasına geçelim.
Kodun çoğunu aynı bırakacağız, ancak iki pizza örneği olan pizza_01
ve pizza_02
nesnelerinin parametrelerini değiştireceğiz:
pizza.py
import logging
logging.basicConfig(filename="test.log", level=logging.DEBUG)
class Pizza():
def __init__(self, ad, fiyat):
self.ad = ad
self.fiyat = fiyat
logging.debug("Pizza oluşturuldu: {} (${})".format(self.ad, self.fiyat))
def yap(self, adet=1):
logging.debug("{} adet {} pizza yapıldı".format(adet, self.ad))
def ye(self, adet=1):
logging.debug("{} adet pizza yendi".format(adet))
# pizza_01 nesnesinin parametrelerini değiştiriyoruz
pizza_01 = Pizza("Sicilian", 18)
pizza_01.yap(5)
pizza_01.ye(4)
# pizza_02 nesnesinin parametrelerini değiştiriyoruz
pizza_02 = Pizza("quattro formaggi", 16)
pizza_02.yap(2)
pizza_02.ye(2)
Bu değişikliklerle kodu python pizza.py
komutuyla tekrar çalıştıralım.
Kod çalıştıktan sonra test.log
dosyasını yeniden açalım:
$ nano test.log
Dosyayı incelediğimizde, programın önceki çalışmasından kalan logların korunduğunu ve yeni satırların eklendiğini göreceğiz:
test.log
DEBUG:root:Pizza oluşturuldu: artichoke ($15)
DEBUG:root:1 adet artichoke pizza yapıldı
DEBUG:root:1 adet pizza yendi
DEBUG:root:Pizza oluşturuldu: margherita ($12)
DEBUG:root:2 adet margherita pizza yapıldı
DEBUG:root:1 adet pizza yendi
DEBUG:root:Pizza oluşturuldu: Sicilian ($18)
DEBUG:root:5 adet Sicilian pizza yapıldı
DEBUG:root:4 adet pizza yendi
DEBUG:root:Pizza oluşturuldu: quattro formaggi ($16)
DEBUG:root:2 adet quattro formaggi pizza yapıldı
DEBUG:root:2 adet pizza yendi
Bu bilgiler oldukça faydalıdır, ancak log dosyasını daha bilgilendirici hale getirmek için ek LogRecord (Kayıt) özellikleri ekleyebiliriz. Özellikle, logların oluşturulma zamanını insan tarafından okunabilir bir biçimde eklemek istiyoruz.
Bunu, format adlı bir parametreye ekleyerek yapabiliriz. Örneğin:
format="%(asctime)s:%(levelname)s:%(message)s"
Bu, zamana ek olarak DEBUG
seviyesi ve log mesajını da koruyacaktır. Yeni kod şu şekilde olacaktır:
pizza.py
import logging
logging.basicConfig(
filename="test.log",
level=logging.DEBUG,
format="%(asctime)s:%(levelname)s:%(message)s"
)
class Pizza():
def __init__(self, ad, fiyat):
self.ad = ad
self.fiyat = fiyat
logging.debug("Pizza oluşturuldu: {} (${})".format(self.ad, self.fiyat))
def yap(self, adet=1):
logging.debug("{} adet {} pizza yapıldı".format(adet, self.ad))
def ye(self, adet=1):
logging.debug("{} adet pizza yendi".format(adet))
pizza_01 = Pizza("Sicilian", 18)
pizza_01.yap(5)
pizza_01.ye(4)
pizza_02 = Pizza("quattro formaggi", 16)
pizza_02.yap(2)
pizza_02.ye(2)
Kodun yukarıdaki eklenen özelliklerle birlikte python pizza.py
komutuyla çalıştırılmasıyla, test.log
dosyasına insan tarafından okunabilir bir zaman damgası, DEBUG seviyesinin adı ve loglayıcıya iletilen ilgili mesajlar dahil olmak üzere yeni satırlar eklenecektir.
test.log
2021-08-19 23:31:34,484:DEBUG:Pizza oluşturuldu: Sicilian ($18)
2021-08-19 23:31:34,484:DEBUG:5 adet Sicilian pizza yapıldı
2021-08-19 23:31:34,484:DEBUG:4 adet pizza yendi
2021-08-19 23:31:34,484:DEBUG:Pizza oluşturuldu: quattro formaggi ($16)
2021-08-19 23:31:34,484:DEBUG:2 adet quattro formaggi pizza yapıldı
2021-08-19 23:31:34,484:DEBUG:2 adet pizza yendi
Bu yöntemle, programınızın zaman içindeki durumunu daha iyi izleyebilir, gerektiğinde sorunları kolayca çözebilirsiniz. Hata ayıklama ve diğer mesajları ayrı dosyalara kaydetmek, Python programınızı zaman içinde bütünsel bir şekilde anlamanızı sağlar. Bu, programa yapılan geçmiş çalışmaların yanı sıra meydana gelen olaylar ve işlemlerle bilgilendirilmiş bir şekilde kodunuzu sorun giderme ve değiştirme fırsatı sunar.
Loglama Seviyeleri Tablosu
Bir geliştirici olarak, loglayıcıda yakalanan bir olaya bir önem seviyesi atayabilirsiniz. Önem seviyeleri aşağıdaki tabloda gösterilmiştir.
Loglama seviyeleri teknik olarak tamsayıdır (bir sabit) ve hepsi 10'un katlarıdır. Loglayıcı, başlangıç seviyesi olarak sayısal değeri 0 olan NOTSET
ile başlatılır.
Ayrıca, tanımlı seviyelere göre kendi seviyelerinizi tanımlayabilirsiniz. Aynı sayısal değeri kullanan bir seviye tanımlarsanız, o değere karşılık gelen adı geçersiz kılarsınız.
Aşağıdaki tablo, çeşitli seviye adlarını, sayısal değerlerini, seviyeyi çağırmak için kullanılacak fonksiyonu ve bu seviyenin ne için kullanıldığını gösterir:
Seviye | Sayısal Değer | Fonksiyon | Kullanım Amacı |
---|---|---|---|
CRITICAL | 50 | logging.critical() |
Ciddi bir hatayı gösterir, program çalışmaya devam edemeyebilir. |
ERROR | 40 | logging.error() |
Daha ciddi bir sorunu gösterir. |
WARNING | 30 | logging.warning() |
Beklenmedik bir şey olduğunu ya da olabileceğini belirtir. |
INFO | 20 | logging.info() |
İşlerin beklendiği gibi çalıştığını onaylar. |
DEBUG | 10 | logging.debug() |
Sorunları teşhis eder, ayrıntılı bilgi sağlar. |
`logging
modülü varsayılan olarak seviyeyi WARNING olarak ayarlar, bu nedenle varsayılan olarak
WARNING,
ERRORve
CRITICAL` seviyeleri loglanır. Yukarıdaki örnekte, yapılandırmayı DEBUG seviyesini içerecek şekilde şu kodla değiştirdik:
logging.basicConfig(level=logging.DEBUG)
Komutlar ve hata ayıklayıcıyla çalışma hakkında daha fazla bilgiyi resmi loglama dokümantasyonundan okuyabilirsiniz.
Sonuç
Hata ayıklama, herhangi bir yazılım geliştirme projesinin önemli bir adımıdır. logging
modülü Python standart kütüphanesinin bir parçasıdır, yazılım çalışırken meydana gelen olayları izler ve bu olayları ayrı bir log dosyasına kaydedebilir. Bu, zaman içinde programınızı çalıştırmaktan kaynaklanan çeşitli olayları anlamanıza dayalı olarak kodunuzu hata ayıklama fırsatı sunar.
Lisa Tagliaferri tarafından yazılan How To Use Logging in Python 3 Program makalesinin düzenlenmiş çevirisi.
Daha Fazla Oku:
- Önceki Makale: Python ile Etkileşimli Konsolda Hata Ayıklama