Log4Net ile Hata Ayıklama

   Yazılım geliştiricilerin en büyük derdidir sahaya gönderilen uygulamada oluşan hataları çözmek, özellikle de son kullanıcıdan aldığı "hata oldu", "uygulama dondu", "program kendini kapattı" gibi ucu açık geri bildirimleri düşünecek olursak. Kimi zaman uygulamada oluşan öyle hatalar olabilir ki bunları yazılım geliştirici kendi bilgisayarında dahi debug ederken çözmekte/görmekte zorlanabilir. Bir de hiç bir arayüzü olmayan windows hizmeti uygulamalarını düşünün, müdahale edebileceğiniz noktayı bulmak bile zaman zaman başlı başına bir sorun olabilir.

   Böylesi durumlarda yazılım geliştiricinin en büyük dostu hiç süphesiz ki bir dosyaya ya da event loglara atılan mesajlardır. Kodun içinde hata olabilecek noktalara yerleştirilen aşağıdakine benzer küçük kod parçacıkları ile bir dosyada ya da event log’unda  tutulan uygulama günlükleri, oluşan exception’larda detay bilgiye ulaşabilmenizi sağlayacaktır.

try {
    // Hata alınabilecek olan uygulama 
// iş mantığı kodu bu alana
}
catch (Exception ex) {
    using (var gunlukDosyası = System.IO.File.AppendText("UygulamaGunlugu.txt")) {
        gunlukDosyası.WriteLine("Uygulama hatası oluştu : " + ex);
    }
}

   Yukarıda paylaştığım yöntemle, uygulama günlüklerini tutarak hatayı yakalamada başladığımız noktadan çok daha iyi bir noktaya gelmiş oluruz; fakat bu durumda tahmin edilebilecek hataların sadece bir kısmında işe yarayacaktır. Bunun bir kaç nedeni var;

  • Hatanın kaynağı her zaman içim exception nesnesi içinde bulunmayabilir
  • Hata mesajı çok genel geçer olabilir
  • Exception başka bir noktada yakalanıp üzerinden işlem yapıldıktan sonra yeniden fırlatılır, bu da stack trace’in kaybolmasına ve asıl hataya ulaşılamamasına neden olabilir
  • Uygulamanız thread’ler ile çalışıyorsa günlük dosyasına aynı anda yazılmaya çalışılabilir
  • Hatanın temelinde exception nesnesinde olmayan bir bilgi yatabilir; örneğin uygulamanın başında kullanıcının sayı girmesi gereken bir alana karakter girmesi.

   Sıraladığım bu ve benzer nedenler zaman zaman hatanın temeline gitmeyi engelleyebilir. Sahada aktif kullanılan bir ürününüz var ise riski göze alamayacağınız için daha detaylı günlük tutmaya başlamak bir alternatif olacaktır. "Kullanıcıdan veri alınıyor", "Kullanıcı abc değerini girmiştir", "hesaplama işi başlatılıyor", "veri gönderiliyor" v.s. v.s. Dönüp uygulamanıza ve uygulama günlüklerine baktığınızda bu defa da çok fazla girdi ile dolduğunu görmeye başlarsınız. Aldığınız hata, uygulamanın bir modülünden gelmesine karşın günlük dosyasında uygulamaya ait tüm girdileri görmeye başlarsınız. Bu defa kod içerisinde değil; ama günlük dosyasının içerisinde hatanın nedenini ararken kaybolmaya başlarsınız. Uygulamanız günlük dosyasına kayıt yapmaya çalışırken çok fazla yazma/okuma işlemi yapacağından yavaşlamaya başlar. İşte bu noktadan sonra işler iyice çığrından çıkacaktır.

   Gördüğünüz gibi ne günlük tutmadan oluyor, ne de günlük tutunca oluyor. Peki bu durumda yazılım geliştiricilerin nasıl haraket etmesi gerekiyor? Şanslıyız ki bu konuda yukarıda saydığım sıkıntılar ve daha fazlasını yaşayan tek yazılım geliştiriciler bizler değiliz. Bu sıkıntıdan yakınan ve bu alandaki eksikliği farkeden bir gurup yazılım geliştirici çoktan bizim yerimize günlük tutma işlemlerini yöneten oldukça güzel bir kütüphane geliştirmiş, Log4Net.

   Log4Net, oldukça esnek tasarımıyla günlük tutmak için gerek duyacağınız neredeyse tüm ihtiyaçlarımızı karşılar bir kütüphanedir. Apache Foundation tarafından desteklenmekte olan bu kütüphane ve ilgili dokümantasyona http://logging.apache.org/log4net/index.html adresinden  ulaşılabilir. Dosya sisteminde, event log’larda günlük tutmaktan Ado.Net veri kaynaklarında, SMTP ile mail adreslerinde, UDP ile ağ üzerinde günlük tutmaktaya kadar pek çok farklı yöntemi destekleyen Log4Net, aynı zamanda bu günlükte yer alan girdileri Debug, Info, Warn, Error, and Fatal gibi farklı düzeylerde tutarak mesajınızın önemini belirtmenizi de sağlamakta. Doğru bir yapılandırma ile örneği sadece A.B.C namespace’i altındaki sınıflar için Error ve Fatal seviyesinde günlük tutulmasını ve bunlarında event loglar içerisinde toplanmasını sağlayabilirsiniz. Üstelik bunu yaparken, paralelde diğer verilerinden bir günlük dosyasında toplanmasını sağlamanız da mümkün. Tüm bu yapılandırma işlemleri uygulama yapılandırma dosyalarında (app.config, web.config) tutulması sayesinde koda dokunmadan yeni günlükler eklemeyi, günlük tutma seviyesini değiştirerek görmek istediğiniz detaya ulaşmayı ya da günlük tutmayı tamamen kapatmayı rahatlıkla yapabilirsiniz.

   Böylesi uzun bir tanıtım ve giriş sonrasında isterseniz biraz da koda dokunarak, Log4Net’i geliştirdiğimiz uygulamalar içerisinde nasıl kullanabileceğimizi görelim. Tanımamız gereken ilk arayüzümüz ILog olmalı. uygulamamızın günlük kütüphanesinde bir köprü olan ILog arayüzünü uygulayan sınıflar sayesinde günlük tutabilmekteyiz. ILog arayüzü aşağıdaki yapıdadır;

