Registro en C# y .NET

.NET admite un registro estructurado de alto rendimiento a través de la API ILogger para ayudar a supervisar el comportamiento de la aplicación y diagnosticar problemas. Los registros se pueden escribir en distintos destinos mediante la configuración de diferentes proveedores de registro. Los proveedores de registro básicos están integrados y también hay muchos proveedores de terceros disponibles.

Introducción

En este primer ejemplo, se muestran los aspectos básicos, pero esto solo es adecuado para una aplicación de consola básica. En la siguiente sección verá cómo mejorar el código teniendo en cuenta la escala, el rendimiento, la configuración y los patrones de programación típicos.

using Microsoft.Extensions.Logging;

using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole());
ILogger logger = factory.CreateLogger("Program");
logger.LogInformation("Hello World! Logging is {Description}.", "fun");

Ejemplo anterior:

  • Crea una interfaz ILoggerFactory. El ILoggerFactory almacena toda la configuración que determina dónde se envían los mensajes de registro. En este caso, se configura el proveedor de registro de la consola para que los mensajes de registro se escriban en la consola.
  • Crea un ILogger con una categoría denominada "Programa". La categoría es un string asociado a cada mensaje registrado por el objeto ILogger. Se usa para agrupar los mensajes de registro de la misma clase (o categoría) al buscar o filtrar registros.
  • Llama a LogInformation para realizar el registro en el nivel Information. El nivel de registro indica la gravedad del evento registrado y se usa para filtrar los mensajes de registro menos importantes. La entrada de registro también incluye una plantilla de mensaje"Hello World! Logging is {Description}." y un par clave-valor Description = fun. El nombre de clave (o marcador de posición) procede de la palabra dentro de las llaves de la plantilla y el valor procede del argumento del método restante.

El archivo de proyecto de este ejemplo incluye dos paquetes NuGet:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
  </ItemGroup>

</Project>

Sugerencia

Todo el código fuente del ejemplo de registro está disponible en el Explorador de ejemplos para su descarga. Para obtener más información, consulte Examinación de ejemplos de código: registro en .NET.

Inicio de sesión en una aplicación no trivial

Hay varios cambios que debería considerar hacer en el ejemplo anterior cuando se registre en un escenario menos trivial:

  • Si su aplicación usa Inserción de dependencias (ID) o un host como WebApplication de ASP.NET o Generic Host de ASP.NET, deberá usar ILoggerFactory y objetos ILogger de sus respectivos contenedores de DI en lugar de crearlos directamente. Para obtener más información, consulte Integración con DI y hosts.

  • El registro de generación de origen en tiempo de compilación suele ser una alternativa mejor a los métodos de extensión ILogger, como LogInformation. La generación de orígenes de registro ofrece un mejor rendimiento, una tipificación más fuerte y evita la propagación de constantes string en todos los métodos. El inconveniente es que el uso de esta técnica requiere un poco más de código.

using Microsoft.Extensions.Logging;

internal partial class Program
{
    static void Main(string[] args)
    {
        using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole());
        ILogger logger = factory.CreateLogger("Program");
        LogStartupMessage(logger, "fun");
    }

    [LoggerMessage(Level = LogLevel.Information, Message = "Hello World! Logging is {Description}.")]
    static partial void LogStartupMessage(ILogger logger, string description);
}
  • La práctica recomendada para los nombres de categoría de registro es usar el nombre completo de la clase que crea el mensaje de registro. Esto ayuda a relacionar los mensajes de registro con el código que los generó y ofrece un buen nivel de control al filtrar los registros. CreateLogger acepta unType para que la nomenclatura sea fácil de hacer.
using Microsoft.Extensions.Logging;

internal class Program
{
    static void Main(string[] args)
    {
        using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole());
        ILogger logger = factory.CreateLogger<Program>();
        logger.LogInformation("Hello World! Logging is {Description}.", "fun");
    }
}
using Microsoft.Extensions.Logging;
using OpenTelemetry.Logs;

using ILoggerFactory factory = LoggerFactory.Create(builder =>
{
    builder.AddOpenTelemetry(logging =>
    {
        logging.AddOtlpExporter();
    });
});
ILogger logger = factory.CreateLogger("Program");
logger.LogInformation("Hello World! Logging is {Description}.", "fun");

Integración con hosts e inserción de dependencias

