Asp.NET Core – Serilog İle Veri Loglama ve Seq İle Görselleştirme

Merhaba,

Yazılım uygulamalarının en can alıcı noktalarından biri işleyişteki her bir adımı raporlayıp kayıt altına alan log mekanizmalarıdır. Bu mekanizmaların dışında toplanan verileri görselleştirmek ve bu şekilde yorumlamak son zamanların en popüler uğraşları arasındadır. Günümüzde, aralarında avantaj ve dezavantaj olan birçok log kütüphanesi bu ihtiyaç doğrultusunda bizlere eşlik etmekte ve özel hazırlanmış yahut hazır kütüphaneler eşliğinde görselleştirmeler gerçekleştirilmektedir. Bizler bu içeriğimizde loglama kütüphanelerinden birini, özellikle diğerlerinden ayıran en büyük özellik olarak güçlü yapılandırılmış olay veriler(structured event data) ile geliştirilmiş olma özelliğini taşıyan Serilog kütüphanesini ve ayrıca bu kütüphane ile toplanan verileri görselleştirmek içinde Seq platformunu ele alıyor olacağız.

Serilog Nedir?

Yukarıda bahsedildiği gibi structured event data(yapılandırılmış olay veriler) yapısını destekleyen, open source bir logging library’dir. Konfigürasyon açısından oldukça basit, kolayca implemente edilebilir ve güçlü filtering özelliği barındırmaktadır.

Serilog çıktıları nereye kaydediliyor?
Serilog çıktı olarak console, veritabanı yahut desteklenen herhangi bir dosya vs. gibi birçok ortama çıktılarını kaydedebilmektedir. Serilog kayıt gerçekleştirdiği ortamı ‘Sink’ ismi ile nitelendirmektedir ve Serilog.Sinks namespace’i altında toplamaktadır.

Serilog ile neler yapabiliriz?
Serilog ile mesaj şablonları(message template) oluşturabilmekte ve loglama esnasında bu şablonları parametre ismi olarak kullanabilmekteyiz. Haliyle oluşturulan bu şablonlar bizlere filtreleme, sıralama, serilize etme vb. gibi işlemlerde inanılmaz yardımcı olmakta ve efektif bir loglama süreci geçirmemizi sağlamaktadır.

Seq Nedir?

İçeriğin ilk paragrafında elde edilen logları görselleştirmemizi sağlayan bir platform olduğundan bahsetmiştik. Halbuki görselleştirilecek dataları kendi bünyesinde tutma sorumluluğunu da üstlenmekte ve arama, filtreleme vs. gibi özelliklerinin yanında sql query’leri ile chart oluşturulmasını desteklemektedir. Tüm bunların yanında mail veya sms gönderebilmekte veya thirdparty bir uygulama üzerinden entegre edilebilmektedir. Bizler bu içeriğimizde Serilog’a Sink olarak bu yetenekli Seq platformunu tercih edeceğiz.

Seq Kurulumu Nasıldır?
Tabi herşeyden önce Seq platformunda çalışabilmek için ilgili bilgisayarda kurulu olması gerekmektedir. Bunun için Seq’i şuradan indirebilir ve şuradaki önergelere göre bilgisayarınıza yükleyebilirsiniz. Ya da bilgisayarında docker yüklü olanlar şurada bahsedildiği gibi docker run --name seq -e ACCEPT_EULA=Y -p 5341:80 datalust/seq:latest komutu eşliğinde Seq’i tek çelsede kurabilirler.

Her iki kurulum neticesinde Seq platformuna erişim sağlayabilmek için tarayıcı üzerinden http://localhost:5341 adresine istek gönderilmesi yeterli olacaktır.

Bu teorik tanımlamalardan sonra herhangi bir Asp.NET Core uygulamasında Serilog entegrasyonunu ele alacak ve ardından Seq ile görselleştirmeyi sağlayacağız. Buyrun başlayalım…

Asp.NET Core Uygulamasına Serilog Entegrasyonu

Şimdi bir Asp.NET Core uygulamasına nasıl Serilog entegrasyonunun gerçekleştirilebildiğini inceleyeceğiz. Bunun için ilk olarak Serilog.AspNetCore kütüphanesinin uygulamaya entegre edilmesi gerekmektedir.

dotnet add package Serilog.AspNetCore

