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

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

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

 <log4net>
<!--<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="C:\testlog.txt" />
<appendToFile value="true" />
<maxSizeRollBackups value="" />
<maximumFileSize value="" />
<rollingStyle value="Date" />
<datePattern value="yyyy-MM-dd" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="记录时间:%date 线程ID:[%thread] 日志级别:%-5level 出错类:%logger property:[%property{NDC}] - 错误描述:%message%newline " />
</layout>
</appender>-->
<appender name="AdoNetAppender_Sqlserver" type="log4net.Appender.AdoNetAppender">
<connectionType value="System.Data.SqlClient.SqlConnection,System.Data, Version=4.0.0.0, Culture=neutral,PublicKeyToken=b77a5c561934e089" />
<connectionString value="Data Source=.;database=RCBLog;Integrated Security=True; MultipleActiveResultSets=True;" />
<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)" />
<bufferSize value="10" />
<parameter>
<parameterName value="@log_date" />
<dbType value="DateTime" />
<layout type="log4net.Layout.RawTimeStampLayout" />
</parameter>
<parameter>
<parameterName value="@log_level" />
<dbType value="String" />
<size value="" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%level" />
</layout>
</parameter>
<parameter>
<parameterName value="@logger" />
<dbType value="String" />
<size value="" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%logger" />
</layout>
</parameter>
<parameter>
<parameterName value="@source" />
<dbType value="String" />
<size value="" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%file:%line" />
</layout>
</parameter>
<!--自定义属性-->
<parameter>
<parameterName value="@LogId" />
<dbType value="String" />
<size value="" />
<layout type="MP.Infrastructure.SystemLog.CustomLayout,MP.Infrastructure">
<conversionPattern value="%LogId" />
</layout>
</parameter>
<parameter>
<parameterName value="@LogException" />
<dbType value="String" />
<size value="" />
<layout type="MP.Infrastructure.SystemLog.CustomLayout,MP.Infrastructure">
<conversionPattern value="%LogException" />
</layout>
</parameter>
<parameter>
<parameterName value="@LogMessage" />
<dbType value="String" />
<size value="" />
<layout type="MP.Infrastructure.SystemLog.CustomLayout,MP.Infrastructure">
<conversionPattern value="%LogMessage" />
</layout>
</parameter>
<parameter>
<parameterName value="@LogOperator" />
<dbType value="String" />
<size value="" />
<layout type="MP.Infrastructure.SystemLog.CustomLayout,MP.Infrastructure">
<conversionPattern value="%OperatorId" />
</layout>
</parameter>
<parameter>
<parameterName value="@OperatorAccountName" />
<dbType value="String" />
<size value="" />
<layout type="MP.Infrastructure.SystemLog.CustomLayout,MP.Infrastructure">
<conversionPattern value="%OperatorAccountName" />
</layout>
</parameter>
</appender>
<logger name="RCB.Logger.Error">
<level value="ERROR" />
<!--<appender-ref ref="RollingFileAppender" />-->
<appender-ref ref="AdoNetAppender_Sqlserver" />
</logger>
</log4net>

注:

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

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

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

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

     /// <summary>
/// 系统错误日志
/// </summary>
public class ErrorLog
{
/// <summary>
/// ID(GUID字符串)
/// </summary>
public string LOGID { get; set; } /// <summary>
/// 日志时间
/// </summary>
public DateTime LOG_DATE { get; set; } /// <summary>
/// 日志错误信息
/// </summary>
public string LOG_MESSAGE { get; set; } /// <summary>
/// 异常信息详情
/// </summary>
public string LOG_EXCEPTION { get; set; } /// <summary>
/// 错误级别
/// </summary>
public string LOG_LEVEL { get; set; } /// <summary>
/// 记录器(PRMMS.Logger)
/// </summary>
public string LOGGER { get; set; } /// <summary>
/// 日志产生位置
/// </summary>
public string LOG_SOURCE { get; set; } /// <summary>
/// 操作人ID
/// </summary>
public string OperatorId { get; set; } /// <summary>
/// 操作账户名
/// </summary>
public string OperatorAccountName { get; set; } /// <summary>
/// 自动创建ID
/// </summary>
public ErrorLog()
{
this.LOGID = Guid.NewGuid().ToString("N").ToUpper();
}
}

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

     public class CustomLayout : PatternLayout
{
public CustomLayout()
{
base.AddConverter("LogId", typeof(LogId));
base.AddConverter("LogMessage", typeof(LogMessage));
base.AddConverter("OperatorId", typeof(OperatorId));
base.AddConverter("OperatorAccountName", typeof(OperatorAccountName));
base.AddConverter("LogException", typeof(LogException));
}
}

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

     internal sealed class LogId : PatternLayoutConverter
{
protected override void Convert(TextWriter writer, LoggingEvent loggingEvent)
{
var content = loggingEvent.MessageObject as ErrorLog;
if (content != null)
{
writer.Write(content.LOGID);
}
}
}

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

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

     /// <summary>
