
Logimi është një mjet shumë i rëndësishëm për zhvilluesin, por kur krijoni sisteme të shpërndara, ai bëhet një gur themeli që duhet të vendoset drejtpërdrejt në themelin e aplikacionit tuaj, përndryshe kompleksiteti i zhvillimit të mikrosherbimeve do të shfaqet shumë shpejt.
Në .Net Core 3 është shtuar një mundësi e shkëlqyer , prandaj, nëse aplikacionet tuaja përdorin thirrje të drejtpërdrejta HTTP për ndërveprimin midis shërbimeve, ju mund të shfrytëzoni këtë funksionalitet të integruar. Megjithatë, nëse arkitektura e backend-it tuaj parashikon ndërveprimin përmes një brokeri mesazhesh (RabbitMQ, Kafka etj.), atëherë ju ende duhet të shqetësoheni për përcjelljen e kontekstit të korrelacionit përmes këtyre mesazheve vetë.
Në këtë artikull do të marrim një aplikacion të thjeshtë web-API dhe do të organizojmë logimin që do të
ruajë korrelacionin e plotë midis logëve të shërbimeve të pavarura në një mënyrë që të mund të shikoni lehtësisht të gjitha aktivitetet që janë thirrur nga një kërkesë specifike nga klienti.
të kemi një pikë të vetme hyrjeje me një analizë të lehtë, në mënyrë që instrumenti i logimit të mund të përdoret edhe nga Mbështetja, e cila merr pyetje si "kam një gabim në aplikacion me një ID të tillë të kërkesës".
Fillimisht, na nevojitet të vendosim për një ofrues logimi në aplikacionin tonë. Kërkesa kryesore për një logim modern është strukturaliteti, dmth. ne duhet të punojmë jo me mesazhe tekstuale të sheshta, por me objekte. Falë këtyre logëve ne mund të ndihmojmë në ndërtimin e pamjeve të mesazheve tona në mënyra të ndryshme dhe të kryejmë analitikë.
Për aplikacionin tonë do të përdorim paketën Serilog, e cila ka një mbështetje të shkëlqyer për logimin struktural dhe një sistem të pasur shtesash. Unë do të kaloj fazat bazë të konfigurimit të saj (mund të gjeni një numër të madh artikujsh mbi këtë temë) dhe do të supozoj se
Serilog është konfiguruar tashmë dhe është logger-i i parazgjedhur në ofruesin tuaj të injektimit të varësive
në konfigurimin e tij është e përfshirë pasurimi i mesazheve me pronat e kontekstit (Enrich.FromLogContext)
Hapi tjetĂ«r duhet tĂ« zgjidhni nĂ« cilĂ«n sistem tĂ« mbledhjes qendrore tĂ« logĂ«ve do tĂ« dĂ«rgoni mesazhet nga Serilog. NdĂ«r opsionet mĂ« tĂ« njohura tĂ« softuerit tĂ« hapur sot Ă«shtĂ« staku ELK (Elasticsearch, Logstash dhe Kibana), dhe atĂ« do ta pĂ«rdorim. PĂ«r kĂ«tĂ« do tĂ« shfrytĂ«zojmĂ« ofertĂ«n nga â pas regjistrimit nĂ« planin falas, ne kemi nĂ« duar tĂ« gjithĂ« fuqinĂ« e motorit tĂ« kĂ«rkimit Lucene.
Na mbetet të shtojmë në projektin tonë paketën
Instalo-Paket Serilog.Sinks.Logzio
Dhe të shtojmë efekteshën përkatëse në konfigurimin e logerit tonë, duke i dhënë atij tokenin e aksesit
LoggerConfiguration loggerConfig = new LoggerConfiguration();
loggerConfig.WriteTo.Logzio(secrets.LogzioToken, 10, TimeSpan.FromSeconds(10), null, LogEventLevel.Debug);
Pas nisjes së aplikacionit do të mund të shohim mesazhet tona jo vetëm në konsolë, por edhe në Kibana.

Interfacet

