Appearance
启用日志
概述
EF Core 内置了强大的日志记录功能,可以记录 SQL 查询、命令执行、变更跟踪等详细信息。正确配置日志对于调试问题、性能优化和生产监控至关重要。
日志级别
| 级别 | 说明 | 适用场景 |
|---|---|---|
| Trace | 最详细的信息 | 深度调试 |
| Debug | 调试信息 | 开发环境 |
| Information | 一般信息(SQL 命令) | ✅ 开发/测试 |
| Warning | 警告(客户端评估) | ⚠️ 需要注意 |
| Error | 错误(异常) | ❌ 需要修复 |
| Critical | 严重错误 | 🚨 立即处理 |
| None | 不记录 | 生产环境(部分) |
基础配置
Minimal API 配置
csharp
// Program.cs
using Microsoft.Extensions.Logging;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<AppDbContext>(options =>
{
options.UseSqlServer(connectionString);
// ✅ 最简单的日志配置
options.LogTo(Console.WriteLine, LogLevel.Information);
});
var app = builder.Build();
app.Run();输出到 Debug 窗口
csharp
// Visual Studio Output 窗口
options.LogTo(message => Debug.WriteLine(message), LogLevel.Information);输出到文件
csharp
var logFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ef-logs.txt");
options.LogTo(
message => File.AppendAllText(logFilePath, $"{DateTime.Now}: {message}{Environment.NewLine}"),
LogLevel.Information);过滤日志类别
EF Core 日志类别
csharp
// 所有可用类别
DbLoggerCategory.Database.Command // SQL 命令执行
DbLoggerCategory.Database.Transaction // 事务
DbLoggerCategory.Query // 查询编译
DbLoggerCategory.Update // SaveChanges
DbLoggerCategory.Model // 模型构建
DbLoggerCategory.Infrastructure // 基础设施
DbLoggerCategory.Migrations // 迁移仅记录 SQL 命令
csharp
options.LogTo(
Console.WriteLine,
new[] { DbLoggerCategory.Database.Command.Name }, // 仅 SQL
LogLevel.Information);
// 输出示例:
// info: Microsoft.EntityFrameworkCore.Database.Command[20101]
// Executed DbCommand (12ms) [Parameters=[@__id_0='?'], CommandType='Text', CommandTimeout='30']
// SELECT [p].[Id], [p].[Name] FROM [Products] AS [p] WHERE [p].[Id] = @__id_0记录查询和命令
csharp
options.LogTo(
Console.WriteLine,
new[]
{
DbLoggerCategory.Database.Command.Name,
DbLoggerCategory.Query.Name
},
LogLevel.Information);排除某些类别
csharp
options.LogTo(
Console.WriteLine,
LogLevel.Debug,
filter: (category, level) =>
{
// 排除模型构建日志(启动时大量输出)
return category != DbLoggerCategory.Model.Name;
});格式化输出
自定义格式
csharp
options.LogTo(
message =>
{
var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine($"[{timestamp}] EF: {message}");
Console.ResetColor();
},
LogLevel.Information);使用 ILogger
csharp
// Program.cs
builder.Services.AddDbContext<AppDbContext>((sp, options) =>
{
var loggerFactory = sp.GetRequiredService<ILoggerFactory>();
options.UseLoggerFactory(loggerFactory); // 使用应用的日志系统
options.UseSqlServer(connectionString);
});
// appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.EntityFrameworkCore": "Information",
"Microsoft.EntityFrameworkCore.Database.Command": "Debug"
}
}
}敏感数据日志
启用敏感数据
csharp
// ⚠️ 仅在开发环境启用!
if (builder.Environment.IsDevelopment())
{
options.EnableSensitiveDataLogging(); // 显示参数值
options.EnableDetailedErrors(); // 详细错误信息
}
// 输出对比:
// ❌ 未启用敏感数据日志
// Parameters=[@__price_0='?' (DbType = Decimal)]
// ✅ 启用敏感数据日志
// Parameters=[@__price_0='99.99' (DbType = Decimal)]生产环境安全
csharp
// ✅ 正确: 根据环境配置
if (builder.Environment.IsDevelopment())
{
options.EnableSensitiveDataLogging();
}
else
{
// 生产环境: 不记录参数值
options.ConfigureWarnings(warnings =>
warnings.Ignore(RelationalEventId.SensitiveDataLoggingEnabledWarning));
}事件 ID 过滤
特定事件 ID
csharp
// 常见事件 ID
// 20101: Command executed
// 20102: Command error
// 10107: Query execution plan cached
// 40100: Transaction started
options.LogTo(
Console.WriteLine,
new EventId[] { 20101, 20102 }, // 仅记录命令执行
LogLevel.Information);使用枚举
csharp
options.LogTo(
Console.WriteLine,
new[]
{
RelationalEventId.CommandExecuted.Name,
RelationalEventId.CommandError.Name
},
LogLevel.Information);性能监控
记录慢查询
csharp
public class SlowQueryLogger
{
private readonly ILogger<SlowQueryLogger> _logger;
private readonly TimeSpan _threshold = TimeSpan.FromSeconds(1);
public SlowQueryLogger(ILogger<SlowQueryLogger> logger)
{
_logger = logger;
}
public void LogIfSlow(string message)
{
// 解析执行时间(从日志消息中提取)
if (message.Contains("Executed DbCommand"))
{
var match = System.Text.RegularExpressions.Regex.Match(
message, @"Executed DbCommand \((\d+)ms\)");
if (match.Success && int.TryParse(match.Groups[1].Value, out int ms))
{
if (TimeSpan.FromMilliseconds(ms) > _threshold)
{
_logger.LogWarning("SLOW QUERY ({Ms}ms): {Message}", ms, message);
}
}
}
}
}
// 使用
var slowQueryLogger = new SlowQueryLogger(loggerFactory.CreateLogger<SlowQueryLogger>());
options.LogTo(
message =>
{
Console.WriteLine(message);
slowQueryLogger.LogIfSlow(message);
},
LogLevel.Information);统计查询次数
csharp
public class QueryCounter
{
private int _queryCount = 0;
private long _totalExecutionTime = 0;
public void CountQuery(string message)
{
if (message.Contains("Executed DbCommand"))
{
Interlocked.Increment(ref _queryCount);
var match = System.Text.RegularExpressions.Regex.Match(
message, @"Executed DbCommand \((\d+)ms\)");
if (match.Success && int.TryParse(match.Groups[1].Value, out int ms))
{
Interlocked.Add(ref _totalExecutionTime, ms);
}
}
}
public void PrintStats()
{
Console.WriteLine($"Total Queries: {_queryCount}");
Console.WriteLine($"Total Execution Time: {_totalExecutionTime}ms");
Console.WriteLine($"Average Execution Time: {_queryCount > 0 ? _totalExecutionTime / _queryCount : 0}ms");
}
}结构化日志
Serilog 集成
bash
dotnet add package Serilog.AspNetCore
dotnet add package Serilog.Sinks.File
dotnet add package Serilog.Sinks.Consolecsharp
// Program.cs
using Serilog;
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console()
.WriteTo.File("logs/ef-.log", rollingInterval: RollingInterval.Day)
.CreateLogger();
builder.Host.UseSerilog();
builder.Services.AddDbContext<AppDbContext>(options =>
{
options.UseSqlServer(connectionString);
options.UseLoggerFactory(Log.ForContext<AppDbContext>().LoggerFactory);
});输出 JSON 格式
json
{
"Timestamp": "2026-04-06T10:30:45.123Z",
"Level": "Information",
"MessageTemplate": "Executed DbCommand ({Duration}ms)",
"Properties": {
"Duration": 12,
"CommandType": "Text",
"CommandText": "SELECT [p].[Id], [p].[Name] FROM [Products] AS [p]"
}
}诊断源(DiagnosticSource)
订阅诊断事件
csharp
using System.Diagnostics;
public class EfCoreDiagnosticObserver : IObserver<KeyValuePair<string, object>>
{
private readonly ILogger<EfCoreDiagnosticObserver> _logger;
public EfCoreDiagnosticObserver(ILogger<EfCoreDiagnosticObserver> logger)
{
_logger = logger;
}
public void OnCompleted()
{
}
public void OnError(Exception error)
{
_logger.LogError(error, "Diagnostic error");
}
public void OnNext(KeyValuePair<string, object> value)
{
switch (value.Key)
{
case "Microsoft.EntityFrameworkCore.Database.Command.CommandExecuting":
var command = (DbCommand)value.Value.GetType().GetProperty("Command")!.GetValue(value.Value)!;
_logger.LogInformation("Executing: {CommandText}", command.CommandText);
break;
case "Microsoft.EntityFrameworkCore.Database.Command.CommandExecuted":
var duration = (TimeSpan)value.Value.GetType().GetProperty("Duration")!.GetValue(value.Value)!;
_logger.LogInformation("Executed in {Duration}ms", duration.TotalMilliseconds);
break;
}
}
}
// 注册
var observer = new EfCoreDiagnosticObserver(loggerFactory.CreateLogger<EfCoreDiagnosticObserver>());
var subscription = DiagnosticListener.AllListeners.Subscribe(observer);实际应用示例
开发环境完整配置
csharp
if (builder.Environment.IsDevelopment())
{
builder.Services.AddDbContext<AppDbContext>(options =>
{
options.UseSqlServer(connectionString);
// 记录所有 SQL 命令
options.LogTo(
Console.WriteLine,
new[] { DbLoggerCategory.Database.Command.Name },
LogLevel.Information);
// 显示参数值
options.EnableSensitiveDataLogging();
// 详细错误
options.EnableDetailedErrors();
});
}生产环境配置
csharp
else
{
builder.Services.AddDbContext<AppDbContext>(options =>
{
options.UseSqlServer(connectionString, sqlOptions =>
{
sqlOptions.EnableRetryOnFailure();
});
// 仅记录错误
options.LogTo(
message => logger.LogWarning(message),
LogLevel.Error);
// 忽略敏感数据警告
options.ConfigureWarnings(warnings =>
warnings.Ignore(RelationalEventId.SensitiveDataLoggingEnabledWarning));
});
}测试环境配置
csharp
if (builder.Environment.IsEnvironment("Testing"))
{
builder.Services.AddDbContext<AppDbContext>(options =>
{
options.UseSqlite("Data Source=:memory:");
// 测试时记录详细日志
options.LogTo(
testOutputHelper.WriteLine, // xUnit 输出
LogLevel.Debug);
});
}最佳实践
✅ 推荐做法
1. 根据环境配置日志级别
csharp
if (env.IsDevelopment())
{
options.LogTo(Console.WriteLine, LogLevel.Debug);
options.EnableSensitiveDataLogging();
}
else
{
options.LogTo(logger.LogError, LogLevel.Error);
}2. 仅记录需要的类别
csharp
// 生产环境: 仅记录错误
options.LogTo(logger.LogError, new[] { DbLoggerCategory.Database.Command.Name }, LogLevel.Error);
// 开发环境: 记录 SQL
options.LogTo(Console.WriteLine, new[] { DbLoggerCategory.Database.Command.Name }, LogLevel.Information);3. 使用结构化日志
csharp
// ✅ Serilog 结构化日志
Log.Information("Query executed in {Duration}ms for {Entity}", duration, entityType);4. 定期清理日志文件
csharp
// 滚动日志,保留 7 天
.WriteTo.File("logs/ef-.log",
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: 7)❌ 避免的错误
1. 生产环境启用敏感日志
csharp
// ❌ 危险: 泄漏密码、个人信息
options.EnableSensitiveDataLogging(); // 在生产环境!
// ✅ 仅开发环境
if (env.IsDevelopment())
{
options.EnableSensitiveDataLogging();
}2. 记录过多无用信息
csharp
// ❌ 性能差,日志过大
options.LogTo(Console.WriteLine, LogLevel.Trace);
// ✅ 选择合适的级别
options.LogTo(Console.WriteLine, LogLevel.Information);3. 忘记配置日志轮转
csharp
// ❌ 日志文件无限增长
options.LogTo(message => File.AppendAllText("ef.log", message));
// ✅ 使用滚动日志
.WriteTo.File("ef-.log", rollingInterval: RollingInterval.Day)故障排查
问题 1: 日志不输出
csharp
// 检查 1: 日志级别是否正确
options.LogTo(Console.WriteLine, LogLevel.Information); // 不是 Warning/Error
// 检查 2: 是否被过滤器阻止
// appsettings.json 中检查:
{
"Logging": {
"LogLevel": {
"Microsoft.EntityFrameworkCore": "Information" // ← 确保不是 None
}
}
}
// 检查 3: 是否在正确的环境中
if (builder.Environment.IsDevelopment()) // ← 确认环境匹配
{
options.LogTo(Console.WriteLine, LogLevel.Information);
}问题 2: 日志过多影响性能
csharp
// 解决方案 1: 采样记录
var random = new Random();
options.LogTo(
message =>
{
if (random.Next(100) < 10) // 10% 采样
{
logger.LogInformation(message);
}
},
LogLevel.Information);
// 解决方案 2: 仅记录慢查询
options.LogTo(
message =>
{
if (message.Contains("Executed DbCommand"))
{
// 解析并判断是否慢查询
}
},
LogLevel.Information);总结
日志配置速查表
| 场景 | 配置 |
|---|---|
| 开发调试 | LogTo(Console.WriteLine, Information) + EnableSensitiveDataLogging() |
| 生产监控 | LogTo(logger.LogError, Error) |
| 性能分析 | 自定义拦截器记录慢查询 |
| 单元测试 | LogTo(testOutput.WriteLine, Debug) |
| 问题排查 | LogTo(Console.WriteLine, Debug) + 所有类别 |
核心要点
- 开发环境: 详细日志,显示参数值
- 生产环境: 仅记录错误,避免性能影响
- 敏感数据: 永远不要在生产环境启用
- 日志轮转: 防止磁盘空间耗尽
- 结构化日志: 便于后续分析和查询