小窍门
可以从 GitHub 下载本文的示例 。
Entity Framework Core (EF Core) 简单日志记录可用于在开发和调试应用程序时轻松获取日志。 这种形式的日志记录需要最少的配置,无需额外的 NuGet 包。
小窍门
EF Core 还与 Microsoft.Extensions.Logging 集成,这需要更多的配置,但通常更适合在生产应用程序中进行日志记录。
配置
在配置 DbContext 实例时,可以通过使用LogTo来访问任何类型应用程序中的 EF Core 日志。 此配置通常在重写 DbContext.OnConfiguring中完成。 例如:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(Console.WriteLine);
或者,可以调用LogTo
作为AddDbContext的一部分,或在创建一个要传给DbContext
构造函数的DbContextOptions实例时调用。
小窍门
在使用 AddDbContext 或将 DbContextOptions 实例传递给 DbContext 构造函数时,仍会调用 OnConfiguring。 这样,无论如何构造 DbContext,它都是应用上下文配置的理想位置。
引导日志
登录到控制台
LogTo
需要一个能够接受字符串的 Action<T> 委托。 EF Core 将调用此委托,其中包含生成的每个日志消息的字符串。 然后由代表对给定的这条消息采取行动。
Console.WriteLine 方法通常用于此委托,如上所示。 这会导致将每个日志消息写入控制台。
登录到调试窗口
Debug.WriteLine 可用于将输出发送到 Visual Studio 或其他 IDE 中的“调试”窗口。 在这种情况下必须使用 Lambda 语法,因为该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 不会在异常消息中包含任何数据的值。 这是因为此类数据可能是机密的,如果异常未被处理,在生产使用中可能会暴露这些数据。
但是,了解数据值(尤其是键)在调试时非常有用。 这可以通过调用 EnableSensitiveDataLogging()在 EF Core 中启用。 例如:
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类访问这些特定于关系的消息的 ID。 数据库提供程序也可能在类似的类中具有特定于提供程序的 ID。 例如,SqlServerEventId 是 SQL Server 提供程序的示例。
LogTo
可配置为仅记录与一个或多个事件 ID 关联的消息。 例如,若要仅记录要初始化或释放的上下文的消息:
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.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
类之一获取成员,以及原始 ID 值。 - 事件类别,如上所述。
例如:
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 简单日志记录与 EF6 Database.Log 有两个重要的不同之处:
- 日志消息不仅限于数据库交互
- 必须在上下文初始化时配置日志记录
对于第一个差异,上述筛选可用于限制被记录的消息。
第二个区别是有意更改,以便在不需要日志消息时不生成日志消息来提高性能。 但是,我们仍可以通过在DbContext
上创建一个Log
属性,并在设置后使用它,来获得类似于 EF6 的行为。 例如:
public Action<string> Log { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(s => Log?.Invoke(s));