namespace log4net
{
    public interface ILog
    {
        bool IsDebugEnabled { get; }
        bool IsInfoEnabled { get; }
        bool IsWarnEnabled { get; }
        bool IsErrorEnabled { get; }
        bool IsFatalEnabled { get; }
        
        void Debug(object message);
        void Info(object message);
        void Warn(object message);
        void Error(object message);
        void Fatal(object message);
        
        void Debug(object message, Exception t);
        void Info(object message, Exception t);
        void Warn(object message, Exception t);
        void Error(object message, Exception t);
        void Fatal(object message, Exception t);
        
        void DebugFormat(string format, params object[] args);
        void InfoFormat(string format, params object[] args);
        void WarnFormat(string format, params object[] args);
        void ErrorFormat(string format, params object[] args);
        void FatalFormat(string format, params object[] args);
        
        void DebugFormat(IFormatProvider provider, string format, params object[] args);
        void InfoFormat(IFormatProvider provider, string format, params object[] args);
        void WarnFormat(IFormatProvider provider, string format, params object[] args);
        void ErrorFormat(IFormatProvider provider, string format, params object[] args);
        void FatalFormat(IFormatProvider provider, string format, params object[] args);
    }
}

   Arayüzün ilk grubu ilgili log seviyesinin aktif olup olmadığının programsal olarak kontrol edilmesine olanak vermekte. Bu sayede aktif olmayan bir log seviyesi için gereksiz işlemler yapılmasının önüne geçilerek uygulamanın performansının etkilenmemesi sağlanmakta. İkinci grupta yer alan fonksiyonlar basit düzeyde günlük tutulabilmesini sağlamayı amaç edinerek sadece günlüğe kaydedilmesi istenen nesneyi parametre kabul etmektedir. Genel kullanım olarak string türünden günlüğe kaydedilecek mesajlar verilmektedir. Üçüncü ve dördüncü grupta yer alan fonksiyonlar belirli bir format ile günlük tutulmasına olanak verilmiştir.

