
A naplózás a fejlesztők számára nagyon fontos eszköz, de elosztott rendszerek kialakításakor ez a kő, amelyet az alkalmazásod alapjába kell beépíteni, különben a mikroszolgáltatások fejlesztésének nehézségei nagyon gyorsan meg fognak mutatkozni.
A .Net Core 3-ban egy kiváló , ezért ha az alkalmazásaid közvetlen HTTP-hívásokat használnak a mikroszolgáltatások közötti interakcióhoz, akkor élhetsz ezzel a dobozos funkcionalitással. Azonban, ha a backend architektúrád üzenetbroker (RabbitMQ, Kafka stb.) közötti interakciót feltételez, akkor továbbra is foglalkoznod kell a korrelációs kontextus üzeneteken való továbbításának kérdésével.
Ebben a cikkben egy egyszerű web-API alkalmazást fogunk megnézni, és beállítjuk a naplózást, amely
megőrzi a folyamatos korrelációt a független szolgáltatások naplóiban, hogy könnyen nyomon lehessen követni az összes aktivitást, amely egy adott kliens kérését okozta
egységes belépési ponttal a kényelmes elemzés érdekében, hogy a naplózó eszközt akár a támogatási csapat is tudja használni, amikor kérdések merülnek fel, mint például "Itt egy hiba lépett fel az alkalmazásomban egy adott kérési azonosítóval"
Először is, meg kell határoznunk a naplózási szolgáltatót az alkalmazásunkban. A modern naplózás fő követelménye a struktúrált adatkezelés, azaz nem lapos szöveges üzenetekkel, hanem objektumokkal kell dolgoznunk. Az ilyen naplók segítségével könnyen kialakíthatjuk az üzeneteink különböző nézőpontokból történő bemutatását és elemzését.
Az alkalmazásunkhoz a Serilog csomagot (Szerilóg) fogjuk használni, amely kiváló támogatást nyújt a struktúrált naplózáshoz és gazdag kiegészítőrendszerrel rendelkezik. A konfigurálási alaplépéseket kihagyom (számos cikket találhatsz erről a témáról), és feltételezem, hogy
a Serilog már konfigurálva van, és a függőséginjektorod alapértelmezett naplózója
a konfigurációjában a kontextusból származó tulajdonságokkal való üzenetgazdagítást tartalmaz (Enrich.FromLogContext)
A következő lépésben ki kell választanunk, hogy melyik központosított loggyűjtő rendszerbe küldjük a Serilog üzeneteit. Jelenleg a legelterjedtebb nyílt forráskódú megoldás az ELK stack (Elasticsearch, Logstash és Kibana), ezt fogjuk használni. Ehhez vegyük igénybe a — a ingyenes csomagra való regisztráció után a Lucene keresőmotor teljes ereje a kezünkben van.
Csak annyit kell tennünk, hogy hozzáadjuk a projektünkhöz a
Install-Package Serilog.Sinks.Logzio
És hozzá kell adnunk a megfelelő enrichert a loggerünk konfigurációjához, megadva neki az elérési tokent.
LoggerConfiguration loggerConfig = new LoggerConfiguration();
loggerConfig.WriteTo.Logzio(secrets.LogzioToken, 10, TimeSpan.FromSeconds(10), null, LogEventLevel.Debug);
Ha elindítjuk az alkalmazást, akkor nemcsak a konzolon, hanem a Kibana-ban is láthatjuk az üzeneteinket.

Csatlakozók

