
Günlük kaydı çok önemli bir geliştirici aracıdır, ancak dağıtılmış sistemler oluştururken uygulamanızın temeline yerleştirilmesi gereken bir taş haline gelir, aksi takdirde mikro hizmetlerin geliştirilmesinin karmaşıklığı hızla zarar görecektir.
.Net Core 3 harika bir özellik ekledi Dolayısıyla uygulamalarınız hizmetler arası iletişim için doğrudan HTTP çağrıları kullanıyorsa bu kullanıma hazır işlevsellikten yararlanabilirsiniz. Bununla birlikte, arka uç mimariniz bir mesaj aracısı (RabbitMQ, Kafka, vb.) aracılığıyla etkileşimi içeriyorsa, o zaman korelasyon bağlamını bu mesajlar aracılığıyla kendiniz aktarma konusunda yine de endişelenmeniz gerekir.
Bu yazıda basit bir web-api uygulamasını alıp günlük kaydını düzenleyeceğiz.
İstemciden gelen belirli bir isteğin neden olduğu tüm etkinlikleri kolayca görüntüleyebilmeniz için bağımsız hizmetlerin günlükleri arasında uçtan uca korelasyon sağlayın
Uygun analize sahip tek bir giriş noktasına sahip olun, böylece Destek bile günlük tutma aracını kullanabilir, bu sayede “Uygulamada falan bir istek kimliğiyle hata aldım” gibi sorular kullanılabilir.
Öncelikle uygulamamız için bir kayıt sağlayıcısına karar vermemiz gerekiyor. Modern günlüğe kaydetmenin temel gereksinimi yapıdır; düz metin mesajlarıyla değil, nesnelerle çalışmalıyız. Bu tür loglar sayesinde mesajlarımızın farklı perspektiflerden görünümlerini kolaylıkla oluşturabiliyor ve analiz yapabiliyoruz.
Uygulamamız için, yapılandırılmış günlük kaydı için mükemmel desteğe ve zengin bir eklenti sistemine sahip olan Serilog paketini kullanacağız. Kurulumun temel adımlarını atlayacağım (bu konuyla ilgili çok sayıda makale bulabilirsiniz) ve şu varsayımı yapacağım:
Serilog zaten yapılandırılmıştır ve bağımlılık enjeksiyon sağlayıcınız için varsayılan günlükçüdür
yapılandırması, içerik özellikleriyle mesajların zenginleştirilmesini içerir (Enrich.FromLogContext)
Bir sonraki adım Serilog'dan hangi merkezi log toplama sistemine mesaj gönderileceğini seçmektir. Belki de günümüzde en yaygın açık kaynak seçeneği ELK yığınıdır (Elasticsearch, Logstash ve Kibana), o yüzden onu alalım. Bunu yapmak için şuradaki teklifi kullanacağız: — Ücretsiz bir plana kaydolduktan sonra Lucene arama motorunun tüm gücü elimizde.
Tek yapmamız gereken paketi projemize eklemek.
Install-Package Serilog.Sinks.Logzio
Ve karşılık gelen zenginleştiriciyi kaydedicimizin yapılandırmasına ekleyin ve ona bir erişim jetonu besleyin
LoggerConfiguration loggerConfig = new LoggerConfiguration();
loggerConfig.WriteTo.Logzio(secrets.LogzioToken, 10, TimeSpan.FromSeconds(10), null, LogEventLevel.Debug);
Uygulamayı çalıştırdığımızda mesajlarımızı sadece konsolda değil Kibana'da da gözlemleyebileceğiz.

Arayüzler

