Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 617|回复: 0

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

[复制链接]
  • TA的每日心情
    奋斗
    2024-11-24 15:47
  • 签到天数: 804 天

    [LV.10]以坛为家III

    2053

    主题

    2111

    帖子

    72万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    726782
    发表于 2021-5-17 17:10:28 | 显示全部楼层 |阅读模式

    好吧,还是那个社区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();

     

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

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-12-23 06:13 , Processed in 0.057337 second(s), 28 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表