// İkinci grupta yer alan fonksiyonlar kullanılarak formatlanan 
// bir günlük girdisi
logger.Info(string.Format("Kullanıcı {0} değerini girmiştir", girilenDeger));

// Aynı girdinin üçüncü grupta yer alan fonksiyonlar kullanılarak
// yazılmış hali
logger.InfoFormat("Kullanıcı {0} değerini girmiştir", girilenDeger);

   Yukarıdaki örnekte de görüldüğü gibi günlüğe kaydedilecek mesajınızı belirli bir formatta vermek istemeniz durumunda kullanabileceğiniz iki yöntem bulunmakta. Benim size tavsiyem bu iki yöntemde ikincisi seçerek üçüncü grupta yer alan fonksiyonları kullanmanız. İkinci grup fonksiyonları kullanarak yapılan bir günlük kaydı işleminde ilgili seviye aktif olmasa dahi string üzerinde bir format işlemi yapılmakta. String işlemlerinin maliyetinin yüksek olması ve mümkün olduğunca kaçınılması gerektiğini hatırlayacak olursak üçüncü grup fonksiyonlarda ilgili seviyenin aktif olmaması durumunda herhangi bir string işlemi yapılmaması nedeniyle daha performanslı çalışacağını rahatlıkla görebiliriz.

   Yukarıda örneklediğim ve ILog arayüzünü uygulayan logger değişkeni, Log4Net ile birlikte gelen, LogManager sınıfı yardımıyla programsal olarak oluşturulabilir.  LogManager içerisinde yer alan static GetLogger metodu bize ihtiyacımız olan değişkeni verecektir. Log4Net içerisinde istediğimiz kadar logger kullanabiliyor olmamız nedeniyle GetLogger fonksiyonuna bunlardan hangisi istediğimizi belirtmeliyiz. Bunun için doğrudan istediğimiz logger’ın adını verebileceğimiz gibi logger’ı kullanacağımız sınıfımızı belirterek bir logger almakta mümkün.

private static readonly ILog logger = LogManager.GetLogger(typeof(IsSinifi));
private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType)
ILog logger = LogManager.GetLogger(this.GetType());
private static readonly ILog logger = LogManager.GetLogger("IOIslemleri");

   Burada dikkat edilmesi gereken nokta iki yöntemin birbirinden farklı sonuçları olacağıdır. Kullanacağımız sınıfı vererek oluşturulacak bir logger, hiyerarşik olarak doğrudan bu sınıf için doğru bir şekilde konumlanır. Bu sayede örneğin yapılandırma dosyamızda sınıfın bulunduğu namespace için vereceğimiz bir filterede (bu namespace için sadece Error ve üzeri seviyeler geçerli olsun gibi) bu sınıf içerisinde tuttuğumuz günlükler de etkilenecektir. Doğrudan logger adı verilmesi durumunda bir önce belirttiğim hiyerarşi söz konusu olmadığı için filtreleme için daha farklı işlemler yapılması gerekecektir.

   Bir kısıtlama ya da zorunluluk olmamasına karşın, kullanım olarak günlüklerinize aşağıdaki şekilde veri kaydetmenizi tavsiye ederim. Bu şekilde uygulamanız daha performanslı çalışacaktır;

public class IsSinifi {
    private static readonly ILog logger = LogManager.GetLogger(typeof(IsSinifi));

