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. Esta aplicación de consola de ejemplo se basa en los siguientes paquetes NuGet:
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 objetoILogger
. 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-valorDescription = 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="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.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 objetosILogger
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
, comoLogInformation
. La generación de orígenes de registro ofrece un mejor rendimiento, una tipificación más fuerte y evita la propagación de constantesstring
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 un
Type
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");
}
}
- Si no usa registros de consola como única solución de supervisión de producción, agregue los proveedores de registro que tiene previsto usar. Por ejemplo, puede usar OpenTelemetry para enviar registros a través de OTLP (protocolo OpenTelemetry):
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ónExampleHandler.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 objetoILogger
. El contenedor de inserción de dependencias busca unaILogger
con la categoría correcta y la proporciona como argumento constructor. Si aún no existe ningunaILogger
con esa categoría, el contenedor de inserción de dependencias lo crea automáticamente desde elILoggerFactory
en el proveedor de servicios. - El parámetro
logger
recibido en el constructor se usó para iniciar sesión en la funciónHandleRequest
.
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 unILogger<ExampleService>
que se usará como argumento constructor. - Se ha invocado
ExampleService.DoSomeWork
que usó elILogger<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 registroWarning
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 deLogging.LogLevel
. Por ejemplo, el nivel deDebug.LogLevel.Default
invalida el nivel deLogLevel.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:
- Al crear directamente
ILoggerFactory
, configure en LoggerFactory.Create. - Cuando se usa la inserción de dependencias sin un host, configure en LoggingServiceCollectionExtensions.AddLogging.
- Al usar un host, configure con HostApplicationBuilder.Logging, WebApplicationBuilder.Logging u otras API específicas del host.
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ónLog{LogLevel}
llaman al método Log
y especifican 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 Log{LogLevel}
parámetro, 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
oDebug
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 nivelTrace
aDebug
en un almacén de datos de alto volumen y bajo costo. Considere la posibilidad de limitarTrace
yDebug
a categorías específicas. - El registro entre los niveles
Warning
yCritical
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.
- El registro en los niveles
- En desarrollo:
- Establézcalo en
Warning
. - Agregue los mensajes
Trace
oDebug
al solucionar problemas. Para limitar la salida, establezcaTrace
oDebug
solo para las categorías que se están investigando.
- Establézcalo en
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
yRunTime
. - 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:
- es un tipo IDisposable devuelto por el método BeginScope;
- se mantiene hasta que se elimina.
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:
- Las interfaces están en Microsoft.Extensions.Logging.Abstractions.
- Las implementaciones predeterminadas se encuentran en Microsoft.Extensions.Logging.
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
- Proveedores de registro en .NET
- Implementación de un proveedor de registro personalizado en .NET
- Formato de registro de la consola
- Registro de alto rendimiento en .NET
- Guía de registro para autores de bibliotecas de .NET
- Los errores de registro deben crearse en el repositorio github.com/dotnet/runtime