asp.net core版本:5.0
log4net:2.0
一般情况下,我比较习惯把日志记录到日志文件中。但这种方式有一个缺点:把项目发布到生产环境后,每次查看日志都需要远程登录到服务器去查看日志文件,有点不太方便,所以我想把日志写入到数据库中。
首先我们新建一个asp.net core webapi项目,nuget添加如下包:
向日志系统中添加log4net:
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
}).ConfigureLogging(logBuilder =>
{
logBuilder.AddLog4Net();
});
配置log4net.config:
<?xml version="1.0" encoding="utf-8"?>
<log4net>
<logger name="actionLog">
<level value="ERROR" />
<appender-ref ref="adoNetAppender_SqlServer"/>
</logger>
<appender name="adoNetAppender_SqlServer" type="log4net.Appender.ADONetAppender">
<!--日志缓存写入条数 设置为0时只要有一条就立刻写到数据库 生产环境可改为10-100写入一次-->
<bufferSize value="1" />
<!-- 日志数据库连接类型(此处非常重要,写错会导致无法写入数据库) -->
<connectionType value="System.Data.SqlClient.SqlConnection,System.Data"/>
<!--日志数据库连接串-->
<connectionString value="server=.;database=TestDb;uid=sa;pwd=123456" />
<!--日志数据库脚本-->
<commandText value="INSERT INTO LogError(LogDateTime,HttpMethod,Uri,ErrMessage,ErrStackTrace) VALUES(@LogDateTime,@HttpMethod,@Uri,@ErrMessage,@ErrStackTrace)" />
<!--日志时间 -->
<parameter>
<parameterName value="@LogDateTime" />
<dbType value="DateTime" />
<layout type="log4net.Layout.RawTimeStampLayout" />
</parameter>
<parameter>
<parameterName value="@HttpMethod" />
<dbType value="String" />
<layout type="LogDemo.LogDbConfig.ActionLayoutPattern">
<conversionPattern value = "%pattern{HttpMethod}"/>
</layout>
</parameter>
<parameter>
<parameterName value="@Uri" />
<dbType value="String" />
<layout type="LogDemo.LogDbConfig.ActionLayoutPattern">
<conversionPattern value = "%pattern{Uri}"/>
</layout>
</parameter>
<parameter>
<parameterName value="@ErrMessage" />
<dbType value="String" />
<layout type="LogDemo.LogDbConfig.ActionLayoutPattern">
<conversionPattern value = "%pattern{ErrMessage}"/>
</layout>
</parameter>
<parameter>
<parameterName value="@ErrStackTrace" />
<dbType value="String" />
<layout type="LogDemo.LogDbConfig.ActionLayoutPattern">
<conversionPattern value = "%pattern{ErrStackTrace}"/>
</layout>
</parameter>
</appender>
<root>
</root>
</log4net>
下面我们讲解一下这个配置文件。
logger节点,name随意命名,需要与LogManager.GetLogger的name参数一样。level子节点我配置为ERROR,只记录level为ERROR的日志。appender-ref,ref属性的值指定要添加的appender,即下面的appender
我们看appender这个节点,name属性可以按照我们的喜好命名,type则必须是log4net.Appender.ADONetAppender,因为我们使用的是sqlserver数据库。如果你使用的是mysql数据库,那就不是这个了。
connectionType,因为我们已经引入了System.Data.SqlClient包,所以不用添加Version和PublicKeyToken。这里有2个地方需要注意一下:1、我看有的博客在这个地方写的是Microsoft.Data.SqlClient.SqlConnection,Microsoft.Data,但是他又没告诉我们要引入Microsoft.Data.SqlClient。这种方式能不能用我没有试过,有兴趣的可以试一下。2、这个地方你写的包名必须要引入到你的项目中。
parameter,这个节点对应insert values里面的每一个插入字段,是对插入字段做一个配置。
重点看一下layout,type属性就是对字段做配置的类名,这个值是类所在命名控件.类名,所以这个值要根据你自己的项目来确定。conversionPattern的value属性中的pattern字符串与AddConverter的第一个参数name必须相同。我的这个类的代码如下:
using log4net.Layout;
namespace LogDemo.LogDbConfig
{
public class ActionLayoutPattern : PatternLayout
{
public ActionLayoutPattern()
{
this.AddConverter("pattern", typeof(FieldsConverter));
}
}
}
FieldsConverter类就是给每个自定义字段赋值,代码如下:
using log4net.Layout.Pattern;
public class FieldsConverter : PatternLayoutConverter
{
protected override void Convert(TextWriter writer, LoggingEvent loggingEvent)
{
var dto = loggingEvent.MessageObject as LogItems;
switch (this.Option)
{
case "Uri":
writer.Write(dto.Uri);
break;
case "HttpMethod":
writer.Write(dto.HttpMethod);
break;
case "ErrMessage":
writer.Write(dto.ErrMessage);
break;
case "ErrStackTrace":
writer.Write(dto.ErrStackTrace);
break;
default:
writer.Write("");
break;
}
}
}
LogItems,这个类是自定义字段的类,代码如下:
public class LogItems
{
public string Uri { get; set; }
public string HttpMethod { get; set; }
public string ErrMessage { get; set; }
public string ErrStackTrace { get; set; }
}
接下来我们写入数据库:
var logData = new LogItems();
logData.Uri = Request.Path;
logData.HttpMethod = Request.Method;
logData.ErrMessage = ex.Message;
logData.ErrStackTrace = ex.StackTrace;
var iLog = LogManager.GetLogger("actionLog");
iLog.Error(logData);
这段代码是写在catch里面的,ex就是Exception的实例。Request是ControllerBase的一个属性,用来获取当前http请求的元数据。
GetLogger的参数actionLog对应log4net.config中的<logger name="actionLog">
LogDateTime这个字段的值layout的type=log4net.Layout.RawTimeStampLayout,由log4net赋值,所以这里不需要赋值
数据库表结构如下:
运行程序,故意写几行索引超出范围的代码。结果如下:
?我这里是把错误日志写入到数据库,但是其他Level的日志我还是想写入到文件中,这该怎么做呢?其实这个也很简单,和之前只写入到文件中一样,加一个写入文件的appender即可,配置如下:
<?xml version="1.0" encoding="utf-8"?>
<log4net>
<logger name="actionLog">
<level value="ERROR" />
<appender-ref ref="adoNetAppender_SqlServer"/>
</logger>
<appender name="adoNetAppender_SqlServer" type="log4net.Appender.ADONetAppender">
<!--日志缓存写入条数 设置为0时只要有一条就立刻写到数据库 生产环境可改为10-100写入一次-->
<bufferSize value="1" />
<!-- 日志数据库连接类型(此处非常重要,写错会导致无法写入数据库) -->
<connectionType value="System.Data.SqlClient.SqlConnection,System.Data"/>
<!--日志数据库连接串-->
<connectionString value="server=.;database=TestDb;uid=sa;pwd=123456" />
<!--日志数据库脚本-->
<commandText value="INSERT INTO LogError(LogDateTime,HttpMethod,Uri,ErrMessage,ErrStackTrace) VALUES(@LogDateTime,@HttpMethod,@Uri,@ErrMessage,@ErrStackTrace)" />
<!--日志时间 -->
<parameter>
<parameterName value="@LogDateTime" />
<dbType value="DateTime" />
<layout type="log4net.Layout.RawTimeStampLayout" />
</parameter>
<parameter>
<parameterName value="@HttpMethod" />
<dbType value="String" />
<layout type="LogDemo.LogDbConfig.ActionLayoutPattern">
<conversionPattern value = "%pattern{HttpMethod}"/>
</layout>
</parameter>
<parameter>
<parameterName value="@Uri" />
<dbType value="String" />
<layout type="LogDemo.LogDbConfig.ActionLayoutPattern">
<conversionPattern value = "%pattern{Uri}"/>
</layout>
</parameter>
<parameter>
<parameterName value="@ErrMessage" />
<dbType value="String" />
<layout type="LogDemo.LogDbConfig.ActionLayoutPattern">
<conversionPattern value = "%pattern{ErrMessage}"/>
</layout>
</parameter>
<parameter>
<parameterName value="@ErrStackTrace" />
<dbType value="String" />
<layout type="LogDemo.LogDbConfig.ActionLayoutPattern">
<conversionPattern value = "%pattern{ErrStackTrace}"/>
</layout>
</parameter>
</appender>
<appender name="RollingInfoFile" type="log4net.Appender.RollingFileAppender">
<file value="Log/Info/" />
<appendToFile value="true" />
<!--记录日志写入文件时,不锁定文本文件,防止多线程时不能写Log,官方说线程非安全-->
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<Encoding value="UTF-8" />
<!-- 每个文件的大小限制 -->
<maximumFileSize value="1MB" />
<!-- 切割最多文件数 -1表示不限制产生日志文件数-->
<MaxSizeRollBackups value="-1" />
<DatePattern value="yyyyMMdd".log"" />
<!--按照何种方式产生多个日志文件(日期[Date],文件大小[Size],混合[Composite])-->
<RollingStyle value="Composite" />
<param name="DatePattern" value="yyyy-MM-dd/"Info.log"" />
<StaticLogFileName value="false" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="[%date{yyyy-MM-dd HH:mm:ss}] %-5logger - %m%n"/>
</layout>
<!--日志等级:OFF > FATAL > ERROR > WARN > INFO > DEBUG > ALL -->
<filter type="log4net.Filter.LevelRangeFilter">
<levelMin value="INFO" />
<levelMax value="INFO" />
</filter>
</appender>
<root>
<level value="ALL"/>
<appender-ref ref="RollingInfoFile" />
</root>
</log4net>
如果不需要把日志写入到数据库,那我们就可以不用log4net的ILog类来写入(LogManager.GetLogger返回的就是ILog),而是通过Microsoft.Extensions.Logging.ILogger接口来写入。这里顺便提一下这个接口,它是asp.net core自带的日志接口,内置了console logger和debug logger(还有其他的logger,我记不清了),每次调用这个接口写入方法时(LogInfo、LogError等),它就向所有实现这个接口的logger发送日志。因为我们调用了ConfigureLogging把log4net也加入了日志系统(logBuilder.AddLog4Net()),所以使用ILogger写入日志时也会把日志写入到log4net。
至此,无论是把日志写入到数据库还是文件都可以了。
最后提醒一下大家,一定要认真检查commandText的insert语句,因为这个语句是写在log4net.config配置文件中,没有语法错误提示。如果写错了,程序运行的时候也不会有任何的错误提示,让你搞不清楚到底是哪里的问题,我就在这个地方耗费了很多的时间。
|