    public void KullanicidanVeriOku() {
        if (logger.IsDebugEnabled) {
            logger.Debug("Kullanıcıdan veri alınıyor");
        }

// ...
        // İş mantığı kodları
        // ...

        if (logger.IsInfoEnabled) {
            logger.InfoFormat("Kullanıcı {0} değerini girmiştir",  girilenDeger);
        }
    }
}

    Uygulamamızda günlük tutmak istediğimiz yerlerde LogManager snıfı GetLogger fonksiyonu yardımıyla aldığımız logger değişkenimizi oluşturup yukarıda sıraladığım yöntemleri kullanarak uygulamamızda günlük tutmaya başlamış olmakla birlikte henüz yapmamız gerekenler bitmiş değil. Son olarak uygulama yapılandırma dosyasında gerekli ayarlamaları yaparak loglamayı aktif hale getirmemiz gerekmekte.

   Log4Net, ihtiyacı olan yapılandırma bilgisine app.config/web.config dosyasında bulunan log4net bölümünden ulaşmaktadır. Bu sebeple öncelikle bu bölümü tanıtmamız gerekmekte. Tanımlama için yapılması gereken yapılandırma dosyası içerisinde configSections elementi altına log4net adı ve "log4net.Config.Log4NetConfigurationSectionHandler, log4net" türüyle aşağıda örneklendiği şekilde bir girdi eklemektir;

<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
<!-- diğer yapılandırma bölümleri-->
</configSections>

<log4net>
<!-- Log4Net yapılandırması-->
</log4net>

<!-- diğer yapılandırma bilgileri-->
</configuration>

   Bu adım sonrası, tüm yapılandırma bilgisi log4net elementi altında toplanmalıdır. Log4Net yapılandırması konusu başlı başına anlatılması gereken bir konu olması nedeniyle bu makale içerisinde değinmemekle birlikte, Log4Net’ı hızlıca kullanmaya başlayabilmeniz/pratik kazanabilmeniz adına aşağıda basit yapılandırma bilgisini paylaşıyorum;

<log4net>
<appender name="DosyaAppender" type="log4net.Appender.FileAppender,log4net">
<param name="File" value="GunlukDosyasi.txt"/>
<param name="AppendToFile" value="true"/>
<layout type="log4net.Layout.PatternLayout,log4net">
  <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] - %m%n"/>
</layout>
    </appender>

<root>
<level value="ALL" />

<appender-ref ref="DosyaAppender" />
</root>
</log4net>

   Örnek yapılandırmamızda öncelikle DosyaAppender adıyla bir appender tanımlanmakta. Günlüğün GunlukDosyasi.txt adında bir text dosyada tutulmasını sağlayan bu appender root elementi içerisine eklenerek tüm hiyerarşilerde tanımlı olması sağlanmakta. root elementi içerisinde yer alan level elementinde value özniteliğine ALL verilerek tüm seviyeler için loglama yapılması sağlanmakta. Günlük tutma seviyesinin belirtildiği leve elementi value öznitelinde aşağıdaki değerler kullanılabilir;

  • ALL
  • DEBUG
  • INFO
  • WARN
  • ERROR
  • FATAL
  • OFF

   Bu seviyeler ile ilgili olarak düşülmesi gereken önemli bir not; yukarıda sıralanan seviyeler aşağıdan yukarı doğru bir birini kapsamaktadır. Konuyu biraz daha açarsak; örneğin seviye olarak WARN belirtilmesi durumunda sadece uyarı (Warn) seviyesi değil daha üst seviyeler olarak ERROR ve FATAL seviyesindeki günlük girdileri de kayıt edilecektir. ALL ve DEBUG seviyerleri ise tüm günlük girdilerinin kaydedilmesini sağlayacağından üretim ortamında bulunan uygulamalarda gerekmedikçe verilmemesini tavsiye ederim.

   Her ne kadar örnek yapılandırmamızda tek bir hiyerarşi için tanımlamalar bulunsa da, birden fazla hiyerarşi için farklı seviyelerde ve farklı logger’larda günlük tutulmasını sağlamakta mümkündür.

   Herhangi bir sebeple bir hiyerarşi ve altındaki tüm loglamayı iptal etmek istememiz durumunda kodumunuza dokunmaya gerek kalmadan yapılandırma dosyasına aşağıdaki girdiyi ekleyerek ilgili hiyerarşi için loglamayı kapatmamız mümkündür;

