ASP.NET Core如何为Logger添加异常处理并将日志存储至数据库
实现方案
总共分两步,先搞定全局异常自动日志记录,再实现日志落库。
一、未处理异常自动记录
你现在抛异常没日志的原因很简单:
- Index方法里
throw new Exception("testing")之后的代码不会执行,你写的访问日志逻辑根本跑不到 - 没有全局异常拦截机制,未处理异常不会自动走到日志记录流程里
不要在每个Action里套try-catch,直接用全局异常中间件统一处理,覆盖所有接口和页面:
在Program.cs(.NET 6+)或Startup.Configure中,把异常处理中间件放到所有其他中间件的最前面:
// 开发环境保留开发者异常页,生产环境用自定义异常处理 if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler(errorApp => { errorApp.Run(async context => { var exceptionFeature = context.Features.Get<IExceptionHandlerPathFeature>(); if (exceptionFeature?.Error != null) { var logger = context.RequestServices.GetRequiredService<ILogger<CascadingController>>(); // 直接把异常对象传给LogError,自动记录错误消息、堆栈、请求路径 logger.LogError(exceptionFeature.Error, "请求路径 {Path} 发生未处理异常", context.Request.Path); context.Response.StatusCode = StatusCodes.Status500InternalServerError; // 跳转到你项目里已经写好的Error页 context.Response.Redirect("/Cascading/Error"); return; } context.Response.StatusCode = StatusCodes.Status500InternalServerError; }); }); } else { app.UseDeveloperExceptionPage(); } // 后面放你原来的中间件:UseHttpsRedirection、UseRouting、UseAuthorization、MapControllerRoute这些
加完之后,任何Action抛出的未处理异常都会被自动捕获并记录,不需要单独在业务代码里加异常处理逻辑。
二、日志持久化到数据库
你项目已经集成了EF Core,直接自定义日志提供程序,把所有ILogger输出的日志(包括你手动写的访问日志、全局捕获的异常日志)自动存到数据库即可,不需要引入第三方组件。
- 首先新增日志表实体,放到Models目录下:
public class SystemLog { public long Id { get; set; } public string LogLevel { get; set; } public string Category { get; set; } public string Content { get; set; } public string? ExceptionStack { get; set; } public DateTimeOffset CreateTime { get; set; } } - 在你现有的
newnewContext中添加对应DbSet:
执行EF Core迁移命令创建日志表:public DbSet<SystemLog> SystemLogs { get; set; }Add-Migration AddSystemLogTable Update-Database - 编写自定义日志提供程序,实现日志写入数据库的逻辑:
public class EfCoreLogger : ILogger { private readonly string _category; private readonly IServiceProvider _sp; public EfCoreLogger(string category, IServiceProvider sp) { _category = category; _sp = sp; } public IDisposable? BeginScope<TState>(TState state) where TState : notnull => default; public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None; public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter) { if (!IsEnabled(logLevel)) return; // 手动创建作用域解析DbContext,避免单例日志组件直接引用Scoped生命周期的DbContext using var scope = _sp.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService<newnewContext>(); dbContext.SystemLogs.Add(new SystemLog { LogLevel = logLevel.ToString(), Category = _category, Content = formatter(state, exception), ExceptionStack = exception?.ToString(), CreateTime = DateTimeOffset.Now }); dbContext.SaveChanges(); } } public class EfCoreLoggerProvider : ILoggerProvider { private readonly IServiceProvider _sp; public EfCoreLoggerProvider(IServiceProvider sp) => _sp = sp; public ILogger CreateLogger(string categoryName) => new EfCoreLogger(categoryName, _sp); public void Dispose() { } } - 在Program.cs的服务注册部分,把自定义日志提供程序注册到容器:
var builder = WebApplication.CreateBuilder(args); // 这里放你原来的服务注册代码:AddControllersWithViews、AddDbContext等 // 注册自定义日志提供程序 builder.Services.AddSingleton<ILoggerProvider, EfCoreLoggerProvider>();
补充说明
- 你现有Index方法里
throw语句之后的访问日志是死代码,永远不会执行,如果需要记录访问日志,把日志代码移到throw前面即可。如果要统一记录所有请求的访问日志,直接加个请求日志中间件就行,不用每个Action单独写。 - 小项目用上面的同步写库逻辑完全够用,如果接口访问量高,可以加个内存队列做异步批量写入,避免每次日志IO影响接口响应速度。
- 你之前在Country、State、District方法里写的
LogInformation访问日志,加完自定义日志提供程序之后会自动存到数据库,不需要修改原有代码。
内容的提问来源于stack exchange,提问作者gourav kumar
相关产品推荐
相关产品推荐

