Простое ведение журнала

Совет

Пример этой статьи можно скачать на сайте GitHub.

Простое ведение журнала Entity Framework Core (EF Core) можно использовать для легкого получения журналов при разработке и отладке приложений. Эта форма ведения журнала требует минимальной конфигурации и дополнительных пакетов NuGet.

Совет

EF Core также интегрируется с Microsoft.Extensions.Logging, которая требует больше конфигурации, но часто подходит для ведения журнала в рабочих приложениях.

Настройка

Доступ к журналам EF Core можно получить из любого типа приложений с помощью метода LogTo при настройке экземпляра DbContext. Такая конфигурация обычно выполняется при переопределении DbContext.OnConfiguring. Например:

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

Кроме того, LogTo можно вызывать как часть AddDbContext или при создании экземпляра DbContextOptions для передачи конструктору DbContext .

Совет

OnConfiguring по-прежнему вызывается при использовании AddDbContext или экземпляре DbContextOptions передается конструктору DbContext. Это делает его идеальным местом для применения конфигурации контекста независимо от того, как создается DbContext.

Направление журналов

Ведение журнала в консоли

LogTo требует делегата Action<T> , который принимает строку. EF Core вызовет этот делегат со строкой для каждого созданного сообщения журнала. Затем делегат должен сделать что-то с заданным сообщением.

Метод Console.WriteLine часто используется для этого делегата, как показано выше. Это приводит к записи каждого сообщения журнала в консоль.

Ведение журнала в окне отладки

Debug.WriteLine можно использовать для отправки выходных данных в окно отладки в Visual Studio или других удостоверяемом идентификаторах. Лямбда-синтаксис должен использоваться в этом случае, так как Debug класс компилируется из сборок выпуска. Например:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(message => Debug.WriteLine(message));

Ведение журнала в файле

Запись в файл требует создания StreamWriter или аналогичного файла. Затем WriteLine этот метод можно использовать, как в других примерах выше. Не забудьте убедиться, что файл закрыт чисто путем удаления модуля записи при удалении контекста. Например:

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

Совет

Рекомендуется использовать Microsoft.Extensions.Logging для ведения журнала в файлах в рабочих приложениях.

Получение подробных сообщений

Конфиденциальные данные

По умолчанию EF Core не будет включать значения любых данных в сообщениях об исключениях. Это связано с тем, что такие данные могут быть конфиденциальными и могут быть выявлены в рабочей среде, если исключение не обрабатывается.

Однако знание значений данных, особенно для ключей, может оказаться очень полезным при отладке. Это можно включить в EF Core путем вызова EnableSensitiveDataLogging(). Например:

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

Подробные исключения запросов

По соображениям производительности EF Core не упаковывает каждый вызов для чтения значения от поставщика базы данных в блоке try-catch. Однако иногда это приводит к возникновению исключений, которые трудно диагностировать, особенно если база данных возвращает значение NULL, если это не разрешено моделью.

EnableDetailedErrors Включение приведет к тому, что EF вводит эти блоки try-catch и тем самым предоставляет более подробные ошибки. Например:

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

Фильтрация

Уровни журнала

Каждое сообщение журнала EF Core назначается уровню, определенному перечислением LogLevel . По умолчанию простое ведение журнала EF Core включает каждое сообщение на Debug уровне или выше. LogTo можно передать более высокий минимальный уровень, чтобы отфильтровать некоторые сообщения. Например, передача Information результатов приводит к минимальному набору журналов, ограниченных доступом к базе данных и некоторыми сообщениями о домашней службе.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);

Определенные сообщения

Каждое EventIdсообщение журнала назначается. Эти идентификаторы можно получить из CoreEventId класса или RelationalEventId класса для реляционных сообщений. У поставщика базы данных также могут быть идентификаторы, относящиеся к поставщику, в аналогичном классе. Например, SqlServerEventId для поставщика SQL Server.

LogTo можно настроить только журнал сообщений, связанных с одним или несколькими идентификаторами событий. Например, чтобы регистрировать только сообщения для контекста, инициализированного или управляемого:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .LogTo(Console.WriteLine, new[] { CoreEventId.ContextDisposed, CoreEventId.ContextInitialized });

Категории сообщений

Каждое сообщение журнала назначается именованной категории иерархического средства ведения журнала. а именно следующему.

Категория Сообщения
Microsoft.EntityFrameworkCore Все сообщения EF Core
Microsoft.EntityFrameworkCore.Database Все взаимодействия с базой данных
Microsoft.EntityFrameworkCore.Database. Подключение ion Использование подключения к базе данных
Microsoft.EntityFrameworkCore.Database.Command Использование команды базы данных
Microsoft.EntityFrameworkCore.Database.Transaction Использование транзакции базы данных
Microsoft.EntityFrameworkCore.Update Сохранение сущностей, за исключением взаимодействия с базой данных
Microsoft.EntityFrameworkCore.Model Все взаимодействия модели и метаданных
Microsoft.EntityFrameworkCore.Model.Validation Проверка модели
Microsoft.EntityFrameworkCore.Query Запросы, за исключением взаимодействия с базой данных
Microsoft.EntityFrameworkCore.Infrastructure Общие события, такие как создание контекста
Microsoft.EntityFrameworkCore.Scaffolding Реверсивная инженерия базы данных
Microsoft.EntityFrameworkCore.Migrations Миграции
Microsoft.EntityFrameworkCore.ChangeTracking Взаимодействие с отслеживанием изменений

