
Logging is in heul wichtich ark foar ûntwikkelders, mar by it meitsjen fan ferdielde systemen wurdt it in stien dy't direkt yn 'e stifting fan jo applikaasje lein wurde moat, oars sil de kompleksiteit fan it ûntwikkeljen fan mikrotsjinsten fluch syn tol nimme.
.Net Core 3 hat tafoege in grutte , dus as jo applikaasjes direkte HTTP-oproppen brûke foar kommunikaasje tusken tsjinsten, dan kinne jo profitearje fan dizze out-of-the-box-funksjonaliteit. As jo backend-arsjitektuer lykwols ynteraksje omfettet fia in berjochtmakelaar (RabbitMQ, Kafka, ensfh.), Dan moatte jo noch soargen meitsje oer it trochjaan fan de korrelaasjekontekst troch dizze berjochten sels.
Yn dit artikel sille wy in ienfâldige web-api-applikaasje nimme en logging organisearje, wat sil
hanthavenje ein-oan-ein korrelaasje tusken logs fan ûnôfhinklike tsjinsten, sadat jo maklik kinne besjen alle aktiviteiten dy't waarden feroarsake troch in spesifyk fersyk fan de klant
hawwe ien yngongspunt mei handige analyse, sadat sels Support it logging-ark brûke kin, wêrop fragen lykas "Ik haw in flater yn 'e applikaasje mei sa'n en sa'n fersyk-ID" kinne wurde brûkt
Earst moatte wy beslute oer in logging provider foar ús applikaasje. De wichtichste eask foar moderne houtkap is struktuer, d.w.s. wy moatte net wurkje mei platte tekstberjochten, mar mei objekten. Mei tank oan sokke logs kinne wy maklik werjeften fan ús berjochten bouwe út ferskate perspektiven en analytiken útfiere.
Foar ús applikaasje sille wy it Serilog-pakket brûke, dat poerbêste stipe hat foar strukturearre logging en in ryk tafoegingssysteem. Ik sil de basisstappen fan it ynstellen oerslaan (jo kinne in grut oantal artikels fine oer dit ûnderwerp) en de oanname meitsje dat
Serilog is al konfigurearre en is de standertlogger foar jo provider fan ôfhinklikensynjeksje
syn konfiguraasje omfettet ferriking fan berjochten mei konteksteigenskippen (Enrich.FromLogContext)
De folgjende stap is om te kiezen nei hokker sintralisearre logboeksammelsysteem om berjochten fan Serilog te stjoeren. Miskien is de meast foarkommende iepen boarne-opsje hjoed de ELK-stapel (Elasticsearch, Logstash en Kibana), dus litte wy it nimme. Om dit te dwaan, sille wy it oanbod brûke fan - nei registraasje foar in fergees plan, hawwe wy alle krêft fan 'e Lucene-sykmasjine yn ús hannen.
Alles wat wy hoege te dwaan is it pakket ta te foegjen oan ús projekt
Install-Package Serilog.Sinks.Logzio
En foegje de oerienkommende ferriker ta oan 'e konfiguraasje fan ús logger, fiede it in tagongstoken
LoggerConfiguration loggerConfig = new LoggerConfiguration();
loggerConfig.WriteTo.Logzio(secrets.LogzioToken, 10, TimeSpan.FromSeconds(10), null, LogEventLevel.Debug);
Troch de applikaasje te starten, sille wy ús berjochten net allinich yn 'e konsole kinne observearje, mar ek yn Kibana.

Interfaces

Yn in applikaasje fan tsjinsttype kinne wy twa haadynterfaces ûnderskiede foar har ynteraksje mei de bûtenwrâld, litte wy se oanwize as fertikaal en horizontaal. De fertikale ynterface is in web-API wêrmei petearen fan 'e kliïntapplikaasje oankomme. Horizontaal is in berjochtmakelaar dy't brûkt wurdt om gegevens út te wikseljen mei oare ynterne tsjinsten.
Litte wy de stadia beskôgje fan it ynfieren fan korrelaasje op elk fan dizze ynterfaces.
Korrelaasje yn HTTP-oanfragen
Om safolle mooglik ynformaasje te krijen, moatte wy in korrelaasje-identifikaasje generearje sa ticht mooglik by it begjin fan 'e aktiviteit, d.w.s. op 'e poarte of direkt op' e klant (mobyl of web). Sûnt wy hjoed te meitsjen hawwe mei in backend-applikaasje, sille wy derop gewoan de eask oanjaan foar de ferplichte koptekst "X-Correlation-ID" yn alle oanfragen nei de web API.
It tafoegjen fan in pakket , waans funksje is om de wearde te nimmen fan 'e koptekst dy't wy nedich binne
Install-Package CorrelationID
Litte wy it tafoegje oan 'e pipeline foar ferwurkjen fan fersyk
public class Startup
{
public void Configure(IApplicationBuilder application)
{
application
.UseCorrelationId(new CorrelationIdOptions
{
Header = "X-Correlation-ID",
IncludeInResponse = false,
UpdateTraceIdentifier = false,
UseGuidForCorrelationId = false
});
}
}
No sille wy it brûke om in ienfâldich aksjefilter te meitsjen:
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();
}
}
En foegje it ta oan de controller
[Route("[controller]")]
[ApiController]
[ServiceFilter(typeof(ApiRequestFilter))]
public class CarsController : ControllerBase
{
}
As gefolch, de controller sil werjaan 400 Min fersyk foar alle fersiken sûnder in koptekst mei de byhearrende identifier.
Nei't wy in identifier fan 'e kliïnt begon te ûntfangen, moatte wy it tafoegje oan 'e logkontekst, wy sille in framinglaach meitsje foar dit:
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);
}
}
}
Yn ús applikaasje brûke wy de standert ILogger fan it pakket Microsoft.Extensions.Logging.Abstractions, dus sille wy de wearde tafoegje mei in ienfâldige útwreiding deroan.
public static IDisposable BeginScopeWith(this ILogger logger, params (string key, object value)[] keys)
{
return logger.BeginScope(keys.ToDictionary(x => x.key, x => x.value));
}
Wy foegje in laach ta oan 'e pipeline foar ferwurkjen fan fersyk en krije it winske resultaat.
public class Startup
{
public void Configure(IApplicationBuilder application)
{
application.UseMiddleware<CorrelationIdContextLogger>();
}
}
No befetsje alle aktiviteiten dy't wurde generearre troch oanfragen nei ús web API in korrelaasje-identifikaasje wêrmei se maklik kinne wurde keppele.