<log4net threshold="OFF" />

   Bu adımlar sonrasında her şey hazır durumda, son yapılacak işlem log4net yapılandırmasının yapılmasıdır;

XmlConfigurator.Configure(new System.IO.FileInfo("yapilandirmaDosyasi.xml"));

   app.config ya da web.config gibi bir uygulama config dosyası üzerinden yapılabilecek olan log4net yapılandırması istenirse aynı yöntemle normal bir xml dosyasından da yapılabilir. Bu durumda loglama başlamadan önce, genellikle uygulamanın ilk satırında, aşağıdaki kod parçasıyla harici yapılandırma dosyasının belirtilerek yapılandırmanın tamamlanması gereklidir;

XmlConfigurator.Configure(new System.IO.FileInfo("yapilandirmaDosyasi.xml"));

   XmlConfigurator sınıfı Configure metodu pek çok parametre alması nedeniyle yapılandırma bildileri çok farklı kaynaklarda tutulabilir. Örneğin uygulamada gömülü bi Bir başka yöntem de XmlConfiguratorAttribute özniteliğini kullanarak yapılandırmanın tamamlanmasıdır. Bu yöntemde assembly seviyesindeki XmlConfiguratorAttribute özniteliği kullanılarak yapılandırma bilgileri verilmektedir. Zorunlu olmamakla birlikte bu yöntemde XmlConfiguratorAttribute özniteliğini AssemblyInfo.cs dosyasına eklemek kodunuzun daha derli toplu olmasını sağlayacaktır. XmlConfiguratorAttribute özniteliği aşağıdaki 3 parametreyi kabul etmektedir;

  1. ConfigFile : Yapılandırma bilgilerinin okunacağı dosyayı belirtir. Zorunlu olmayan bu parametrede verilen değer uygulamanın ana dizinine bağlı göreceli bir yol tanımlamadır.
  2. ConfigFileExtension : Yapılandırma dosyasını uzantısını belirtmekte kullanılır. ConfigFile parametresiyle birlikte kullanılamayan ve zorunlu olmayan bu parametrede verilecek değere göre aranacak olan yapılandırma dosyası belirlenir. Yapılandırma dosyası aranırken uygulamanın başlatıldığı assembly adının sonuna bu parametrede verilen değer eklenerek oluşan dosya adıyla bir yapılandırma dosyası olup olmadığına bakılır, bulunması durumunda yüklenir.
  3. Watch : true ve false değerlerini alan ve zorunlu olmayan bu parametrede yapılandırma dosyasındaki değişikliklerin takip edilip edilmeyeceği belirlenir. Parametrenin true değeri alması durumunda yapılandırma dosyasının her değişimde yeniden Log4Net yapılandırması yapılacaktır.

  Aşağıda XmlConfiguratorAttribute özeniteliğinin kullanımına dair iki örnek bulabilirsiniz;

// Yapılandırma dosyası değişiklikler için izlenecektir
[assembly: log4net.Config.XmlConfigurator(Watch=true)]

