Registro en aplicaciones C#: mejores prácticas y herramientas (.NET)
El registro no es solo escribir mensajes en un archivo. Es un componente esencial de cualquier aplicación de producción en C#. Sin un sistema de registro de calidad, trabajas a ciegas: los errores pasan desapercibidos, el rendimiento es difícil de analizar y la depuración se convierte en un juego de adivinanzas. En este artículo, exploraremos las mejores prácticas de registro en el ecosistema .NET, revisaremos bibliotecas populares (Serilog, NLog), los niveles de registro correctos y un enfoque estructurado para la recopilación de datos.
1. Elección de la biblioteca de registro: Serilog vs NLog
En el mundo .NET existen varias soluciones maduras. Las dos más populares son Serilog y NLog. Ambas admiten registro asíncrono, múltiples sinks (destinos de salida) e integración con .NET Core/5+ a través de ILogger.
Serilog
Serilog es conocido por su filosofía de registro estructurado. En lugar de cadenas simples, pasas objetos y Serilog los serializa a JSON. Esto es ideal para sistemas como Elasticsearch, Seq o Datadog.
// Ejemplo Serilogusing Serilog;
Log.Logger = new LoggerConfiguration() .MinimumLevel.Information() .WriteTo.Console() .WriteTo.File("logs/app-.log", rollingInterval: RollingInterval.Day) .CreateLogger();
Log.Information("Usuario {UserId} inició sesión en {Time}", userId, DateTime.UtcNow);NLog
NLog es una herramienta más tradicional, pero igualmente potente. Es ideal para proyectos que necesitan una configuración flexible a través de XML o código. Admite rotación de archivos, envío por correo electrónico e integración con bases de datos.
// Ejemplo NLogusing NLog;
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
Logger.Info("Usuario {UserId} realizó una acción", userId);¿Cuándo elegir qué? Si estás construyendo microservicios o usando el stack ELK, elige Serilog. Si necesitas una configuración sencilla en proyectos legacy, elige NLog. En cualquier caso, ambos admiten el estándar ILogger de Microsoft.Extensions.Logging.
2. Niveles de registro: cuándo y qué escribir
Uno de los errores más comunes es escribir todo en el nivel Information o, por el contrario, poner Debug en todas partes. Usa los niveles correctos:
- Trace (0) — información extremadamente detallada: entrada/salida de métodos, valores de variables. Se usa solo durante la depuración.
- Debug (1) — mensajes de depuración útiles para el desarrollador, pero no para el operador.
- Information (2) — eventos de negocio: "pedido creado", "usuario registrado". Este es el nivel de trabajo principal.
- Warning (3) — situación anómala que no rompe la aplicación: tiempo de espera de solicitud, reintento de conexión.
- Error (4) — excepciones manejadas, errores de lógica de negocio, fallos en el funcionamiento de un módulo.
- Critical (5) — errores fatales: pérdida de conexión con la base de datos, falta de memoria, caída de toda la aplicación.
// Ejemplo de uso correcto de nivelespublic async Task<Order> CreateOrderAsync(CreateOrderRequest request){ Log.Debug("Inicio de creación de pedido para el usuario {UserId}", request.UserId);
try { var order = new Order(request); await _repository.SaveAsync(order); Log.Information("Pedido {OrderId} creado por el usuario {UserId}", order.Id, request.UserId); return order; } catch (DbException ex) { Log.Error(ex, "Error de BD al crear pedido para el usuario {UserId}", request.UserId); throw; // Relanzamos hacia arriba }}3. Registro estructurado: ¿por qué es importante?
El registro tradicional a través de cadenas de texto simples tiene limitaciones. Con el registro estructurado, cada entrada de registro es un objeto con propiedades con nombre. Esto permite:
- Búsqueda eficiente — puedes encontrar fácilmente todos los errores para un usuario específico o transacción.
- Análisis automatizado — herramientas como Elasticsearch, Seq o Datadog indexan campos y crean paneles.
- Contexto enriquecido — cada entrada contiene no solo el mensaje, sino también metadatos: ID de correlación, nombre del método, versión de la aplicación.
// Ejemplo de registro estructurado con SerilogLog.Information("Pedido {OrderId} procesado. Total: {Total:C}, Método de pago: {PaymentMethod}", order.Id, order.Total, order.PaymentMethod);
// En el archivo de registro (JSON):// {\"@t\":\"2024-01-15T10:30:00Z\",\"@mt\":\"Pedido {OrderId} procesado...\",\"OrderId\":123,\"Total\":150.00,\"PaymentMethod\":\"Tarjeta\"}4. Configuración de sinks: dónde almacenar los registros
Un sink es el destino final de tus registros. Las opciones populares en .NET:
| Sink | Descripción | Cuándo usar |
|---|---|---|
| Consola | Salida a stdout/stderr | Desarrollo, contenedores Docker |
| Archivo | Escritura en archivos de texto con rotación | Aplicaciones locales, servidores simples |
| Seq | Servidor centralizado de registros con interfaz web | Equipos pequeños y medianos |
| Elasticsearch | Almacenamiento en el stack ELK | Grandes sistemas, análisis complejos |
| Application Insights | Integración con Azure Monitor | Aplicaciones en la nube de Azure |
// Ejemplo de configuración de múltiples sinks en SerilogLog.Logger = new LoggerConfiguration() .MinimumLevel.Information() .WriteTo.Console() .WriteTo.File("logs/app-.log", rollingInterval: RollingInterval.Day) .WriteTo.Seq("http://localhost:5341") .CreateLogger();5. Registro asíncrono y rendimiento
El registro no debe ralentizar tu aplicación. Tanto Serilog como NLog admiten escritura asíncrona. En Serilog, usa el wrapper Async:
// Registro asíncrono en SerilogLog.Logger = new LoggerConfiguration() .WriteTo.Async(a => a.File(\"logs/app-.log\", rollingInterval: RollingInterval.Day)) .CreateLogger();En NLog, la asincronía se configura a través de async en el archivo de configuración:
<targets async=\"true\"> <target name=\"file\" xsi:type=\"File\" fileName=\"logs/app.log\" /></targets>Importante: No uses el registro en bucles críticos para el rendimiento. Si necesitas registrar cada iteración, usa Log.Verbose o Log.Trace y desactívalos en producción.
6. Enriquecimiento de contexto: añade significado a los registros
Los registros simples sin contexto son difíciles de analizar. Usa enriquecedores para añadir automáticamente información útil:
// Ejemplo de enriquecimiento en SerilogLog.Logger = new LoggerConfiguration() .Enrich.WithMachineName() .Enrich.WithThreadId() .Enrich.WithProperty("Application", "MyApp") .WriteTo.Console() .CreateLogger();
// Cada entrada de registro ahora contendrá:// {MachineName: \"SRV-01\", ThreadId: 42, Application: \"MyApp\"}En NLog, el enriquecimiento se configura a través de layout renderers:
${machinename} ${threadid} ${appdomain:format={1}}7. Integración con ILogger de Microsoft
La mayoría de las aplicaciones modernas en .NET usan la abstracción ILogger<T> de Microsoft. Tanto Serilog como NLog se integran fácilmente:
// Program.cs (Serilog)builder.Host.UseSerilog((context, config) =>{ config.ReadFrom.Configuration(context.Configuration);});
// En el controladorpublic class OrderController : ControllerBase{ private readonly ILogger<OrderController> _logger;
public OrderController(ILogger<OrderController> logger) { _logger = logger; }
public async Task<IActionResult> CreateOrder(CreateOrderRequest request) { _logger.LogInformation("Creando pedido para el usuario {UserId}", request.UserId); // ... lógica }}8. Errores comunes en el registro
- Registrar datos sensibles — contraseñas, tokens, números de tarjetas de crédito. Usa filtros o enmascáralos.
- Demasiados registros — el nivel Debug en producción puede generar gigabytes de datos por hora.
- Falta de contexto — registrar "Error al guardar" sin el ID del objeto o la traza de la pila es inútil.
- Ignorar excepciones — registrar solo el mensaje sin la excepción pierde información valiosa.
- Formato inconsistente — mezclar diferentes formatos de fecha, niveles o estructuras de mensajes.
// Ejemplo de registro incorrectoLog.Error("Error al guardar el pedido"); // ❌ Sin contexto
// Ejemplo de registro correctoLog.Error(ex, "Error al guardar el pedido {OrderId} para el usuario {UserId}", orderId, userId); // ✅Conclusión
El registro en aplicaciones C# no es un lujo, sino una necesidad. Usar bibliotecas modernas como Serilog o NLog, niveles de registro correctos y un enfoque estructurado te ayudará a:
- Encontrar y corregir errores rápidamente
- Analizar el rendimiento de la aplicación
- Cumplir con los requisitos de auditoría
- Mantener la cordura durante la depuración nocturna
Comienza con una configuración simple, añade sinks gradualmente y recuerda: el mejor registro es el que te da respuestas, no solo datos.