Un sistema que no se puede observar es un sistema que no se puede operar. NLog lleva más de 20 años siendo uno de los frameworks de logging más maduros del ecosistema .NET y, integrado con la infraestructura de telemetría moderna, sigue siendo una elección sólida para aplicaciones empresariales que necesitan control fino sobre dónde, cómo y qué se registra.
¿Por qué NLog en 2025?
Con opciones como Serilog, Microsoft.Extensions.Logging nativo y OpenTelemetry, vale la pena preguntarse dónde encaja NLog hoy. La respuesta está en tres fortalezas que mantiene únicas: su sistema de targets extensible con más de 80 destinos oficiales, su configuración en XML o JSON sin recompilación (ideal para equipos de operaciones), y su rendimiento de logging asíncrono que minimiza el impacto en el hilo principal de la aplicación.
En escenarios empresariales guatemaltecos y latinoamericanos donde los equipos de infraestructura gestionan la configuración separada del equipo de desarrollo, esa separación de responsabilidades es especialmente valiosa.
Niveles de log: la base de una estrategia de observabilidad
NLog define seis niveles en orden de severidad creciente. Usarlos correctamente es la diferencia entre logs útiles y ruido que nadie lee:

Regla práctica: En producción, configura el nivel mínimo en Info. Reserva Debug y Trace para ambientes de desarrollo o diagnóstico activo. Cambiar el nivel sin redesplegar es una de las ventajas clave de NLog con configuración externa.
Instalación e integración con ASP.NET Core
NLog se integra nativamente con Microsoft.Extensions.Logging, lo que significa que funciona con la abstracción estándar de .NET sin acoplar tu código al framework de logging específico:
# Paquetes principales dotnet add package NLog.Web.AspNetCore dotnet add package NLog.Extensions.Logging # Targets adicionales según necesidad dotnet add package NLog.Targets.Seq # Seq dotnet add package NLog.Targets.ElasticSearch # Elasticsearch dotnet add package NLog.Loki # Grafana Loki
C# Program.cs
using NLog.Web; var builder = WebApplication.CreateBuilder(args); // Integrar NLog como proveedor de ILogger<T> builder.Logging.ClearProviders(); builder.Host.UseNLog(); var app = builder.Build(); app.Run();
Con esto, cualquier ILogger<T> inyectado en tus servicios usa NLog internamente sin cambios en el código de negocio.
Configuración con nlog.config
El archivo nlog.config define targets (destinos) y rules (reglas de enrutamiento). Esta separación permite que operaciones ajuste el comportamiento en producción sin tocar el código:
<?xml version="1.0" encoding="utf-8"?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
autoReload="true"
throwConfigExceptions="true">
<extensions>
<add assembly="NLog.Web.AspNetCore"/>
<add assembly="NLog.Loki"/>
</extensions>
<targets async="true">
<!-- Consola con layout estructurado -->
<target xsi:type="Console"
name="consola"
layout="${longdate} [${level:uppercase=true}] ${logger:shortName=true} | ${message} ${exception:format=tostring}"/>
<!-- Archivo rotativo por día, retención 30 días -->
<target xsi:type="File"
name="archivo"
fileName="logs/app-${shortdate}.log"
archiveEvery="Day"
archiveNumbering="Date"
maxArchiveFiles="30"
layout="${longdate}|${level}|${logger}|${message}|${exception:format=tostring}"/>
<!-- JSON estructurado para Grafana Loki -->
<target xsi:type="Loki"
name="loki"
endpoint="http://loki:3100">
<label name="app" layout="sistema-ventas"/>
<label name="env" layout="${environment:ASPNETCORE_ENVIRONMENT}"/>
<label name="nivel" layout="${level}"/>
</target>
</targets>
<rules>
<!-- Silenciar ruido de Microsoft y System -->
<logger name="Microsoft.*" maxlevel="Info" final="true"/>
<logger name="System.*" maxlevel="Info" final="true"/>
<!-- App: consola + archivo en Debug, Loki solo en Info+ -->
<logger name="SistemaVentas.*" minlevel="Debug"
writeTo="consola,archivo"/>
<logger name="SistemaVentas.*" minlevel="Info"
writeTo="loki"/>
</rules>
</nlog>
autoReload=”true”: NLog detecta cambios en nlog.config en caliente y los aplica sin reiniciar la aplicación. En producción esto permite pasar temporalmente de Info a Debug para diagnosticar un incidente y volver sin downtime.
Logs estructurados en C#
Los logs estructurados permiten filtrar y consultar por propiedades en lugar de parsear texto plano. NLog soporta el estándar de message templates compartido con Serilog:
public class PedidoService(ILogger<PedidoService> logger,
TiendaDbContext db)
{
public async Task<PedidoDto> CrearPedidoAsync(
CrearPedidoRequest request,
CancellationToken ct = default)
{
// Log estructurado: {ClienteId} y {CantidadItems} se almacenan
// como propiedades consultables, no solo como texto
logger.LogInformation(
"Iniciando creación de pedido para cliente {ClienteId} con {CantidadItems} items",
request.ClienteId, request.Items.Count);
try
{
var pedido = await ProcesarPedidoAsync(request, ct);
logger.LogInformation(
"Pedido {PedidoId} creado exitosamente. Total: Q{Total:N2}",
pedido.Id, pedido.Total);
return pedido.ToDto();
}
catch (StockInsuficienteException ex)
{
// Warning: problema de negocio, no fallo del sistema
logger.LogWarning(
"Stock insuficiente para producto {ProductoNombre}. "
+ "Solicitado: {Solicitado}, Disponible: {Disponible}",
ex.ProductoNombre, ex.CantidadSolicitada, ex.StockActual);
throw;
}
catch (Exception ex)
{
// Error: fallo inesperado con contexto completo
logger.LogError(ex,
"Error al crear pedido para cliente {ClienteId}",
request.ClienteId);
throw;
}
}
}
Contexto con Mapped Diagnostic Context (MDC)
El MDC permite enriquecer automáticamente todos los logs de un request con propiedades de contexto como el ID de correlación, el usuario autenticado o el tenant en aplicaciones multi-tenant. Se configura una sola vez en un middleware:
public class LogContextMiddleware(RequestDelegate next)
{
public async Task InvokeAsync(HttpContext context)
{
// Obtener o generar ID de correlación
var correlationId = context.Request.Headers["X-Correlation-Id"]
.FirstOrDefault() ?? Guid.NewGuid().ToString("N")[..12];
var usuario = context.User.Identity?.Name ?? "anonimo";
var tenantId = context.User.FindFirst("tenant_id")?.Value ?? "default";
// Establecer propiedades en el MDC — disponibles en todo el request
using (MappedDiagnosticsLogicalContext.SetScoped("CorrelationId", correlationId))
using (MappedDiagnosticsLogicalContext.SetScoped("Usuario", usuario))
using (MappedDiagnosticsLogicalContext.SetScoped("TenantId", tenantId))
{
context.Response.Headers["X-Correlation-Id"] = correlationId;
await next(context);
}
}
}
// Registrar en Program.cs
app.UseMiddleware<LogContextMiddleware>();
Luego en el layout de NLog incluyes esas propiedades automáticamente en cada línea:
<target xsi:type="File" name="archivo"
layout="${longdate} [${level:uppercase=true}]
[corr:${mdlc:CorrelationId}]
[user:${mdlc:Usuario}]
[tenant:${mdlc:TenantId}]
${logger:shortName=true} | ${message}
${exception:format=tostring}"/>
Targets disponibles: destinos para cada escenario

