Naprakész mikroszolgáltatás-logging a .Net környezetben

Naprakész mikroszolgáltatás-logging a .Net környezetben

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ó lehetőség került bevezetésre a korrelációs kontextus HTTP-fejlécekben történő továbbítására, 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 Logz.IO — 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 Serilog.Sinks.Logzio

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.

Naprakész mikroszolgáltatás-logging a .Net környezetben

Csatlakozók

Naprakész mikroszolgáltatás-logging a .Net környezetben

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 CorrelationID, 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.

Naprakész mikroszolgáltatás-logging a .Net környezetben

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 MassTransit.SerilogIntegration

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. Serilog.Enrichers.MassTransitMessage. 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.

Naprakész mikroszolgáltatás-logging a .Net környezetben

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: github.com/a-postx/YA.ServiceTemplate

Forrás: habr.com

Megbízható hosting vásárlása weboldalak számára, DDoS védelemmel, VPS VDS szerverek. 🔥 Megbízható webtárhely vásárlása DDoS védelemmel, VPS VDS szerverek | ProHoster