Në një aplikacion të tipit shërbim, mund të identifikojmë dy ndërfaqet kryesore të bashkëveprimit me botën e jashtme, do t'i quajmë si ndërfaqe vertikale dhe horizontale. Ndërfaqja vertikale është API e web-it, përmes së cilës vijnë thirrjet nga aplikacioni klient. Ndërfaqja horizontale është një broker mesazhesh që përdoret për shkëmbimin e të dhënave me shërbime të tjera të brendshme.
Le të shqyrtojmë fazat e implementimit të korrelacionit në secilën prej këtyre ndërfaqeve.
Korrelacioni në kërkesat HTTP
Për të marrë sa më shumë informacion, na nevojitet të krijojmë një identifikues korrelacioni sa më afër fillimit të aktiviteteve, dmth. në portën ose direkt në klient (mobil ose web). Pasi që sot po merremi me një aplikacion backend, thjesht do të përcaktojmë në të kërkesën për një heading të detyrueshëm "X-Correlation-ID" në të gjitha kërkesat për API-në e web-it.
Shto paketën , funksioni i së cilës është të marrë vlerën nga headingu i nevojshëm
Instalo-Paket CorrelationID
Do ta shtojmë në procesin e përpunimit të kërkesës
public class Startup
{
public void Configure(IApplicationBuilder application)
{
application
.UseCorrelationId(new CorrelationIdOptions
{
Header = "X-Correlation-ID",
IncludeInResponse = false,
UpdateTraceIdentifier = false,
UseGuidForCorrelationId = false
});
}
}
Tani me ndihmën e tij do të krijojmë një filtrin e thjeshtë të veprimit:
klasa publike e vulosur 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();
}
}
Dhe do ta shtojmë atë në kontrollues
[Route("[controller]")]
[ApiController]
[ServiceFilter(typeof(ApiRequestFilter))]
public class CarsController : ControllerBase
{
}
Si rezultat, kontrolluesi do të japë 400 Bad request për të gjitha kërkesat pa një header me identifikuesin përkatës.
Pasi filluam të merrnim identifikuesin nga klienti, duhet ta shtojmë atë në kontekstin e regjistrimit, do ta bëjmë një ndërmjetësim për këtë:
public class CorrelationIdContextLogger
{
public CorrelationIdContextLogger(RequestDelegate next)
{
_next = next ?? throw new ArgumentNullException(nameof(next));
}
readonly RequestDelegate _next;
public async Task InvokeAsync(HttpContext httpContext, ILogger logger, ICorrelationContextAccessor correlationContextAccessor)
{
if (Guid.TryParse(correlationContextAccessor.CorrelationContext.CorrelationId, out Guid correlationId))
{
using (logger.BeginScopeWith(("CorrelationId", correlationId)))
{
await _next(context);
}
}
else
{
await _next(context);
}
}
}
Në aplikacionin tonë ne përdorim ILogger standard nga paketi Microsoft.Extensions.Logging.Abstractions, kështu që do ta shtojmë vlerën me ndihmën e një zgjerimi të thjeshtë.
public static IDisposable BeginScopeWith(this ILogger logger, params (string key, object value)[] keys)
{
return logger.BeginScope(keys.ToDictionary(x => x.key, x => x.value));
}
Shtojmë ndërmjetësin në pipeline e përpunimit të kërkesave dhe marrim rezultatin e dëshiruar.
public class Startup
{
public void Configure(IApplicationBuilder application)
{
application.UseMiddleware();
}
}
Tani të gjitha aktivitetet që lindin nga kërkesat për API-në tonë të uebit përmbajnë identifikuesin korellues me të cilin mund të lidhen lehtësisht.