Si su aplicación usa Inserción de dependencias (ID) o un host como WebApplication de ASP.NET o Generic Host de ASP.NET, deberá usar ILoggerFactory y objetos ILogger del contenedor de DI en lugar de crearlos directamente.

Obtención de un ILogger de DI

En este ejemplo se obtiene un objeto ILogger en una aplicación hospedada mediante ASP.NET Minimal API mínimas:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton<ExampleHandler>();

var app = builder.Build();

var handler = app.Services.GetRequiredService<ExampleHandler>();
app.MapGet("/", handler.HandleRequest);

app.Run();

partial class ExampleHandler(ILogger<ExampleHandler> logger)
{
    public string HandleRequest()
    {
        LogHandleRequest(logger);
        return "Hello World";
    }

    [LoggerMessage(LogLevel.Information, "ExampleHandler.HandleRequest was called")]
    public static partial void LogHandleRequest(ILogger logger);
}

Ejemplo anterior:

  • Ha creado un servicio singleton denominado ExampleHandler y asignado solicitudes web entrantes para ejecutar la función ExampleHandler.HandleRequest.
  • La línea 8 define un constructor principal para ExampleHandler, una característica agregada en C# 12. El uso del constructor de C# del estilo anterior funcionaría igual de bien, pero es un poco más detallado.
  • El constructor define un parámetro de tipo ILogger<ExampleHandler>. ILogger<TCategoryName> deriva de ILogger e indica la categoría que tiene el objeto ILogger. El contenedor de inserción de dependencias busca una ILogger con la categoría correcta y la proporciona como argumento constructor. Si aún no existe ninguna ILogger con esa categoría, el contenedor de inserción de dependencias lo crea automáticamente desde el ILoggerFactory en el proveedor de servicios.
  • El parámetro logger recibido en el constructor se usó para iniciar sesión en la función HandleRequest.

ILoggerFactory proporcionado por host

Los generadores de hosts inicializan configuración predeterminada y, a continuación, agregan un objeto ILoggerFactory configurado al contenedor de inserción de dependencias del host cuando se compila el host. Antes de compilar el host, puede ajustar la configuración de registro a través HostApplicationBuilder.Logging, WebApplicationBuilder.Loggingo API similares en otros hosts. Los hosts también aplican la configuración de registro de orígenes de configuración predeterminados como appsettings.json y variables de entorno. Para obtener más información, vea Configuración en .NET.

Este ejemplo amplía el anterior para personalizar el ILoggerFactory proporcionado por WebApplicationBuilder. Agrega OpenTelemetry como proveedor de registro que transmite los registros a través del OTLP (protocolo OpenTelemetry):

var builder = WebApplication.CreateBuilder(args);
builder.Logging.AddOpenTelemetry(logging => logging.AddOtlpExporter());
builder.Services.AddSingleton<ExampleHandler>();
var app = builder.Build();

Creación de un ILoggerFactory con inserción de dependencias

Si usa un contenedor de inserción de dependencias sin un host, use AddLogging para configurar y agregue ILoggerFactory al contenedor.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

// Add services to the container including logging
var services = new ServiceCollection();
services.AddLogging(builder => builder.AddConsole());
services.AddSingleton<ExampleService>();
IServiceProvider serviceProvider = services.BuildServiceProvider();

// Get the ExampleService object from the container
ExampleService service = serviceProvider.GetRequiredService<ExampleService>();

// Do some pretend work
service.DoSomeWork(10, 20);

class ExampleService(ILogger<ExampleService> logger)
{
    public void DoSomeWork(int x, int y)
    {
        logger.LogInformation("DoSomeWork was called. x={X}, y={Y}", x, y);
    }
}

Ejemplo anterior:

  • Se ha creado un contenedor de servicio de inserción de dependencias que contiene un ILoggerFactory configurado para escribir en la consola
  • Se ha agregado un singleton ExampleService al contenedor
  • Se ha creado una instancia del ExampleService desde el contenedor de inserción de dependencias que también creó automáticamente un ILogger<ExampleService> que se usará como argumento constructor.
  • Se ha invocado ExampleService.DoSomeWork que usó el ILogger<ExampleService> para registrar un mensaje en la consola.

registro