A szolgáltatás típusú alkalmazásban két fő interfészt emelhetünk ki, amelyeken keresztül kapcsolatba lép a külvilággal, ezeket függőleges és vízszintes interfészként jelöljük. A függőleges interfész a web API, amelyen keresztül a kliens alkalmazás hívásai érkeznek. A vízszintes interfész egy üzenetközvetítő, amelyet adatok cseréjére használnak más belső szolgáltatásokkal.
Nézzük meg a korreláció bevezetésének lépéseit mindkét interfészen.
HTTP-kérések korrelációja
Ahhoz, hogy minél több információt kapjunk, a korrelációs azonosítót a lehető legkorábban kell generálnunk, azaz a gateway-ben vagy közvetlenül a kliensnél (mobil vagy web). Mivel ma egy back-end alkalmazással foglalkozunk, a kötelező „X-Correlation-ID” fejlécet fogjuk előírni minden web API-hoz intézett kérésnél.
Hozzáadjuk a csomagot , amelynek feladata, hogy megkapja az értéket a szükséges fejlécből.
Install-Package CorrelationID
Hozzáadjuk a kérés feldolgozásának csatornájához.
public class Startup
{
public void Configure(IApplicationBuilder application)
{
application
.UseCorrelationId(new CorrelationIdOptions
{
Header = "X-Correlation-ID",
IncludeInResponse = false,
UpdateTraceIdentifier = false,
UseGuidForCorrelationId = false
});
}
}
Most ennek segítségével készítsünk egy egyszerű action filtert:
nyilvános zárt osztály ApiRequestFilter : ActionFilterAttribute
{
nyilvános ApiRequestFilter(IApiRequestTracker apiRequestTracker, ICorrelationContextAccessor correlationContextAccessor)
{
_correlationContextAccessor = correlationContextAccessor ?? dobj ArgumentNullException(nameof(correlationContextAccessor));
}
privát readonly ICorrelationContextAccessor _correlationContextAccessor;
nyilvános felülírás aszinkron feladat OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
ha (!Guid.TryParse(_correlationContextAccessor.CorrelationContext.CorrelationId, out Guid correlationId))
{
context.Result = új BadRequestResult();
vissza;
}
várakozás next.Invoke();
}
nyilvános felülírás aszinkron feladat OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
várakozás next.Invoke();
}
}
És hozzáadjuk a vezérlőhöz
[Route("[controller]")]
[ApiController]
[ServiceFilter(typeof(ApiRequestFilter))]
nyilvános osztály CarsController : ControllerBase
{
}
Ennek eredményeként a vezérlő 400 Bad request-t fog visszaadni minden olyan kérésre, amely nem tartalmazza a megfelelő azonosítót.
Miután megkaptuk az azonosítót az ügyféltől, azt a naplózási kontextushoz kell adnunk, ehhez létrehozunk egy burkoló réteget:
nyilvános osztály CorrelationIdContextLogger
{
nyilvános CorrelationIdContextLogger(RequestDelegate next)
{
_next = next ?? dobj ArgumentNullException(nameof(next));
}
readonly RequestDelegate _next;
nyilvános aszinkron feladat InvokeAsync(HttpContext httpContext, ILogger<CorrelationIdContextLogger> logger, ICorrelationContextAccessor correlationContextAccessor)
{
ha (Guid.TryParse(correlationContextAccessor.CorrelationContext.CorrelationId, out Guid correlationId))
{
használatával (logger.BeginScopeWith(("CorrelationId", correlationId)))
{
várakozás _next(context);
}
}
máskülönben
{
várakozás _next(context);
}
}
}
Az alkalmazásunkban a Microsoft.Extensions.Logging.Abstractions csomagból származó standard ILogger-t használjuk, ezért az értéket egy egyszerű kiterjesztéssel adjuk hozzá.
nyilvános statikus IDisposable BeginScopeWith(this ILogger logger, params (string kulcs, object érték)[] kulcsok)
{
visszatér logger.BeginScope(kulcsok.ToDictionary(x => x.kulcs, x => x.érték));
}
Hozzáadjuk a réteget a kérés feldolgozási folyamatához, és megkapjuk a szükséges eredményt.
nyilvános osztály Startup
{
nyilvános void Configure(IApplicationBuilder alkalmazás)
{
alkalmazás.UseMiddleware<CorrelationIdContextLogger>();
}
}
Mostantól minden tevékenység, amely a web API-nkhoz intézett kérelmekből származik, tartalmazza a korrelációs azonosítót, amellyel könnyen összekapcsolhatók.

