WebAPI 用ExceptionFilterAttribute实现错误(异常)日志的记录(log4net做写库操作)

好吧,还是那个社区APP,非管理系统,用户行为日志感觉不是很必要的,但是,错误日志咱还是得记录则个。总不能上线后报bug了让自己手足无措吧,虽然不管有木有错误日志报bug都是件很头疼的事...

我们知道webAPI也有好几个Filter,上篇文章我们做token与权限用到了ActionFilterAttribute,这次我们用ExceptionFilterAttribute来做异常日志的记录。首先我们的代码里面会主动的捕获一些异常手动抛出,例如对用户输入数据的验证,权限的验证,业务的验证等。也会有一些我们无法预料的异常,可能是代码的漏洞或者逻辑的漏洞...那么我们肯定是想能够在一个切面全部拦截这些异常,记录到错误日志中,以便作分析使用...

博主使用的log4net做日志的写库操作,这里就不介绍log4net的基本用法了,直接上代码:

  1 <log4net>
2 <!--<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
3 <file value="C:\testlog.txt" />
4 <appendToFile value="true" />
5 <maxSizeRollBackups value="10" />
6 <maximumFileSize value="100" />
7 <rollingStyle value="Date" />
8 <datePattern value="yyyy-MM-dd" />
9 <staticLogFileName value="true" />
10 <layout type="log4net.Layout.PatternLayout">
11 <conversionPattern value="记录时间:%date 线程ID:[%thread] 日志级别:%-5level 出错类:%logger property:[%property{NDC}] - 错误描述:%message%newline " />
12 </layout>
13 </appender>-->
14 <appender name="AdoNetAppender_Sqlserver" type="log4net.Appender.AdoNetAppender">
15 <connectionType value="System.Data.SqlClient.SqlConnection,System.Data, Version=4.0.0.0, Culture=neutral,PublicKeyToken=b77a5c561934e089" />
21 <connectionString value="Data Source=.;database=RCBLog;Integrated Security=True; MultipleActiveResultSets=True;" />
22 <commandText value="INSERT INTO ErrorLog (LOGID,LOG_DATE,LOG_MESSAGE,LOG_EXCEPTION,LOG_LEVEL,LOGGER,LOG_SOURCE,OPERATORID,OPERATORACCOUNTNAME) VALUES (@LogId,@log_date,@LogMessage,@LogException,@log_level, @logger, @source,@LogOperator,@OperatorAccountName)" />
23 <bufferSize value="100" />
24 <parameter>
25 <parameterName value="@log_date" />
26 <dbType value="DateTime" />
27 <layout type="log4net.Layout.RawTimeStampLayout" />
28 </parameter>
29 <parameter>
30 <parameterName value="@log_level" />
31 <dbType value="String" />
32 <size value="100" />
33 <layout type="log4net.Layout.PatternLayout">
34 <conversionPattern value="%level" />
35 </layout>
36 </parameter>
37 <parameter>
38 <parameterName value="@logger" />
39 <dbType value="String" />
40 <size value="255" />
41 <layout type="log4net.Layout.PatternLayout">
42 <conversionPattern value="%logger" />
43 </layout>
44 </parameter>
45 <parameter>
46 <parameterName value="@source" />
47 <dbType value="String" />
48 <size value="2000" />
49 <layout type="log4net.Layout.PatternLayout">
50 <conversionPattern value="%file:%line" />
51 </layout>
52 </parameter>
53 <!--自定义属性-->
54 <parameter>
55 <parameterName value="@LogId" />
56 <dbType value="String" />
57 <size value="255" />
58 <layout type="MP.Infrastructure.SystemLog.CustomLayout,MP.Infrastructure">
59 <conversionPattern value="%LogId" />
60 </layout>
61 </parameter>
62 <parameter>
63 <parameterName value="@LogException" />
64 <dbType value="String" />
65 <size value="4000" />
66 <layout type="MP.Infrastructure.SystemLog.CustomLayout,MP.Infrastructure">
67 <conversionPattern value="%LogException" />
68 </layout>
69 </parameter>
70 <parameter>
71 <parameterName value="@LogMessage" />
72 <dbType value="String" />
73 <size value="4000" />
74 <layout type="MP.Infrastructure.SystemLog.CustomLayout,MP.Infrastructure">
75 <conversionPattern value="%LogMessage" />
76 </layout>
77 </parameter>
78 <parameter>
79 <parameterName value="@LogOperator" />
80 <dbType value="String" />
81 <size value="255" />
82 <layout type="MP.Infrastructure.SystemLog.CustomLayout,MP.Infrastructure">
83 <conversionPattern value="%OperatorId" />
84 </layout>
85 </parameter>
86 <parameter>
87 <parameterName value="@OperatorAccountName" />
88 <dbType value="String" />
89 <size value="255" />
90 <layout type="MP.Infrastructure.SystemLog.CustomLayout,MP.Infrastructure">
91 <conversionPattern value="%OperatorAccountName" />
92 </layout>
93 </parameter>
94 </appender>
95 <logger name="RCB.Logger.Error">
96 <level value="ERROR" />
97 <!--<appender-ref ref="RollingFileAppender" />-->
98 <appender-ref ref="AdoNetAppender_Sqlserver" />
99 </logger>
100 </log4net>

