Skip to content

启用日志 ​

概述 ​

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.Console
csharp
// 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) + 所有类别

核心要点 ​

  1. 开发环境: 详细日志,显示参数值
  2. 生产环境: 仅记录错误,避免性能影响
  3. 敏感数据: 永远不要在生产环境启用
  4. 日志轮转: 防止磁盘空间耗尽
  5. 结构化日志: 便于后续分析和查询

基于 MIT 许可发布