Compartir a través de


Registro simple

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 sobreescritura 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 Action<T> delegado 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 Console.WriteLine método 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 o algo similar. A continuación, el WriteLine método 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

Datos confidenciales

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 los datos, especialmente de 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 envuelve 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 de EnableDetailedErrors hará que EF introduzca los bloques de try-catch y, por tanto, proporcione errores más detallados. Por ejemplo:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .LogTo(Console.WriteLine)
        .EnableDetailedErrors();

Filtros

Niveles de registro

Cada mensaje de registro de EF Core se asigna a un nivel definido por la LogLevel enumeración. De forma predeterminada, el registro simple de EF Core incluye todos los mensajes en el nivel Debug o superiores. LogTo se puede pasar un nivel mínimo superior para filtrar algunos mensajes. Por ejemplo, al pasar Information, se obtienen un conjunto mínimo de registros limitados al acceso a la base de datos y algunos mensajes administrativos.

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 relación. 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.

LogTo se puede configurar 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. Las 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 de 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 base de datos
Microsoft.EntityFrameworkCore.Update Guardar entidades, excluyendo interacciones con 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

LogTo se puede configurar 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 DbLoggerCategory clase 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, la categoría Database incluirá 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 LogTo delegado. 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 de EF Core ConfigureWarnings permite a las aplicaciones cambiar lo que sucede cuando se encuentra un evento específico. Esto se puede usar para:

  • Cambio del nivel de registro en el que se registra el evento
  • Omitir el registro del evento por completo
  • Lanzar una excepción cuando se produce el evento

Cambio del 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 Debug eventos 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 comprendido. Por ejemplo:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .ConfigureWarnings(b => b.Ignore(CoreEventId.DetachedLazyLoadingWarning))
        .LogTo(Console.WriteLine);

Organizar un evento

Por último, EF Core se puede configurar para iniciar un evento determinado. Esto resulta especialmente útil para cambiar una advertencia a un error. (De hecho, este era el propósito original del ConfigureWarnings método, por lo tanto 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 está formateado a través de varias líneas. La primera línea contiene metadatos de mensaje:

  • LogLevel como prefijo de cuatro caracteres
  • Marca de tiempo local, en un formato adecuado para la cultura actual
  • El EventId en el formulario que se puede copiar/pegar para obtener el miembro de CoreEventId o de las otras clases EventId, además del valor 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 uso local durante la depuración. Usa DbContextLoggerOptions.DefaultWithUtcTime para usar marcas de tiempo UTC independientes de la cultura en su lugar, pero mantén 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 Log propiedad 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));