Compartir a través de


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));