如何在.NET Core 3.1应用中借助Serilog SQL Server sink全局记录已处理异常
核心原因
操作过滤器、全局异常中间件仅能捕获向上冒泡到控制器层的未处理异常,仓储层嵌套try/catch逻辑已经将异常内部消化,未向上抛出,因此上层全局捕获组件无法拿到异常信息。
可行解决方案
方案1:仓储层新增异常日志写入逻辑(改造成本最低)
无需修改现有业务流程,仅需在所有catch块内追加日志写入逻辑即可。你之前出现独立类调用日志无法写入数据库的问题,大概率是ILogger依赖注入异常导致,直接使用Serilog提供的静态日志类即可绕开DI问题。
代码示例:
public class EfRepository<T> : IRepository<T> { public async Task YourNestedMethod() { try { // 原有业务逻辑 } catch (Exception ex) { // 保留原有异常处理逻辑,不影响业务正常运行 HandleOriginalException(ex); // 新增日志写入,可自定义附加参数 Serilog.Log.Error(ex, "仓储层执行异常,方法名:{MethodName}", nameof(YourNestedMethod)); } } }
如果需要同步写入用户ID、请求路径等上下文信息,可在Serilog配置中开启Enrich.FromLogContext(),并新增中间件统一注入请求上下文:
public class LogContextMiddleware { private readonly RequestDelegate _next; public LogContextMiddleware(RequestDelegate next) => _next = next; public async Task InvokeAsync(HttpContext context) { var userId = context.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value; using (Serilog.Context.LogContext.PushProperty("UserId", userId)) using (Serilog.Context.LogContext.PushProperty("RequestPath", context.Request.Path)) { await _next(context); } } }
在Startup的Configure方法中将该中间件放在请求管道最前面即可,后续所有位置写入的Serilog日志都会自动携带上下文参数,和你现有操作过滤器的日志格式保持统一。
方案2:AOP动态代理拦截(无侵入改造)
如果不想修改现有仓储层代码,可使用Castle DynamicProxy等AOP框架对所有仓储层方法做动态拦截,自动为方法套入异常捕获逻辑,捕获异常后写入日志再将异常抛出给原有catch块处理,完全不影响现有逻辑。
步骤如下:
- 安装
Castle.CoreNuget包 - 实现拦截器:
public class RepositoryLoggingInterceptor : IInterceptor { public void Intercept(IInvocation invocation) { try { invocation.Proceed(); // 处理异步方法的异常捕获 if (invocation.ReturnValue is Task task) { task.ContinueWith(t => { if (t.IsFaulted) { Serilog.Log.Error(t.Exception, "仓储方法{Method}执行异常", invocation.Method.Name); } }); } } catch (Exception ex) { Serilog.Log.Error(ex, "仓储方法{Method}执行异常", invocation.Method.Name); // 继续抛出异常,交由原有catch块处理 throw; } } }
- 在DI注册仓储时用拦截器包装即可,.NET Core 3.1自带DI不支持拦截器,可搭配Autofac等第三方DI框架实现。
方案3:统一异常上报服务(扩展性最强)
如果后续需要对接监控、告警等能力,可单独抽取异常上报服务,所有catch块统一调用该服务做异常处理,便于后续逻辑扩展:
public interface IExceptionReportService { void Report(Exception ex, string moduleName, string methodName); } public class SerilogExceptionReportService : IExceptionReportService { public void Report(Exception ex, string moduleName, string methodName) { // 可扩展告警、监控上报等逻辑 Serilog.Log.Error(ex, "{Module}模块{Method}方法执行异常", moduleName, methodName); } }
将该服务注册为单例后注入到需要的位置使用即可。
日志无法写入数据库排查思路
如果出现日志未写入数据库的情况,可按顺序排查:
- 本地调试时将Serilog同时输出到控制台,确认日志是否正常生成
- 检查Serilog配置中SqlServer Sink的连接字符串是否正确,是否开启了即时写入(关闭缓冲区)
- 确认数据库账号有对应表的写入权限
内容的提问来源于stack exchange,提问作者Tony
相关产品推荐
相关产品推荐

