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

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

时间: 10-08

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

C#错误异常日志记录到文件

当我们将网站布署到线上之后,为了实时了解网站的运行情况,如是否有错误页面.网站运行速度.是否有攻击等.那么我们就很有必要为网站加上错误与异常记录到日志文件,这样就可以随时查看网站的线上运行情况,另有一个好处是当网站有运行错误页面时,根据错误日志我们可以快速到定位到错误行进行排查原因.解决问题,这个是对于运行在线上而不能调试的网站的一个非常有必要的功能. 具体实现方法: 在全局文件Global.asax.cs中添加Application_Error的方法.只要当程序有错误时程序就会自动执行该方法,

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

http://www.cnblogs.com/kyo-yo/archive/2010/06/11/use-log4net-to-log-exception.html 在前端时间开发的时候由于需要将异常保存到数据库中,所以就到网上搜了下专门的日志记录工具,一搜果然很多,比如:log4net,NLog,EntLib Logging等等,但是还是log4net名气最大,所以就下载下来试用了一番,果然很方便,其涵盖了所有常用的日志记录方式具体的可以看下表: AdoNetAppender 将日志记录到数据

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

应用场景和使用目的 很多时候,我们在访问页面的时候,由于程序异常.系统崩溃会导致出现黄页.在通常的情况下,黄页对于我们来说,帮助是极大的,因为它可以帮助我们知道问题根源,甚至是哪一行代码出现了错误.但这对于用户是非常可怕的,因为用户不知道发生了什么,也无法了解黄页给出的内容.甚至,如果我们遇到一些不友好的人,他们会拿这些内容大做文章,对我们网站产生威胁. 那我们如何在程序异常.系统崩溃时,不会出现黄页,并且还可以给出一些更加友好的提示呢?甚至在我们需要的时候,可以收集这些异常信息,并加以分析,能

[转载]针对IIS7以上的ASP.NET网站自定义错误页面与异常日志总结

针对IIS7以上的ASP.NET网站自定义错误页面与异常日志总结 汪宇杰 2014-1-11 星期六 02:31 455 Reads 1 Comments 自定义错误页面和异常记录是个很古老的话题了,但依旧可以让人爆到现在.在我做了无数次试验并总结经验和原则后,写下本文,已警后人. 本文的范围和限制 本文仅仅适用于部署在IIS7或以上版本中的ASP.NET 4.0集成模式应用程序.IIS7以上的意思是Windows Server 2008以上服务器适用.我已在WS2012R2,IIS8上测过.

angular代码设计之异常日志设计

angular代码分析之异常日志设计 错误异常是面向对象开发中的记录提示程序执行问题的一种重要机制,在程序执行发生问题的条件下,异常会在中断程序执行,同时会沿着代码的执行路径一步一步的向上抛出异常,最终会由顶层抛出异常信息.而与异常同时出现的往往是日志,而日志往往需要记录具体发生异常的模块.编码.详细的错误信息.执行堆栈等,方便问题的快速定位分析.angularjs作为前端的框架,其对异常信息的处理是怎样的呢? 一.定义模块和异常类型 首先每个模块的日志模块编码是一样的,只需要本模块负责初始化一

11 异常, 日志, 断言和调试

处理异常 java程序设计中, 异常对象都是派生于Throwable类的一个实例, 如果java中内置的异常类不能够满足要求, 用户可以自己创建自己的异常类. Error 类层次结构描述了java运行时系统的内部错误和资源耗尽错误. 应用程序不应该抛出这种类型的对象, 如果出现了内部错误, 除了通知用户, 并尽力使程序安全地终止之外, 也没无能为力了, 这种情况很少出现. 需要主要关注的是 Exception层次结构, 这个层次结构分为两个分支: 有程序错误导致的异常属于 RuntimeExce

.NET WebAPI 正确抛出错误详细信息

最近把之前的WebAPI项目发布到服务器上,数据库由SQL Server2008 EXPRESS改为SQL Server2000,但在分页查询时服务器抛出了"500错误",但却看不到错误的详细信息,按照之前的经验把配置文件的customErrors打开依然看不到详细信息,由于项目是发布在服务器上,没有办法进行调试,且服务器是windows20003,页没有办法进行远程调试,写try-catch-捕捉action中的错误并抛出依然获取不到详细信息,得不到错误详细信息就没有方向对错误进行修

点评阿里JAVA手册之异常日志(异常处理 日志规约 )

下载原版阿里JAVA开发手册  [阿里巴巴Java开发手册v1.2.0] 本文主要是对照阿里开发手册,注释自己在工作中运用情况. 本文内容:异常处理 日志规约 本文难度系数为一星(★) 本文为第三篇 第一篇 点评阿里JAVA手册之编程规约(命名风格.常量定义.代码风格.控制语句.注释规约) 第二篇 点评阿里JAVA手册之编程规约(OOP 规约 .集合处理 .并发处理 .其他) 码出高效.码出质量. 代码的字里行间流淌的是软件生命中的血液,质量的提升是尽可能少踩坑,杜绝踩重复的坑,切实提升质量意识

在 C# 控制台中记录异常日志并输出

最近做了一个小程序,要求在控制台中记录程序运行的异常并输出到指定的文件夹中,以下是我的具体的程序代码: public static void ErrorLog(Exception ex) { string FilePath = "/ErrorLog.txt"; StringBuilder msg = new StringBuilder (); msg.Append("*************************************** \n"); msg.