
Logovanie je pre vývojárov veľmi dôležitým nástrojom, no pri budovaní distribuovaných systémov sa stáva kameňom, ktorý je potrebné položiť priamo do základov vašej aplikácie, inak sa zložitosť vývoja mikroslužieb veľmi rýchlo prejaví.
.Net Core 3 pridal skvelé , takže ak vaše aplikácie používajú priame volania HTTP na komunikáciu medzi službami, môžete využiť túto krabicovú funkčnosť. Ak však architektúra vášho backendu zahŕňa interakciu prostredníctvom sprostredkovateľa správ (RabbitMQ, Kafka atď.), Potom sa o tému odovzdávania korelačného kontextu cez tieto správy musíte postarať sami.
V tomto článku vezmeme jednoduchú webovú aplikáciu API a zorganizujeme protokolovanie, ktoré bude
udržiavať úplnú koreláciu medzi protokolmi nezávislých služieb, aby ste mohli ľahko vidieť všetky aktivity, ktoré boli spôsobené konkrétnou požiadavkou od klienta
mať jeden vstupný bod s pohodlnou analýzou, takže aj podpora môže používať protokolovací nástroj, na ktorý sa v aplikácii vyskytli otázky ako „vyskytla sa chyba s takým a takým ID žiadosti“
Najprv sa musíme rozhodnúť pre poskytovateľa protokolovania v našej aplikácii. Hlavnou požiadavkou na modernú ťažbu dreva je štruktúra, t.j. nemali by sme pracovať s plochými textovými správami, ale s objektmi. Vďaka takýmto protokolom môžeme jednoducho vytvárať zobrazenia našich správ v rôznych sekciách a vykonávať analýzy.
Pre našu aplikáciu použijeme balík Serilog (Serilog), ktorý má vynikajúcu podporu pre štrukturálne protokolovanie a bohatý doplnkový systém. Vynechám základné kroky pre jeho nastavenie (článkov na túto tému nájdete veľké množstvo) a vychádzam z toho, že
Serilog je už nakonfigurovaný a je predvoleným zapisovačom vášho poskytovateľa injekcií závislostí
Obohacovanie správ o kontextové vlastnosti je povolené v jeho konfigurácii (Enrich.FromLogContext)
Ďalším krokom je vybrať, na ktorý centralizovaný protokolovací systém sa majú odosielať správy zo Serilogu. Snáď najbežnejšou možnosťou open source je dnes ELK stack (Elasticsearch, Logstash a Kibana) a zoberme si to. Na to používame návrh z - po registrácii do bezplatného plánu je plná sila vyhľadávača Lucene v našich rukách.
Zostáva nám pridať balíček do nášho projektu
Install-Package Serilog.Sinks.Logzio
A pridajte príslušný Enricher do našej konfigurácie zapisovača tak, že mu poskytnete prístupový token
LoggerConfiguration loggerConfig = new LoggerConfiguration();
loggerConfig.WriteTo.Logzio(secrets.LogzioToken, 10, TimeSpan.FromSeconds(10), null, LogEventLevel.Debug);
Spustením aplikácie budeme môcť naše správy sledovať nielen v konzole, ale aj v Kibane.

Rozhranie

V aplikácii typu služby možno rozlíšiť dve hlavné rozhrania jej interakcie s vonkajším svetom, budeme ich označovať ako vertikálne a horizontálne. Vertikálne rozhranie je webové rozhranie API, cez ktoré prichádzajú hovory z klientskej aplikácie. Horizontal je sprostredkovateľ správ, ktorý sa používa na výmenu údajov s inými internými službami.
Uvažujme o fázach implementácie korelácie na každom z týchto rozhraní.
Korelácia v HTTP požiadavkách
Aby sme získali čo najviac informácií, musíme si vygenerovať ID korelácie čo najbližšie k začiatku aktivity, t.j. na bráne alebo priamo na klientovi (mobile alebo webe). Keďže dnes máme do činenia s back-endovou aplikáciou, jednoducho na nej uvedieme požiadavku na povinnú hlavičku „X-Correlation-ID“ vo všetkých požiadavkách na webové rozhranie API.
Pridanie balíka , ktorého funkciou je prevziať hodnotu z hlavičky, ktorú potrebujeme
Install-Package CorrelationID
Pridajte ho do kanála spracovania žiadostí
public class Startup
{
public void Configure(IApplicationBuilder application)
{
application
.UseCorrelationId(new CorrelationIdOptions
{
Header = "X-Correlation-ID",
IncludeInResponse = false,
UpdateTraceIdentifier = false,
UseGuidForCorrelationId = false
});
}
}
Teraz s ním urobme jednoduchý akčný filter:
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();
}
}
A pridajte ho do ovládača
[Route("[controller]")]
[ApiController]
[ServiceFilter(typeof(ApiRequestFilter))]
public class CarsController : ControllerBase
{
}
Výsledkom je, že kontrolér zobrazí 400 Bad request pre všetky požiadavky bez hlavičky s príslušným identifikátorom.
Keď sme od klienta začali dostávať identifikátor, musíme ho pridať do kontextu protokolovania, na tento účel vytvoríme rámcovú vrstvu:
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);
}
}
}
V našej aplikácii používame štandardný ILogger z balíka Microsoft.Extensions.Logging.Abstractions, takže k nemu pridáme hodnotu pomocou jednoduchého rozšírenia.
public static IDisposable BeginScopeWith(this ILogger logger, params (string key, object value)[] keys)
{
return logger.BeginScope(keys.ToDictionary(x => x.key, x => x.value));
}
Pridáme vrstvu do procesu spracovania požiadaviek a získame požadovaný výsledok.
public class Startup
{
public void Configure(IApplicationBuilder application)
{
application.UseMiddleware<CorrelationIdContextLogger>();
}
}
Teraz všetky aktivity, ktoré sú generované požiadavkami na naše webové rozhranie API, obsahujú korelačný identifikátor, pomocou ktorého sa dajú ľahko prepojiť.

