Bu makalede, günlüğe kaydetme endişelerinin ve kodun altyapı ve iş kodundan nasıl ayrılabileceğini ve ayrıştırılabileceğini incelemeyi sonlandıracağız. Önceki makalede , altyapı operasyonlarında bunu başarmak için DiagnosticSource ve DiagnosticListener'ın nasıl kullanılacağını inceledik.
Şimdi, günlüğe kaydedilen verilerin ek bağlamla nasıl zenginleştirileceğini inceleyeceğiz.
Temel olarak, günlüğe kaydedilen verileri zenginleştirme kavramı, günlük mesajlarıyla birlikte gitmesi gereken ek bağlamın veya verilerin kaydedilmesi etrafında döner. Bu bağlam, basit bir diziden karmaşık bir nesneye kadar herhangi bir şey olabilir ve günlük mesajlarının yazıldığı anda değil, altyapı veya iş kodu içinde bir anda üretilir veya bilinir.
Dolayısıyla, bu kod herhangi bir günlük çıktısı üretmediğinden veya bağlamın, yürütme zincirinde üretilebilen veya üretilmeyen birden fazla günlük iletisine eklenmesini istediğimizden, günlük iletilerine başka bir özellik ekleyemeyiz.
Bu bağlam koşullu bile olabilir; örneğin belirli verileri yalnızca hata günlüğü mesajlarına eklerken diğer tüm mesajların buna ihtiyacı yoktur.
Serilog'u çok esnek ve güçlü hale getirdiği için Serilog'u ve onun zenginleştirme özelliğini kullanacağız. Diğer çözümler de farklı olgunluk seviyelerinde benzer özelliklere sahiptir ve Serilog'un zenginleşmesini Microsoft.Extensions.Logging'in kutudan çıktıklarında sağladığı şeylerle karşılaştıracağız.
Serilog, bazı senaryolar için çok kullanışlı olabilecek birçok faydalı zenginleştiriciyle birlikte gelir. Zenginleştiricilerin tam listesini ve açıklamalarını görmek için bu sayfayı - https://github.com/serilog/serilog/wiki/Enrichment - ziyaret edebilirsiniz.
Örneğin, eşleşen bir kapsamda yazılmaları durumunda günlük mesajlarıyla birlikte günlüğe kaydedilmek üzere fazladan veri gönderilmesine olanak tanıyan LogContext ve GlobalLogContext zenginleştiricileri vardır.
LogContext zenginleştirici, Microsoft.Extensions.Logging'deki günlüğe kaydetme kapsamları kavramına çok benzer. Temel olarak, her ikisi de bazı özel verileri aktarır ve verileri günlük bağlamından kaldırmak için atılması gereken bir IDisposable nesnesi sağlar.
Yani, IDisposable nesnesi kapsam dahilinde olduğu sürece veriler, bu kapsamda yazılan tüm günlük iletilerine eklenecektir. Veriler imha edildiğinde artık eklenmeyecektir.
Serilog ve Microsoft belgeleri şu örnekleri sağlar:
// For Serilog log.Information("No contextual properties"); using (LogContext.PushProperty("A", 1)) { log.Information("Carries property A = 1"); using (LogContext.PushProperty("A", 2)) using (LogContext.PushProperty("B", 1)) { log.Information("Carries A = 2 and B = 1"); } log.Information("Carries property A = 1, again"); } // For Microsoft.Extensions.Logging scopes using (logger.BeginScope(new List<KeyValuePair<string, object>> { new KeyValuePair<string, object>("TransactionId", transactionId), })) { _logger.LogInformation(MyLogEvents.GetItem, "Getting item {Id}", id); todoItem = await _context.TodoItems.FindAsync(id); if (todoItem == null) { _logger.LogWarning(MyLogEvents.GetItemNotFound, "Get({Id}) NOT FOUND", id); return NotFound(); } }
Bazı senaryolar için yararlı olsa da oldukça sınırlıdırlar. Bu tür günlük zenginleştirmenin en iyi kullanımı, kapsamın iyi tanımlandığı, verilerin kapsam oluşturma anında bilindiği ve verilerin belirtilenler dışında hiçbir değere sahip olmadığından emin olduğumuz ara katman yazılımı veya dekoratör türü uygulamalardır. kapsam.
Örneğin, bunu tek bir HTTP istek işleme kapsamındaki tüm günlük mesajlarına bir korelasyon kimliği eklemek için kullanabiliriz.
GlobalLogContext zenginleştirici, LogContext'e benzer, ancak geneldir; verileri bir uygulama içinde yazılan tüm günlük iletilerine iletir. Ancak bu tür zenginleştirmenin gerçek kullanım durumları çok sınırlıdır.
Aslına bakılırsa, Serilog için özel günlük zenginleştiricileri uygulamak çok kolaydır - sadece ILogEventEnricher
arayüzünü uygulayın ve zenginleştiriciyi günlükçü yapılandırmasına kaydedin. Arayüzün uygulayacağı tek bir yöntem vardır - Enrich
- log olayını kabul eder ve onu istediğiniz verilerle zenginleştirir.
Özel bir günlük zenginleştiriciye yönelik örnek uygulamayı inceleyelim.
public sealed class CustomLogEnricher : ILogEventEnricher { private readonly IHttpContextAccessor contextAccessor; public CustomLogEnricher(IHttpContextAccessor contextAccessor) { this.contextAccessor = contextAccessor; } public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) { var source = contextAccessor.HttpContext?.RequestServices.GetService<ICustomLogEnricherSource>(); if (source is null) { return; } var loggedProperties = source.GetCustomProperties(logEvent.Level); foreach (var item in loggedProperties) { var property = item.ProduceProperty(propertyFactory); logEvent.AddOrUpdateProperty(property); } } }
Gördüğümüz gibi bu zenginleştirici, log olayını zenginleştirecek özel özellikleri elde etmek için bir ICustomLogEnricherSource
dayanır. Normalde günlük zenginleştiriciler uzun ömürlü örneklerdir ve hatta Singleton modelini takip ederler; uygulama başlangıcında bir kez kaydedilirler ve tüm uygulama ömrü boyunca yaşarlar.
Bu nedenle, zenginleştiriciyi özel özelliklerin kaynağından ayırmamız gerekir ve kaynak, sınırlı bir ömre sahip kapsamlı bir örnek olabilir.
public sealed class CustomLogEnricherSource : ICustomLogEnricherSource { private readonly Dictionary<string, CustomLogEventProperty> properties = new(); public ICustomLogEnricherSource With<T>(string property, T? value, LogEventLevel logLevel) where T : class { if (value is null) { return this; } properties[property] = new CustomLogEventProperty(property, value, logLevel); return this; } public void Remove(string property) { properties.Remove(property); } public IEnumerable<CustomLogEventProperty> GetCustomProperties(LogEventLevel logLevel) { foreach (var item in properties.Values) { if (item.Level <= logLevel) { yield return item; } } } } // And CustomLogEventProperty is a simple struct (you can make it a class, too): public struct CustomLogEventProperty { private LogEventProperty? propertyValue; public CustomLogEventProperty(string property, object value, LogEventLevel level) { Name = property; Value = value; Level = level; } public string Name { get; } public object Value { get; } public LogEventLevel Level { get; } public LogEventProperty ProduceProperty(ILogEventPropertyFactory propertyFactory) { propertyValue ??= propertyFactory.CreateProperty(Name, Value, destructureObjects: true); return propertyValue; } }
Bu uygulamanın birkaç basit ayrıntısı vardır:
ICustomLogEnricherSource
örneklerinin bir listesi kullanılarak yapılabilir.
ICustomLogEnricherSource
), günlük iletilerini özel özelliklerle zenginleştirmesi gereken herhangi bir bileşene eklenebilir. İdeal olarak, özel özelliklerin süresinin dolması için uygun bir yol sağlamadığından kapsamlı bir örnek olmalıdır.
LogEvent
özelliklerine, hatta uygulama durumuna bağlı olabilir.
CustomLogEventProperty
birden fazla günlük iletisine eklenebileceğinden, aynı özel özellik için birden çok kez oluşturulmasını önlemek amacıyla üretilmiş bir LogEventProperty
örneğini önbelleğe alır.
Bu kod satırındaki bir diğer önemli ayrıntı da destructureObjects
parametresidir:
propertyFactory.CreateProperty(Name, Value, destructureObjects: true);
Karmaşık nesneler (sınıflar, kayıtlar, yapılar, koleksiyonlar vb.) eklediğimizde ve bu parametre true
olarak ayarlanmadığında, Serilog nesne üzerinde ToString
çağıracak ve sonucu log mesajına ekleyecektir. Kayıtların tüm ortak özelliklerin çıktısını alacak bir ToString
uygulaması olmasına rağmen sınıflarımız ve yapılarımız için ToString
geçersiz kılabiliriz, ancak durum her zaman böyle değildir.
Ayrıca böyle bir çıktı, basit bir dize olacağından ve eklenen nesnenin özelliklerine göre günlük mesajlarını arayıp filtreleyemeyeceğimizden, yapılandırılmış günlük kaydı için iyi sonuç vermez. Bu parametreyi burada true
olarak ayarladık. Basit türler (değer türleri ve dizeler), bu parametrenin her iki değeriyle de gayet iyi sonuç verir.
Bu uygulamanın diğer bir faydası da, özel bir özellik kaynağa kaydedildiğinde, kaldırılana veya kaynak imha edilene kadar tüm günlük mesajları için orada kalmasıdır (bunun kapsamlı bir örnek olması gerektiğini unutmayın). Bazı uygulamaların, özel özelliklerin kullanım ömrü üzerinde daha iyi bir kontrole ihtiyacı olabilir ve bu, farklı yollarla başarılabilir.
Örneğin, belirli CustomLogEventProperty
uygulamaları sağlayarak veya özel özelliğin kaldırılması gerekip gerekmediğini kontrol etmek için bir geri arama sağlayarak.
Bu süre sonu mantığı, özel bir özelliği önceden kontrol etmek ve süresi dolmuşsa onu kaynaktan kaldırmak için GetCustomProperties
yönteminde kullanılabilir.
İdeal olarak, günlüğe kaydetme endişelerini iş ve altyapı koduyla karıştırmak istemeyiz. Bu, iş ve altyapı kodunu günlüğe kaydetmeye özgü kodla 'sarmaya' olanak tanıyan çeşitli dekoratörler, ara yazılımlar ve diğer modellerle başarılabilir.
Ancak bu her zaman mümkün olmayabilir ve bazen iş ve altyapı koduna ICustomLogEnricherSource
gibi bir aracı soyutlamayı enjekte etmek ve günlüğe kaydetme için özel verileri kaydetmesine izin vermek daha uygun olabilir.
Bu şekilde, iş ve altyapı kodunun, günlük kaydına duyarlı bazı kod parçalarıyla karıştırmaya devam ederken, gerçek günlük kaydı hakkında hiçbir şey bilmesine gerek kalmaz.
Her neyse, bu kod çok daha az bağlantılı ve çok daha fazla test edilebilir olacak. Bazı çok sıcak yol senaryolarında herhangi bir performans ve bellek kullanmayacak, işlem yapılmayan bir uygulama için NullLogEnricherSource
gibi bir şeyi bile tanıtabiliriz.
Microsoft'un belirttiği gibi,
Günlüğe kaydetme o kadar hızlı olmalıdır ki, eşzamansız kodun performans maliyetine değmemelidir.
Bu nedenle günlük mesajlarımızı ek bağlamla zenginleştirdiğimizde performans sonuçlarının farkında olmalıyız. Genel olarak konuşursak, Serilog log zenginleştirme uygulaması çok hızlıdır, en azından Microsoft loglama kapsamlarıyla aynıdır, ancak log mesajlarına daha fazla veri eklersek log mesajlarının üretilmesi daha fazla zaman alacaktır.
Ne kadar çok özel özellik eklersek, bunların arasında o kadar karmaşık nesneler bulunur ve bir günlük mesajı oluşturmak için o kadar fazla zaman ve bellek gerekir. Bu nedenle bir geliştiricinin, günlük mesajlarına hangi verileri ekleyeceği, ne zaman ekleyeceği ve ömrünün ne kadar olması gerektiği konusunda çok dikkatli olması gerekir.
Aşağıda hem Serilog günlük zenginleştirmesi hem de Microsoft günlük kapsamları için performansı ve bellek tüketimini gösteren küçük bir kıyaslama sonuçları tablosu bulunmaktadır. Her kıyaslama yöntemi, bunları özel özelliklerle zenginleştirmenin farklı yollarını içeren 20 günlük mesajı üretecektir.
Hiçbir özel özellik eklenmedi:
| Method | Mean | Error | StdDev | Gen0 | Allocated | | SerilogLoggingNoEnrichment | 74.22 us | 1.194 us | 1.116 us | 1.2207 | 21.25 KB | | MicrosoftLoggingNoEnrichment | 72.58 us | 0.733 us | 0.685 us | 1.2207 | 21.25 KB |
Her günlük mesajına eklenen üç dize bulunur:
| Method | Mean | Error | StdDev | Gen0 | Allocated | | SerilogLoggingWithContext | 77.47 us | 0.551 us | 0.516 us | 1.7090 | 28.6 KB | | SerilogLoggingWithEnrichment | 79.89 us | 1.482 us | 2.028 us | 1.7090 | 29.75 KB | | MicrosoftLoggingWithEnrichment | 81.86 us | 1.209 us | 1.131 us | 1.8311 | 31.22 KB |
Her günlük mesajına eklenmiş üç karmaşık nesne (kayıt) bulunur:
| Method | Mean | Error | StdDev | Gen0 | Allocated | | SerilogLoggingWithObjectContext | 108.49 us | 1.193 us | 1.058 us | 5.3711 | 88.18 KB | | SerilogLoggingWithObjectEnrichment | 106.07 us | 0.489 us | 0.409 us | 5.3711 | 89.33 KB | | MicrosoftLoggingWithObjectEnrichment | 99.63 us | 1.655 us | 1.468 us | 6.1035 | 100.28 KB |The
Serilog*Context
yöntemleri LogContext.PushProperty
kullanır, Serilog*Enrichment
yöntemleri makalede verilen özel zenginleştiriciyi ve kaynak uygulamasını kullanırken Microsoft*
yöntemleri günlüğe kaydetme kapsamlarını kullanır.
Çoğu durumda performans ve bellek tüketiminin çok benzer olduğunu ve karmaşık nesnelerle zenginleştirmenin, basit türlerle zenginleştirmeye göre daha maliyetli olduğunu görebiliriz. Bunun tek istisnası, günlük iletilerine kayıt eklediğimizde Microsoft uygulamasıyla günlüğe kaydetmedir.
Bunun nedeni, günlük kapsamlarının karmaşık nesnelerin yapısını bozmaması ve bunları günlük iletilerine eklerken serileştirme için ToString
yöntemini kullanmasıdır. Bu, Microsoft'un uygulamasını biraz daha hızlı hale getirir ancak daha fazla bellek tüketir.
Sınıfları varsayılan ToString
uygulamasıyla kullanırsak, bellek tüketimi çok daha az olur, ancak bu özel nesneler tam olarak nitelenmiş tür adları olarak günlüğe kaydedilir - tamamen işe yaramaz.
Ve her halükarda, Microsoft uygulamasıyla bu nesnelerin özelliklerine göre log mesajlarını yok edemediğimiz için arayabilir ve filtreleyemeyiz.
Bu, Microsoft günlüğe kaydetme kapsamlarının bilmemiz gereken sınırlamasıdır; basit türler iyi bir şekilde günlüğe kaydedilirken ve aranabilir ve filtrelenebilirken, karmaşık nesnelerin yapısı bozulmaz.
Not: Bu makalenin karşılaştırmalı kaynak kodu ve kod örnekleri GitHub deposunda bulunabilir.
Günlüklerin uygun şekilde zenginleştirilmesi, kodun geliştirilmesi ve bakımı için 'yaşam kalitesinin iyileştirilmesidir'. Günlük mesajının oluşturulduğu anda bilinmeyen günlük mesajlarına ek içerik ekleme olanağı sağlar.
Bu bağlam, basit bir diziden karmaşık bir nesneye kadar herhangi bir şey olabilir ve bir noktada altyapı veya iş kodu içinde üretilir veya bilinir.
Günlüğe kaydetme ve gözlemlenebilirlik kodunu altyapı ve iş kodundan ayırmanın ve kodu daha sürdürülebilir, test edilebilir ve okunabilir hale getirmenin bir yoludur.