注:

1.<log4net>节点需要在<configuration>节点下

2.注释掉的2-13行与97行是写文件

3.第23行的数值表示缓存值,调试阶段可以设置成0,才能及时的在数据库看到错误日志

4.博主使用到了自定义属性,就顺便说说自定义属性的用法,先看看博主的错误日志类:

 1     /// <summary>
2 /// 系统错误日志
3 /// </summary>
4 public class ErrorLog
5 {
6 /// <summary>
7 /// ID(GUID字符串)
8 /// </summary>
9 public string LOGID { get; set; }
10
11 /// <summary>
12 /// 日志时间
13 /// </summary>
14 public DateTime LOG_DATE { get; set; }
15
16 /// <summary>
17 /// 日志错误信息
18 /// </summary>
19 public string LOG_MESSAGE { get; set; }
20
21 /// <summary>
22 /// 异常信息详情
23 /// </summary>
24 public string LOG_EXCEPTION { get; set; }
25
26 /// <summary>
27 /// 错误级别
28 /// </summary>
29 public string LOG_LEVEL { get; set; }
30
31 /// <summary>
32 /// 记录器(PRMMS.Logger)
33 /// </summary>
34 public string LOGGER { get; set; }
35
36 /// <summary>
37 /// 日志产生位置
38 /// </summary>
39 public string LOG_SOURCE { get; set; }
40
41 /// <summary>
42 /// 操作人ID
43 /// </summary>
44 public string OperatorId { get; set; }
45
46 /// <summary>
47 /// 操作账户名
48 /// </summary>
49 public string OperatorAccountName { get; set; }
50
51 /// <summary>
52 /// 自动创建ID
53 /// </summary>
54 public ErrorLog()
55 {
56 this.LOGID = Guid.NewGuid().ToString("N").ToUpper();
57 }
58 }

其中,LogId、LogMessage、OperatorId、OperatorAccountName、LogException等字段是log4net不带有的,属于自定义属性,需要做一个配置。我们新建一个CustomLayout类,继承于PatternLayout

 1     public class CustomLayout : PatternLayout
2 {
3 public CustomLayout()
4 {
5 base.AddConverter("LogId", typeof(LogId));
6 base.AddConverter("LogMessage", typeof(LogMessage));
7 base.AddConverter("OperatorId", typeof(OperatorId));
8 base.AddConverter("OperatorAccountName", typeof(OperatorAccountName));
9 base.AddConverter("LogException", typeof(LogException));
10 }
11 }

其中,typeof(LogId)中的LogId是需要我们新建类继承PatternLayoutConverter实现Convert方法的

 1     internal sealed class LogId : PatternLayoutConverter
2 {
3 protected override void Convert(TextWriter writer, LoggingEvent loggingEvent)
4 {
5 var content = loggingEvent.MessageObject as ErrorLog;
6 if (content != null)
7 {
8 writer.Write(content.LOGID);
9 }
10 }
11 }

当然,剩余几个typeof()做同样处理即可。

log4net写库配置好了,我们还需要一个日志工具类,用来调用log4net写日志,博主在这儿简单写了几个方法,其中截取2000长度纯属个人原因,没有特别意义:

 1     /// <summary>