Servis tipi bir uygulamada dış dünya ile etkileşimi için iki ana arayüzü ayırt edebiliriz, bunları dikey ve yatay olarak tanımlayalım. Dikey arayüz, istemci uygulamasından gelen çağrıların geldiği bir web API'sidir. Yatay, diğer dahili hizmetlerle veri alışverişinde bulunmak için kullanılan bir mesaj aracısıdır.
Bu arayüzlerin her birine korelasyon eklemenin aşamalarını ele alalım.
HTTP isteklerindeki korelasyon
Mümkün olduğu kadar çok bilgi elde etmek için, aktivitenin başlangıcına mümkün olduğu kadar yakın bir korelasyon tanımlayıcısı oluşturmamız gerekir; ağ geçidinde veya doğrudan istemcide (mobil veya web). Bugün bir arka uç uygulamasıyla uğraştığımız için, web API'sine yapılan tüm isteklerde zorunlu "X-Korelasyon Kimliği" başlığının gerekliliğini basitçe belirteceğiz.
Paket ekleme işlevi ihtiyacımız olan başlıktan değeri almak olan
Install-Package CorrelationID
Bunu istek işleme hattına ekleyelim
public class Startup
{
public void Configure(IApplicationBuilder application)
{
application
.UseCorrelationId(new CorrelationIdOptions
{
Header = "X-Correlation-ID",
IncludeInResponse = false,
UpdateTraceIdentifier = false,
UseGuidForCorrelationId = false
});
}
}
Şimdi bunu basit bir eylem filtresi oluşturmak için kullanacağız:
public sealed class ApiRequestFilter : ActionFilterAttribute
{
public ApiRequestFilter(IApiRequestTracker apiRequestTracker, ICorrelationContextAccessor correlationContextAccessor)
{
_correlationContextAccessor = correlationContextAccessor ?? throw new ArgumentNullException(nameof(correlationContextAccessor));
}
private readonly ICorrelationContextAccessor _correlationContextAccessor;
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (!Guid.TryParse(_correlationContextAccessor.CorrelationContext.CorrelationId, out Guid correlationId))
{
context.Result = new BadRequestResult();
return;
}
await next.Invoke();
}
public override async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
await next.Invoke();
}
}
Ve onu denetleyiciye ekleyin
[Route("[controller]")]
[ApiController]
[ServiceFilter(typeof(ApiRequestFilter))]
public class CarsController : ControllerBase
{
}
Sonuç olarak denetleyici, ilgili tanımlayıcıyı içeren bir başlık olmadan tüm istekler için 400 Hatalı istek görüntüleyecektir.
İstemciden bir tanımlayıcı almaya başladıktan sonra, bunu günlük kaydı bağlamına eklemeliyiz, bunun için bir çerçeveleme katmanı oluşturacağız:
public class CorrelationIdContextLogger
{
public CorrelationIdContextLogger(RequestDelegate next)
{
_next = next ?? throw new ArgumentNullException(nameof(next));
}
readonly RequestDelegate _next;
public async Task InvokeAsync(HttpContext httpContext, ILogger<CorrelationIdContextLogger> logger, ICorrelationContextAccessor correlationContextAccessor)
{
if (Guid.TryParse(correlationContextAccessor.CorrelationContext.CorrelationId, out Guid correlationId))
{
using (logger.BeginScopeWith(("CorrelationId", correlationId)))
{
await _next(context);
}
}
else
{
await _next(context);
}
}
}
Uygulamamızda Microsoft.Extensions.Logging.Abstractions paketindeki standart ILogger'ı kullanıyoruz, dolayısıyla ona basit bir uzantı kullanarak değeri ekleyeceğiz.
public static IDisposable BeginScopeWith(this ILogger logger, params (string key, object value)[] keys)
{
return logger.BeginScope(keys.ToDictionary(x => x.key, x => x.value));
}
İstek işleme hattına bir katman ekliyoruz ve istenen sonucu alıyoruz.
public class Startup
{
public void Configure(IApplicationBuilder application)
{
application.UseMiddleware<CorrelationIdContextLogger>();
}
}
Artık web API'mize yapılan istekler tarafından oluşturulan tüm etkinlikler, kolayca bağlanabilecekleri bir korelasyon tanımlayıcı içerir.