Korelácia v správach makléra
Ďalším krokom je nastavenie prenosu a príjmu korelačného identifikátora cez sprostredkovateľa správ. V našom príklade použijeme RabbitMQ a ako klienta si vezmeme rámec MassTransit (MassTranzit). Opäť preskočme počiatočné nastavenie pre prácu s MassTransit a prejdime priamo k nastaveniu protokolovania.
Na začiatok môžeme povoliť protokoly samotného MassTransit, preto do našej aplikácie pridáme balík
Install-Package MassTransit.SerilogIntegration
Teraz, po pridaní zapisovača do nastavení MassTransit, budeme môcť vidieť protokoly rámca.
services
.AddSingleton(provider =>
{
return Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.UseSerilog();
});
});
Nechajte našu aplikáciu odoslať udalosť SomethingDoneMessage s hodnotou „done“ ako odpoveď na požiadavku POST. Zmluvu takejto správy možno opísať takto:
namespace MbMessages
{
public interface ISomethingDoneMessageV1
{
string Value { get; }
}
}
Správy MassTransit sú v podstate obálky obsahujúce správy makléra. Obálka vyzerá takto:
{
"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"
}
}
Správa zobrazuje polia služieb, ktoré sú potrebné na fungovanie samotného rámca, ale do tejto obálky máme možnosť pridať vlastné ďalšie vlastnosti. Okrem toho má MassTransit vstavané nástroje na prácu s niektorými voliteľnými poľami, z ktorých najzaujímavejšie z nich nás zaujíma CorrelationId.
Pridajte rozhranie CorrelatedBy do zmluvy o správe:
namespace MbMessages
{
public interface ISomethingDoneMessageV1 : CorrelatedBy<Guid>
{
string Value { get; }
}
}
Poďme to implementovať a pri vytváraní správy priraďme hodnotu vlastnosti CorrelationId:
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; }
}
Ak sa pozrieme na aktualizovanú správu, uvidíme, že korelačný identifikátor sa stal nielen súčasťou našej správy, ale aj súčasťou obálky – tento identifikátor sa teraz bude používať aj vo všetkých protokoloch MassTransit, čo znamená, že to bude oveľa jednoduchšie aby sme riešili problémy na úrovni sprostredkovateľa správ.
{
"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"
}
}
Zostáva nám nakonfigurovať protokolovanie týchto vlastností služby správy, na to pridáme do projektu balík . Balík pridáva filter do procesu spracovania správ MassTransit, ktorý posúva kontext správy do zásobníka bezpečného pre vlákna. Serilog číta kontext zo zásobníka a pridáva tieto dodatočné vlastnosti do našich log objektov.
Install-Package Serilog.Enrichers.MassTransitMessage
Vložte filter do MassTransit
services
.AddSingleton(provider =>
{
return Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.UseSerilog();
cfg.UseSerilogMessagePropertiesEnricher();
});
});
A v konfigurácii Serilog pridajte Enricher
Log.Logger = new LoggerConfiguration()
.Enrich.FromMassTransitMessage()
.CreateLogger();
Keďže aplikácia, ktorá prijíma správu z frontu RabbitMQ, má prístup ku všetkým vlastnostiam obálky MassTransit, môžeme použiť prijatý korelačný identifikátor vo vnútri konzumujúcej aplikácie a tiež ho odovzdať ďalej v reťazci hovorov.
Výsledkom bolo, že naše protokoly začali obsahovať CorrelationId nielen v rámci tej istej služby, ale aj pri interakcii s inými aplikáciami.

Výsledný logovací systém v .Net aplikáciách nám teda umožňuje bez problémov korelovať logy z úplne iných mikroslužieb – dokonca aj z tých, ktoré fungujú cez sprostredkovateľa správ. A pomocou Elasticsearch môžeme rýchlo a pohodlne analyzovať protokoly vytvorením dashboardov, ktoré potrebujeme v Kibana (príklad je uvedený na obrázku v príspevku).
Logovanie v tejto forme samozrejme nepokryje komplexné možnosti interakcie vašich služieb a rôznych externých systémov, no založenie takejto objednávky na samom začiatku vývoja projektu je jednou z vecí, za ktoré sa viackrát poďakujete.
Zdrojovému kódu výsledného systému môžete porozumieť v projekte:
Zdroj: hab.com