La configuración de registro se establece en código o a través de orígenes externos, como archivos de configuración y variables de entorno. El uso de la configuración externa es beneficioso siempre que sea posible porque se puede cambiar sin volver a compilar la aplicación. Sin embargo, algunas tareas, como establecer proveedores de registro, solo se pueden configurar desde el código.

Configuración del registro sin código

En el caso de las aplicaciones que usan un host, la configuración de registro se proporciona normalmente en la sección "Logging" de appsettings.{Environment}archivos .json. En el caso de las aplicaciones que no usan un host, los orígenes de configuración externos se configuran explícitamente o se configuran en el código en su lugar.

El siguiente archivo appsettings.Development.json se genera mediante las plantillas de servicio de trabajo de .NET:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  }
}

En el código JSON anterior:

  • Se especifican las categorías de nivel de registro "Default", "Microsoft" y "Microsoft.Hosting.Lifetime".
  • El valor "Default" se aplica a todas las categorías que no se especifican de otro modo, convirtiendo de hecho todos los valores predeterminados de todas las categorías en "Information". Puede invalidar este comportamiento especificando un valor para una categoría.
  • La categoría "Microsoft" se aplica a todas las categorías que comienzan por "Microsoft".
  • La categoría "Microsoft" se registra en el nivel de registro Warning y superiores.
  • La categoría "Microsoft.Hosting.Lifetime" es más específica que la categoría "Microsoft", por lo que la categoría "Microsoft.Hosting.Lifetime" se registra en el nivel de registro "Information" y superiores.
  • No se especifica un proveedor de registro específico, por lo que LogLevel se aplica a todos los proveedores de registro habilitados, excepto Windows EventLog.

La propiedad Logging puede tener LogLevel y registrar propiedades del proveedor de registro. LogLevel especifica el nivel mínimo que se va a registrar para las categorías seleccionadas. En el código JSON anterior, se especifican los niveles de registro Information y Warning. LogLevel indica la gravedad del registro y los valores están entre 0 y 6:

Trace = 0, Debug = 1, Information = 2, Warning = 3, Error = 4, Critical = 5 y None = 6.

Cuando se especifica LogLevel, el registro está habilitado para los mensajes tanto en el nivel especificado como en los superiores. En el código JSON anterior, se registra la categoría Default para Information y los niveles posteriores. Por ejemplo, se registran los mensajes Information, Warning, Error y Critical. Si no se especifica LogLevel, el nivel predeterminado del registro es Information. Para obtener más información, consulte Niveles de registro.

Una propiedad de proveedor puede especificar una propiedad de LogLevel. LogLevel en un proveedor especifica los niveles que se van a registrar para ese proveedor, e invalida la configuración de registro que no es de proveedor. Fíjese en el siguiente archivo appsettings.json:

{
    "Logging": {
        "LogLevel": {
            "Default": "Error",
            "Microsoft": "Warning"
        },
        "Debug": {
            "LogLevel": {
                "Default": "Information",
                "Microsoft.Hosting": "Trace"
            }
        },
        "EventSource": {
            "LogLevel": {
                "Default": "Warning"
            }
        }
    }
}

La configuración de Logging.{ProviderName}.LogLevel invalida la configuración de Logging.LogLevel. En el código JSON anterior, el nivel de registro predeterminado del proveedor Debug se establece en Information:

Logging:Debug:LogLevel:Default:Information

La configuración anterior especifica el nivel de registro Information para cada categoría de Logging:Debug:, excepto Microsoft.Hosting. Cuando se muestra una categoría específica, esa categoría invalida la categoría predeterminada. En el JSON anterior, las categorías de Logging:Debug:LogLevel"Microsoft.Hosting" y "Default" invalidan la configuración de Logging:LogLevel

Se puede especificar el nivel de registro mínimo para:

  • Proveedores específicos: Por ejemplo, Logging:EventSource:LogLevel:Default:Information.
  • Categorías específicas: Por ejemplo, Logging:LogLevel:Microsoft:Warning.
  • Todos los proveedores y todas las categorías: Logging:LogLevel:Default:Warning

Los registros situados por debajo del nivel mínimo no hacen lo siguiente:

  • no se pasan proveedor;
  • no se registran ni se muestran.

Para suprimir todos los registros, especifique LogLevel.None. LogLevel.None tiene un valor de 6, que es mayor que LogLevel.Critical (5).

Si un proveedor admite ámbitos de registro, IncludeScopes indica si están habilitados. Para obtener más información, consulte Ámbitos de registro.