Broker mesajlarındaki korelasyon
Bir sonraki adım, mesaj komisyoncusu aracılığıyla korelasyon tanımlayıcısının iletimini ve alımını ayarlamaktır. Örneğimizde RabbitMQ kullanacağız ve istemci olarak MassTransit çerçevesini alacağız. Yine MassTransit ile çalışmanın ilk kurulumunu atlayalım ve doğrudan günlük kaydı kurulumuna geçelim.
Başlangıç olarak MassTransit'in loglarını dahil edebiliriz; bunun için uygulamamıza bir paket ekleyeceğiz.
Install-Package MassTransit.SerilogIntegration
Artık Logger'ı MassTransit ayarlarına ekledikten sonra framework loglarını görebileceğiz.
services
.AddSingleton(provider =>
{
return Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.UseSerilog();
});
});
Uygulamamızın POST isteğine “done” değerine sahip SomethingDoneMessage olayı göndererek yanıt vermesine izin verin. Böyle bir mesajın sözleşmesi şu şekilde tanımlanabilir:
namespace MbMessages
{
public interface ISomethingDoneMessageV1
{
string Value { get; }
}
}
MassTransit mesajları esasen komisyoncu mesajlarının içine alındığı bir zarftır. Zarf şuna benziyor:
{
"messageId": "59020000-5dba-0015-10b8-08d77ec28593",
"requestId": "59020000-5dba-0015-5674-08d77ec28592",
"conversationId": "59020000-5dba-0015-bca8-08d77ec28594",
"destinationAddress": "rabbitmq://bear.rmq.cloudamqp.com/aelzlsta/ya.servicetemplate.receiveendpoint",
"headers": {},
"messageType": [
"urn:message:MbMessages:ISomethingDoneMessageV1"
],
"message": {
"value": "done"
}
}
Mesaj, çerçevenin çalışması için gerekli olan hizmet alanlarını gösterir, ancak bu zarfa kendi ek özelliklerimizi ekleme olanağımız vardır. Üstelik MassTransit, bazı isteğe bağlı alanlarla çalışmak için yerleşik araçlara sahiptir; bunlardan en ilginci CorrelationId korelasyon tanımlayıcısıdır.
CorlationBy arayüzünü mesaj sözleşmesine ekleyelim:
namespace MbMessages
{
public interface ISomethingDoneMessageV1 : CorrelatedBy<Guid>
{
string Value { get; }
}
}
Bunu uygulayalım ve mesaj oluştururken CorrelationId özelliğine bir değer atayalım:
internal class SomethingDoneMessageV1 : ISomethingDoneMessageV1
{
internal SomethingDoneMessageV1(Guid correlationId, string value)
{
CorrelationId = correlationId;
Value = value;
}
public Guid CorrelationId { get; private set; }
public string Value { get; private set; }
}
Güncellenen mesaja bakarsak, korelasyon tanımlayıcısının yalnızca mesajımızın bir parçası değil, aynı zamanda zarfın da bir parçası haline geldiğini göreceğiz - bu tanımlayıcı artık tüm MassTransit günlüklerinde de kullanılacak, bu da çok daha kolay olacağı anlamına geliyor mesaj komisyoncusu düzeyindeki sorunlarla ilgilenmemiz için.
{
"messageId": "59020000-5dba-0015-10b8-08d77ec28593",
"requestId": "59020000-5dba-0015-5674-08d77ec28592",
"conversationId": "59020000-5dba-0015-bca8-08d77ec28594",
"correlationId": "c7ff562a-b639-415b-9add-c9e524a727cc",
"destinationAddress": "rabbitmq://bear.rmq.cloudamqp.com/aelzlsta/ya.servicetemplate.receiveendpoint",
"headers": {},
"messageType": [
"urn:message:MbMessages:ISomethingDoneMessageV1"
],
"message": {
"correlationId": "c7ff562a-b639-415b-9add-c9e524a727cc",
"value": "Hello"
}
}
Tek yapmamız gereken mesajın bu servis özelliklerinin günlüğünü yapılandırmak; bunun için projeye bir paket ekleyeceğiz . Paket, MassTransit mesaj işleme hattına, mesaj içeriğini iş parçacığı açısından güvenli bir yığına yığan bir filtre ekler. Serilog, yığının içeriğini okur ve bu ek özellikleri günlük nesnelerimize ekler.
Install-Package Serilog.Enrichers.MassTransitMessage
MassTransit'e bir filtre ekliyoruz
services
.AddSingleton(provider =>
{
return Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.UseSerilog();
cfg.UseSerilogMessagePropertiesEnricher();
});
});
Ve Serilog konfigürasyonuna bir zenginleştirici ekliyoruz
Log.Logger = new LoggerConfiguration()
.Enrich.FromMassTransitMessage()
.CreateLogger();
RabbitMQ kuyruğundan mesajı alan uygulama, MassTransit zarfının tüm özelliklerine erişime sahip olduğundan, ortaya çıkan korelasyon tanımlayıcısını tüketen uygulama içinde kullanabilir ve bunu çağrı zinciri boyunca daha da iletebiliriz.
Sonuç olarak, günlüklerimiz CorrelationId'yi yalnızca bir hizmet içinde değil, diğer uygulamalarla etkileşimde bulunurken de içermeye başladı.

Böylece, .Net uygulamalarında ortaya çıkan günlük kaydı sistemi, tamamen farklı mikro hizmetlerden (bir mesaj aracısı aracılığıyla çalışanlar bile) gelen günlükleri herhangi bir sorun olmadan ilişkilendirmemize olanak tanır. Elasticsearch'ün yardımıyla Kibana'da ihtiyaç duyduğumuz kontrol panellerini oluşturarak günlükleri hızlı ve kolay bir şekilde analiz edebiliriz (yazıdaki resimde bir örnek gösterilmektedir).
Elbette, bu forma giriş yapmak, hizmetleriniz ve çeşitli dış sistemler arasındaki karmaşık etkileşimleri kapsamayacaktır, ancak projenin gelişiminin en başında böyle bir düzenin kurulması, kendinize defalarca teşekkür edeceğiniz şeylerden biridir.
Ortaya çıkan sistemin kaynak kodunu projeden anlayabilirsiniz:
Kaynak: habr.com
