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

Примечание

Эта возможность появилась в EF Core 5.0.

Совет

Пример этой статьи можно скачать на сайте 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.Connection Использование подключения к базе данных
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.CommandDatabase.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));