Ardından Serilog’u uygulamaya implement edebilmek için konfigürasyonunun gerçekleştirilmesi gerekmektedir. Burada konfigürasyon appsettings.json ile önayarlı bir şekilde veya runtime olmak üzere iki türlü gerçekleştirilebilmektedir. Her iki türlü konfigürasyonu da incelersek eğer;

  • appsettings.json Konfigürasyonu
    Serilog, Microsoft.Extensions.Configuration(appsettings.json) sınıfı üzerinde tanımlanan konfigürasyon değerlerini okuyabilmekte ve işleyebilmekte ve böylece konfigürasyonlar öntanımlı gerçekleştirilebilmektedir.

    Konfigürasyon için appsettings.json dosyasında aşağıdaki gibi bir elemanın tanımlanması default olarak ilgili değerlerin buradan okunması için yeterlidir.

      "Serilog": {
        "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ],
        "MinimumLevel": "Information",
        "WriteTo": [
          { "Name": "Console" },
          {
            "Name": "File",
            "Args": { "path": "Logs/log.txt" }
          }
        ],
        "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ],
        "Destructure": [
          {
            "Name": "ToMaximumDepth",
            "Args": { "maximumDestructuringDepth": 4 }
          },
          {
            "Name": "ToMaximumStringLength",
            "Args": { "maximumStringLength": 100 }
          },
          {
            "Name": "ToMaximumCollectionCount",
            "Args": { "maximumCollectionCount": 10 }
          }
        ],
        "Properties": {
          "Application": "Sample"
        }
      }
    

    Bu konfigürasyonların uygulamaya dahil edilmesi için ‘Program.cs’ dosyasında aşağıdaki çalışmanın yapılması gerekmektedir.

            public static void Main(string[] args)
            {
                IConfigurationRoot configuration = new ConfigurationBuilder()
                    .SetBasePath(Directory.GetCurrentDirectory())
                    .AddJsonFile("appsettings.json")
                    .Build();
    
                Logger logger = new LoggerConfiguration()
                    .ReadFrom.Configuration(configuration)
                    .CreateLogger();
    
                logger.Information("Selam, millet!");
    
                CreateHostBuilder(args).Build().Run();
            }
    

    Burada 3. satırda uygulama içerisindeki appsettings.json dosyası elde edilmekte ve 8. satırda Serilog kütüphanesine bu dosya konfigürasyon kaynağı olarak verilmektedir.

    Yukarıda tanımlanan elemanın değerlerini incelersek eğer;

    • 1. Satır(Serilog)
      Default olarak konfigürasyonların okunabilmesi için tüm ayarlar Serilog etiketi altında başlatılmalıdır. Bu değer değişkenlik gösterebilir ve değiştirildiği taktirde aşağıdaki gibi ‘Program.cs’ içerisinde belirtilmelidir.

      {
        "CustomSection": {
          ...
        }
      }
      
                  Logger logger = new LoggerConfiguration()
                      .ReadFrom.Configuration(configuration, sectionName: "CustomSection")
                      .CreateLogger();
      
    • 2. Satır(Using)
      Serilog’un loglama yapacağı ‘Sink’ platformları namespace olarak seçilmelidir.
    • 3. Satır(MinimumLevel)
      Log etkinlik seviyelerinden birini minimum seviye olarak uygulamamızı sağlar.

      Genellikle Asp.NET Core uygulamalarında aşağıdaki gibi birden fazla ortama göre ‘MinimumLevel’ durumu belirlenebilir.

      "MinimumLevel": {
          "Default": "Information",
          "Override": {
              "Microsoft": "Warning",
              "System": "Warning"
          }
      }
      

      Bu arada tüm hata seviyelerini detaylıca ele aldığım SignalR Log Seviyeleri başlıklı makalemi inceleyebilirsiniz.

    • 4. Satır(WriteTo)
      Log verilerinin hangi Sink’e yazdırılacağı burada belirtilir. ‘Console’ ve ‘File’ değerlerinin dışında ‘Seq’ konfigürasyonu için Serilog.Sinks.Seq kütüphanesi yüklenmeli ve aşağıdaki gibi bir tanımlama yapılması yeterlidir;

        "Serilog": {
          "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File", "Serilog.Sinks.Seq" ],
          "MinimumLevel": "Information",
          "WriteTo": [
            {
              "Name": "Seq",
              "Args": {
                "serverUrl": "http://localhost:5341",
                "apiKey": "hY7i2m8w6NMDHKDBIVCv"
              }
            }
          ]
        }
      

      Yazımızın ileriki noktalarında ‘Seq Platform’ konfigürasyonunu daha detaylı inceliyor olacağız.

    • 11. Satır(Enrich)
      Enrich(zenginleştiriciler), bir log event’ına dinamik olarak özellik eklememizi sağlayan bileşenlerdir.
    • 12. Satır(Destructure)
      Serilog’da soyut parametre türlerini(abstract parameter types) belirlememizi sağlar.

      Peki nedir bu soyut parametreler?
      Serilog, ismi itibariyle ‘logger’ı serileştirmekten(serialize) türemektedir. Dolayısıyla buradaki serileştirmek kütüphane açısından yok etmek ile(destructure) hemen hemen eş anlamlı kullanılmıştır.

    • 30. Satır(Properties)
      Serilog seviyesinde uygulamaya dair genel tanımlamalar yapmamızı sağlar.

     
    Ayrıca yukarıda örneklendirilmese de ‘Filter’ seçeneği de eklenebilmektedir. ‘Filter’, Serilog’a uygulanacak filtreleri tanımlamaktadır.

    Örneğin;

    "Filter": [{
      "Name": "ByIncludingOnly",
      "Args": {
          "expression": "Application = 'Sample'"
      }
    }]
    

    şeklinde kullanılabilir.

  • Runtime Konfigürasyonu
    Serilog’un bir diğer konfigürasyon sahası runtime’da karşımıza çıkmaktadır. Bu yöntem ile gerçekleştirilen konfigürasyon, ‘appsettings.json’a nazaran daha efektif ve hızlı çözüm getirmemizi sağlamaktadır.

    Şöyle ki;

        public class Program
        {
            public static void Main(string[] args)
            {
                Logger logger = new LoggerConfiguration()
                      .CreateLogger();
    
                CreateHostBuilder(args).Build().Run();
            }
    
            public static IHostBuilder CreateHostBuilder(string[] args) =>
                Host.CreateDefaultBuilder(args)
                    .ConfigureWebHostDefaults(webBuilder =>
                    {
                        webBuilder.UseStartup<Startup>();
                    })
                .ConfigureLogging(config => config.ClearProviders())
                .UseSerilog();
        }
    

    görüldüğü üzere ‘LoggerConfiguration’ sınıfından üretilen nesne üzerinden ‘CreateLogger’ metodu çağrılarak, logger ayağa kaldırılmaktadır. Ayriyetten bu logger’ı .Net Core pipeline’ının da kullanabilmesi için 18. satırda ‘UseSerilog’ extension metodu çağrılmakta ve öncesinde ‘ConfigureLogging’ ile Serilog’dan önce implement edilmiş provider’ları silinmektedir.

    Bu temelden sonra ihtiyaca dönük diğer ayarları ‘CreateLogger’ metodundan önce fonksiyonel olarak konfigürasyona yerleştirebiliriz. Şimdi sırasıyla konfigürasyonlarımızı görelim;

    Sink Belirleme
    Sink belirleyebilmek için aşağıdaki gibi ‘WriteTo…’ komutunun kullanılması yeterlidir.

                Log.Logger = new LoggerConfiguration()
                     .WriteTo.Console()
                     .WriteTo.Debug(outputTemplate: DateTime.Now.ToString())
                     .WriteTo.File("log.txt", rollingInterval: RollingInterval.Day)
                     .WriteTo.Seq("http://localhost:5341/")
                     .CreateLogger();
    

    Yukarıdaki örnek kod bloğunda birçok desteklenen Sink örneklendirilmiştir. Bunları tek tek izah etmemiz gerekirse eğer;

    • Console
      Console platformunda log çıktılarını verir. Kullanabilmek için Serilog.Sinks.Console kütüphanesinin yüklü olması gerekmektedir.
    • Debug
      Visual Studio ‘Output’ penceresinde log çıktılarını verir. Serilog.Sinks.Debug kütüphanesini gerektirir.
    • File
      Bir metin dosyasına log çıktılarını verir. Serilog.Sinks.File kütüphanesini gerektirir.

      ‘rollingInterval’ parametresi ile log dosyasının yuvarlama aralığı belirtilebilir. Örneğin; ‘RollingInterval.Day’ değeri verildiğinde her gün için bir dosya üretecek ve o dosyaya loglama işlemini gerçekleştirecektir.

    • Seq
      Log çıktılarını Seq platformunda verir. Serilog.Sinks.Seq kütüphanesini gerektirir.

    MinimumLevel Belirleme

                Log.Logger = new LoggerConfiguration()
                     .WriteTo.Console()
                     .WriteTo.Debug(outputTemplate: DateTime.Now.ToString())
                     .WriteTo.File("log.txt", rollingInterval: RollingInterval.Day)
                     .WriteTo.Seq("http://localhost:5341/")
                     .MinimumLevel.Information()
                     .CreateLogger();
    

    şeklinde belirlenebilir.

    MinimumLevel belirlenmediği taktirde default olarak ‘Information’ baz alınır.

    Filter Belirleme
    Misal, MinimumLevel information belirlenmiştir lakin ‘Microsoft.AspNetCore’ namespace’i içerisindeki çalışmalarda ‘MinimumLevel’ değerini ‘Warning’ yapabiliriz. Şöyle ki;

                Log.Logger = new LoggerConfiguration()
                     .WriteTo.Console()
                     .WriteTo.Debug(outputTemplate: DateTime.Now.ToString())
                     .WriteTo.File("log.txt", rollingInterval: RollingInterval.Day)
                     .WriteTo.Seq("http://localhost:5341/")
                     .MinimumLevel.Information()
                     .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
                     .CreateLogger();
    

    Enrich Belirleme
    Atılacak log’a dair ekstradan propertyler aşağıdaki gibi tanımlanabilmektedir.

                Log.Logger = new LoggerConfiguration()
                     .WriteTo.Console()
                     .WriteTo.Debug(outputTemplate: DateTime.Now.ToString())
                     .WriteTo.File("log.txt", rollingInterval: RollingInterval.Day)
                     .WriteTo.Seq("http://localhost:5341/")
                     .MinimumLevel.Information()
                     .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
                     .Enrich.WithProperty("AppName", "Serilog Example")
                     .Enrich.WithProperty("Environment", "Development")
                     .Enrich.WithProperty("Coder", "Gençay")
                     .CreateLogger();
    

    Dinamik Enrich Belirleme
    Log atılmadan önce ilgili log’a dair atanacak bir value varsa bunu dinamik olarak ‘ILogEventEnricher’ interface’i eşliğinde geliştirebilir ve ‘Enrich.With(…)’ fonksiyonuyla sisteme entegre edebiliriz..

    Misal;
    LogEvent ile birlikte ThreadId değerinin kaydedilmesini istiyorsak eğer bunun için dinamik enrich kullanmamız gerekmektedir.

        class ThreadIdEnricher : ILogEventEnricher
        {
            public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
            =>
                logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(
                     "ThreadId", Thread.CurrentThread.ManagedThreadId));
        }
    
                Log.Logger = new LoggerConfiguration()
                     .WriteTo.Console()
                     .WriteTo.Debug(outputTemplate: DateTime.Now.ToString())
                     .WriteTo.File("log.txt", rollingInterval: RollingInterval.Day)
                     .WriteTo.Seq("http://localhost:5341/")
                     .MinimumLevel.Information()
                     .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
                     .Enrich.WithProperty("AppName", "Serilog Example")
                     .Enrich.WithProperty("Environment", "Development")
                     .Enrich.WithProperty("Coder", "Gençay")
                     .Enrich.With(new ThreadIdEnricher())
                     .CreateLogger();
    

