Простое ведение журнала
Совет
Пример этой статьи можно скачать на сайте 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));