Loggning i mikrotjÀnstmiljöer .Net i praktiken

Loggning i mikrotjÀnstmiljöer .Net i praktiken

Loggning Àr ett mycket viktigt verktyg för utvecklare, men nÀr man skapar distribuerade system blir det en grundsten som mÄste lÀggas direkt i fundamentet av din applikation, annars kommer komplexiteten i utvecklingen av mikrotjÀnster snabbt att ge sig till kÀnna.

I .Net Core 3 har en utmÀrkt möjlighet till överföring av korrelationskontext i HTTP-huvuden lagts till, sÄ om dina applikationer anvÀnder direkta HTTP-anrop för inter-tjÀnstkommunikation kan du dra nytta av den hÀr inbyggda funktionen. Men om din backend-arkitektur innebÀr kommunikation via en meddelandebroker (RabbitMQ, Kafka osv.), mÄste du fortfarande ta hand om överföringen av korrelationskontext genom dessa meddelanden sjÀlv.

I den hÀr artikeln kommer vi att ta en enkel webb-API-applikation och organisera loggning som kommer att

  • behĂ„lla genomgĂ„ende korrelation mellan loggar frĂ„n oberoende tjĂ€nster sĂ„ att man lĂ€tt kan se alla aktiviteter som initierats av en specifik begĂ€ran frĂ„n klienten

  • ha en enda ingĂ„ngspunkt med enkel analys sĂ„ att loggningsverktyget kan anvĂ€ndas Ă€ven av supporten som fĂ„r frĂ„gor som "jag fick ett fel i appen med det hĂ€r begĂ€rnings-ID:t".

Först mÄste vi bestÀmma oss för vilken loggningsleverantör vi ska anvÀnda i vÄr applikation. Huvudkravet för modern loggning Àr struktur, det vill sÀga att vi mÄste arbeta med objekt snarare Àn platta textmeddelanden. Tack vare sÄdana loggar kan vi enkelt bygga representationer av vÄra meddelanden i olika perspektiv och utföra analys.

För vÄr applikation kommer vi att anvÀnda paketet Serilog, som har utmÀrkt stöd för strukturell loggning och ett rikt system av tillÀgg. Jag kommer att hoppa över de grundlÀggande stegen för att konfigurera det (du kan hitta mÄnga artiklar om detta Àmne) och göra antagandet att

  • Serilog redan Ă€r konfigurerat och Ă€r standardloggern för din beroendeinjektionsleverantör

  • och i dess konfiguration Ă€r berikande av meddelanden med kontextens egenskaper (Enrich.FromLogContext) aktiverat.

Det nĂ€sta steget Ă€r att vĂ€lja vilket system för central loggsamling som meddelanden frĂ„n Serilog ska skickas till. För nĂ€rvarande Ă€r det mest anvĂ€nda alternativet inom öppen programvara ELK-stack (Elasticsearch, Logstash och Kibana), sĂ„ vi vĂ€ljer det. För detta Ă€ndamĂ„l anvĂ€nder vi erbjudandet frĂ„n Logz.IO — efter registrering pĂ„ den kostnadsfria planen fĂ„r vi kraften frĂ„n sökmotorn Lucene i vĂ„ra hĂ€nder.

Vi behöver bara lÀgga till paketet i vÄrt projekt Serilog.Sinks.Logzio

Install-Package Serilog.Sinks.Logzio

Och lÀgga till den motsvarande enrichern i konfigurationen av vÄr logger, genom att ge den Ätkomsttoken.

LoggerConfiguration loggerConfig = new LoggerConfiguration();
loggerConfig.WriteTo.Logzio(secrets.LogzioToken, 10, TimeSpan.FromSeconds(10), null, LogEventLevel.Debug);

NÀr vi startar applikationen kommer vi att kunna se vÄra meddelanden inte bara i konsolen utan ocksÄ i Kibana.

Loggning i mikrotjÀnstmiljöer .Net i praktiken

GrÀnssnitt

Loggning i mikrotjÀnstmiljöer .Net i praktiken

I en tjÀnstepplikation kan vi identifiera tvÄ huvudsakliga grÀnssnitt för dess interaktion med omvÀrlden, som vi benÀmner vertikalt och horisontellt. Det vertikala grÀnssnittet Àr webb-API:t, genom vilket anrop frÄn klientapplikationen kommer in. Det horisontella Àr meddelandebrokern som anvÀnds för att utbyta information med andra interna tjÀnster.

LÄt oss gÄ igenom stegen för att implementera korrelation pÄ vart och ett av dessa grÀnssnitt.

Korrelation i HTTP-förfrÄgningar

För att fĂ„ sĂ„ mycket information som möjligt behöver vi generera en korrelationsidentifierare sĂ„ tidigt som möjligt i aktiviteten, dvs. vid gatewayen eller direkt pĂ„ klienten (mobil eller webb). Eftersom vi idag hanterar en backendapplikation ska vi bara ange kravet pĂ„ den obligatoriska rubriken „X-Correlation-ID” i alla förfrĂ„gningar till webb-API:t.

Vi lÀgger till paketet CorrelationID, vars funktion Àr att hÀmta vÀrdet frÄn den nödvÀndiga rubriken.

Install-Package CorrelationID

LÄt oss lÀgga till det i begÀrans bearbetningspipeline.

public class Startup
{
    public void Configure(IApplicationBuilder application)
    {
        application
	    .UseCorrelationId(new CorrelationIdOptions
        {
            Header = "X-Correlation-ID",
            IncludeInResponse = false,
            UpdateTraceIdentifier = false,
            UseGuidForCorrelationId = false
        });
    }
}

Nu ska vi med hjÀlp av detta skapa ett enkelt action-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();
    }
}

Och vi lÀgger till den i kontrollern

[Route("[controller]")]
[ApiController]
[ServiceFilter(typeof(ApiRequestFilter))]
public class CarsController : ControllerBase
{

}

Som ett resultat kommer kontrollern att returnera 400 Bad Request för alla förfrÄgningar utan en header med motsvarande identifierare.

NÀr vi börjar ta emot identifieraren frÄn klienten mÄste vi lÀgga till den i loggkontexten, sÄ vi gör en kringliggande mellanlager:

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(httpContext);
            }
        }
        else
        {
            await _next(httpContext);
        }
    }
}

I vÄr applikation anvÀnder vi standard ILogger frÄn paketet Microsoft.Extensions.Logging.Abstractions, sÄ vÀrdet kommer att lÀggas till med hjÀlp av en enkel förlÀngning till den.

public static IDisposable BeginScopeWith(this ILogger logger, params (string key, object value)[] keys)
{
    return logger.BeginScope(keys.ToDictionary(x => x.key, x => x.value));
}

Vi lÀgger till mellanlagret i begÀrningshanteringskonverteraren och fÄr önskat resultat.

public class Startup
{
    public void Configure(IApplicationBuilder application)
    {
        application.UseMiddleware();
    }
}

Nu innehÄller alla aktiviteter som genereras av förfrÄgningar till vÄr webb-API det korrelations-ID som gör att de lÀtt kan kopplas ihop.

Loggning i mikrotjÀnstmiljöer .Net i praktiken

Korrelation i meddelanden frÄn mÀklaren

NĂ€sta steg Ă€r att etablera överföring och mottagning av korrelationsidentifieraren via meddelandebrokern. I vĂ„rt exempel kommer vi anvĂ€nda RabbitMQ, och som klient vĂ€ljer vi ramen MassTransit. Återigen hoppar vi över den initiala konfigurationen av MassTransit och gĂ„r direkt vidare till instĂ€llningen av loggning.

För att börja kan vi aktivera loggorna för MassTransit sjÀlv, för detta lÀgger vi till paketet i vÄr applikation MassTransit.SerilogIntegration

Install-Package MassTransit.SerilogIntegration

Nu, efter att loggaren har lagts till i instÀllningarna för MassTransit, kan vi se loggarna för ramen.

services
    .AddSingleton(provider =>
        {
            return Bus.Factory.CreateUsingRabbitMq(cfg =>
            {
                cfg.UseSerilog();
            });
        });

LÄt vÄr applikation som en reaktion pÄ en POST-begÀran skicka hÀndelsen SomethingDoneMessage med vÀrdet "done". Kontraktet för ett sÄdant meddelande kan beskrivas sÄ hÀr:

namespace MbMessages
{
    public interface ISomethingDoneMessageV1
    {
        string Value { get; }
    }
}

Meddelanden frÄn MassTransit Àr i huvudsak ett kuvert, dÀr meddelanden frÄn brokern Àr inlagda. Kuvertet ser ungefÀr ut sÄ hÀr:

{
  "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"
  }
}

I meddelandet syns de tjÀnstefÀlt som krÀvs för att ramverket ska fungera, men vi kan ocksÄ lÀgga till egna ytterligare egenskaper i detta kuvert. Dessutom har MassTransit inbyggda verktyg för att arbeta med vissa valfria fÀlt, av vilka vi frÀmst Àr intresserade av korrelationsidentifikatorn CorrelationId.

LÄt oss lÀgga till grÀnssnittet CorrelatedBy i meddelandets kontrakt:

namespace MbMessages
{
    public interface ISomethingDoneMessageV1 : CorrelatedBy
    {
        string Value { get; }
    }
}

Vi implementerar det och tilldelar vÀrdet till egenskapen CorrelationId vid skapandet av meddelandet:

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; }
}

Om vi tittar pĂ„ det uppdaterade meddelandet ser vi att korrelations-ID:t blivit en del av vĂ„rt meddelande, sĂ„vĂ€l som av kuvertet – detta ID kommer nu ocksĂ„ att anvĂ€ndas i alla loggar för MassTransit, vilket gör det betydligt enklare att hantera problem pĂ„ meddelandebrokernivĂ„.

{
  "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"
  }
}

Vi behöver fortfarande konfigurera loggning av dessa meddelandets egenskaper, för detta lÀgger vi till paketet Serilog.Enrichers.MassTransitMessage. Paketet lÀgger till en filter i meddelandehanteringsrörledningen för MassTransit, vilket placerar meddelandets sammanhang i en trÄdsÀker stack. Serilog lÀser kontexten frÄn stacken och lÀgger till dessa tillÀggsegenskaper i vÄra loggobjekt.

Install-Package Serilog.Enrichers.MassTransitMessage

I MassTransit lÀgger vi till filtret

services
    .AddSingleton(provider =>
        {
            return Bus.Factory.CreateUsingRabbitMq(cfg =>
            {
                cfg.UseSerilog();
                cfg.UseSerilogMessagePropertiesEnricher();
            });
        });

Och i Serilog-konfigurationen lÀgger vi till enrichern

Log.Logger = new LoggerConfiguration()
    .Enrich.FromMassTransitMessage()
    .CreateLogger();

Eftersom applikationen som tar emot meddelanden frÄn RabbitMQ har tillgÄng till alla egenskaper i MassTransit-kuvertet, kan vi anvÀnda det mottagna korrelations-ID:t inuti konsumentapplikationen samt vidarebefordra det genom hela anropskedjan.

Som ett resultat innehÄller vÄra loggar nu CorrelationId inte bara inom en tjÀnst utan ocksÄ vid interaktion med andra applikationer.

Loggning i mikrotjÀnstmiljöer .Net i praktiken

SĂ„ den erhĂ„llna loggningssystemet i .Net-applikationer gör det möjligt för oss att utan större problem korrelera loggar frĂ„n helt olika mikrotjĂ€nster – Ă€ven de som fungerar via meddelandebrokern. Och med hjĂ€lp av Elasticsearch kan vi snabbt och enkelt analysera loggar, genom att bygga nödvĂ€ndiga dashboards i Kibana (exemplet visas pĂ„ bilden till inlĂ€gget).

SjÀlvklart kommer loggning i detta format inte att tÀcka komplexa interaktioner mellan dina tjÀnster och olika externa system, men att införa en sÄdan ordning i början av projektutvecklingen Àr nÄgot du kommer att tacka dig sjÀlv för flera gÄnger.

Du kan undersöka kÀllkoden för det resulterande systemet i projektet: github.com/a-postx/YA.ServiceTemplate

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster