一、数据准备
在SQL Server中创建记录日志的数据表LogDetail:
CREATE TABLE [dbo].[LogDetail](
[LogID] [INT] IDENTITY(1,1) NOT NULL, --自增ID
[LogDate] [DATETIME] NULL, --日志时间
[LogLevel] [NVARCHAR](10) NULL, --日志级别
[LogThread] [NVARCHAR](10) NULL, --线程ID
[Logger] [NVARCHAR](50) NULL, --日志名称
[LogMessage] [NVARCHAR](3000) NULL, --日志内容
CONSTRAINT [PK_LogDetail] PRIMARY KEY CLUSTERED
(
[LogID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
在此表中,日志时间、日志级别、线程ID、日志名称都是可以通过配置文件从Log4Net库中取值的,需要重点处理的是日志内容字段。
二、记录日志到数据库
2.1、配置文件
添加一个ConfigFile文件夹,然后在其下面新建一个Log4NetToDB.config的配置文件,接着在其属性的复制到输出目录项下选择始终复制。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections>
<log4net debug="false">
<!--type:表示用哪种类型记录日志,log4net.Appender.ADONetAppender表示用数据库记录日志。-->
<appender name="ADONetAppender" type="log4net.Appender.ADONetAppender">
<!--日志缓存写入条数,设置为0时只要有一条就立刻写到数据库。-->
<bufferSize value="0" />
<!--数据库连接串-->
<!--C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\Config\machine.config-->
<connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<!--数据库连接字符串-->
<connectionString value="Server=.;Database=Test;Uid=sa;Pwd=********;" />
<!--数据库脚本-->
<commandText value="INSERT INTO LogDetail (LogDate,LogLevel,LogThread,Logger,LogMessage) VALUES (@LogDate,@LogLevel,@LogThread,@Logger,@LogMessage)" />
<!--日志时间-->
<parameter>
<parameterName value="@LogDate" />
<dbType value="DateTime" />
<layout type="log4net.Layout.RawTimeStampLayout" />
</parameter>
<!--日志级别-->
<parameter>
<parameterName value="@LogLevel" />
<dbType value="String" />
<size value="10" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%p" />
</layout>
</parameter>
<!--线程ID-->
<parameter>
<parameterName value="@LogThread" />
<dbType value="String" />
<size value="10" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%t" />
</layout>
</parameter>
<!--日志名称-->
<parameter>
<parameterName value="@Logger" />
<dbType value="String" />
<size value="50" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%logger" />
</layout>
</parameter>
<!--日志内容-->
<parameter>
<parameterName value="@LogMessage" />
<dbType value="String" />
<size value="3000" />
<layout type="LinkTo.Test.ConsoleLog4Net.Utility.CustomLayout">
<conversionPattern value="%property{LogMessage}" />
</layout>
</parameter>
</appender>
<root>
<priority value="ALL" />
<level value="ALL" />
<appender-ref ref="ADONetAppender" />
</root>
</log4net>
</configuration>
2.2、日志内容处理过程
注:日志内容处理涉及的4个类(含帮助类)都是存放在Utility文件夹下。
从配置的<layout type="LinkTo.Test.ConsoleLog4Net.Utility.CustomLayout">可以看出,日志内容的取值来源于一个自定义的Layout类CustomLayout:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using log4net.Layout;
namespace LinkTo.Test.ConsoleLog4Net.Utility
{
public class CustomLayout : PatternLayout
{
/// <summary>
/// 构造函数:把需要写入数据库的属性添加进来
/// </summary>
public CustomLayout()
{
AddConverter("property", typeof(CustomLayoutConverter));
}
}
}
CustomLayout类添加属性时,类型来源于CustomLayoutConverter类:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using log4net.Core;
using log4net.Layout.Pattern;
namespace LinkTo.Test.ConsoleLog4Net.Utility
{
public class CustomLayoutConverter : PatternLayoutConverter
{
protected override void Convert(TextWriter writer, LoggingEvent loggingEvent)
{
if (Option != null)
{
//写入指定键的值
WriteObject(writer, loggingEvent.Repository, LookupProperty(Option, loggingEvent));
}
else
{
//Write all the key value pairs
WriteDictionary(writer, loggingEvent.Repository, loggingEvent.GetProperties());
}
}
/// <summary>
/// 通过反射获取传入的日志对象的某个属性的值
/// </summary>
/// <param name="property"></param>
/// <param name="loggingEvent"></param>
/// <returns></returns>
private object LookupProperty(string property, LoggingEvent loggingEvent)
{
object propertyValue = string.Empty;
PropertyInfo propertyInfo = loggingEvent.MessageObject.GetType().GetProperty(property);
if (propertyInfo != null)
propertyValue = propertyInfo.GetValue(loggingEvent.MessageObject, null);
return propertyValue;
}
}
}
从配置的<conversionPattern value="%property{LogMessage}" />可以看出,日志内容的取值来源于属性LogMessage,而这个LogMessage,统一来源于一个实体类LogContent:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LinkTo.Test.ConsoleLog4Net.Utility
{
public class LogContent
{
/// <summary>
/// 日志内容
/// </summary>
public string LogMessage { get; set; }
public LogContent(string logMessage)
{
LogMessage = logMessage;
}
}
}
2.3、帮助类
为了简化写日志的过程,封装了一个简单的帮助类LogHelper:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using log4net;
namespace LinkTo.Test.ConsoleLog4Net.Utility
{
public class LogHelper
{
public static readonly ILog logger = LogManager.GetLogger("LinkTo.Test.ConsoleLog4Net"); //这里的参数不能使用Type类型
public static void Fatal(LogContent content)
{
logger.Fatal(content);
}
public static void Error(LogContent content)
{
logger.Error(content);
}
public static void Warn(LogContent content)
{
logger.Warn(content);
}
public static void Info(LogContent content)
{
logger.Info(content);
}
public static void Debug(LogContent content)
{
logger.Debug(content);
}
}
}
2.4、测试代码
class Program
{
static void Main(string[] args)
{
XmlConfigurator.Configure(new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "ConfigFile\\Log4NetToDB.config")));
LogHelper.Fatal(new LogContent("This is fatal message."));
LogHelper.Error(new LogContent("This is error message."));
LogHelper.Warn(new LogContent("This is warn message."));
LogHelper.Info(new LogContent("This is info message."));
LogHelper.Debug(new LogContent("This is debug message."));
Console.Read();
}
}
2.5、运行结果
2.6、一点优化
每次写日志时,都需要进行Log4Net文件配置的话,肯定是没有必要的:
XmlConfigurator.Configure(new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "ConfigFile\\Log4NetToDB.config")));
可以在项目的Properties\AssemblyInfo最下面加上下面这一句,进行全局的统一配置:
[assembly: log4net.Config.XmlConfigurator(ConfigFile = "ConfigFile\\Log4NetToDB.config")]
来源:oschina
链接:https://my.oschina.net/u/4399347/blog/4276785