
El registro es una herramienta muy importante para un desarrollador, pero al construir sistemas distribuidos, se convierte en una piedra que debe colocarse en la base de su aplicación; de lo contrario, la complejidad del desarrollo de microservicios se hará sentir muy rápidamente.
.Net Core 3 añadió una gran , por lo que si sus aplicaciones utilizan llamadas HTTP directas para la comunicación entre servicios, puede aprovechar esta funcionalidad encajonada. Sin embargo, si la arquitectura de su backend implica la interacción a través de un intermediario de mensajes (RabbitMQ, Kafka, etc.), entonces aún debe ocuparse del tema de pasar el contexto de correlación a través de estos mensajes usted mismo.
En este artículo, tomaremos una aplicación API web simple y organizaremos el registro, lo que
mantenga una correlación de un extremo a otro entre los registros de servicios independientes para que pueda ver fácilmente todas las actividades que fueron causadas por una solicitud específica del cliente
tener un único punto de entrada con un análisis conveniente para que incluso el Soporte pueda usar la herramienta de registro, a lo que preguntas como "Recibí un error con tal o cual ID de solicitud" aparecieron en la aplicación
Primero, debemos decidir el proveedor de registro en nuestra aplicación. El principal requisito para la tala moderna es la estructura, es decir. No deberíamos trabajar con mensajes de texto planos, sino con objetos. Gracias a dichos registros, podemos crear fácilmente vistas de nuestros mensajes en diferentes secciones y realizar análisis.
Para nuestra aplicación, utilizaremos el paquete Serilog, que tiene un excelente soporte para el registro estructural y un rico sistema complementario. Omitiré los pasos básicos para configurarlo (puede encontrar una gran cantidad de artículos sobre este tema) y asumiré que
El serilog ya está configurado y es el registrador predeterminado de su proveedor de inyección de dependencia.
Enriquecer mensajes con propiedades de contexto está habilitado en su configuración (Enrich.FromLogContext)
El siguiente paso es elegir a qué sistema de registro centralizado enviar mensajes desde Serilog. Quizás la opción de código abierto más común hoy en día sea la pila ELK (Elasticsearch, Logstash y Kibana), y vamos a por ella. Para ello utilizamos la propuesta de - Después de registrarte en un plan gratuito, todo el poder del motor de búsqueda Lucene está en nuestras manos.
Nos queda agregar un paquete a nuestro proyecto.
Install-Package Serilog.Sinks.Logzio
Y agregue el Enricher apropiado a la configuración de nuestro registrador alimentándolo con un token de acceso.
LoggerConfiguration loggerConfig = new LoggerConfiguration();
loggerConfig.WriteTo.Logzio(secrets.LogzioToken, 10, TimeSpan.FromSeconds(10), null, LogEventLevel.Debug);
Al ejecutar la aplicación, podremos observar nuestros mensajes no solo en la consola, sino también en Kibane.

Interfaces

En una aplicación de tipo servicio se pueden distinguir dos interfaces principales de su interacción con el mundo exterior, las denominaremos vertical y horizontal. Una interfaz vertical es una API web a través de la cual llegan las llamadas desde una aplicación cliente. Horizontal es un intermediario de mensajes que se utiliza para intercambiar datos con otros servicios internos.
Consideremos las etapas de implementación de la correlación en cada una de estas interfaces.
Correlación en solicitudes HTTP
Para obtener la mayor cantidad de información posible, necesitamos generar un ID de correlación lo más cerca posible del inicio de la actividad, es decir, en la puerta de enlace o directamente en el cliente (móvil o web). Dado que hoy estamos tratando con una aplicación de back-end, simplemente indicaremos en ella el requisito del encabezado obligatorio "X-Correlation-ID" en todas las solicitudes a la API web.
Agregar un paquete , cuya función es tomar el valor del encabezado que necesitamos
Install-Package CorrelationID
Agréguelo al proceso de procesamiento de solicitudes
public class Startup
{
public void Configure(IApplicationBuilder application)
{
application
.UseCorrelationId(new CorrelationIdOptions
{
Header = "X-Correlation-ID",
IncludeInResponse = false,
UpdateTraceIdentifier = false,
UseGuidForCorrelationId = false
});
}
}
Ahora hagamos un filtro de acción simple con él:
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();
}
}
Y agréguelo al controlador.
[Route("[controller]")]
[ApiController]
[ServiceFilter(typeof(ApiRequestFilter))]
public class CarsController : ControllerBase
{
}
Como resultado, el controlador mostrará 400 Solicitud incorrecta para todas las solicitudes sin un encabezado con el identificador correspondiente.
Después de que comenzamos a recibir un identificador del cliente, debemos agregarlo al contexto de registro, para esto crearemos una capa de marco:
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);
}
}
}
En nuestra aplicación, usamos el ILogger estándar del paquete Microsoft.Extensions.Logging.Abstractions, por lo que le agregaremos un valor usando una extensión simple.
public static IDisposable BeginScopeWith(this ILogger logger, params (string key, object value)[] keys)
{
return logger.BeginScope(keys.ToDictionary(x => x.key, x => x.value));
}
Agregamos una capa al proceso de procesamiento de solicitudes y obtenemos el resultado deseado.
public class Startup
{
public void Configure(IApplicationBuilder application)
{
application.UseMiddleware<CorrelationIdContextLogger>();
}
}
Ahora todas las actividades generadas por solicitudes a nuestra API web contienen un identificador de correlación mediante el cual se pueden vincular fácilmente.

Correlación en mensajes de intermediarios
El siguiente paso es configurar la transmisión y recepción del identificador de correlación a través del intermediario de mensajes. En nuestro ejemplo, usaremos RabbitMQ y como cliente tomaremos el marco MassTransit (MassTranzit). Nuevamente, saltemos la configuración inicial para trabajar con MassTransit y vayamos directamente a configurar el registro.
Para empezar podemos habilitar los registros del propio MassTransit, para ello agregaremos un paquete a nuestra aplicación
Install-Package MassTransit.SerilogIntegration
Ahora, después de agregar el registrador a la configuración de MassTransit, podremos ver los registros del marco.
services
.AddSingleton(provider =>
{
return Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.UseSerilog();
});
});
Dejemos que nuestra aplicación envíe el evento SomethingDoneMessage con el valor "hecho" como respuesta a la solicitud POST. El contrato de dicho mensaje se puede describir de la siguiente manera:
namespace MbMessages
{
public interface ISomethingDoneMessageV1
{
string Value { get; }
}
}
Los mensajes de MassTransit son esencialmente un sobre que contiene mensajes de intermediarios. El sobre se ve así:
{
"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"
}
}
El mensaje muestra los campos de servicio que son necesarios para que el marco funcione, pero tenemos la capacidad de agregar nuestras propias propiedades adicionales a este sobre. Además, MassTransit tiene herramientas integradas para trabajar con algunos campos opcionales, el más interesante de los que nos interesa es CorrelationId.
Agregue la interfaz Cor relatedBy al contrato de mensaje:
namespace MbMessages
{
public interface ISomethingDoneMessageV1 : CorrelatedBy<Guid>
{
string Value { get; }
}
}
Implementémoslo y asignemos un valor a la propiedad CorrelationId al crear un mensaje:
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; }
}
Si miramos el mensaje actualizado, veremos que el identificador de correlación se ha convertido no solo en parte de nuestro mensaje, sino también en parte del sobre; este identificador ahora también se usará en todos los registros de MassTransit, lo que significa que será mucho más fácil. para que podamos abordar los problemas a nivel del intermediario de mensajes.
{
"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"
}
}
Nos queda configurar el registro de estas propiedades de servicio del mensaje, para ello agregaremos un paquete al proyecto. . El paquete agrega un filtro a la canalización de procesamiento de mensajes de MassTransit que coloca el contexto del mensaje en una pila segura para subprocesos. El serilog lee el contexto de la pila y agrega estas propiedades adicionales a nuestros objetos de registro.
Install-Package Serilog.Enrichers.MassTransitMessage
Insertar un filtro en MassTransit
services
.AddSingleton(provider =>
{
return Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.UseSerilog();
cfg.UseSerilogMessagePropertiesEnricher();
});
});
Y en la configuración de Serilog, agregue un Enriquecedor
Log.Logger = new LoggerConfiguration()
.Enrich.FromMassTransitMessage()
.CreateLogger();
Dado que la aplicación que recibe el mensaje de la cola RabbitMQ tiene acceso a todas las propiedades del sobre MassTransit, podemos usar el identificador de correlación recibido dentro de la aplicación consumidora y también pasarlo a lo largo de la cadena de llamadas.
Como resultado, nuestros registros comenzaron a contener CorrelationId no solo dentro del mismo servicio, sino también al interactuar con otras aplicaciones.

Por lo tanto, el sistema de registro resultante en aplicaciones .Net nos permite correlacionar registros de microservicios completamente diferentes sin ningún problema, incluso aquellos que funcionan a través de un intermediario de mensajes. Y con la ayuda de Elasticsearch, podemos analizar registros de manera rápida y conveniente creando los paneles que necesitamos en Kibana (se muestra un ejemplo en la imagen de la publicación).
Por supuesto, iniciar sesión en este formulario no cubrirá opciones complejas para la interacción de sus servicios y varios sistemas externos, pero establecer dicho orden al comienzo del desarrollo del proyecto es una de esas cosas por las que se agradecerá más de una vez.
Puede comprender el código fuente del sistema resultante en el proyecto:
Fuente: habr.com