İşte bu kadar.

Her iki yöntemden biriyle Serilog kütüphanesinin konfigürasyonlarını gerçekleştirdikten sonra sıra teste gelmiştir. Testte kullanabilmek için öncelikle aşağıdaki controller’ı oluşturmakta fayda var.

    [Route("api/[controller]")]
    [ApiController]
    public class HomeController : ControllerBase
    {
        private readonly ILogger<HomeController> _logger;
        public HomeController(ILogger<HomeController> logger) => _logger = logger;
        public void Index() => _logger.LogInformation("Home index tetiklenmiştir.");
    }

Log Testi

Şimdi log testini gerçekleştirebilmek için uygulamayı ayağa kaldıralım ve yukarıda oluşturulan controller’a bir istek yollayalım. Kaydedilen log’u gözlemleyebilmek için ağırlık olarak içeriğimizin ana teması olan Seq platformunu inceleyeceğiz lakin diğer Sink’leride göz ardı etmeyeceğiz. O yüzden incelemeyi öncelikle diğer Sink’lerden başlatacak ve ardından Seq platformunu değerlendireceğiz.

Console, File ve Debug
Console File Debug
Asp.NET Core - Serilog İle Veri Loglama ve Seq İle Görselleştirme Asp.NET Core - Serilog İle Veri Loglama ve Seq İle Görselleştirme Asp.NET Core - Serilog İle Veri Loglama ve Seq İle Görselleştirme
Seq İle Log Görselleştirme

Asp.NET Core - Serilog İle Veri Loglama ve Seq İle Görselleştirme
Evet, görüldüğü üzere Seq platformu ile log datalarını görselleştirdiğimizde Serilog için yapılan tüm konfigürasyon ayarlarının, enrich’lerin ve dinamik enrich’lerin kaydedildiğini görebilmekteyiz.

Seq platformunun en gözde nimetlerinden birisi ise aşağıda görüldüğü üzere arayüz üzerinden filtreleme işleminin yapılabilmesidir. Aranan değerin türüne göre yazılan format sayısal değerlerde direkt olarak tırnaksızken, metinsel değerlerde tırnak içerisinde olmak üzere standart programlama semantiğini benimsemiştir. Ayrıca Contains, EndsWith, StartsWith vs. gibi fonksiyonel aramalarda gerçekleştirilebilmektedir.
Asp.NET Core - Serilog İle Veri Loglama ve Seq İle Görselleştirme

{..} Operatörü İle Runtime’da Veriable Loglama

Serilog, runtime’da oluşturulan bir variable’ı {..} operatörü ile JSON formatta loglamamızı sağlamaktadır.

        public void Index()
        {
            string text = "Selam, millet!";
            _logger.LogInformation("{text} değeri loglandı.", text);
        }

Yukarıdaki kod bloğunu incelerseniz eğer, {text} ifadesine ‘text’ değişkeninin değerini atamaktadır. Böylece formatlandırmayı runtime’da hızlıca gerçekleştirebilmekteyiz.
Asp.NET Core - Serilog İle Veri Loglama ve Seq İle Görselleştirme
Ayrıca Serilog {..} operatörü ile kullanılan ifadeyi ayrı bir property olarak değerlendirdiğine dikkatinizi çekerim.

