Registro sencillo
Sugerencia
Puede descargar el ejemplo de este artículo desde GitHub.
El registro sencillo de Entity Framework Core (EF Core) se puede usar para obtener fácilmente registros al desarrollar y depurar aplicaciones. Esta forma de registro requiere una configuración mínima y ningún paquete NuGet adicional.
Sugerencia
EF Core también se integra con Microsoft.Extensions.Logging, que requiere más configuración, pero a menudo es más adecuado para el registro en aplicaciones de producción.
Configuración
Se puede acceder a los registros de EF Core desde cualquier tipo de aplicación mediante el uso de LogTo al configurar una instancia de DbContext. Esta configuración se realiza normalmente en una invalidación de DbContext.OnConfiguring. Por ejemplo:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(Console.WriteLine);
Como alternativa, LogTo
se puede llamar como parte de AddDbContext o al crear una DbContextOptions instancia para pasar al DbContext
constructor.
Sugerencia
Se sigue llamando a OnConfiguring cuando se usa AddDbContext o se pasa una instancia dbContextOptions al constructor DbContext. Esto hace que sea el lugar ideal para aplicar la configuración de contexto independientemente de cómo se construye DbContext.
Dirigir los registros
Registro en la consola
LogTo
requiere un delegado Action<T> que acepte una cadena. EF Core llamará a este delegado con una cadena para cada mensaje de registro generado. A continuación, es necesario que el delegado haga algo con el mensaje especificado.
El método Console.WriteLine se usa a menudo para este delegado, como se muestra anteriormente. Esto da como resultado que cada mensaje de registro se escriba en la consola.
Registro en la ventana de depuración
Debug.WriteLine se puede usar para enviar la salida a la ventana Depurar en Visual Studio u otros IDE. La sintaxis lambda debe usarse en este caso porque la Debug
clase se compila fuera de las compilaciones de versión. Por ejemplo:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(message => Debug.WriteLine(message));
Registro en un archivo
Escribir en un archivo requiere crear un StreamWriter archivo o similar para el archivo. A continuación, el método WriteLine se puede usar como en los otros ejemplos anteriores. Recuerde asegurarse de que el archivo se cierra limpiamente eliminando el escritor cuando se elimina el contexto. Por ejemplo:
private readonly StreamWriter _logStream = new StreamWriter("mylog.txt", append: true);
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(_logStream.WriteLine);
public override void Dispose()
{
base.Dispose();
_logStream.Dispose();
}
public override async ValueTask DisposeAsync()
{
await base.DisposeAsync();
await _logStream.DisposeAsync();
}
Sugerencia
Considere la posibilidad de usar Microsoft.Extensions.Logging para iniciar sesión en archivos en aplicaciones de producción.
Obtención de mensajes detallados
Información confidencial
De forma predeterminada, EF Core no incluirá los valores de ningún dato en los mensajes de excepción. Esto se debe a que estos datos pueden ser confidenciales y podrían revelarse en el uso de producción si no se controla una excepción.
Sin embargo, conocer los valores de datos, especialmente para las claves, puede ser muy útil al depurar. Esto se puede habilitar en EF Core llamando a EnableSensitiveDataLogging(). Por ejemplo:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(Console.WriteLine)
.EnableSensitiveDataLogging();
Excepciones de consulta detalladas
Por motivos de rendimiento, EF Core no ajusta cada llamada para leer un valor del proveedor de base de datos en un bloque try-catch. Sin embargo, esto a veces da como resultado excepciones difíciles de diagnosticar, especialmente cuando la base de datos devuelve un valor NULL cuando el modelo no lo permite.
La activación EnableDetailedErrors hará que EF introduzca estos bloques try-catch y, por tanto, proporcione errores más detallados. Por ejemplo:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(Console.WriteLine)
.EnableDetailedErrors();
Filtrado
Niveles de registro
Cada mensaje de registro de EF Core se asigna a un nivel definido por la enumeración LogLevel. De forma predeterminada, el registro simple de EF Core incluye todos los mensajes en el nivel Debug
o superior. LogTo
se puede pasar un nivel mínimo superior para filtrar algunos mensajes. Por ejemplo, pasar Information
resultados en un conjunto mínimo de registros limitados al acceso a la base de datos y algunos mensajes de limpieza.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
Mensajes específicos
A cada mensaje de registro se le asigna un EventId. Se puede acceder a estos identificadores desde la clase CoreEventId o la clase RelationalEventId para mensajes específicos de relacionales. Un proveedor de base de datos también puede tener identificadores específicos del proveedor en una clase similar. Por ejemplo, SqlServerEventId para el proveedor de SQL Server.
se puede configurar LogTo
para registrar solo los mensajes asociados a uno o varios identificadores de evento. Por ejemplo, para registrar solo los mensajes del contexto que se inicializa o se elimina:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(Console.WriteLine, new[] { CoreEventId.ContextDisposed, CoreEventId.ContextInitialized });
Categorías de mensajes
Cada mensaje de registro se asigna a una categoría de registrador jerárquico con nombre. Estas categorías son:
Categoría | Mensajes |
---|---|
Microsoft.EntityFrameworkCore | Todos los mensajes de EF Core |
Microsoft.EntityFrameworkCore.Database | Todas las interacciones de la base de datos |
Microsoft.EntityFrameworkCore.Database.Connection | Usos de una conexión a una base de datos |
Microsoft.EntityFrameworkCore.Database.Command | Usos de un comando de base de datos |
Microsoft.EntityFrameworkCore.Database.Transaction | Usos de una transacción de bases de datos |
Microsoft.EntityFrameworkCore.Update | Guardar entidades, excepto las interacciones de la base de datos |
Microsoft.EntityFrameworkCore.Model | Todas las interacciones de modelo y metadatos |
Microsoft.EntityFrameworkCore.Model.Validation | Validación de modelos |
Microsoft.EntityFrameworkCore.Query | Consultas, excepto las interacciones de la base de datos |
Microsoft.EntityFrameworkCore.Infrastructure | Eventos generales, como la creación de contextos |
Microsoft.EntityFrameworkCore.Scaffolding | Ingeniería inversa de base de datos |
Microsoft.EntityFrameworkCore.Migrations | Migraciones |
Microsoft.EntityFrameworkCore.ChangeTracking | Interacciones de seguimiento de cambios |
se puede configurar LogTo
para registrar solo los mensajes de una o varias categorías. Por ejemplo, para registrar solo las interacciones de la base de datos:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(Console.WriteLine, new[] { DbLoggerCategory.Database.Name });
Tenga en cuenta que la clase DbLoggerCategory proporciona una API jerárquica para buscar una categoría y evita la necesidad de codificar cadenas de forma rígida.
Dado que las categorías son jerárquicas, en este ejemplo Database
se incluirán todos los mensajes de las subcategorías Database.Connection
, Database.Command
, y Database.Transaction
.
Filtros personalizados
LogTo
permite usar un filtro personalizado para los casos en los que ninguna de las opciones de filtrado anteriores sea suficiente. Por ejemplo, para registrar cualquier mensaje en el nivel Information
o superior, así como mensajes para abrir y cerrar una conexión:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(
Console.WriteLine,
(eventId, logLevel) => logLevel >= LogLevel.Information
|| eventId == RelationalEventId.ConnectionOpened
|| eventId == RelationalEventId.ConnectionClosed);
Sugerencia
El filtrado mediante filtros personalizados o el uso de cualquiera de las otras opciones que se muestran aquí es más eficaz que el filtrado en el delegadoLogTo
. Esto se debe a que si el filtro determina que el mensaje no se debe registrar, ni siquiera se crea el mensaje de registro.
Configuración de mensajes específicos
La API ConfigureWarnings de EF Core permite a las aplicaciones cambiar lo que sucede cuando se encuentra un evento específico. Esto se puede usar para:
- Cambiar el nivel en el que se registra el evento
- Omitir completamente el registro del evento
- Iniciar una excepción cuando ocurra el evento
Cambiar el nivel de registro de un evento
En el ejemplo anterior se usó un filtro personalizado para registrar todos los mensajes, LogLevel.Information
así como dos eventos definidos para LogLevel.Debug
. Se puede lograr lo mismo cambiando el nivel de registro de los dos eventos Debug
a Information
:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.ConfigureWarnings(
b => b.Log(
(RelationalEventId.ConnectionOpened, LogLevel.Information),
(RelationalEventId.ConnectionClosed, LogLevel.Information)))
.LogTo(Console.WriteLine, LogLevel.Information);
Suprimir el registro de un evento
De forma similar, se puede suprimir un evento individual del registro. Esto es especialmente útil para ignorar una advertencia que se ha revisado y reconocido. Por ejemplo:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.ConfigureWarnings(b => b.Ignore(CoreEventId.DetachedLazyLoadingWarning))
.LogTo(Console.WriteLine);
Iniciar para un evento
Finalmente, EF Core puede configurarse para iniciar un evento determinado. Esto resulta especialmente útil para cambiar que una advertencia pase a ser un error. (De hecho, este era el propósito original del método ConfigureWarnings
, de ahí el nombre). Por ejemplo:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.ConfigureWarnings(b => b.Throw(RelationalEventId.MultipleCollectionIncludeWarning))
.LogTo(Console.WriteLine);
Contenido y formato de mensajes
El contenido predeterminado de LogTo
se formatea en varias líneas. La primera línea contiene metadatos de mensaje:
- El LogLevel como prefijo de cuatro caracteres
- Marca de tiempo local, con formato para la referencia cultural actual
- El EventId en el formulario que se puede copiar o pegar para obtener el miembro de CoreEventId o una de las otras clases de
EventId
, además del valor de id. sin formato - Categoría de eventos, como se ha descrito anteriormente.
Por ejemplo:
info: 10/6/2020 10:52:45.581 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE TABLE "Blogs" (
"Id" INTEGER NOT NULL CONSTRAINT "PK_Blogs" PRIMARY KEY AUTOINCREMENT,
"Name" INTEGER NOT NULL
);
dbug: 10/6/2020 10:52:45.582 RelationalEventId.TransactionCommitting[20210] (Microsoft.EntityFrameworkCore.Database.Transaction)
Committing transaction.
dbug: 10/6/2020 10:52:45.585 RelationalEventId.TransactionCommitted[20202] (Microsoft.EntityFrameworkCore.Database.Transaction)
Committed transaction.
Este contenido se puede personalizar pasando valores de DbContextLoggerOptions, como se muestra en las secciones siguientes.
Sugerencia
Considere la posibilidad de usar Microsoft.Extensions.Logging para obtener más control sobre el formato de registro.
Uso de la hora UTC
De forma predeterminada, las marcas de tiempo están diseñadas para el consumo local durante la depuración. Use DbContextLoggerOptions.DefaultWithUtcTime para usar marcas de tiempo UTC independientes de la referencia cultural en su lugar, pero mantenga todo lo demás igual. Por ejemplo:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(
Console.WriteLine,
LogLevel.Debug,
DbContextLoggerOptions.DefaultWithUtcTime);
Este ejemplo da como resultado el siguiente formato de registro:
info: 2020-10-06T17:55:39.0333701Z RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
CREATE TABLE "Blogs" (
"Id" INTEGER NOT NULL CONSTRAINT "PK_Blogs" PRIMARY KEY AUTOINCREMENT,
"Name" INTEGER NOT NULL
);
dbug: 2020-10-06T17:55:39.0333892Z RelationalEventId.TransactionCommitting[20210] (Microsoft.EntityFrameworkCore.Database.Transaction)
Committing transaction.
dbug: 2020-10-06T17:55:39.0351684Z RelationalEventId.TransactionCommitted[20202] (Microsoft.EntityFrameworkCore.Database.Transaction)
Committed transaction.
Registro de una sola línea
A veces resulta útil obtener exactamente una línea por mensaje de registro. Esto se puede habilitar mediante DbContextLoggerOptions.SingleLine. Por ejemplo:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(
Console.WriteLine,
LogLevel.Debug,
DbContextLoggerOptions.DefaultWithLocalTime | DbContextLoggerOptions.SingleLine);
Este ejemplo da como resultado el siguiente formato de registro:
info: 10/6/2020 10:52:45.723 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) -> Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']CREATE TABLE "Blogs" ( "Id" INTEGER NOT NULL CONSTRAINT "PK_Blogs" PRIMARY KEY AUTOINCREMENT, "Name" INTEGER NOT NULL);
dbug: 10/6/2020 10:52:45.723 RelationalEventId.TransactionCommitting[20210] (Microsoft.EntityFrameworkCore.Database.Transaction) -> Committing transaction.
dbug: 10/6/2020 10:52:45.725 RelationalEventId.TransactionCommitted[20202] (Microsoft.EntityFrameworkCore.Database.Transaction) -> Committed transaction.
Otras opciones de contenido
Otras marcas de DbContextLoggerOptions se pueden usar para reducir la cantidad de metadatos incluidos en el registro. Esto puede ser útil junto con el registro de una sola línea. Por ejemplo:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(
Console.WriteLine,
LogLevel.Debug,
DbContextLoggerOptions.UtcTime | DbContextLoggerOptions.SingleLine);
Este ejemplo da como resultado el siguiente formato de registro:
2020-10-06T17:52:45.7320362Z -> Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']CREATE TABLE "Blogs" ( "Id" INTEGER NOT NULL CONSTRAINT "PK_Blogs" PRIMARY KEY AUTOINCREMENT, "Name" INTEGER NOT NULL);
2020-10-06T17:52:45.7320531Z -> Committing transaction.
2020-10-06T17:52:45.7339441Z -> Committed transaction.
Migración desde EF6
El registro simple de EF Core difiere de Database.Log en EF6 de dos maneras importantes:
- Los mensajes de registro no se limitan solo a las interacciones de la base de datos
- El registro debe configurarse en el momento de inicialización del contexto
Para la primera diferencia, el filtrado descrito anteriormente se puede usar para limitar qué mensajes se registran.
La segunda diferencia es un cambio intencionado para mejorar el rendimiento al no generar mensajes de registro cuando no son necesarios. Sin embargo, todavía es posible obtener un comportamiento similar a EF6 mediante la creación de una propiedad Log
en DbContext
y a continuación, usarla solo cuando se ha establecido. Por ejemplo:
public Action<string> Log { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(s => Log?.Invoke(s));