// Uygulamayı başlatan assembly'nin abc.exe olması durumunda
// abc.exe.log4net adıyla bir yapılandırma dosyası aranacaktır
[assembly: log4net.Config.XmlConfigurator(ConfigFileExtension="log4net",Watch=true)]

   Her ne kadar yapılandırma dosyası dışında programsal olarak loglamayı aktif hale getirmek hatta yeni appender’lar tanımlamak mümkünse de, bu yönteme Log4Net giriş makalesinde yer vermek yerine ayrı bir makalede incelemek daha doğru olacaktır.

   Tüm bu adımları takip ederekn uygulamamızda da gerekli günlük kayıtlarını yazdıysak artık işler bizim için çok daha kolay olacaktır. İlk kullanımlarınızda gereğinden az ya da fazla log atıyor olmanız muhtemeldir. Zaman içerisinde nerede, hangi düzeyde  ve ne şekilde log atmanız gerektiği konusu oturacaktır.

   Log4Net ve loglama işlemleri için anlatılabilecekler bu makaleye sığmayacaktır. Loglama ve Log4Net ile hızlı bir tanışma ve kolaylıkla uygulamalarınızda kullanmaya başlamanızı amaçladığım bu makalemde umarım sizler için yararlı bilgiler verebilmişimdir.

Fatih Boy

Ankara'da yaşayan Fatih, bir kamu kurumunda danışman olarak çalışmaktadır. ALM süreçleri, kurumsal veri yolu sistemleri, kurumsal altyapı ve yazılım geliştirme konularında destek vermektedir. Boş zamanlarında açık kaynak kodlu projeler geliştirmeyi ve bilgisini yazdığı makalelerle paylaşmayı seven Fatih, aynı zamanda Visual C# ve Visual Studio teknolojileri konusundan Microsoft tarafından altı yıl üst üste MVP (En Değerli Profesyonel) ödülüne layık görülmüştür. İş hayatı boyunca masaüstü uygulamaları, web teknolojileri, akıllı istemciler gibi konularda Asp.Net, Php, C#, Java programlama dilleri ve MySql, MsSql ve Oracle gibi veritabanı yönetim yazılımları ile çalışmıştır. İngilizce ve Türkçe olarak yayınlanan makalelerini gerek İngilizce bloğunda, gerekse de Türkçe bloğunda bulabileceğiniz gibi web sitesinden de açık kaynak kodlu geliştirdiği yazılımlarına ulaşabilirsiniz. vCard - Twitter - Facebook - Google+

3 yorum

  1. Pingback: Tweets that mention Log4Net ile Hata Ayıklama | Fatih'in Notları -- Topsy.com

  2. hasan   •  

    hocam loglamanın hızlı olması icin app.config mi kullanmak daha iyi yoksa xml dosyasında mı ayarları yapmak daha efektif?Bir de loglamayı paralel kullanmak icin thread falan mı kullnayım yoksa log4neet in paralel programlama thread desteği var mı?
    tşk ler

    • Fatih Boy   •     Yazar

      Merhaba Hasan,
      Performans açısından app.config, xml ya da kod ile log4Net’i yapılandırma arasında bir fark bulunmuyor. Log4Net kodları incelendiğinde hepsi arkaplanda aynı yapıyı kullanmakta. Log4Net’in uygulama performansına olan etkisini minimum yapmak istersen sana tavsiyem öncelikle loglama yapacağın sırada kullanacağın log seviyesi ve/veya loglama appender’ının aktif olup olmadığını kontrol etmen. Bunu aşağıdaki gibi basit bir if cümlesiyle yapabilmen mümkün;
      if (logger.IsDebugEnabled) {
      logger.Debug("Kullanıcıdan veri alınıyor");
      }

      Paralel loglama konusuna geldiğimizde ise; bu konuda Log4Net ile birlikte gelen bir appender bulunmuyor, ama IAppender arayüzü ya da AppenderSkeleton sınıfı yardımıyla kendi appender’ını yazarak bunu yapabilmen mümkün. Bu noktada paralel loglamanın’da kendine göre zorlukları olduğunu da belirtmeliyim. Örnek vermek gerekirse paralel olarak loglanmayı bekleyen mesajlar daha işlenmeden uygulaman sonlanabilir; bu durumda mesajlarının loglanıp loglanmadığından her zaman için emin olamazsın. başka bir örnek, loglama işlemi paralel olacağı için yazdığın logların sırası her zaman için ardıl olmayabilir.

Bir Cevap Yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir