Andreani.Arq.Observability¶
Autor José Menéndez.¶
Objetivo¶
El objetivo de esta librería es facilitar y hacer transparente el cambio entre proveedores de observabilidad, como Elastic APM
y OpenTelemetry
.
Arquitectura de paquetes¶
Atención
La Aplicación debe utilizar la implementación correspondiente a su estrategia de observabilidad. Por defecto es ElasticApm. En caso de necesitar cambiar a OpenTelemetry, se debe reemplazar las librerias de observabilidad de ElasticApm a OpenTelemetry.
Instalación¶
- Instalar en el proyecto
Application
la dependencia conObservability
dotnet add package Andreani.Arq.Observability
- Instalar en el proyecto
WebApi
la dependencia conObservability.ElasticApm
oObservability.OpenTelemetry
dotnet add package Andreani.Arq.Observability.ElasticApm dotnet add package Andreani.Arq.Observability.OpenTelemetry
Intefaces¶
public interface ICustomTracer
{
public bool IsConfigured { get; }
public bool IsConfigEnabled { get; }
public ICustomTransaction StartTransaction(string name, string type, string? links = null);
public ICustomTransaction GetCurrentTransaction();
public ICustomSpan GetCurrentSpan();
public T CaptureTransaction<T>(string name, string type, Func<ICustomTransaction, T> func, string? links = null);
public T CaptureTransaction<T>(string name, string type, Func<T> func, string? links = null);
public void CaptureTransaction(string name, string type, Action<ICustomTransaction> func, string? links = null);
public void CaptureTransaction(string name, string type, Action func, string? links = null);
public Task<T> CaptureTransactionAsync<T>(string name, string type, Func<ICustomTransaction, Task<T>> func, string? links = null);
public Task<T> CaptureTransactionAsync<T>(string name, string type, Func<Task<T>> func, string? links = null);
public Task CaptureTransactionAsync(string name, string type, Func<ICustomTransaction,Task> func, string? links = null);
public Task CaptureTransactionAsync(string name, string type, Func<Task> func, string? links = null);
}
public interface ICustomSpan
{
public string? Links { get; }
public void End();
public ICustomSpan StartSpan(string name, string type = "Internal", string? subType = null);
public T CaptureSpan<T>(string name, string type, Func<ICustomSpan, T> func, string? subType = null);
public T CaptureSpan<T>(string name, string type, Func<T> func, string? subType = null);
public void CaptureSpan(string name, string type, Action<ICustomSpan> func, string? subType = null);
public void CaptureSpan(string name, string type, Action func, string? subType = null);
public Task<T> CaptureSpanAsync<T>(string name, string type, Func<ICustomSpan, Task<T>> func, string? subType = null);
public Task<T> CaptureSpanAsync<T>(string name, string type, Func<Task<T>> func, string? subType = null);
public Task CaptureSpanAsync(string name, string type, Func<ICustomSpan,Task> func, string? subType = null);
public Task CaptureSpanAsync(string name, string type, Func<Task> func, string? subType = null);
public void SetTag(string key, string value);
public void CaptureException(Exception exception);
public void CaptureError(string message, string type = "log");
}
public interface ICustomTransaction : ICustomSpan
{
}
Configuración¶
Para configurar Observability en nuestro proyecto, debemos tener las siguientes variables de entorno:
Observability:
ServerUrls: "https://localhost:8200"
ServiceName: "ServiceName"
name | descripción | requerido | default |
---|---|---|---|
ServiceName | El nombre del servicio | sí | |
ServerUrls | La url del servicio | sí | |
Environment | La variable de entorno. Opciones Development, QA, Production. | no | Development |
ServiceVersion | La versión del servicio | no | 1.0 |
Enabled | Es para activar o desactivar la observabilidad | no | true |
Token | El token para conectarse al servicio en caso de ser necesario autenticarse. | no | "" |
Luego en nuestro archivo Program
, debemos utilizar los métodos AddObservability()
y UseObservability()
de la siguiente manera:
var builder = WebApplication.CreateBuilder(args);
builder.Host.ConfigureAndreaniWebHost(args)
.AddObservability();
//...
var app = builder.Build();
app.UseObservability();
app.ConfigureAndreani();
//...
app.Run();
Metodos de configuración¶
-AddObservability()
: Este método se encarga de inyectar las interfaces y configurar el proveedor, por ejemplo, ElasticApm
si utilizamos la librería Andreani.Arq.Observability.ElasticApm
.
-UseObservability()
: Este método se encarga de agregar configuraciones extras y necesarias para la conexión con el proveedor.
-Metodos que extienden de AddObservability()
:
-
.WithMongo()
: Este método se encarga de configurar la observabilidad enMongo
, en caso de que estemos utilizando la libreríaAndreani.Arq.Mongo
en nuestro proyecto.var builder = WebApplication.CreateBuilder(args); builder.Host.ConfigureAndreaniWebHost(args) .AddObservability() .WithMongo(); //... var app = builder.Build(); app.UseObservability(); app.ConfigureAndreani(); //... app.Run();
-
.WithRedis()
: Este método se encarga de configurar la observabilidad enRedis
en caso de que estemos utilizando la libreríaAndreani.Arq.Redis
en nuestro proyecto.var builder = WebApplication.CreateBuilder(args); builder.Host.ConfigureAndreaniWebHost(args) .AddObservability() .WithRedis(); //... var app = builder.Build(); app.UseObservability(); app.ConfigureAndreani(); //... app.Run();
Uso de Prometheus con Opentelemtry
En el caso de Andreani.Arq.Observability.Opentelemetry
se debe agregar app.MapPrometheusScrapingEndpoint()
después del app.ConfigureAndreani()
, esto se agrega para mapear el endpoint al que Prometheus arroja las metricas.
Transacciones¶
Como crear Transacciones¶
Para crear una nueva transacción, podemos utilizar el método StarTransaction()
o CaptureTransaction()
de la interfaz ICustomTracer
. Primero, debemos crear una variable de tipo ICustomTracer
y recibirla en el constructor de nuestro handler. A continuación, se muestra un ejemplo de cómo usarlo:
StarTransaction()
:¶
public record struct ListPerson : IRequest<Response<List<Person>>>
{
}
public class ListPersonHandler : IRequestHandler<ListPerson, Response<List<Person>>>
{
private readonly IMongoRepository<Person> _query;
private readonly ICustomTracer _tracer;
public ListPersonHandler(IMongoRepository<Person> query, ICustomTracer tracer)
{
_query = query;
_tracer = tracer;
}
public async Task<Response<List<Person>>> Handle(ListPerson request, CancellationToken cancellationToken)
{
List<Person> result= new();
var trans = _tracer.StartTransaction(name:"ListPerson", type:"query");
try
{
result = await _query.GetAllAsync();
}
catch (Exception ex)
{
trans.CaptureException(ex);
}
finally
{
trans.End();
}
return new Response<List<Person>>
{
Content = result,
StatusCode = System.Net.HttpStatusCode.OK
};
}
}
Warning
Siempre que se utilice **`StarTransaction()`**, se debe cerrar la transacción con el método **`End()`**
Tip
Como se muestra en el ejemplo, podemos utilizar **`CaptureException()`** en caso de que el código genere una excepción para capturarla en la transacción.
CaptureTransaction()
:¶
public record struct ListPerson : IRequest<Response<List<Person>>>
{
}
public class ListPersonHandler : IRequestHandler<ListPerson, Response<List<Person>>>
{
private readonly IMongoRepository<Person> _query;
private readonly ICustomTracer _tracer;
public ListPersonHandler(IMongoRepository<Person> query, ICustomTracer tracer)
{
_query = query;
_tracer = tracer;
}
public async Task<Response<List<Person>>> Handle(ListPerson request, CancellationToken cancellationToken)
{
List<Person> result= await _tracer.CaptureTransaction(name:"ListPerson", type:"query",
async ()=>
{
return await _query.GetAllAsync();
});
return new Response<List<Person>>
{
Content = result,
StatusCode = System.Net.HttpStatusCode.OK
};
}
}
Seguimiento de la transacción¶
Esta funcionalidad está diseña para cuando queremos continuar la transacción en un sistema externo.
En ICustomTransaction
, tenemos la propiedad Links
, que guarda los datos de la transacción en un string
para realizar un seguimiento de la misma en varias APIs o servicios.
var links= _tracer.GetCurrentTransaction().Links
Luego, pasamos el string a la transacción utilizando el método StartTransaction(string name, string type, string? links = null)
o CaptureTransaction
, ambos métodos tienen el parámetro opcional string? links
.
var transaction2 = _tracer.StartTransaction("Transaction2", "request",links);
Spans¶
Como crear un span¶
Para crear un nuevo span, podemos utilizar el método StarSpan()
o CaptureSpan()
de la interfaz ICustomTransaction
o ICustomSpan
. Podemos utilizar ICustomTracer
para crear una transacción, como se mostró anteriormente. Aquí hay un ejemplo de cómo usar ambas funciones:
StarSpan()
:¶
public record struct ListPerson : IRequest<Response<List<Person>>>
{
}
public class ListPersonHandler : IRequestHandler<ListPerson, Response<List<Person>>>
{
private readonly IMongoRepository<Person> _query;
private readonly ICustomTracer _tracer;
public ListPersonHandler(IMongoRepository<Person> query, ICustomTracer tracer)
{
_query = query;
_tracer = tracer;
}
public async Task<Response<List<Person>>> Handle(ListPerson request, CancellationToken cancellationToken)
{
List<Person> result= new();
var trans = _tracer.StartTransaction(name:"ListPerson", type:"query");
var span = trans.StartSpan("GetAllPerson");
try
{
result = await _query.GetAllAsync();
}
catch (Exception ex)
{
span.CaptureException(ex);
trans.CaptureException(ex);
}
finally
{
span.End();
trans.End();
}
return new Response<List<Person>>
{
Content = result,
StatusCode = System.Net.HttpStatusCode.OK
};
}
}
Warning
Siempre que se utilice **`StarSpan()`**, se debe cerrar el span con el método **`End()`**
Tip
Como se muestra en el ejemplo, podemos utilizar **`CaptureException()`** en caso de que el código genere una excepción para capturarla en la transacción.
CaptureSpan()
:¶
public record struct ListPerson : IRequest<Response<List<Person>>>
{
}
public class ListPersonHandler : IRequestHandler<ListPerson, Response<List<Person>>>
{
private readonly IMongoRepository<Person> _query;
private readonly ICustomTracer _tracer;
public ListPersonHandler(IMongoRepository<Person> query, ICustomTracer tracer)
{
_query = query;
_tracer = tracer;
}
public async Task<Response<List<Person>>> Handle(ListPerson request, CancellationToken cancellationToken)
{
List<Person> result= await _tracer.CaptureSpan(name:"GetAllPerson","Internal",
async ()=>
{
return await _query.GetAllAsync();
});
return new Response<List<Person>>
{
Content = result,
StatusCode = System.Net.HttpStatusCode.OK
};
}
}
Tags¶
Las etiquetas (Atributos) permiten adjuntar pares clave/valor a un archivo para que contenga más información sobre la operación actual de la que se realiza el seguimiento.
Atributos¶
Los atributos son pares clave-valor que contienen metadatos que se pueden usar para anotar un Span para llevar información sobre la operación que está rastreando.
Por ejemplo, si un intervalo realiza un seguimiento de una operación que agrega un elemento a la propiedad de un usuario carrito de compras en un sistema de comercio electrónico, puede capturar la identificación del usuario, la identificación de el artículo que se va a añadir al carrito y el ID del carrito.
Podemos agregar tags(labels) a una transacción o un span utilizando el método SetTag(string key, string value)
.
Transacciones:¶
var trans = _tracer.GetCurrentTransaction();
trans.SetTag("TagKey", "TagValue");
Spans:¶
var span = _tracer.GetCurrentSpan();
span.SetTag("TagKey", "TagValue");
Captura de errores¶
Podemos capturar errores en una transacción o un span utilizando el método CaptureError(string message, string type = "log")
Transacciones:¶
var trans = _tracer.GetCurrentTransaction();
trans.CaptureError("Error message");
Spans:¶
var span = _tracer.GetCurrentSpan();
span.CaptureError("Error message");
Para más ejemplos de como utilizar Andreani.Arq.Observability.OpenTelemetry
o Andreani.Arq.Observability.ElasticApm
, visete lo ejemplos de platform-net examples.