El siguiente archivo appsettings.json contiene la configuración de todos los proveedores integrados:

{
    "Logging": {
        "LogLevel": {
            "Default": "Error",
            "Microsoft": "Warning",
            "Microsoft.Hosting.Lifetime": "Warning"
        },
        "Debug": {
            "LogLevel": {
                "Default": "Information"
            }
        },
        "Console": {
            "IncludeScopes": true,
            "LogLevel": {
                "Microsoft.Extensions.Hosting": "Warning",
                "Default": "Information"
            }
        },
        "EventSource": {
            "LogLevel": {
                "Microsoft": "Information"
            }
        },
        "EventLog": {
            "LogLevel": {
                "Microsoft": "Information"
            }
        },
        "AzureAppServicesFile": {
            "IncludeScopes": true,
            "LogLevel": {
                "Default": "Warning"
            }
        },
        "AzureAppServicesBlob": {
            "IncludeScopes": true,
            "LogLevel": {
                "Microsoft": "Information"
            }
        },
        "ApplicationInsights": {
            "LogLevel": {
                "Default": "Information"
            }
        }
    }
}

En el ejemplo anterior:

  • Las categorías y los niveles no son valores sugeridos. El objetivo del ejemplo es mostrar todos los proveedores predeterminados.
  • La configuración de Logging.{ProviderName}.LogLevel invalida la configuración de Logging.LogLevel. Por ejemplo, el nivel de Debug.LogLevel.Default invalida el nivel de LogLevel.Default.
  • Se usa cada alias de proveedor. Cada proveedor define un alias que se puede utilizar en la configuración en lugar del nombre de tipo completo. Los alias de proveedores integrados son los siguientes:
    • Console
    • Debug
    • EventSource
    • EventLog
    • AzureAppServicesFile
    • AzureAppServicesBlob
    • ApplicationInsights

Establecimiento del nivel de registro mediante la línea de comandos, las variables de entorno y otra configuración

El nivel de registro se puede establecer con cualquiera de los proveedores de configuración. Por ejemplo, puede crear una variable de entorno persistente denominada Logging:LogLevel:Microsoft con un valor de Information.

Cree y asigne una variable de entorno persistente según el valor de nivel de registro.

:: Assigns the env var to the value
setx "Logging__LogLevel__Microsoft" "Information" /M

En una nueva instancia del símbolo del sistema, lea la variable de entorno.

:: Prints the env var value
echo %Logging__LogLevel__Microsoft%

La configuración del entorno anterior se conserva en el entorno. Para probar la configuración cuando se usa una aplicación creada con las plantillas de servicio de trabajo de .NET, use el comando dotnet run en el directorio del proyecto después de asignar la variable de entorno.

dotnet run

Sugerencia

Después de establecer una variable de entorno, reinicie su entorno de desarrollo integrado (IDE) para asegurarse de que las variables de entorno recién agregadas están disponibles.

En Azure App Service, seleccione Nueva configuración de la aplicación en la página Configuración > Configuración. Los ajustes de configuración de Azure App Service:

  • Se cifran en reposo y se transmiten a través de un canal cifrado.
  • Se exponen como variables de entorno.

Para más información sobre cómo establecer los valores de configuración de .NET mediante variables de entorno, vea Variables de entorno.

Configuración del registro con código

Para configurar el registro en el código, use la API ILoggingBuilder. Se puede acceder desde diferentes lugares:

En este ejemplo se muestra cómo establecer el proveedor de registro de la consola y varios filtros.

using Microsoft.Extensions.Logging;

using var loggerFactory = LoggerFactory.Create(static builder =>
{
    builder
        .AddFilter("Microsoft", LogLevel.Warning)
        .AddFilter("System", LogLevel.Warning)
        .AddFilter("LoggingConsoleApp.Program", LogLevel.Debug)
        .AddConsole();
});

ILogger logger = loggerFactory.CreateLogger<Program>();
logger.LogDebug("Hello {Target}", "Everyone");

En el ejemplo AddFilter anterior se usa para ajustar el nivel de registro que está habilitado para varias categorías. AddConsole se usa para agregar el proveedor de registro de consola. De forma predeterminada, los registros con gravedad Debug no están habilitados, pero como la configuración ajusta los filtros, el mensaje de depuración "Hola" se muestra en la consola.