2 /// 日志工具类
3 /// </summary>
4 public class LogUtils
5 {
6 private static readonly log4net.ILog errorLog = log4net.LogManager.GetLogger("RCB.Logger.Error");
7
8 /// <summary>
9 /// 将指定的<see cref="Exception"/>实例详细信息写入错误日志。
10 /// </summary>
11 /// <returns></returns>
12 public static void ErrorLog(Guid userId, string userName, Exception exception)
13 {
14 if (exception != null)
15 {
16 var exceptionString = exception.ToString();
17 if (exceptionString.Length > 2000)
18 {
19 exceptionString = exceptionString.Substring(0, 1999);
20 }
21 errorLog.Error(new ErrorLog
22 {
23 OperatorId = userId.ToString("N").ToUpper(),
24 OperatorAccountName = userName,
25 LOG_MESSAGE = exception.Message,
26 LOG_EXCEPTION = exceptionString
27 });
28 }
29 }
30
31 /// <summary>
32 /// 将指定的<see cref="Exception"/>实例详细信息写入错误日志。
33 ///
34 /// 记录IP地址
35 /// </summary>
36 /// <returns></returns>
37 public static void ErrorLog(string userIp, Exception exception)
38 {
39 if (exception != null)
40 {
41 var exceptionString = exception.ToString();
42 if (exceptionString.Length > 2000)
43 {
44 exceptionString = exceptionString.Substring(0, 1999);
45 }
46 errorLog.Error(new ErrorLog
47 {
48 OperatorId = userIp,
49 LOG_MESSAGE = exception.Message,
50 LOG_EXCEPTION = exceptionString
51 });
52 }
53 }
54
55 /// <summary>
56 /// 将指定的<see cref="Exception"/>实例详细信息写入错误日志。
57 /// </summary>
58 /// <returns></returns>
59 public static void ErrorLog(Exception exception)
60 {
61 if (exception != null)
62 {
63 var exceptionString = exception.ToString();
64 if (exceptionString.Length > 2000)
65 {
66 exceptionString = exception.ToString().Substring(0, 1999);
67 }
68 errorLog.Error(new ErrorLog
69 {
70 LOG_MESSAGE = exception.Message,
71 LOG_EXCEPTION = exceptionString
72 });
73 }
74 }
75 }

接下来,我们就是要写Filter了。新建一个ExceptionFilter类,继承于ExceptionFilterAttribute

 1     /// <summary>
2 /// 异常拦截器
3 /// </summary>
4 public class ExceptionFilter : ExceptionFilterAttribute
5 {
6 private HttpResponseMessage GetResponse(int code, string message)
7 {
8 var resultModel = new ApiModelsBase() { Code = code, Message = message };
9
10 return new HttpResponseMessage()
11 {
12 Content = new ObjectContent<ApiModelsBase>(
13 resultModel,
14 new JsonMediaTypeFormatter(),
15 "application/json"
16 )
17 };
18 }
19
20 public override void OnException(HttpActionExecutedContext actionExecutedContext)
21 {
22 var code = -1;
23 var message = "请求失败!";
24
25 if (actionExecutedContext.Exception is UserDisplayException)
26 {
27 message = actionExecutedContext.Exception.Message;
28 }
29 if (actionExecutedContext.Exception is UserLoginException)
30 {
31 code = -2;
32 message = actionExecutedContext.Exception.Message;
33 }
34
35 if (actionExecutedContext.Response == null)
36 {
37 actionExecutedContext.Response = GetResponse(code, message);
38 }
39
40 //记录错误日志
41 LogUtils.ErrorLog(SecurityHelper.GetUserIp(), actionExecutedContext.Exception);
42
43 base.OnException(actionExecutedContext);
44 }
45 }

注:

1.博主单独把登录异常通过特定code值返回,方便客户端分辨处理

2.GetResponse()方法主要是填充json数据到response

到这一步,还是没办法写日志的,为什么呢???因为我们的ExceptionFilter还没有注册,在App_Start文件夹下WebApiConfig.cs文件Register方法添加下句代码:

 config.Filters.Add(new ExceptionFilter());

OK,至此我们的错误日志记录就算搞定了。只需要在代码中抛出手动捕获的异常,或者意料之外未捕获的异常都会记录在错误日志中,并友好反馈到客户端。

当然,log4net的配置信息也是需要注册的,千万别忘了在Global.asax中Application_Start方法加上这样一句代码

log4net.Config.XmlConfigurator.Configure();

博主自知水平有限,如有不对的地方或各位有更好的解决方案,请随意指点,必当虚心请假,希望共同进步....

 
分类: Web API

WebAPI 用ExceptionFilterAttribute实现错误(异常)日志的记录(log4net做写库操作)的更多相关文章

  1. .NET WebAPI 用ExceptionFilterAttribute实现错误(异常)日志的记录(log4net做写库操作)

    好吧,还是那个社区APP,非管理系统,用户行为日志感觉不是很必要的,但是,错误日志咱还是得记录则个.总不能上线后报bug了让自己手足无措吧,虽然不管有木有错误日志报bug都是件很头疼的事... 我们知 ...

  2. 转:使用log4net完成程序异常日志记录(使用SQLite数据库记录和普通文本记录)

    http://www.cnblogs.com/kyo-yo/archive/2010/06/11/use-log4net-to-log-exception.html 在前端时间开发的时候由于需要将异常 ...

  3. 使用SpringBoot AOP 记录操作日志、异常日志

    平时我们在做项目时经常需要对一些重要功能操作记录日志,方便以后跟踪是谁在操作此功能:我们在操作某些功能时也有可能会发生异常,但是每次发生异常要定位原因我们都要到服务器去查询日志才能找到,而且也不能对发 ...

  4. 老生常谈SpringAop日志收集与处理做的工具包

    AopLog是基于Spring Aop 和ThreadLocal实现的一个专门对请求方法内容日志的拦截与处理的日志工具包. 场景 : 我想知道一些重要的请求方法的请求参数,响应参数,请求头,以及耗时, ...

  5. ASP.NET全局错误处理和异常日志记录以及IIS配置自定义错误页面

    应用场景和使用目的 很多时候,我们在访问页面的时候,由于程序异常.系统崩溃会导致出现黄页.在通常的情况下,黄页对于我们来说,帮助是极大的,因为它可以帮助我们知道问题根源,甚至是哪一行代码出现了错误.但 ...

  6. ASP.NET Web API 异常日志记录

    如果在 ASP.NET MVC 应用程序中记录异常信息,我们只需要在 Global.asax 的 Application_Error 中添加代码就可以了,比如: public class MvcApp ...

  7. C#实现多级子目录Zip压缩解压实例 NET4.6下的UTC时间转换 [译]ASP.NET Core Web API 中使用Oracle数据库和Dapper看这篇就够了 asp.Net Core免费开源分布式异常日志收集框架Exceptionless安装配置以及简单使用图文教程 asp.net core异步进行新增操作并且需要判断某些字段是否重复的三种解决方案 .NET Core开发日志

    C#实现多级子目录Zip压缩解压实例 参考 https://blog.csdn.net/lki_suidongdong/article/details/20942977 重点: 实现多级子目录的压缩, ...

  8. angular代码分析之异常日志设计

    angular代码分析之异常日志设计 错误异常是面向对象开发中的记录提示程序执行问题的一种重要机制,在程序执行发生问题的条件下,异常会在中断程序执行,同时会沿着代码的执行路径一步一步的向上抛出异常,最 ...

  9. 使用Log4Net完成异常日志处理

    1.在MVC的Modal文件夹建一个异常处理过滤器 public class MyExceptionAttribute:HandleErrorAttribute { public static Que ...

随机推荐

  1. Linux下Oracle11G RAC报错:在安装oracle软件时报file not found一例

    Linux下Oracle11G RAC报错:在安装oracle软件时报file notfound一例 1.现象 之前安装一切都比較顺利,安装oracle软件时,进度到30%时报错:file not f ...

  2. 屏蔽DataGridView控件DataError 事件提示的异常信息

    DataGridView.DataError 事件简单介绍: 出现故障.则外部数据分析或验证操作引发异常,或者.当尝试提交数据写入数据源失败. 具体信息:參见MSDN this.dgvState.Da ...

  3. 完整的java字符串编码转换代码

    package book.String; import java.io.UnsupportedEncodingException; /** *//** * 转换字符串的编码 * @author joe ...

  4. hdu 4464 水

    http://acm.hdu.edu.cn/showproblem.php?pid=4464 现场赛总会有水题,这就是最水的一道,预计也就是能当高校的上机题,保研用,呵呵~~~ #include &l ...

  5. zabbix通过脚本发送短信

    zabbix通过脚本发送短信 原则 和zabbix电子邮件是一样的,他们是action内部配置,司的api来完毕.当然网上有不少利用139邮箱来发的,这个事实上算调用email的一种,这里复述的是调用 ...

  6. MultiROM for the XIAOMI MI2S/2C/2! (Kexec HardBoot Enabled with Kexec HardBoot Patch!)

    Introduction This is a port of Tassadar's MultiROM, a multi-boot mod for XIAOMI MI2/2S/2C. The main ...

  7. VS2008下直接安装Boost库1.46.1版本号

    Boost图书馆是一个移植.提供源代码C++库.作为一个备份标准库,这是C++发动机之间的一种标准化的过程. Boost图书馆由C++图书馆标准委员会工作组成员发起,一些内容有望成为下一代C++标准库 ...

  8. C# WinForm多线程(二)ThreadPool 与 Timer

    本文接上文,继续探讨WinForm中的多线程问题,再次主要探讨threadpool 和timer 一  ThreadPool 线程池(ThreadPool)是一种相对较简单的方法,它适应于一些需要多个 ...

  9. 【Unity技能】做一个简单的NPC

    1. 写在前面 前几天我看到cgcookie一个教程.学习了下怎么依据已有人物模型制作一个仿版的NPC人物.感觉挺好玩的,整理一下放到博客里! 先看一下教程里面的终于效果. 是不是非常像个幽灵~ 以下 ...

  10. poj 3414 Pots (bfs+线索)

    Pots Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 10071   Accepted: 4237   Special J ...