Distributed Sistemlerde Traceability – Derinlemesine İnceleme | Asp.NET Core App. | Console App. | NLog
Merhaba,
Günümüzdeki yazılım yaklaşımlarındaki en gözde parametreler; performans, yüksek kullanılabilirlik, ölçeklenebilirlik ve güvenirliktir diyebiliriz. Hal böyleyken, bu parametreleri mevcut kaynaklarımız doğrultusunda en ideal hale getirebilmek için doğru bir mimari tasarımına ihtiyaç duyulmakta ve artık günün tercihi olarak distributed(dağıtılmış) sistemlerin inşa edilmeleri mevzu bahis olmaktadır. Distributed sistemlerin; inşa, tasarım ve senkronizasyon bedelleri ister istemez monolitik sistemlere nazaran daha maliyetli olabilmekte ve bu maliyet doğal olarak inşa edilmiş olan dağıtık sistemlerin izlenebilirliğini esas unsur olarak yorumlamamızı sağlamakla birlikte mühim bir konuma getirmektedir. İşte bu noktada da distributed sistemlerde Traceability kavramı ön plana çıkmaktadır. Bizler bu içeriğimizde dağıtık sistemlerde Traceability kavramını teorik olarak ele alacak ve biryandan da yaygın olarak kullanılan bir yöntem olan Correlation Id(İlişkilendirme Kimliği) üzerinden de bir çalışmayı pratiksel açıdan tecrübe ediyor olacağız. O halde buyurun başlayalım…
Traceability(İzlenebilirlik) Nedir?
Bir distributed sistemdeki bileşenlerin, gereksinim doğrultusundaki unsurların ve genel anlamda uygulanan tasarımın izlenebilirliğini yahut geri takibini ifade eden bir kavramdır. Traceability sayesinde, gereksinimler doğrultusunda geliştirilen bileşenlerin ne kadar amacına uygun bir şekilde inşa edildiğini anlayabilmekte ve sistemdeki olası değişikliklerin etkilerini belirleyebilmekteyiz. Böylece sistemde meydana gelen hataları tespit edebilmekte ve değişikliklerin nedenlerini daha rahat analiz edebilmekteyiz ve uzun lafın kısası tüm bu süreç neticesinde nihai olarak inşa edilmiş olan sistemi daha iyi anlayabilmekteyiz.
Traceability, bir işlemin yahut veri parçasının kaynağından hedefine kadar tüm aşamalarda geçtiği yolun takip edilmesi, gözlenmesidir.
Traceability sayesinde, distributed sistemdeki işlemler ve veriler tüm yaşam döngüsü boyunca kayıt altına alınarak, takip edilmekte ve doğrulanmaktadır. Bu süreçte, ilgili işlemlerin nerede başladığı, hangi bileşenler veya hizmetler üzerinden işlevler yürüttüğü ve nihai olarak hangi hedefe ulaştığı net bir şekilde belirlenebilmektedir.
Şimdi ‘la hoca, burada söylemeye çalıştığın şey loglama mı la allahisen?’ dediğinizi duyar gibiyim… Evet, bu tarz bir açıklama eşliğinde akla direkt loglama yapısının gelmesi oldukça doğaldır. Amma velakin traceability ile kastedilen log değildir. Şimdi gelin bunun mukayesesini yaparak devam edelim…
Traceability ile Log Arasındaki Fark Nedir?
Traceability ile log, birbirini tamamlayan lakin farklı olan kavramlardır. Bu ikisi arasındaki temel farkları ‘Kapsam ve Amaç’, ‘İçerik’ ve ‘Kullanım Amacı’ açısından aşağıdaki gibi değerlendirelim;
Traceability | Log | |
---|---|---|
Kapsam ve Amaç | Traceability, bir işlemin veya veri parçasının kaynağından hedefine kadar geçtiği yolun takip edilebilmesini sağlamak için kullanılan bir kavramdır. Amaç, işlemin veya verinin geçtiği bileşenleri, hizmetleri veya sistemleri izlemek ve takip etmektir. Genellikle; güvenlik, sorun giderme, uyumluluk ve performans analizi gibi amaçlarla kullanılır. | Loglar ise, bir sistemin veya uygulamanın faaliyetlerini kaydetmek için kullanılan kayıtlardır. Sistemde gerçekleşen olayları, hataları, uyarıları, kullanıcı etkileşimlerini ve diğer önemli bilgileri kaydetmek amacıyla oluşturulur. Loglar genellikle; sistem durumunu anlamak, sorunları tespit etmek ve gidermek ve hataları izlemek için kullanılır. |
İçerik | Traceability, genellikle bir işlemin veya veri parçasının geçtiği bileşenlerin yahut hizmetlerin tanımlandığı ilişkilendirme verilerini içerir. Bu veriler; işlemin kaynağı, hedefi, ara bileşenler, kullanılan protokoller, zaman damgaları(timestamp) vb. gibi bilgileri içerebilir.
|
Loglar ise, kaydedilen olayların ayrıntılarını içerir. Bu olaylar; işlemler, hatalar, uyarılar, kullanıcı etkileşimleri ve diğer önemli bilgiler olabilir. Loglar, sistemde gerçekleşen olayların ayrıntılı bir kaydını tutar ve belirli bir olayın gerçekleştiği zamana, içeriğe ve diğer ilgili verilere sahip olmayı sağlar. |
Kullanım Amacı | Traceability’de sistemin genel işleyini anlamak, verilerin kaynağından hedefine olan yolunu takip ederek/gözlemleyerek sistem performansını analiz etmek esastır. Bu yüzden genellikle; sistem tasarımı, güvenlik analizi, sorun giderme ve iyileştirme süreçlerinde hesaba katılır. | Loglar’da ki temel gaye ise sistemdeki olayları/davranışları kaydetmek ve sistemin çalışma sürecindeki hata ve uyarılar gibi reflekslerini tespit maksadıyla takip etmektir. Böylece loglar’daki temel amacın, sorunların izlenmesi ve giderilmesi olduğunu söyleyebiliriz. Log verileri sistem yöneticileri, geliştiriciler ve diğer yetkililer tarafından erişilebilir, bu erişim neticesinde yorumlanarak sistem durumu, sorunları ve sistemin performansı hakkında bilgi edinilebilir. |
Özet olarak; traceability, bir işlemin veya veri parçasının işleniş yolunu izlemek ve takip etmek söz konusuyken, logda ise sistemin veya uygulamanın faaliyetlerini kaydetmek ve sistem durumuyla ilgili bilgiler sağlamak temel gayedir. İşte aralarındaki temel fark bunlardan ibarettir.
Şimdi de traceability ile log arasındaki farkı örnek bir senaryo üzerinden ele alarak daha da netleştirmeye çalışalım;
Bir e-ticaret platformunda, kullanıcı tarafından herhangi bir ürün sipariş verildiğinde, bu siparişin işlenmesi ve teslimat süreci çeşitli bileşenler ve hizmetler arasında gerçekleşecektir. Bu süreçte traceability ve loglar aşağıdaki gibi şekillenecektir;
Traceability | Log |
---|---|
|
|
Verisel Örnek | |
|
|
Evet, örnek senaryodan da görüldüğü üzere traceability bilgileri, siparişin kaynağından hedefine kadar geçtiği bileşenleri ve hizmetleri belirlerken, loglar ise sistemdeki faaliyetleri ve işlemleri kaydederek sorun giderme ve analiz için önemli birer kaynak sağlamaktadırlar.
Distributed Sistemlerde Traceability Bileşenleri
Traceability’nin log ile arasındaki farkı inceledikten sonra distributed sistemlerdeki traceability için önem arz eden bileşenlere değinmekte fayda vardır. Bunlar; ‘Gereksinim İzlenebilirliği’, ‘Tasarım İzlenebilirliği’, ‘Kod İzlenebilirliği’ ve ‘Test İzlenebilirliği’dir. Şimdi gelin bunları sırasıyla hafiften özetleyelim;
- Gereksinim İzlenebilirliği
Sistem gereksinimlerinin izlenebilirliği, her bir gereksinimin sistemdeki bileşenlere nasıl dönüştüğünü gösterir. Böylece, gereksinimlerin nasıl uygulandığı, değiştirildiği ve kaldırıldığı takip edilebilir. - Tasarım İzlenebilirliği
Tasarım izlenebilirliği ise gereksinimlerin nasıl bir tasarıma dönüştüğünü göstermektedir. Böylece de sistemdeki bileşenlerin birbirleriyle nasıl etkileşime girdiğini, veri akışını ve diğer sistem özelliklerini daha rahat anlamamızı sağlar. - Kod İzlenebilirliği
Kod izlenebilirliği ile de tasarımın nasıl uygulandığı takip edilebilir. Bileşenin nasıl geliştirildiği, değiştirildiği ve hata düzeltmesinin nasıl yapıldığı takip edilebilir. - Test İzlenebilirliği
Bir bileşenin veya sistem özelliğini nasıl test edildiğini gösterir. Bir test senaryosunun nasıl uygulandığını, sonuçlarının nasıl analiz edildiğini ve hataların nasıl düzeltildiğini takip etmemizi sağlar.
Traceability, distributed sistemlerin karmaşıklığını yönetmek ve sistemdeki değişikliklerin yan etkilerini anlamak için kritik bir öneme sahiptir.
Anlayacağınız; traceability, gelecekteki sistem değişikliklerini planlama sürecinde rehberlik edebilecek ve böylece ekip üyeleri arasındaki iletişimi kolaylaştırabilecek ve tüm bunların yanında sistem dokümantasyonunun güncel ve tutarlı olmasını sağlayabilecek bir davranıştır.
Traceability, sistem dokümantasyonunun güncel ve tutarlı olmasını sağlar.
Projelerde Traceability’i Sağlamak
Projelerimizde, projenin ölçeğine ve gereksinimlerine bağlı olarak çeşitli yöntemler eşliğinde traceability sağlanabilmektedir. Şimdi bizler tüm projeler için traceability’i temel prensipleri eşliğinde masaya yatırıyor olacak ve içeriğimizin devamında ise Correlation ID (İlişkilendirme Kimliği) üzerinden de pratik bir inceleme eşliğinde tecrübede bulunuyor olacağız.
Bir projede traceability’i aşağıdaki prensipler eşliğinde uygulayabilirsiniz;
- Gereksinim Belirleme (Gereksinimlerin İzlenmesi)
Gereksinimleri Jira, Trello vs. gibi bir gereksinim yönetim aracında tanımlayın ve tüm gereksinimlere benzersiz bir kimlik (ID) atayın. - Tasarım ve Kodlama (Tasarımın ve Kodun İzlenmesi)
Sistemin tasarımını belirleyin ve bunu UML diyagramları, akış şemaları ve gerekli olabilecek diğer belgelerle ifade edin. Kodunuzu ise versiyon kontrol sisteminde yönetin ve her geliştirmeyi ve değişikliği açıklayan commit mesajları kullanın. - Test Senaryolarının Tanımlanması (Kodun İzlenmesi)
Gereksinimleri doğrulamak için gerekli test senaryolarını tanımlayın. Her test senaryosu için bir kimlik oluşturun ve bu kimlikleri gereksinimlerle ilişkilendirin. Devamında ise test senaryolarınızı ve sonuçlarını NUnit veya xUnit gibi bir test yönetim aracıyla takip edin. - Raporlama
Nihai olarak gereksinimlerin hangi bileşenlerle ilişkili olduğunu, hangi bileşenlerin hangi tasarımlara dayandığını ve hangi tasarımların hangi kod parçaları tarafından uygulandığını gösterecek raporlar oluşturun.
Bu adımları takip ederek her bir gereksinimin, tasarımın, kodun ve testin birbirleriyle ilişkilendirildiği bir traceability mekanizması oluşturabilir ve bu sayede bir gereksinimde yapılan değişikliklerin; tasarımı, kodu ve test senaryolarını nasıl etkilediğini bütünsel olarak gözlemleyebilirsiniz.
Unutulmamalıdır ki Traceability, sürekli olarak güncellenmesi ve takip edilmesi gereken bir süreçtir. Projeniz ilerledikçe yeni gereksinimler ortaya çıkabilir, tasarımlar ve kodlar değişebilir ve testlerin güncellenmesi gerekebilir. Bu nedenle, Traceability’i proje yaşam döngüsü boyunca sürdürmek ve güncel tutmak önemlidir.
Correlation ID İle Traceability’i Sağlamak
Projenin genel çerçevesi açısından traceability’nin nasıl uygulanacağını ilkeleriyle birlikte yukarıdaki satırlarda yeterince aydınlattık kanaatindeyim. Şimdi ise traceability’i pratiksel olarak geliştirme süreçlerinde nasıl uygulayabileceğimize odaklanalım istiyorum. Yani perspektifi biraz daha daraltıp, olaya kod üzerinden yani operasyon sürecinden göz atalım istiyorum.
Özellikle .NET ortamında traceability’i uygulayabilmek için Correlation ID yöntemi yaygın olarak kullanılmaktadır. Correlation ID, kullanıcı tarafından başlatılan bir request’in sistem içerisindeki tüm bileşenler arasında izlenebilirliğini sağlamak için kullanılan benzersiz bir kimliktir. Bu kimlik sayesinde request’in sistemin hangi noktasında ne gibi bir durumda olduğunu rahatlıkla takip edebilir ve süreci gözlemleyebiliriz.
Console Uygulamasında Traceability
Şimdi ilk olarak Console uygulaması üzerinden bir çalışma gerçekleştirelim. Bunun için ilk etapta aşağıdaki kütüphaneleri uygulamaya yükleyerek başlayalım.
dotnet add package NLog.Extensions.Logging
dotnet add package Microsoft.Extensions.DependencyInjection
dotnet add package Microsoft.Extensions.Configuration.Json
dotnet add package NLog
dotnet add package NLog.Database
dotnet add package System.Data.SqlClient
Evet, bizler traceability’i uygularken NLog kütüphanesinden istifade ediyor olacak ve SQL Server veritabanı üzerinde loglarımızı tutuyor olacağız. Şimdi uygulama üzerinde ‘nlog.config’ isimli dosyayı root dizinine oluşturalım ve yapılandırmasını aşağıdaki gibi gerçekleştirelim;
<?xml version="1.0" encoding="utf-8" ?> <!-- XSD manual extracted from package NLog.Schema: https://www.nuget.org/packages/NLog.Schema--> <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xsi:schemaLocation="NLog NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true" internalLogFile="c:\temp\console-example-internal.log" internalLogLevel="Info" > <!-- the targets to write to --> <targets> <!-- write logs to file --> <target xsi:type="Database" name="database" keepConnection="true" dbProvider="System.Data.SqlClient" commandType="Text"> <connectionString> Server=localhost, 1433;Database=TraceabilityDB;User ID=***;Password=***;TrustServerCertificate=True </connectionString> <commandText> INSERT INTO TraceabilityLogs (EventDateTime, EventLevel, CorrelationId, MachineName, EventMessage, ErrorSource, ErrorClass, ErrorMethod, ErrorMessage, InnerErrorMessage) VALUES (@EventDateTime, @EventLevel, @CorrelationId, @MachineName, @EventMessage, @ErrorSource, @ErrorClass, @ErrorMethod, @ErrorMessage, @InnerErrorMessage) </commandText> <!-- parameters for the command --> <parameter name="@EventDateTime" layout="${date:s}" /> <parameter name="@EventLevel" layout="${level}" /> <parameter name="@CorrelationId" layout="${activityid}"/> <parameter name="@MachineName" layout="${machinename}" /> <parameter name="@EventMessage" layout="${message}" /> <parameter name="@ErrorSource" layout="${event-context:item=error-source}" /> <parameter name="@ErrorClass" layout="${event-context:item=error-class}" /> <parameter name="@ErrorMethod" layout="${event-context:item=error-method}" /> <parameter name="@ErrorMessage" layout="${event-context:item=error-message}" /> <parameter name="@InnerErrorMessage" layout="${event-context:item=inner-error-message}" /> </target> </targets> <!-- rules to map from logger name to target --> <rules> <logger name="*" minlevel="Debug" writeTo="database" /> </rules> </nlog>
Yukarıdaki yapılandırma dosyasına göz atarsanız loglamanın hangi sunucuya yapılacağı konfigüre edilmekte ve 46 ile 56. satır aralığındaki parametreler eşliğinde yakalanan verilerin, 21 ile 44. satır aralığında commandText ile veritabanına insert edilmesi gerçekleştirilmektedir.
Ayrıca, 46 ile 56. satır aralığındaki parametrelere de şöyle bi göz attığımız da sunucu adından tutun da correlation id değerine kadar traceability sürecinde ihtiyacımız olabilecek tüm değerler tanımlanmış bulunmaktadır. Burada dikkat ederseniz, 49. satırdaki ‘CorrelationId’, değerini activityid parametresinden almaktadır. Bu parametre NLog kütüphanesinin log mesajlarını trace edebilmek için bizlere sunduğu ekstra bir özelliktir ve bu özellik neticesinde activityid parametresi, correlation id değerini System.Diagnostics namespace’i altındaki Trace.CorrelationManager.ActivityId property’sinden edinmektedir.
Ek olarak 6. satırdaki internalLogFile
özelliğinden de bahsetmek istiyorum. Bu özellik, NLog kütüphanesi tarafından üretilen hata ve uyarı mesajlarını kontrol edebilmemiz için kendince tuttuğu logların konumunu ifade etmektedir. Eğer ki, olası bir veritabanı kesintisinden yahut konfigürasyonel hatadan kaynaklı kütüphanenin sağlıklı çalışmadığını görürseniz bu özelliğe verilen konumdaki log dosyası üzerinden duruma dair detaylı bilgi edinebilir ve hızlıca çözüm için aksiyon alabilirsiniz.
Velhasıl,
Yapılandırma dosyasını uygulamada kullanabilmek için ‘.csproj’ uzantılı dosyada aşağıdaki gibi tanımda bulunulması gerekmektedir.
<Project Sdk="Microsoft.NET.Sdk"> . . . <ItemGroup> <None Update="nlog.config" CopyToOutputDirectory="Always" /> </ItemGroup> </Project>
Bu işlemin ardından artık bu yapılandırmada connection string’te belirtilen veritabanı ve bu veritabanı içerisinde de parametrelere karşılık kolonları barındıran tabloyu oluşturmamız gerekmektedir. Bunun için aşağıdaki script’ten istifade edebilirsiniz.
CREATE DATABASE TraceabilityDB GO USE TraceabilityDB GO CREATE TABLE [dbo].[TraceabilityLogs] ( [Id] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY, [EventDateTime] [datetime] NOT NULL, [EventLevel] [nvarchar](100) NOT NULL, [CorrelationId] [uniqueidentifier] NOT NULL, [MachineName] [nvarchar](100) NOT NULL, [EventMessage] [nvarchar](max) NOT NULL, [ErrorSource] [nvarchar](100) NULL, [ErrorClass] [nvarchar](500) NULL, [ErrorMethod] [nvarchar](max) NULL, [ErrorMessage] [nvarchar](max) NULL, [InnerErrorMessage] [nvarchar](max) NULL )
Tüm bu çalışmalardan sonra artık ‘Program.cs’de aşağıdaki gibi basit bir program yazarak traceability’i gözlemleyebiliriz.
using NLog; Logger _logger = LogManager.GetCurrentClassLogger(); System.Diagnostics.Trace.CorrelationManager.ActivityId = Guid.NewGuid(); Work1(); void Work1() { _logger.Debug("Work1 metodu çalıştırıldı..."); Work2(); } void Work2() { _logger.Debug("Work2 metodu çalıştırıldı..."); Work3(); } void Work3() { _logger.Debug("Work3 metodu çalıştırıldı..."); }
Yukarıdaki kod bloğunu incelerseniz eğer görüldüğü üzere iç içe sequentially olarak ilerleyen 3 adet metot üzerinden gerekli testi gerçekleştirmekteyiz. Burada NLog kütüphanesinin correlation id’yi ‘ActivityId’den alabilmesi için System.Diagnostics.Trace.CorrelationManager.ActivityId
property’sine bir guid değer üretip set etmemiz yeterli olacaktır. Bu aşamadan sonra ‘Work1’ metoduna yapılan call neticesinde, programın sonuna kadar geçtiği tüm aşamalardaki durumları takip edecek ve bunu ‘ActivityId’ üzerinden ilişkilendirerek gözlemleyebiliyor olacağız.
Uygulamayı derleyip, çalıştırdığımızda aşağıdaki gibi neticeyle karşılaşacağız.Evet, görüldüğü üzere program çalıştığı süreçte atılan loglar genel anlamda uygulamanın verisel işlem sürecini bizlere özetleyen ve bunu da correlation id üzerinden bir bütün olarak ilişkilendirerek sunan görüntüyle karşılaşmış bulunuyoruz…
Console uygulamasında işte bu kadar 🙂
Şimdi benzer mantığı biraz daha komplike bir yapı olan distributed sistem üzerinden örneklendirelim. Bunun için de Asp.NET Core mimarisinden istifade edeceğiz.
Asp.NET Core Uygulamasında Traceability
Asp.NET Core uygulamalarında correlation id ile traceability’i sağlayabilmek için mantıken aşağıdaki sıralamada bir çalışma sergilenmesi gerekmektedir;
- Correlation ID Oluşturma ve İletme
Asp.NET Core uygulamasına gelen HTTP request’lerini işleyecek olan bir middleware oluşturunuz ve ardından bu middleware içerisinde her request’e bir correlation id ekleyiniz ya da varsa bir id onunla ilişkilendiriniz. Bu ilişkilendirmeyi request’in header’ındaX-Correlation-ID
gibi bir key’e karşılık gerçekleştirebilirsiniz. - Correlation ID’nin İşlem Zinciri Boyunca İletilmesi
Uygulama sürecinde gelen request’in işleneceği bileşenler arasında correlation id’yi taşıyabilmek için de yine bu middleware’den istifade edebilirsiniz. Buradaki mantık, request neticesinde gelen correlation id’yi alıp, bunu bir sonraki bileşen tarafından erişilebilir olması için iletilebilir kılmaktır. - Correlation ID’nin Log Kayıtlarında ve Response’da Kullanılması
Correlation ID eşliğinde logları atınız. Böylece bir request’e karşılık tüm logları ilişkisel bir şekilde ifade edebilir ve kümülatif olarak süreci izlenebilir kılmış olacaksınız. Benzer şekilde response’larla correlation id’yi de ilişkilendirerek request’lerin response süreçlerini de bu bütünselliğe katabilir ve daha geniş yelpazede bir izlenebilirlik ortaya koyabilirsiniz.
Şimdi gelin tüm bu süreci pratik olarak tecrübe edelim.
Öncelikle bir Asp.NET Core uygulamasına aşağıdaki kütüphaneleri yükleyerek başlayalım;
dotnet add package NLog
dotnet add package NLog.Database
dotnet add package NLog.Web.AspNetCore
dotnet add package System.Data.SqlClient
Asp.NET Core uygulamasında da tüm traceability sürecini NLog ile takip ediyor olacağız. Ee haliyle yukarıdaki console uygulamasında yaptığımız gibi uygulamanın root dizininde ‘nlog.config’ isimli bir dosyayı oluşturalım ve içeriğini aşağıdaki gibi şekillendirelim.
<?xml version="1.0" encoding="utf-8" ?> <!-- XSD manual extracted from package NLog.Schema: https://www.nuget.org/packages/NLog.Schema--> <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xsi:schemaLocation="NLog NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true" internalLogFile="c:\temp\console-example-internal.log" internalLogLevel="Info" > <!-- the targets to write to --> <targets> <!-- write logs to file --> <target xsi:type="Database" name="database" keepConnection="true" dbProvider="System.Data.SqlClient" commandType="Text"> <connectionString> Server=localhost, 1433;Database=TraceabilityDB;User ID=SA;Password=1q2w3e4r+!;TrustServerCertificate=True </connectionString> <commandText> INSERT INTO TraceabilityLogs (EventDateTime, EventLevel, CorrelationId, MachineName, EventMessage, ErrorSource, ErrorClass, ErrorMethod, ErrorMessage, InnerErrorMessage) VALUES (@EventDateTime, @EventLevel, @CorrelationId, @MachineName, @EventMessage, @ErrorSource, @ErrorClass, @ErrorMethod, @ErrorMessage, @InnerErrorMessage) </commandText> <!-- parameters for the command --> <parameter name="@EventDateTime" layout="${date:s}" /> <parameter name="@EventLevel" layout="${level}" /> <parameter name="@CorrelationId" layout="${mdc:item=CorrelationId}"/> <parameter name="@MachineName" layout="${machinename}" /> <parameter name="@EventMessage" layout="${message}" /> <parameter name="@ErrorSource" layout="${event-context:item=error-source}" /> <parameter name="@ErrorClass" layout="${event-context:item=error-class}" /> <parameter name="@ErrorMethod" layout="${event-context:item=error-method}" /> <parameter name="@ErrorMessage" layout="${event-context:item=error-message}" /> <parameter name="@InnerErrorMessage" layout="${event-context:item=inner-error-message}" /> </target> </targets> <!-- rules to map from logger name to target --> <rules> <logger name="*" minlevel="Debug" writeTo="database" mdc="true" /> </rules> </nlog>
Bu sefer yukarıdaki yapılandırma dosyasında bir noktaya dikkatinizi çekmek istiyorum. Asp.NET Core uygulamasında NLog kütüphanesini kullanarak correlation id üzerinden traceability’i sağlamaya çalışıyorsanız bu değer console uygulamalarında ki gibi ‘activityid’ üzerinden gelmeyecektir. Artık mimariye uygun bir şekilde bu değeri özel bir formatla eşleştirmeniz gerekecektir. Bunun için 49. satıra göz atarsanız eğer correlation id parametresinin ‘layout’ değerine ${mdc:item=CorrelationId}
şeklinde bir tanımlamada bulunulmuştur. Bu tanımlama MDC(Mapped Diagnostics Context) denen özelleştirilmiş bir parametre değerini ifade etmektedir ve birazdan kod üzerinden bu parametrenin değerini veriyor olacağız. Ayrıca bu konuya bağlı olarak NLog’da MDC kullanabilmek için 62. satırda olduğu gibi <logger ... mdc="true" />
ifadesi eşliğinde MDC’nin aktifleştirilmesi gerekmektedir.
Şimdi de uygulamada NLog entegrasyonunu gerçekleştirelim;
var builder = WebApplication.CreateBuilder(args); . . . #region NLog Setup builder.Logging.ClearProviders(); builder.Host.UseNLog(); #endregion var app = builder.Build(); . . . app.Run();
Log yapılandırmasından sonra correlation id’yi oluşturmaktan ve iletmekten sorumlu olacak olan middleware’i aşağıdaki gibi oluşturalım.
public class CorrelationIdMiddleware { private const string correlationIdHeaderKey = "X-Correlation-ID"; private readonly RequestDelegate _next; public CorrelationIdMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext context, ILogger<CorrelationIdMiddleware> _logger) { //Request'e karşılık yeni bir correlation id üretiliyor. string correlationId = Guid.NewGuid().ToString(); //Request'in header'ını kontrol ediliyor eğer ki isteğin correlation id'si varsa elde ediliyor, yoksa üretilen correlation id değeri header'a ekleniyor. if (context.Request.Headers.TryGetValue(correlationIdHeaderKey, out StringValues _correlationId)) correlationId = _correlationId.ToString(); else context.Request.Headers.Add(correlationIdHeaderKey, correlationId); //nlog.config dosyasındaki Mapped Diagnostics Context (MDC) olan @CorrelationId parametresine değeri set ediliyor. NLog.MappedDiagnosticsContext.Set("CorrelationId", correlationId); //Correlation Id üzerinden kümülatif log atılıyor. _logger.LogDebug("Asp.NET Core App. Log Example"); //Bütünsel bir izlenebilirlik kazandırabilmek için correlation id response'la da ilişkilendiriliyor. context.Response.OnStarting(() => { if (!context.Response.Headers.TryGetValue(correlationIdHeaderKey, out _)) context.Response.Headers.Add(correlationIdHeaderKey, correlationId); return Task.CompletedTask; }); //Correlation ID sonraki bileşene aktarılıyor. context.Items["CorrelationId"] = correlationId; await _next(context); } }
Bu middleware’i detaylandırırsak eğer; uygulamaya gelen her request’te yeni bir correlation id oluşturularak header’a yerleştirilmekte yahut gelen request’in header’ın da varsa o değer kullanılmaktadır. Ardından bu correlation id eşliğinde loglama gerçekleştirilmektedir ve bunun için 23. satırdaki NLog.MappedDiagnosticsContext.Set("CorrelationId", correlationId);
komutu eşliğinde NLog kütüphanesindeki ‘CorrelationId’ parametresine değeri gönderilmektedir. Burada 29 ile 34. satır aralığında ilgili correlation id response header’ına da eklenerek, dış dünyaya taşınmakta ve böylece client tarafından da kullanılabilir hale getirilmektedir. Bu sayede correlation id, daha geniş perspektiften izlenebilirliği sağlayacak ve böylece de hata ayıklama süreçleri, değerlendirilebilecek detay veri bolluğu sayesinde olabildiğince daha kolaylaştırılmış olacaktır.
Son olarak, 37. satırda context.Items["CorrelationId"] = correlationId;
komutu eşliğinde request işlem sürecini farklı bileşenler veya katmanlar arasında paylaşabilmek için correlation id’yi context üzerine geçici olacak şekilde depolamış bulunuyoruz. Bu işlem sayesinde correlation id’ye farklı middleware’ler üzerinden yahut controller gibi sınıflardan rahatlıkla erişebilir ve kullanabiliriz.
Bunu hemen şöyle örneklendirelim istiyorum;
Farklı bir middleware’den correlation id’ye erişmek istiyorsanız aşağıdaki gibi yine request’in header’ından okuyabilir ya da context.Items
üzerinden de hızlıca elde edebilirsiniz.
public class OtherMiddleware { private readonly RequestDelegate _next; public OtherMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext context, ILogger<OtherMiddleware> _logger) { var correlationId = context.Request.Headers["X-Correlation-ID"].FirstOrDefault(); //ya da correlationId = context.Items["CorrelationId"].ToString(); NLog.MappedDiagnosticsContext.Set("CorrelationId", correlationId); _logger.LogDebug("OtherMiddleware Log"); await _next(context); } }
Aynı işlemi controller için de aşağıdaki gibi yürütebilirsiniz.
[Route("api/[controller]")] [ApiController] public class ValuesController : ControllerBase { readonly ILogger<ValuesController> _logger; public ValuesController(ILogger<ValuesController> logger) { _logger = logger; } [HttpGet] public async Task Get() { var correlationId = HttpContext.Request.Headers["X-Correlation-ID"].FirstOrDefault(); //ya da correlationId = HttpContext.Items["CorrelationId"].ToString(); NLog.MappedDiagnosticsContext.Set("CorrelationId", correlationId); _logger.LogDebug("ValuesController Log"); } }
İşte bu kadar… Yazdığımız CorrelationIdMiddleware
isimli middleware’i uygulamada kullanabilmek için aşağıdaki extension metottan istifade edebilir;
public static class CorrelationIdMiddlewareExtensions { public static IApplicationBuilder UseCorrelationIdMiddleware(this IApplicationBuilder app) { return app.UseMiddleware<CorrelationIdMiddleware>(); } }
ve uygulamaya şöyle dahil edebiliriz;
var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); . . . var app = builder.Build(); app.UseCorrelationIdMiddleware(); app.UseMiddleware<OtherMiddleware>(); . . . app.Run();
Tüm bu çalışmalardan sonra uygulamayı ayağa kaldırıp, bir yandan da controller’a istek gönderirseniz veritabanında aşağıdaki gibi correlation id eşliğinde logların tutulduğunu gözlemleyebilirsiniz.Bizlerin burada tek bir Asp.NET Core uygulaması üzerinde simüle ettiği bu davranış, birbirleriyle etkileşimde olan birden fazla servisin söz konusu olduğu durumlarda da geçerli olacak ve request’in header’ı üzerinde taşınan correlation id sayesinde distributed yaklaşım sergilenen mimarilerde de bütünsel loglama gerçekleştirilmiş olacaktır.
Peki, iyi hoş güzel de bu traceability yaklaşımını message broker kullanan distributed servislerde uygulamak isteseydik nasıl yapacaktık la hoca? şeklinde sorunuzu duyar gibiyim… Evet, haklısınız. Tasarladığımız her mimari HTTP protokolü üzerinden haberleşmiyor. Hatta distributed mimari dendiğinde ilk olarak akla message broker yapılanması geliyor. Şimdi akıllara gelen bu suali de cevaplandırıp içeriğimizi öyle sonlandıralım istiyorum.
Message Broker Kullanılan Senaryolarda Traceability
Konunun dışına çıkmamak ve içeriği haddinden fazla şişirmemek için direkt kod odaklı bir seyirde bulunalım istiyorum. Eğer ki, message broker’lara dair bilginiz yoksa blog’umda ki RabbitMQ Yazı Dizisi‘ni baştan sona okuyabilir ya da youtube kanalımdaki A’dan Z’ye RabbitMQ Eğitimi‘ni izleyebilirsiniz.
Burada bizler örneklendirmeyi ‘Consumer’ ve ‘Publisher’ olmak üzere iki servis üzerinden gerçekleştireceğiz, sizler ise bunu tüm distributed yaklaşımını sergilediğiniz operasyonlara genelleyeceksiniz.
Publisher;
using MassTransit; using NLog; using Traceability.Example.MessageBrokers.Shared.Messages; string rabbitMQUri = "amqps://befjdvjy:dBfEg0GaJyIWuF1Yxd8J0z9CcOsErzk6@moose.rmq.cloudamqp.com/befjdvjy"; string queueName = "example-queue"; IBusControl bus = Bus.Factory.CreateUsingRabbitMq(factory => { factory.Host(rabbitMQUri); }); var correlationId = Guid.NewGuid(); Logger _logger = LogManager.GetCurrentClassLogger(); System.Diagnostics.Trace.CorrelationManager.ActivityId = correlationId; _logger.Debug("Publisher log"); ISendEndpoint sendEndpoint = await bus.GetSendEndpoint(new($"{rabbitMQUri}/{queueName}")); Console.Write("Gönderilecek mesaj : "); string message = Console.ReadLine(); await sendEndpoint.Send<IMessage>(new ExampleMessage() { Text = message }, context => { context.Headers.Set("CorrelationId", correlationId); }); Console.Read();
Consumer;
using MassTransit; using Traceability.Example.MessageBrokers.Consumer.Consumers; string rabbitMQUri = "amqps://befjdvjy:dBfEg0GaJyIWuF1Yxd8J0z9CcOsErzk6@moose.rmq.cloudamqp.com/befjdvjy"; string queueName = "example-queue"; IBusControl bus = Bus.Factory.CreateUsingRabbitMq(factory => { factory.Host(rabbitMQUri); factory.ReceiveEndpoint(queueName, endpoint => { endpoint.Consumer<ExampleMessageConsumer>(); }); }); await bus.StartAsync(); Console.Read();
public class ExampleMessageConsumer : IConsumer<IMessage> { public Task Consume(ConsumeContext<IMessage> context) { Console.WriteLine($"Gelen mesaj : {context.Message.Text}"); var correlationId = Guid.NewGuid(); if (context.Headers.TryGetHeader("CorrelationId", out object _correlationId)) correlationId = Guid.Parse(_correlationId.ToString()); Logger _logger = LogManager.GetCurrentClassLogger(); System.Diagnostics.Trace.CorrelationManager.ActivityId = correlationId; _logger.Debug("Consumer log"); return Task.CompletedTask; } }
Yukarıdaki çalışmayı incelerseniz eğer, ‘Publisher’ tarafından message broker’a gönderilen mesajın header’ına correlation id verilmekte ve aynı şekilde ‘Consumer’ tarafında da bu değer yine header üzerinden elde edilerek gerekli loglama işlemleri gerçekleştirilmektedir. Bu örnekle birlikte makale sürecindeki tüm çalışmaların detaylarını github’ta ki kaynaktan titizce gözlemlemenizi tavsiye ediyorum. Velhasıl, yukarıdaki dağıtık mimariyi çalıştırma neticesinde veritabanına yansıtılan log aşağıdaki gibi olacaktır.
Nihai olarak;
Bir mimarinin inşa edilmesinden ziyade o mimarinin inşadan sonraki işleyiş sürecindeki takibi biz yazılım geliştiricileri için oldukça önem arz etmektedir. Çünkü geliştirme sürecinde öngöremediğimiz tüm hatalar, süreçte son kullanıcılar tarafından tecrübe edildikleri taktirde surlardaki gedikler misali onarılması gereken hayati durumları ifade etmektedirler. Bizlerin bu hatalardan bilgi sahibi olması ve daha da önemlisi hatalara dair nerede, ne zaman, hangi sunucuda, hangi aşamada, hangi bileşende vs. gibi detaylı bilgilere sahip olması son derece kritik önem göstermektedir. İşte log mekanizması ile traceability’nin önemi bu noktada karşımıza çıkmaktadır.
Bir örümceğin ağındaki ufak bir hasar, örümceğin beslenmesi için nasıl ki bir önem arz ediyorsa, uygulamalarımızdaki olası hatalarda uygulamanın kalitesi ve son kullanıcının gözündeki değeri için o kadar önem arz etmektedir. Ve örümcek ağındaki ufak bir hasara anında müdahale edebilecek refleksin altyapısını geliştirmişse işte bizler de uygulamalarımızdaki hatalara ve kesintilere müdahale edebilecek altyapıyı geliştirmeli ve bunun için hem operasyonel süreçlerde hem de servisler arasındaki akış ve bileşen aktarımlarında, yani uygulamanın her noktasında refleksif ağımızı kurmalı ve en az bir örümcek kadar hızlı aksiyon alarak hasarları onarmalıyız…
İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…
Not : Örnek çalışmaya aşağıdaki github adresinden erişebilirsiniz.
https://github.com/gncyyldz/Traceability.Example