Integración con OpenTelemetry
Para sistemas que ya usan OpenTelemetry como estándar de telemetría, NLog puede actuar como productor de logs que se inyectan en el pipeline OTLP, unificando logs, trazas y métricas bajo el mismo backend (Grafana, Jaeger, Azure Monitor, etc.):
C#Program.cs — NLog + OpenTelemetry
builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddOtlpExporter())
.WithMetrics(metrics => metrics
.AddAspNetCoreInstrumentation()
.AddOtlpExporter())
.WithLogging(logging => logging
.AddOtlpExporter()); // Los logs de NLog fluyen aquí también
// NLog enriquece los logs con TraceId y SpanId de OpenTelemetry
// automáticamente cuando detecta Activity.Current activo
builder.Host.UseNLog();
Con esta configuración, cada log generado por NLog incluye automáticamente el TraceId y SpanId de la traza activa. En Grafana puedes ir de un log de error directamente a la traza distribuida que lo produjo, sin correlación manual.

Performance: logging asíncrono y buffering
El wrapper async=”true” en el bloque <targets> pone una cola en memoria frente a cada target real, desacoplando la escritura del hilo de la aplicación. Para escenarios de alto volumen, puedes ajustar el buffer:
<!-- BufferingWrapper: acumula N mensajes antes de escribir en batch -->
<target xsi:type="BufferingWrapper"
name="loki-buffer"
bufferSize="100"
flushTimeout="5000"
overflowAction="Flush">
<target xsi:type="Loki"
name="loki"
endpoint="http://loki:3100"/>
</target>
<!-- FallbackGroup: si Loki falla, escribe en archivo como respaldo -->
<target xsi:type="FallbackGroup"
name="resiliente"
returnToFirstOnSuccess="true">
<target refName="loki-buffer"/>
<target refName="archivo"/>
</target>
Importante en Kubernetes: En pods con almacenamiento efímero, el target de archivo debe escribir en un volumen persistente o en un sidecar de colección de logs (Fluent Bit). El FallbackGroup es tu red de seguridad cuando el backend de logs no está disponible transitoriamente.
Health check del pipeline de logging
Para verificar que el pipeline de NLog esté operativo como parte de los health checks estándar de ASP.NET Core:
public class NLogHealthCheck : IHealthCheck
{
public Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken ct = default)
{
var config = LogManager.Configuration;
if (config is null)
return Task.FromResult(
HealthCheckResult.Unhealthy("NLog no tiene configuración cargada"));
var targetsActivos = config.AllTargets.Count;
return Task.FromResult(targetsActivos > 0
? HealthCheckResult.Healthy($"NLog operativo con {targetsActivos} targets")
: HealthCheckResult.Degraded("NLog sin targets activos"));
}
}
// Registrar en Program.cs
builder.Services.AddHealthChecks()
.AddCheck<NLogHealthCheck>("nlog-pipeline");
NLog vs Serilog: ¿cuál elegir?
Ambos son excelentes y producción-ready. La elección depende del contexto. NLog es la opción natural cuando el equipo de operaciones necesita control sobre la configuración sin recompilar (XML/JSON externo), cuando se migra una aplicación legacy que ya usa NLog, o cuando se requieren targets muy específicos del catálogo oficial. Serilog brilla cuando se prefiere configuración fluida en C# y la integración con el ecosistema de sinks de .NET moderno es prioritaria.
En la práctica, ambos soportan logs estructurados, OpenTelemetry y los mismos backends. El criterio más importante es la consistencia dentro del equipo, no la superioridad técnica de uno sobre el otro.
NLog sigue siendo una apuesta sólida para observabilidad en .NET en 2025. Su combinación de configuración externa sin recompilación, logging asíncrono de alto rendimiento, targets extensibles y compatibilidad nativa con OpenTelemetry lo hace especialmente adecuado para aplicaciones empresariales donde operaciones y desarrollo son equipos separados. Implementar una estrategia de logging disciplinada, con niveles correctos, logs estructurados y correlación de contexto, es la diferencia entre diagnosticar un incidente en minutos o en horas.
Ingeniero de Software, docente universitario, con más de 13 años de experiencia en el área de ingeniería y arquitectura de software utilizando plataformas y tecnologías como C#, .NET Framework, .NET Core, Java, WCF, Spring Framework, Angular, Android, Ionic Framework, Amazon AWS/S3, Azure, Google Cloud Platform Digital Ocean, Linux, PostgreSQL, Oracle, SQL Server, entre otras.
Apasionado por las nuevas tecnologías, autodidacta y músico ocasional.