Asp.net Core全局异常监控和记录日志
程序员文章站
2023-11-09 22:12:28
前言 系统异常监控可以说是重中之重,系统不可能一直运行良好,开发和运维也不可能24小时盯着系统,系统抛异常后我们应当在第一时间收到异常信息。在Asp.net Core里我使用拦截器和中间件两种方式来监控异常。全局异常监控的数据最好还是写入数据库,方便查询。 配置NLog NLog配置文件 注入NLo ......
前言
系统异常监控可以说是重中之重,系统不可能一直运行良好,开发和运维也不可能24小时盯着系统,系统抛异常后我们应当在第一时间收到异常信息。在asp.net core里我使用拦截器和中间件两种方式来监控异常。全局异常监控的数据最好还是写入数据库,方便查询。
配置nlog
nlog配置文件
<?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" internallogfile="d:\temp\internal-nlog.txt"> <!-- the targets to write to --> <targets> <!-- write logs to file --> <target xsi:type="file" name="allfile" filename="d:\temp\nlog-all-${shortdate}.log" layout="${longdate}|${event-properties:item=eventid.id}|${uppercase:${level}}|${logger}|${message} ${exception}" /> <!-- another file log, only own logs. uses some asp.net core renderers --> <target xsi:type="file" name="ownfile-web" filename="d:\temp\nlog-own-${shortdate}.log" layout="${longdate}|${event-properties:item=eventid.id}|${uppercase:${level}}|${logger}|${message} ${exception}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}" /> <!-- write to the void aka just remove --> <target xsi:type="null" name="blackhole" /> </targets> <!-- rules to map from logger name to target --> <rules> <!--all logs, including from microsoft--> <logger name="*" minlevel="trace" writeto="allfile" /> <!--skip microsoft logs and so log only own logs--> <logger name="microsoft.*" minlevel="trace" writeto="blackhole" final="true" /> <logger name="*" minlevel="trace" writeto="ownfile-web" /> </rules> </nlog>
注入nlog
在program.cs里注入nlog依赖,添加依赖前需要导入两个命名空间microsoft.extensions.logging、 nlog.web。
public class program { public static void main(string[] args) { createhostbuilder(args).build().run(); } public static ihostbuilder createhostbuilder(string[] args) => host.createdefaultbuilder(args) .configurewebhostdefaults(webbuilder => { webbuilder.usestartup<startup>(); }) .configurelogging(logging=> { logging.clearproviders(); logging.setminimumlevel(microsoft.extensions.logging.loglevel.trace); }) .usenlog(); }
拦截器
在asp.mvc里最常用的拦截器,在asp.net core里也是支持的。先定义拦截器,再注入拦截器,这里自定义拦截器实现接口iexceptionfilter,接口会要求实现onexception方法,当系统发生未捕获的异常时就会触发这个方法。这里全局异常信息最好能放入数据库里,方便后台查询,再就是抛异常后最好能给负责人发邮件和发送报警短信,也可以直接拨打电话。
public class globalexceptionfilter : iexceptionfilter { private iwebhostenvironment _env; private ilogger<globalexceptionfilter> _logger; public globalexceptionfilter(iwebhostenvironment _env,ilogger<globalexceptionfilter> _logger) { this._env = _env; this._logger = _logger; } public void onexception(exceptioncontext context) { if (context.exception.gettype() == typeof(busexception)) { //如果是自定义异常,则不做处理 } else { } //日志入库 //向负责人发报警邮件,异步 //向负责人发送报警短信或者报警电话,异步 exception ex = context.exception; //这里给系统分配标识,监控异常肯定不止一个系统。 int sysid = 1; //这里获取服务器ip时,需要考虑如果是使用nginx做了负载,这里要兼容负载后的ip, //监控了ip方便定位到底是那台服务器出故障了 string ip = context.httpcontext.connection.remoteipaddress.tostring(); _logger.logerror($"系统编号:{sysid},主机ip:{ip},堆栈信息:{ex.stacktrace},异常描述:{ex.message}"); context.result = new jsonresult(resultbody.error(ex.message)); context.exceptionhandled = true; } }
在startup.configureservices方法里注入全局异常处理拦截器。
public void configureservices(iservicecollection services) { services.addcontrollerswithviews(); //注入全局异常处理 services.addmvc(option => { option.filters.add(typeof(globalexceptionfilter)); }); }
ok,定义了拦截器后,我们自己抛一个未捕获的异常试试。如图,都会返回统一的json返回值。
如果未使用全局异常捕获,则直接抛出如下异常
客户端抛出异常后,可查看磁盘写入日志,这里看到我关注的系统编号,主机ip,堆栈信息和异常描述信息。
中间件
定义中间件,定义中间件时先导入日志命名空间microsoft.extensions.logging。
public class globalexceptionmiddleware { private readonly requestdelegate next; private ilogger<globalexceptionmiddleware> logger; public globalexceptionmiddleware(requestdelegate next, ilogger<globalexceptionmiddleware> logger) { this.next = next; this.logger = logger; } public async task invoke(httpcontext context) { try { await next.invoke(context); } catch (exception ex) { await handleexceptionasync(context, ex); } } private async task handleexceptionasync(httpcontext context, exception e) { if (e.gettype() == typeof(busexception)) { //如果是自定义异常,则不做处理 } else { } //记日志 int sysid = 1; string ip = context.connection.remoteipaddress.tostring(); logger.logerror($"系统编号:{sysid},主机ip:{ip},堆栈信息:{e.stacktrace},异常描述:{e.message}"); string result = system.text.json.jsonserializer.serialize(resultbody.error(e.message)); await context.response.writeasync(result); } }
在startup.configure方法里注册中间件。
public void configure(iapplicationbuilder app, iwebhostenvironment env,iloggerfactory loggerfactory) { if (env.isdevelopment()) { app.usedeveloperexceptionpage(); } else { app.useexceptionhandler("/home/error"); } //注册异常处理中间件 app.usemiddleware<globalexceptionmiddleware>(); app.usestaticfiles(); app.userouting(); app.useauthorization(); app.useendpoints(endpoints => { endpoints.mapcontrollerroute( name: "default", pattern: "{controller=home}/{action=index}/{id?}"); }); }
中间件这里处理异常最后向客户端响应写入了一个字符串,这是个拦截器处理方式不同的地方。当然对客户端或者前端来说还是json对象更直观些。