Cómo se aplican las reglas de filtro

Cuando se crea un objeto ILogger<TCategoryName>, el objeto ILoggerFactory selecciona una sola regla por proveedor para aplicar a ese registrador. Todos los mensajes escritos por una instancia ILogger se filtran según las reglas seleccionadas. De las reglas disponibles, se selecciona la más específica para cada par de categoría y proveedor.

Cuando se crea un ILogger para una categoría determinada, se usa el algoritmo siguiente para cada proveedor:

  • Se seleccionan todas las reglas que coinciden con el proveedor o su alias. Si no se encuentra ninguna coincidencia, se seleccionan todas las reglas con un proveedor vacío.
  • Del resultado del paso anterior, se seleccionan las reglas con el prefijo de categoría coincidente más largo. Si no se encuentra ninguna coincidencia, se seleccionan todas las reglas que no especifican una categoría.
  • Si se seleccionan varias reglas, se toma la última.
  • Si no hay ninguna regla seleccionada, use LoggingBuilderExtensions.SetMinimumLevel(ILoggingBuilder, LogLevel) para especificar el nivel de registro mínimo.

Categoría de registro

Cuando se crea un objeto ILogger, se especifica una categoría. Esa categoría se incluye con cada mensaje de registro creado por esa instancia de ILogger. La cadena de categoría es arbitraria, pero la convención es usar el nombre de clase completo. Por ejemplo, en una aplicación con un servicio definido como el objeto siguiente, la categoría podría ser "Example.DefaultService":

namespace Example
{
    public class DefaultService : IService
    {
        private readonly ILogger<DefaultService> _logger;

        public DefaultService(ILogger<DefaultService> logger) =>
            _logger = logger;

        // ...
    }
}

Si se desea una categorización adicional, la convención consiste en usar un nombre jerárquico anexando una subcategoría al nombre de clase completo y especificando explícitamente la categoría mediante LoggerFactory.CreateLogger:

namespace Example
{
    public class DefaultService : IService
    {
        private readonly ILogger _logger;

        public DefaultService(ILoggerFactory loggerFactory) =>
            _logger = loggerFactory.CreateLogger("Example.DefaultService.CustomCategory");

        // ...
    }
}

La llamada a CreateLogger con un nombre fijo puede ser útil cuando se usa en varias clases o tipos, por lo que los eventos se pueden organizar por categoría.

ILogger<T> es equivale a llamar a CreateLogger con el nombre de tipo completo de T.

Nivel de registro

En la tabla siguiente se enumeran los valores de LogLevel, el método de extensión Log{LogLevel} oportuno y el uso sugerido:

LogLevel Valor Método Descripción
Seguimiento 0 LogTrace Contienen los mensajes más detallados. Estos mensajes pueden contener datos confidenciales de la aplicación. Están deshabilitados de forma predeterminada y no se deben habilitar en un entorno de producción.
Depurar 1 LogDebug Para depuración y desarrollo. Debido al elevado volumen, tenga precaución cuando lo use en producción.
Información 2 LogInformation Realiza el seguimiento del flujo general de la aplicación. Puede tener un valor a largo plazo.
Advertencia 3 LogWarning Para eventos anómalos o inesperados. Normalmente incluye errores o estados que no provocan un error en la aplicación.
Error 4 LogError Para los errores y excepciones que no se pueden controlar. Estos mensajes indican un error en la operación o solicitud actual, no un error de toda la aplicación.
Critical) (Crítico) 5 LogCritical Para los errores que requieren atención inmediata. Ejemplos: escenarios de pérdida de datos, espacio en disco insuficiente.
Ninguno 6 Especifica que no se debe escribir ningún mensaje.

En la tabla anterior, LogLevel aparece de menor a mayor gravedad.

El primer parámetro del método Log, LogLevel, indica la gravedad del registro. En lugar de llamar a Log(LogLevel, ...), la mayoría de los desarrolladores llaman a los métodos de extensión Log{LogLevel}. Los métodos de extensión Log{LogLevel}llaman al método Log y especifican el LogLevel. Por ejemplo, las dos llamadas de registro siguientes son funcionalmente equivalentes y generan el mismo registro:

public void LogDetails()
{
    var logMessage = "Details for log.";

    _logger.Log(LogLevel.Information, AppLogEvents.Details, logMessage);
    _logger.LogInformation(AppLogEvents.Details, logMessage);
}