/// 日志工具类
/// </summary>
public class LogUtils
{
private static readonly log4net.ILog errorLog = log4net.LogManager.GetLogger("RCB.Logger.Error"); /// <summary>
/// 将指定的<see cref="Exception"/>实例详细信息写入错误日志。
/// </summary>
/// <returns></returns>
public static void ErrorLog(Guid userId, string userName, Exception exception)
{
if (exception != null)
{
var exceptionString = exception.ToString();
if (exceptionString.Length > )
{
exceptionString = exceptionString.Substring(, );
}
errorLog.Error(new ErrorLog
{
OperatorId = userId.ToString("N").ToUpper(),
OperatorAccountName = userName,
LOG_MESSAGE = exception.Message,
LOG_EXCEPTION = exceptionString
});
}
} /// <summary>
/// 将指定的<see cref="Exception"/>实例详细信息写入错误日志。
///
/// 记录IP地址
/// </summary>
/// <returns></returns>
public static void ErrorLog(string userIp, Exception exception)
{
if (exception != null)
{
var exceptionString = exception.ToString();
if (exceptionString.Length > )
{
exceptionString = exceptionString.Substring(, );
}
errorLog.Error(new ErrorLog
{
OperatorId = userIp,
LOG_MESSAGE = exception.Message,
LOG_EXCEPTION = exceptionString
});
}
} /// <summary>
/// 将指定的<see cref="Exception"/>实例详细信息写入错误日志。
/// </summary>
/// <returns></returns>
public static void ErrorLog(Exception exception)
{
if (exception != null)
{
var exceptionString = exception.ToString();
if (exceptionString.Length > )
{
exceptionString = exception.ToString().Substring(, );
}
errorLog.Error(new ErrorLog
{
LOG_MESSAGE = exception.Message,
LOG_EXCEPTION = exceptionString
});
}
}
}

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

     /// <summary>
/// 异常拦截器
/// </summary>
public class ExceptionFilter : ExceptionFilterAttribute
{
private HttpResponseMessage GetResponse(int code, string message)
{
var resultModel = new ApiModelsBase() { Code = code, Message = message }; return new HttpResponseMessage()
{
Content = new ObjectContent<ApiModelsBase>(
resultModel,
new JsonMediaTypeFormatter(),
"application/json"
)
};
} public override void OnException(HttpActionExecutedContext actionExecutedContext)
{
var code = -;
var message = "请求失败!"; if (actionExecutedContext.Exception is UserDisplayException)
{
message = actionExecutedContext.Exception.Message;
}
if (actionExecutedContext.Exception is UserLoginException)
{
code = -;
message = actionExecutedContext.Exception.Message;
} if (actionExecutedContext.Response == null)
{
actionExecutedContext.Response = GetResponse(code, message);
} //记录错误日志
LogUtils.ErrorLog(SecurityHelper.GetUserIp(), actionExecutedContext.Exception); base.OnException(actionExecutedContext);
}
}

注:

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();

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

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

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

    WebAPI 用ExceptionFilterAttribute实现错误(异常)日志的记录(log4net做写库操作) 好吧,还是那个社区APP,非管理系统,用户行为日志感觉不是很必要的,但是,错误日 ...

  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. C#开发微信门户及应用(30)--消息的群发处理和预览功能

    在很多场合下,我们可能需要利用微信公众号的优势,定期给指定用户群发送一些推广消息或者新闻内容,以便给关注客户一种经常更新公众号内容的感觉,同时也方便我们经常和用户进行互动.微信公众号的高级群发接口就是 ...

  2. dbutils基本使用

    dbutils的查询,主要用到的是query方法,增加,修改和删除都是update方法,update方法就不讲了 只要创建ResultSetHandler接口不同的实现类对象就可以得到想要的查询结果, ...

  3. Redis主从复制

    大家可以先看这篇文章ASP.NET Redis 开发对Redis有个初步的了解 Redis的主从复制功能非常强大,一个master可以拥有多个slave,而一个slave又可以拥有多个slave,如此 ...

  4. 关于css3的背景渐变

    关于css3的渐变,目前各大浏览器还未做到很好的支持,所以需要在我们使用时加上各大浏览器前缀. -moz-:使用Mozilla内核的浏览器(Firefox浏览器) -webkit-:使用Webkit内 ...

  5. 使用AxisHelper帮助理解View and Data API中的坐标系统

    大家使用View and Data API做三维模型开发,必然首先要理解View and Data API的坐标系统,即XYZ三个轴向分别是怎么定义的.Three.js里面提供了一个AxisHelpe ...

  6. React Native windows搭建记录

    因为是window电脑上运行的,所以测试的是安卓 1: 安装jdk:jdk-8u45-windows-x64.exe 2: 配置JAVA的环境变量 在安卓的配置基础上添加一个变量ANDROID_HOM ...

  7. TextField和TextView的限制输入长度

    TextField的限制代理方法 只需要在这个代理方法里面code这样的代码就可以了 16 是长度可以自己设置 - (BOOL)textField:(UITextField *)textField s ...

  8. Linux2.6内核进程调度系列--scheduler_tick()函数3.更新普通进程的时间片

    RT /** * 运行到此,说明进程是普通进程.现在开始更新普通进程的时间片. */ /* 首先递减普通进程的时间片计数器.如果用完,继续执行以下操作 */ if (!--p->time_sli ...

  9. 使用GIT进行源码管理 —— 在VisualStudio中使用GIT

    GIT作为源码管理的方式现在是越来越流行了,在VisualStudio 2012中,就通过插件的现实对GIT进行了官方支持,并且这个插件在VS2013中已经转正.本文在这里简单的介绍一下如何在Visu ...

  10. 初试WIX加SQL LocalDB

    最近有个项目需要生成一个自动打包安装App和数据库的MSI文件,经同事推荐WIX,于是乎就试了一试.遇到了一些问题觉得有分享的价值,所以写篇博客记录一下 :) 使用感觉: WIX特点:功能很强大,用X ...