Dinaup.Logs
Dinaup.Logs para .NET: guía de inicio, buenas prácticas y ejemplos
Dinaup.Logs es el módulo de observabilidad de la librería Dinaup que unifica logs, correlación y métricas en tus aplicaciones .NET. Con pocas líneas consigues
Logs estructurados con contexto (Component, Action).
Correlación distribuida (CorrelationId) para rastrear un flujo entre servicios.
Métricas listas para exportar (counters e histogramas) y envío opcional a OpenSearch.
Helpers para envolver código con medición de tiempo, manejo de errores y logs coherentes.
Requisitos
Dinaup .NET
Antes de empezar necesitarás instalar el paquete Dinaup.
Open Search
OpenSearch gestionado (recomendado): OVHcloud Para centralizar y consultar tus logs y métricas, recomendamos contratar OpenSearch gestionado de OVHcloud.
Servicio administrado, con TLS y autenticación integrados.
Escalado sencillo y precio competitivo para dev y producción.
Centros de datos en Europa (cumplimiento y residencia de datos).
Inicio
var openSearchConfig = new Logs.OpenSearchConfig
{
Endpoint = "***",
Username = "***",
Password = "***"
};
Logs.Initialize(
applicationName: "MiApp",
applicationVersion: "1.0.0",
logFilePath: "logs/app.log",
openSearchConfig: openSearchConfig,
environment: "Production",
autoExportContextMetric: true
);
Logs.SetLoggingLevel(LogEventLevel.Debug);Antes de detener la aplicación llama a CloseAndFlush para enviar los datos pendientes
Logs.CloseAndFlush();
var applicationName = "MiApp";
var applicationVersion = "1.0.0";
var environmentName = "Dev";
var logFilePath = "logs/app.log";
Logs.Initialize(
applicationName,
applicationVersion,
logFilePath: logFilePath,
openSearchConfig: null,
environment: environmentName,
autoExportContextMetric: true // activa métrica automática por contexto
);
// Ajusta el nivel en caliente si lo necesitas (Debug/Information/Warning/Error)
Logs.SetLoggingLevel(LogEventLevel.Information);Antes de detener la aplicación llama a CloseAndFlush para enviar los datos pendientes
Logs.CloseAndFlush();Logs básicos y estructurados
Logs.Information("Usuario {UserId} inició sesión", userId);
Logs.Warning("Intentos de acceso fallidos {Count} para {User}", attempts, userEmail);
Logs.Error("Error al procesar pedido {OrderId}: {Message}", orderId, ex.Message);public sealed class StripeService
{
public async Task<string> CreateInvoice(Guid orderId)
{
using (Logs.BeginContext(nameof(StripeService), nameof(CreateInvoice)))
{
Logs.Debug("Generando factura para {OrderId}", orderId);
await Task.Delay(10); // tu lógica real aquí
var invoiceId = $"inv_{Guid.NewGuid():N}";
Logs.Information("Factura {InvoiceId} creada para {OrderId}", invoiceId, orderId);
return invoiceId;
}
}
}
public sealed class JobWorker
{
public async Task RunAsync(IEnumerable<Guid> orderIds, string? correlationId = null)
{
correlationId ??= $"corr-{Guid.NewGuid():N}";
using (Logs.BeginCorrelationContext(nameof(JobWorker), nameof(RunAsync), correlationId))
{
Logs.Information("Procesando {Count} pedidos", orderIds.Count());
foreach (var orderId in orderIds)
{
Logs.Debug("Pedido {OrderId}", orderId);
await Task.Delay(5); // tu lógica real aquí
}
Logs.Information("Lote completado");
}
}
}Métricas
var counter = new Logs.CounterMetric(
metricName: "pagos_ok",
unit: "hit",
initial: 0.0m,
tags: new Dictionary<string, string> { { "status", "ok" }, { "component", "payments" } }
);
counter.Increment(); // +1
counter.Increment(5.0m); // +5
// Fuerza exportación (y reseteo del contador a 0)
Logs.MetricExporter.Flush();var tags = new Dictionary<string, string>
{
{ "component", "web" },
{ "endpoint", "/api/orders" }
};
var buckets = new List<Tuple<double, double>>
{
Tuple.Create(0.0, 100.0),
Tuple.Create(100.0, 200.0),
Tuple.Create(200.0, 500.0)
};
using (var hist = new Logs.HistogramMetric("response_time_ms", "ms", tags, buckets))
{
hist.Record(75); // cae en bucket 0-100
hist.Record(150); // cae en bucket 100-200
hist.Record(180); // 100-200
}
// Si tienes export automático a OpenSearch, se enviará junto a los logs.Helpers de ejecución
HandleAction y HandleActionAsync envuelven tu código con try/catch, cronómetro y logs coherentes, devolviendo un resultado con:
IsOk: indica éxito.
MessageException: detalle del error si falló.
var result = Logs.HandleAction(
component: nameof(PagosService),
action: nameof(PagosService.Cobrar),
actionToRun: () =>
{
// Tu lógica
ProcesarCobro(pedidoId);
},
details: new { PedidoId = pedidoId }
);
if (!result.IsOk)
{
// Manejo de error
Logs.Error("No se pudo cobrar pedido {PedidoId}: {Error}", pedidoId, result.MessageException);
}var result = await Logs.HandleActionAsync(
component: nameof(ServicioAsync),
action: nameof(ServicioAsync.ProcesarAsync),
actionToRunAsync: async () =>
{
await Task.Delay(10);
await ProcesarAsync();
},
details: new { Correlation = correlationId }
);
if (!result.IsOk)
{
Logs.Error("Proceso async falló: {Error}", result.MessageException);
}Middleware de correlación (idea):
app.Use(async (context, next) =>
{
var componentName = "HttpRequest";
var actionName = context.Request.Path.Value ?? "/";
var correlationId = context.Request.Headers["X-Correlation-Id"].FirstOrDefault()
?? $"corr-{Guid.NewGuid():N}";
using (Logs.BeginCorrelationContext(componentName, actionName, correlationId))
{
await next();
}
});Última actualización