AppLogEvents.Details es el identificador del evento y se representa implícitamente mediante un valor Int32 de constante. AppLogEvents es una clase que expone varias constantes de identificador con nombre y se muestra en la sección Id. de evento del registro.

El siguiente código crea los registros Information y Warning:

public async Task<T> GetAsync<T>(string id)
{
    _logger.LogInformation(AppLogEvents.Read, "Reading value for {Id}", id);

    var result = await _repository.GetAsync(id);
    if (result is null)
    {
        _logger.LogWarning(AppLogEvents.ReadNotFound, "GetAsync({Id}) not found", id);
    }

    return result;
}

En el código anterior, el primer parámetro de Log{LogLevel}, AppLogEvents.Read, es el identificador de evento de registro. El segundo parámetro es una plantilla de mensaje con marcadores de posición para los valores de argumento proporcionados por el resto de parámetros de método. Los parámetros de método se explican detalladamente en la sección de la plantilla de mensaje más adelante en este artículo.

Configure el nivel de registro adecuado y llame a los métodos Log{LogLevel} correctos para controlar el volumen de resultados del registro que se escriben en un soporte de almacenamiento determinado. Por ejemplo:

  • En producción:
    • El registro en los niveles Trace o Debug genera un gran volumen de mensajes de registro detallados. Para controlar los costos y no superar los límites de almacenamiento de datos, registre los mensajes de nivel Trace a Debug en un almacén de datos de alto volumen y bajo costo. Considere la posibilidad de limitar Trace y Debug a categorías específicas.
    • El registro entre los niveles Warning y Critical debe generar pocos mensajes de registro.
      • Los costos y los límites de almacenamiento no suelen ser un problema.
      • Cuantos menos registros haya, mayor será la flexibilidad a la hora de elegir el almacén de datos.
  • En desarrollo:
    • Establézcalo en Warning.
    • Agregue los mensajes Trace oDebug al solucionar problemas. Para limitar la salida, establezca Trace o Debug solo para las categorías que se están investigando.

El siguiente JSON establece Logging:Console:LogLevel:Microsoft:Information:

{
    "Logging": {
        "LogLevel": {
            "Microsoft": "Warning"
        },
        "Console": {
            "LogLevel": {
                "Microsoft": "Information"
            }
        }
    }
}

Id. de evento del registro

Cada registro puede especificar un identificador de evento; EventId es una estructura con Id y propiedades opcionales Name de solo lectura. El código fuente de ejemplo usa la clase AppLogEvents para definir los identificadores de evento:

using Microsoft.Extensions.Logging;

internal static class AppLogEvents
{
    internal static EventId Create = new(1000, "Created");
    internal static EventId Read = new(1001, "Read");
    internal static EventId Update = new(1002, "Updated");
    internal static EventId Delete = new(1003, "Deleted");

    // These are also valid EventId instances, as there's
    // an implicit conversion from int to an EventId
    internal const int Details = 3000;
    internal const int Error = 3001;

    internal static EventId ReadNotFound = 4000;
    internal static EventId UpdateNotFound = 4001;

    // ...
}

Sugerencia

Para más información sobre la conversión de un int en un EventId, consulte Operador EventId.Implicit(Int32 to EventId).

Un id. de evento asocia un conjunto de eventos. Por ejemplo, todos los registros relacionados con la lectura de valores de un repositorio pueden ser 1001.

El proveedor de registro puede registrar el id. de evento en un campo de identificador, en el mensaje de registro o no almacenarlo. El proveedor de depuración no muestra los identificadores de evento. El proveedor de consola muestra los identificadores de evento entre corchetes después de la categoría:

info: Example.DefaultService.GetAsync[1001]
      Reading value for a1b2c3
warn: Example.DefaultService.GetAsync[4000]
      GetAsync(a1b2c3) not found

Algunos proveedores de registro almacenan el identificador de evento en un campo, lo que permite filtrar por el id.

Plantilla de mensaje de registro

Cada API de registro usa una plantilla de mensaje. La plantilla de mensaje puede contener marcadores de posición para los que se proporcionan argumentos. Use los nombres de los marcadores de posición, no números. El orden de los marcadores de posición, no sus nombres, determina qué parámetros se usan para proporcionar sus valores. En el código siguiente, los nombres de parámetro están fuera de la secuencia en la plantilla de mensaje:

string p1 = "param1";
string p2 = "param2";
_logger.LogInformation("Parameter values: {p2}, {p1}", p1, p2);

El código anterior crea un mensaje de registro con los valores de parámetro en secuencia:

Parameter values: param1, param2

Nota

Hay que tener cuidado al usar varios marcadores de posición dentro de una sola plantilla de mensajes, ya que están basados en ordinales. Los nombres no se usan para alinear los argumentos con los marcadores de posición.

Este enfoque permite a los proveedores de registro implementar registro semántico o estructurado. Los propios argumentos se pasan al sistema de registro, no solo a la plantilla de mensaje con formato. Esto permite a los proveedores de registro almacenar los valores de parámetro como campos. Observe el siguiente método de registrador:

_logger.LogInformation("Getting item {Id} at {RunTime}", id, DateTime.Now);

Por ejemplo, al registrar en Azure Table Storage:

  • Cada entidad de Azure Table puede tener propiedades ID y RunTime.
  • Las tablas con propiedades simplifican las consultas en los datos registrados. Por ejemplo, una consulta puede buscar todos los registros dentro de un intervalo RunTime determinado sin necesidad de analizar el tiempo de espera del mensaje de texto.

Formato de plantillas de mensajes de registro

Las plantillas de mensajes de registro admiten el formato de marcadores de posición. Las plantillas pueden especificar cualquier formato válido para el argumento del tipo especificado. Por ejemplo, fíjese en la siguiente plantilla de mensajes del registrador Information:

_logger.LogInformation("Logged on {PlaceHolderName:MMMM dd, yyyy}", DateTimeOffset.UtcNow);
// Logged on January 06, 2022

En el ejemplo anterior, la instancia DateTimeOffset es el tipo que corresponde a PlaceHolderName en la plantilla de mensajes del registrador. Este nombre puede ser cualquier cosa, ya que los valores están basados en ordinales. El formato MMMM dd, yyyy es válido para el tipo DateTimeOffset.

Para más información sobre el formato de DateTime y DateTimeOffset, consulte Cadenas de formato de fecha y hora personalizadas.

Ejemplos

En los ejemplos siguientes se muestra cómo dar formato a una plantilla de mensajes mediante la sintaxis del marcador de posición {}. Además, se muestra un ejemplo de escape de la sintaxis del marcador de posición {} con su salida. Por último, también se muestra la interpolación de cadenas con marcadores de posición de plantillas:

logger.LogInformation("Number: {Number}", 1);               // Number: 1
logger.LogInformation("{{Number}}: {Number}", 3);           // {Number}: 3
logger.LogInformation($"{{{{Number}}}}: {{Number}}", 5);    // {Number}: 5

Sugerencia

  • En la mayoría de los casos, debe usar el formato de plantilla de mensajes de registro al hacer el registro. El uso de la interpolación de cadenas puede provocar problemas de rendimiento.
  • La regla de análisis de código CA2254: La plantilla debe ser una expresión estática permite enviarle una alerta de los lugares en los que los mensajes de registro no usan el formato adecuado.

Registro de excepciones

Los métodos de registrador tienen sobrecargas que toman un parámetro de excepción:

public void Test(string id)
{
    try
    {
        if (id is "none")
        {
            throw new Exception("Default Id detected.");
        }
    }
    catch (Exception ex)
    {
        _logger.LogWarning(
            AppLogEvents.Error, ex,
            "Failed to process iteration: {Id}", id);
    }
}

El registro de excepciones es específico del proveedor.

Nivel de registro predeterminado

Si no se establece el nivel de registro predeterminado, su valor será Information.

Por ejemplo, observe la siguiente aplicación de servicio de trabajo:

  • Creada con las plantillas de trabajo de .NET.
  • appsettings.json y appsettings.Development.json eliminados o con el nombre cambiado.

Con la configuración anterior, al navegar a la página de privacidad o de inicio, se generan muchos mensajes de Trace, Debug y Information con Microsoft en el nombre de la categoría.

El código siguiente establece el nivel de registro predeterminado cuando este no se establece en la configuración:

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Logging.SetMinimumLevel(LogLevel.Warning);

using IHost host = builder.Build();

await host.RunAsync();

Función de filtro

