前言
在我们项目开发测试过程中,有时候本地调试环境可能没问题,一部署到正式环境就可能出现各种问题,错误的原因各种各样,那么我们怎么准确的定位到错误呢,这时候就需要弄个可以监听这些业务功能的错误日志,但是又得要全局。这时候贴心的.net已经帮我们想到一种方案了,那就是Filter-过滤器,官网也叫筛选器,字面意思都差不多。
Filter-过滤器
Filter是延续ASP.NET MVC的产物,同样保留了五种的Filter,分别是Authorization Filter、Resource Filter、Action Filter、Exception Filter及Result Filter。 通过不同的Filter可以有效处理封包进出的加工。
更多细项介绍以及一些过滤器之间的优先级别可以参考官网文档:《ASP.NET Core 中的筛选器》
自定义新建一个ExceptionFilter
GlobalExceptionsFilter.cs
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
namespace CuoDing.Core.Filter
{
/// <summary>
/// 全局异常错误日志
/// </summary>
public class GlobalExceptionsFilter : IExceptionFilter
{
private readonly IWebHostEnvironment _env;
private readonly ILogger<GlobalExceptionsFilter> _logger;
public GlobalExceptionsFilter(IWebHostEnvironment env, ILogger<GlobalExceptionsFilter> logger)
{
_env = env;
_logger = logger;
}
public void OnException(ExceptionContext context)
{
var json = new JsonErrorResponse();
json.Message = context.Exception.Message;//错误信息
var errorAudit = "Unable to resolve service for";//特殊错误信息
if (!string.IsNullOrEmpty(json.Message)&& json.Message.Contains(errorAudit))
{
json.Message = json.Message.Replace(errorAudit, $"(若新添加服务,需要重新编译项目){errorAudit}");
}
if (_env.IsDevelopment())
{
json.DevelopmentMessage = context.Exception.StackTrace;//堆栈信息
}
context.Result = new InternalServerErrorObjectResult(json);
//输出错误日志信息
//_logger.LogError(json.Message + WriteLog(json.Message, context.Exception));
_logger.LogError(json.Message + string.Format("\r\n【自定义错误】:{0} \r\n【异常类型】:{1} \r\n【异常信息】:{2} \r\n【堆栈调用】:{3}", new object[] { json.Message,
context.Exception.GetType().Name, context.Exception.Message, context.Exception.StackTrace }));
}
}
public class InternalServerErrorObjectResult : ObjectResult
{
public InternalServerErrorObjectResult(object value) : base(value)
{
StatusCode = StatusCodes.Status500InternalServerError;
}
}
//返回错误信息
public class JsonErrorResponse
{
/// <summary>
/// 生产环境的消息
/// </summary>
public string Message { get; set; }
/// <summary>
/// 开发环境的消息
/// </summary>
public string DevelopmentMessage { get; set; }
}
}
注册过滤器
1、ServiceFilter 注册
在需要拦截的地方增加特性 [ServiceFilter(typeof(GlobalExceptionsFilter))] 并在ConfigureServices 中注册GlobalExceptionsFilter
services.AddScoped(typeof(GlobalExceptionsFilter));
2、TypeFilter 注册
在需要拦截的地方增加特性 [TypeFilter(typeof(GlobalExceptionsFilter))] 此方式不需要在ConfigureServices 中注册
3、FilterFactory 注册
3.1编写Factory 类
public class ExpertFilterFactory : Attribute,IFilterFactory
{
public bool IsReusable => true;
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
return serviceProvider.GetService(typeof(GlobalExceptionsFilter)) as IFilterMetadata;
}
}
3.2在需要拦截的地方增加特性 [ExpertFilterFactory] 并在ConfigureServices 中注册GlobalExceptionsFilter
services.AddScoped(typeof(GlobalExceptionsFilter));
4、全局注册
在ConfigureServices中注册
services.AddControllers(options =>
{
//添加全局异常过滤器
options.Filters.Add<GlobalExceptionsFilter>();
});
对AddControllers陌生的小伙伴可以看看这篇博友「7号南孚电池」写的文章,感觉介绍十分详细:《AddMvcCore,AddControllers,AddControllersWithViews,AddRazorPages的区别》
加入Nlog记录日志信息
1、添加nuget包
<PackageReference Include="NLog" Version="4.7.10" />
<PackageReference Include="NLog.Web.AspNetCore" Version="4.12.0" />
2、添加nlog配置文件nlog.config
<?xml version="1.0" encoding="utf-8"?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true" internalLogLevel="Info">
<!-- 启用.net core的核心布局渲染器 -->
<extensions>
<add assembly="NLog.Web.AspNetCore" />
</extensions>
<!-- 写入日志的目标配置 archiveAboveSize="102400" maxArchiveDays="60" -->
<targets>
<!-- 调试 -->
<target xsi:type="File" name="debug" fileName="${basedir}/logs/debug-${shortdate}.log" layout="${longdate}|${event-properties:item=EventId_Id}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}" />
<!-- 警告 -->
<target xsi:type="File" name="warn" fileName="${basedir}/logs/warn-${shortdate}.log" layout="${longdate}|${event-properties:item=EventId_Id}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}" />
<!-- 错误 -->
<target xsi:type="File" name="error" fileName="${basedir}/logs/error-${shortdate}.log" layout="${longdate}|${event-properties:item=EventId_Id}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}" />
<!-- 控制台 -->
<target xsi:type="Console" name="console" layout="${message}" />
</targets>
<!-- 映射规则 -->
<rules>
<!-- 调试 -->
<logger name="*" minlevel="Trace" maxlevel="Debug" writeTo="debug" />
<!--<logger name="*" minlevel="Trace" writeTo="console" />-->
<!-- 警告 -->
<!--<logger name="*" minlevel="Info" maxlevel="Warn" writeTo="warn" />-->
<!-- 错误 -->
<logger name="*" minlevel="Error" maxlevel="Fatal" writeTo="error" />
<!--跳过不重要的微软日志-->
<logger name="Microsoft.*" maxlevel="Info" final="true" />
</rules>
</nlog>
3、Program修改
注入Nlog
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new AutofacServiceProviderFactory())//替换默认容器
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
}).UseNLog(); // NLog: 为依赖注入设置NLog
监听程序启动过程
public static void Main(string[] args)
{
var logger = NLog.Web.NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
try
{
logger.Debug("初始化 main");
CreateHostBuilder(args).Build().Run();
}
catch (Exception exception)
{
//NLog: 捕获设置错误
logger.Error(exception, "由于异常而停止程序");
throw;
}
finally
{
// 确保在应用程序退出之前刷新并停止内部计时器/线程(避免Linux上出现分段错误)
NLog.LogManager.Shutdown();
}
}
测试
测试一:
webapi项目运行
测试二:
把之前的autofac容器的方法暂时注释掉,用来测试之前接口报错信息
期望接口的结果:报错
重新编译运行项目,任意调用一个接口
日志也没问题,大功告成
完整代码已上传码云:https://gitee.com/shao-jiayong/cuo-ding