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

Примечание

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

Совет

Вы можете скачать пример этой статьи с GitHub.

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

Совет

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

Конфигурация

Доступ к EF Coreным журналам можно получить из любого типа приложения, используя LogTo при LogTo. Такая конфигурация обычно выполняется при переопределении 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 или другом ide. В этом случае следует использовать лямбда-синтаксис , так как класс компилируется из сборок выпуска. Пример:

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. формирование шаблонов Реконструирование базы данных
Microsoft. EntityFrameworkCore. migrations Миграции
Microsoft. EntityFrameworkCore. отслеживания изменений Взаимодействие отслеживания изменений

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 делегате. Это происходит потому, что если фильтр определяет, что сообщение не должно регистрироваться, сообщение журнала не создается даже.

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

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

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

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

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