Se invoca una función de filtro para todos los proveedores y las categorías que no tienen reglas asignadas mediante configuración o código:

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Logging.AddFilter((provider, category, logLevel) =>
{
    return provider.Contains("ConsoleLoggerProvider")
        && (category.Contains("Example") || category.Contains("Microsoft"))
        && logLevel >= LogLevel.Information;
});

using IHost host = builder.Build();

await host.RunAsync();

El código anterior muestra los registros de la consola cuando la categoría contiene Example o Microsoft y el nivel de registro es Information o superior.

Ámbitos de registro

Un ámbito agrupa un conjunto de operaciones lógicas. Esta agrupación se puede utilizar para adjuntar los mismos datos para cada registro que se crea como parte de un conjunto. Por ejemplo, cada registro creado como parte del procesamiento de una transacción puede incluir el identificador de dicha transacción.

Un ámbito:

Los siguientes proveedores admiten ámbitos:

Use un ámbito encapsulando las llamadas de registrador en un bloque using:

public async Task<T> GetAsync<T>(string id)
{
    T result;
    var transactionId = Guid.NewGuid().ToString();

    using (_logger.BeginScope(new List<KeyValuePair<string, object>>
        {
            new KeyValuePair<string, object>("TransactionId", transactionId),
        }))
    {
        _logger.LogInformation(
            AppLogEvents.Read, "Reading value for {Id}", id);

        var result = await _repository.GetAsync(id);
        if (result is null)
        {
            _logger.LogWarning(
                AppLogEvents.ReadNotFound, "GetAsync({Id}) not found", id);
        }
    }

    return result;
}

El JSON siguiente habilita ámbitos para el proveedor de la consola:

{
    "Logging": {
        "Debug": {
            "LogLevel": {
                "Default": "Information"
            }
        },
        "Console": {
            "IncludeScopes": true,
            "LogLevel": {
                "Microsoft": "Warning",
                "Default": "Information"
            }
        },
        "LogLevel": {
            "Default": "Debug"
        }
    }
}

El código siguiente permite ámbitos para el proveedor de la consola:

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Logging.ClearProviders();
builder.Logging.AddConsole(options => options.IncludeScopes = true);

using IHost host = builder.Build();

await host.RunAsync();

Creación de registros en Main

El código siguiente registra en Main mediante la obtención de una instancia de ILogger de inserción de dependencias después de compilar el host:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

using IHost host = Host.CreateApplicationBuilder(args).Build();

var logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogInformation("Host created.");

await host.RunAsync();

El código anterior se basa en dos paquetes NuGet:

El archivo del proyecto sería similar a este:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
    <PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
  </ItemGroup>

</Project>

No hay métodos de registrador asincrónicos

El registro debe ser tan rápido que no merezca la pena el costo de rendimiento del código asincrónico. Si un almacén de datos de registro es lento, no escriba directamente en él. Considere la posibilidad de escribir primero los mensajes de registro en un almacén rápido y, después, moverlos al almacén lento. Por ejemplo, al iniciar sesión en SQL Server, no lo haga directamente en un método Log, ya que los métodos Log son sincrónicos. En su lugar, agregue sincrónicamente mensajes de registro a una cola en memoria y haga que un trabajo en segundo plano extraiga los mensajes de la cola para realizar el trabajo asincrónico de insertar datos en SQL Server.

Cambio de los niveles de registro en una aplicación en ejecución

La API de registro no incluye un escenario que permita cambiar los niveles de registro mientras se ejecuta una aplicación. No obstante, algunos proveedores de configuración pueden volver a cargar la configuración, lo que tiene efecto inmediato en la configuración del registro. Por ejemplo, el Proveedor de configuración de archivo vuelve a cargar la configuración de registro de forma predeterminada. Si se cambia la configuración en el código mientras se ejecuta una aplicación, la aplicación puede llamar a IConfigurationRoot.Reload para actualizar la configuración de registro de la aplicación.

Paquetes NuGet

Las interfaces e implementaciones ILogger<TCategoryName> y ILoggerFactory se incluyen en la mayoría de los SDK de .NET como referencia implícita del paquete. También están disponibles explícitamente en los siguientes paquetes NuGet cuando no se hace referencia implícitamente a ellos:

Para obtener más información sobre qué SDK de .NET incluye referencias de paquete implícitas, consulte SDK de .NET: tabla en espacio de nombres implícito.

Consulte también