Bu arada isterseniz ‘Console’, ‘File’ ya da ‘Output’ Sink’lerini kontrol edebilir ve formatlandırmanın o platformlarda da başarıyla gerçekleştirildiğini görebilirsiniz.

Tabi burada {..} operatörünün birden fazla kullanıldığı durumuda örneklendirmemiz gerekirse eğer;

        public void Index()
        {
            string text = "Selam, millet!";
            string text2 = "Sebepsiz boş yere ayrılacaksan.";
            _logger.LogInformation("{text} değeri loglandı. {test2}", text, text2);
        }

şeklinde çalışılabilmektedir. Burada değişken isimleriyle {..} ifadesinin birebir aynı tanımlamada olma zorunluluğu bulunmamaktadır. İsterseniz aşağıdaki gibi farklı isimlerde de tanımlama ile eşleştirme yapabilirsiniz.

        public void Index()
        {
            string text = "Selam, millet!";
            string text2 = "Sebepsiz boş yere ayrılacaksan.";
            _logger.LogInformation("{a} değeri loglandı. {b}", text, text2);
        }

Her iki türlüde farketmeksizin loglama belirtilen formatta başarıyla sağlanmış olacaktır.

Asp.NET Core - Serilog İle Veri Loglama ve Seq İle Görselleştirme

a ve b property’lerine dikkatinizi çekerim…

Yukarıda {..} operatörünün runtime’da herhangi bir variable’ı JSON olarak formatlayıp, logladığından bahsetmiştik. Gelin birde bunu örneklendirelim.

        public void Index()
        {
            var instance1 = new { Name = "Example", Number = 12345 };
            var instance2 = new { Name = "Example 2", Number = 54321 };
            _logger.LogInformation("{a} değeri loglandı. {b}", instance1, instance2);
        }

Yukarıdaki örnek kod bloğunda olduğu gibi {..} parametrelerine complex type gönderildiği zaman compiler bunu algılamakta ve otomatik serialize etmektedir.

Ya da compiler’ın complex type algılama maliyetini üstlenmek için {..} operatörüne @ operatörünü koyarak {@..} şeklinde bildiride bulunabilirsiniz.

        public void Index()
        {
            var instance1 = new { Name = "Example", Number = 12345 };
            var instance2 = new { Name = "Example 2", Number = 54321 };
            _logger.LogInformation("{@a} değeri loglandı. {@b}", instance1, instance2);
        }

Böylece compiler’a bu parametreye gelecek olan değerin bir complex type olduğu tarafımızca bildirilmiş olacaktır. Nihayetinde her iki türlü farketmeksizin sonuç aşağıdaki gibi olacaktır.
Asp.NET Core - Serilog İle Veri Loglama ve Seq İle Görselleştirme

Nihai olarak
Uygulamalarımızın olmazsa olmaz log mekanizmasını Serilog ile oldukça efektif bir şekilde mimarisel hale getirebilir ve Seq ile toplanan veriler görselleştirilebilir. Böylece uygulamanız açısından fark yaratmış olacak ve hizmet sunulan müşterilerin deneyimi açısından ise süreç daha verimli kılınacaktır.

Severek ve bol keyiflerle kullanmanız dileğiyle…

İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…

Bunlar da hoşunuza gidebilir...

Bir cevap yazın

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

*