LogTo можно настроить только для записи сообщений из одной или нескольких категорий. Например, чтобы регистрировать только взаимодействие с базой данных:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .LogTo(Console.WriteLine, new[] { DbLoggerCategory.Database.Name });

Обратите внимание, что DbLoggerCategory класс предоставляет иерархический API для поиска категории и избегает необходимости жесткого кода строк.

Так как категории являются иерархическими, в этом примере с помощью Database категории будут включены все сообщения для подкатегорий Database.Connection, Database.Commandи Database.Transaction.

Пользовательские фильтры

LogTo позволяет использовать пользовательский фильтр для случаев, когда ни один из описанных выше параметров фильтрации не является достаточным. Например, чтобы записать любое сообщение на уровне Information или выше, а также сообщения для открытия и закрытия подключения:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .LogTo(
            Console.WriteLine,
            (eventId, logLevel) => logLevel >= LogLevel.Information
                                   || eventId == RelationalEventId.ConnectionOpened
                                   || eventId == RelationalEventId.ConnectionClosed);

Совет

Фильтрация с помощью пользовательских фильтров или использование других параметров, показанных здесь, более эффективна, чем фильтрация в делегате LogTo . Это связано с тем, что если фильтр определяет, что сообщение не должно быть зарегистрировано, то сообщение журнала даже не создается.

Конфигурация для определенных сообщений

API EF Core ConfigureWarnings позволяет приложениям изменять то, что происходит при обнаружении определенного события. Это можно использовать для следующих способов:

  • Изменение уровня журнала, на котором регистрируется событие
  • Пропустить ведение журнала события полностью
  • Создание исключения при возникновении события

Изменение уровня журнала для события

В предыдущем примере используется настраиваемый фильтр для регистрации каждого сообщения LogLevel.Information , а также для двух событий, определенных для LogLevel.Debug. Одно и то же можно достичь, изменив уровень журнала двух Debug событий 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);

Подавление ведения журнала события

Таким же образом можно отключить отдельное событие из ведения журнала. Это особенно полезно для игнорировать предупреждение, которое было проверено и понято. Например:

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

Создание события

Наконец, EF Core можно настроить для создания данного события. Это особенно полезно для изменения предупреждения в ошибку. (Действительно, это была исходная цель ConfigureWarnings метода, следовательно, имя.) Например:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .ConfigureWarnings(b => b.Throw(RelationalEventId.MultipleCollectionIncludeWarning))
        .LogTo(Console.WriteLine);

Содержимое сообщения и форматирование

Содержимое LogTo по умолчанию отформатировано по нескольким строкам. Первая строка содержит метаданные сообщения:

  • Префикс LogLevel с четырьмя символами
  • Локальная метка времени, отформатированная для текущего языка и региональных параметров
  • Форма EventId , которая может быть скопирована или вставлена, чтобы получить элемент из CoreEventId одного из других EventId классов, а также значение необработанного идентификатора
  • Категория событий, как описано выше.

Например:

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.

Это содержимое можно настроить, передав значения из DbContextLoggerOptionsследующих разделов.

Совет

Рассмотрите возможность использования Microsoft.Extensions.Logging для получения большего контроля над форматированием журнала.

Использование времени UTC

По умолчанию метки времени предназначены для локального потребления при отладке. Используйте DbContextLoggerOptions.DefaultWithUtcTime вместо этого метки времени UTC, не зависящие от языка и региональных параметров, но сохраняйте все остальное. Например:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(
        Console.WriteLine,
        LogLevel.Debug,
        DbContextLoggerOptions.DefaultWithUtcTime);

В этом примере приводится следующее форматирование журнала:

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.

Ведение журнала одной строки

Иногда полезно получить ровно одну строку для каждого сообщения журнала. Это можно включить.DbContextLoggerOptions.SingleLine Например:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(
        Console.WriteLine,
        LogLevel.Debug,
        DbContextLoggerOptions.DefaultWithLocalTime | DbContextLoggerOptions.SingleLine);

В этом примере приводится следующее форматирование журнала:

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.

Другие параметры содержимого

Другие флаги DbContextLoggerOptions можно использовать для уменьшения объема метаданных, включенных в журнал. Это может быть полезно в сочетании с однострочного ведения журнала. Например:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(
        Console.WriteLine,
        LogLevel.Debug,
        DbContextLoggerOptions.UtcTime | DbContextLoggerOptions.SingleLine);

В этом примере приводится следующее форматирование журнала:

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.

Переход из EF6

Простое ведение журнала EF Core отличается от Database.Log EF6 двумя важными способами:

  • Сообщения журнала не ограничиваются только взаимодействием с базой данных
  • Ведение журнала должно быть настроено во время инициализации контекста

В первую очередь фильтрацию, описанную выше, можно использовать для ограничения того, какие сообщения регистрируются.

Вторая разница заключается в намеренном изменении производительности, не создавая сообщения журнала, если они не нужны. Тем не менее, по-прежнему можно получить аналогичное поведение с EF6, создав Log свойство на вашем DbContext устройстве, а затем используя его только в том случае, если оно установлено. Например:

public Action<string> Log { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(s => Log?.Invoke(s));