Korreláció a broker üzenetekben
A következő lépésben be kell állítanunk a korrelációs azonosító küldését és fogadását egy üzenetküldő broker segítségével. Példánkban a RabbitMQ-t fogjuk használni, a kliens pedig a MassTransit keretrendszer lesz. Most is kihagyjuk a MassTransit kezdeti beállításait, és közvetlenül a naplózás beállítására térünk át.
Első lépésként bekapcsolhatjuk a MassTransit naplózását, ehhez hozzáadunk egy csomagot az alkalmazásunkhoz
Install-Package MassTransit.SerilogIntegration
Most, hogy hozzáadtuk a loggert a MassTransit beállításaihoz, látni fogjuk a keretrendszer naplóit.
services
.AddSingleton(provider =>
{
return Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.UseSerilog();
});
});
Tegyük fel, hogy az alkalmazásunk reagálásként egy POST-kérésre elküldi a SomethingDoneMessage eseményt az "done" értékkel. Az ilyen üzenet szerződése így írható le:
namespace MbMessages
{
public interface ISomethingDoneMessageV1
{
string Value { get; }
}
}
A MassTransit üzenetek lényegében egy boríték, amelybe a broker üzenetei kerülnek. A boríték körülbelül így néz ki:
{
"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"
}
}
Az üzenetben láthatóak azok a rendszerfók, amelyek szükségesek a keretrendszer működéséhez, de lehetőségünk van arra is, hogy saját további tulajdonságokat adjunk hozzá ehhez a borítékhoz. Sőt, a MassTransit beépített eszközöket kínál néhány opcionális mező kezelésére, amelyek közül a legérdekesebb számunkra a korrelációs azonosító, a CorrelationId.
Adjunk hozzá a megüzenetet megvalósító interfészhez egy CorrelatedBy interfészt:
namespace MbMessages
{
public interface ISomethingDoneMessageV1 : CorrelatedBy
{
string Value { get; }
}
}
Valósítsuk meg ezt és állítsuk be a CorrelationId értékét az üzenet létrehozásakor:
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; }
}
Ha megnézzük a frissített üzenetet, láthatjuk, hogy a korrelációs azonosító nemcsak az üzenet részévé vált, hanem a boríték részévé is — ez az azonosító mostantól a MassTransit minden naplózásában is használatban lesz, így sokkal könnyebben tudunk kezelni problémákat az üzenetközvetítő szintjén.
{
"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"
}
}
Még be kell állítanunk az üzenet szolgáltatási tulajdonságainak naplózását, ezért hozzáadunk egy csomagot a projekthez. . A csomag hozzáad egy szűrőt a MassTransit üzenetfeldolgozási folyamatához, amely a naplózott üzenet kontextusát egy szálbiztos verembe helyezi. A Serilog kiolvassa a kontextust a veremből, és hozzáadja ezeket a kiegészítő tulajdonságokat a naplóobjektumainkhoz.
Install-Package Serilog.Enrichers.MassTransitMessage
A MassTransit rendszerében beillesztjük a szűrőt.
services
.AddSingleton(provider =>
{
return Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.UseSerilog();
cfg.UseSerilogMessagePropertiesEnricher();
});
});
A Serilog konfigurációjában hozzáadjuk az enriqueciót.
Log.Logger = new LoggerConfiguration()
.Enrich.FromMassTransitMessage()
.CreateLogger();
Mivel az alkalmazás, amely üzenetet kap a RabbitMQ sorból, hozzáfér minden MassTransit boríték tulajdonsághoz, használhatjuk a kapott korrelációs azonosítót a fogyasztó alkalmazásban, és továbbadhatjuk a hívási láncon.
Ennek eredményeként a naplóink most már tartalmazzák a CorrelationId-t nemcsak egy szolgáltatás keretein belül, hanem más alkalmazásokkal való interakció során is.

Tehát a .Net alkalmazásokban létrehozott naplózási rendszer lehetővé teszi számunkra, hogy problémák nélkül korreláljuk a naplókat különböző mikroszolgáltatásokból — még azokból is, amelyek üzenetközvetítőn keresztül működnek. Az Elasticsearch segítségével gyorsan és kényelmesen elemezhetjük a naplókat, és a Kibana-ban a szükséges irányítópultokat létrehozhatjuk (példa látható a bejegyzés képe alatt).
Nyilvánvaló, hogy ebben a formában a naplózás nem fedi le a szolgáltatásaid és a különböző külső rendszerek közötti összetett interakciókat, de ennek a rendnek a megteremtése a projekt fejlesztésének kezdetén egyike azoknak a dolgoknak, amelyekért később hálás leszel magadnak.
A létrejött rendszer forráskódjában érdemes elmélyedni a projektben:
Forrás: habr.com