Korellimi në mesazhet e brokerit
Hapi i ardhshëm është të arrijmë të transferojmë dhe pranojmë identifikuesin korrelativ përmes nje brokeri të mesazheve. Në shembullin tonë, do të përdorim RabbitMQ, dhe si klient do të marrim frameworkun MassTransit. Sërish, do të anashkalojmë konfigurimin fillestar të MassTransit dhe do të kalojmë direkt në konfigurimin e logimit.
Së pari, mund të aktivizojmë loget e MassTransit, për këtë do të shtojmë në aplikacionin tonë paketën
Install-Package MassTransit.SerilogIntegration
Tani, pasi kemi shtuar logger-in në konfigurimet e MassTransit, do të mund të shikojmë loget e frameworkut.
services
.AddSingleton(provider =>
{
return Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.UseSerilog();
});
});
Le të themi se aplikacioni ynë, si reagim ndaj një POST-requests dërgon një ngjarje SomethingDoneMessage me vlerën "done". Kontrata e një mesazhi të tillë mund të përshkruhet kështu:
namespace MbMessages
{
public interface ISomethingDoneMessageV1
{
string Value { get; }
}
}
Mesazhet e MassTransit, në thelb, janë një konvert që përmban mesazhet e brokerit. Duket pak a shumë kështu:
{
"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"
}
}
Në mesazh janë të dukshme fushat shërbyese, të cilat janë të nevojshme për funksionimin e vetë frameworkut, por ne kemi mundësinë të shtojmë edhe pronësi të tjera shtesë në këtë konvert. Për më tepër, MassTransit ka mjete të integruara për të punuar me disa fusha opsionale, më e rëndësishmja nga të cilat na intereson identifikatori korrelativ CorrelationId.
Shtojmë në kontratën e mesazhit interface-in CorrelatedBy:
namespace MbMessages
{
public interface ISomethingDoneMessageV1 : CorrelatedBy
{
string Value { get; }
}
}
E implementojmë dhe do të caktojmë vlerën e pronës CorrelationId gjatë krijimit të mesazhit:
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; }
}
NĂ«se shikojmĂ« mesazhin e pĂ«rditĂ«suar, do tĂ« shohim se identifikuesi i korrelacionit ka bĂ«rĂ« pjesĂ« jo vetĂ«m nĂ« mesazhin tonĂ«, por edhe nĂ« enĂ« â ky identifikues tani do tĂ« pĂ«rdoret gjithashtu nĂ« tĂ« gjitha log-et e MassTransit-it, qĂ« do tĂ« thotĂ« se do tĂ« na jetĂ« shumĂ« mĂ« e lehtĂ« tĂ« merremi me problemet nĂ« nivelin e brokerit tĂ« mesazheve.
{
"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"
}
}
Na mbetet të konfigurojmë regjistrimin e këtyre pronave shërbimore të mesazhit, për këtë do të shtojmë në projekt paketën . Paketa shton një filtër në procesin e përpunimit të mesazheve MassTransit, i cili ruan kontekstin e mesazhit në një grusht të sigurt për rrjedhë. Serilog lexon kontekstin nga grushti dhe shton këto pronësi shtesë në objektet tona të log-imit.
Install-Package Serilog.Enrichers.MassTransitMessage
NĂ« MassTransit-in vendosim filtrin
services
.AddSingleton(provider =>
{
return Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.UseSerilog();
cfg.UseSerilogMessagePropertiesEnricher();
});
});
Dhe në konfigurimin e Serilog-ut shtojmë enrichërin
Log.Logger = new LoggerConfiguration()
.Enrich.FromMassTransitMessage()
.CreateLogger();
Pasi aplikacioni që merr mesazhin nga radhët RabbitMQ ka akses në të gjitha pronat e enës MassTransit, ne mund ta përdorim identifikuesin e korrelacionit të marrë brenda aplikacionit të konsumatorit, si dhe ta kalojmë atë më tej në të gjithë zinxhirin e thirrjeve.
Si rezultat, log-et tona filluan të përmbajnë CorrelationId jo vetëm brenda një shërbimi, por edhe gjatë ndërveprimit me aplikacione të tjera.

Prandaj, sistemi i regjistrimit tĂ« marrĂ« nĂ« aplikacionet .Net na lejon tĂ« korrelacionim log-et nga mikroshĂ«rbime tĂ« ndryshme â madje edhe ato qĂ« punojnĂ« pĂ«rmes brokerit tĂ« mesazheve. Dhe me ndihmĂ«n e Elasticsearch, ne mund tĂ« analizojmĂ« shpejt dhe lehtĂ« log-et, duke ndĂ«rtuar nĂ« Kibana panelin e nevojshĂ«m pĂ«r ne (shembulli Ă«shtĂ« i ilustruar nĂ« imazhin e postit).
Natyrisht, në këtë formë, regjistrimi nuk do të mbulojë variantet e komplikuara të ndërveprimit midis shërbimeve tuaja dhe sistemeve të jashtme të ndryshme, por vendosja e një rendi të tillë në fillim të zhvillimit të projektit është një nga ato gjëra për të cilat do t'i thoni vetes faleminderit më vonë.
Mund të hidhni një vështrim në kodin burimor të sistemit të krijuar në projektin:
Burimi: habr.com