Korrelaasje yn brokerberjochten
De folgjende stap is om de oerdracht en ûntfangst fan 'e korrelaasje-identifikaasje yn te stellen fia de berjochtmakelaar. Yn ús foarbyld sille wy RabbitMQ brûke, en it MassTransit-ramt nimme as de klant. Nochris, litte wy de earste opset fan wurkje mei MassTransit oerslaan en direkt gean nei it ynstellen fan logboek.
Om te begjinnen kinne wy de logs fan MassTransit sels opnimme; hjirfoar sille wy in pakket tafoegje oan ús applikaasje
Install-Package MassTransit.SerilogIntegration
No nei it tafoegjen fan de logger oan 'e MassTransit-ynstellingen, kinne wy de ramtlogs sjen.
services
.AddSingleton(provider =>
{
return Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.UseSerilog();
});
});
Lit ús applikaasje reagearje op in POST-fersyk troch in SomethingDoneMessage-evenemint te ferstjoeren mei de wearde "done". It kontrakt foar sa'n berjocht kin as folget wurde omskreaun:
namespace MbMessages
{
public interface ISomethingDoneMessageV1
{
string Value { get; }
}
}
MassTransit-berjochten binne yn essinsje in envelop wêryn broker-berjochten binne ynsletten. De envelop sjocht der sa út:
{
"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"
}
}
It berjocht lit tsjinstfjilden sjen dy't nedich binne foar de wurking fan it ramt sels, mar wy hawwe de mooglikheid om ús eigen ekstra eigenskippen ta te foegjen oan dizze envelope. Boppedat hat MassTransit ynboude ark foar it wurkjen mei guon opsjonele fjilden, wêrfan de meast nijsgjirrige is de korrelaasje-identifikaasje CorrelationId.
Litte wy de CorrelatedBy-ynterface tafoegje oan it berjochtkontrakt:
namespace MbMessages
{
public interface ISomethingDoneMessageV1 : CorrelatedBy<Guid>
{
string Value { get; }
}
}
Litte wy it ymplementearje en in wearde tawize oan it CorrelationId-eigenskip by it meitsjen fan in berjocht:
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; }
}
As wy nei it bywurke berjocht sjogge, sille wy sjen dat de korrelaasje-identifikaasje net allinich in diel fan ús berjocht is wurden, mar ek in diel fan 'e envelop - dizze identifier sil no ek brûkt wurde yn alle MassTransit-logs, wat betsjut dat it folle makliker sil wêze foar ús om te gean mei problemen op it nivo fan berjochtmakelaar.
{
"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"
}
}
Alles wat wy hoege te dwaan is de logging fan dizze tsjinsteigenskippen fan it berjocht yn te stellen; hjirfoar sille wy in pakket tafoegje oan it projekt . It pakket foeget in filter ta oan de MassTransit-berjochtferwurkingspipeline dy't de berjochtkontekst steapelt op in thread-feilige stapel. Serilog lêst de kontekst fan 'e stapel en foeget dizze ekstra eigenskippen ta oan ús logobjekten.
Install-Package Serilog.Enrichers.MassTransitMessage
Yn MassTransit foegje wy in filter yn
services
.AddSingleton(provider =>
{
return Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.UseSerilog();
cfg.UseSerilogMessagePropertiesEnricher();
});
});
En yn 'e Serilog-konfiguraasje foegje wy in ferriker ta
Log.Logger = new LoggerConfiguration()
.Enrich.FromMassTransitMessage()
.CreateLogger();
Sûnt de applikaasje dy't it berjocht ûntfangt fan 'e RabbitMQ-wachtrige hat tagong ta alle eigenskippen fan' e MassTransit-envelope, kinne wy de resultearjende korrelaasje-identifikaasje brûke yn 'e konsumearjende applikaasje, en ek trochjaan it fierder lâns de opropketen.
As resultaat begon ús logs CorrelationId net allinich binnen ien tsjinst te befetsjen, mar ek by ynteraksje mei oare applikaasjes.

Dat, it resultearjende logsysteem yn .Net-applikaasjes lit ús logs fan folslein ferskillende mikrotsjinsten korrelearje sûnder problemen - sels dyjingen dy't wurkje fia in berjochtmakelaar. En mei help fan Elasticsearch kinne wy logs fluch en maklik analysearje troch de dashboards te bouwen dy't wy nedich binne yn Kibana (in foarbyld wurdt werjûn yn 'e foto foar de post).
Fansels sil it ynloggen yn dit formulier de komplekse ynteraksjes tusken jo tsjinsten en ferskate eksterne systemen net dekke, mar it fêststellen fan sa'n folchoarder oan it begjin fan 'e ûntwikkeling fan it projekt is ien fan' e dingen wêrfoar jo josels mear dan ien kear tankje.
Jo kinne de boarnekoade fan it resultearjende systeem yn it projekt begripe:
Boarne: www.habr.com
