一、數據準備 在SQL Server中創建記錄日誌的數據表LogDetail: CREATE TABLE [dbo].[LogDetail]( [LogID] [INT] IDENTITY(1,1) NOT NULL, --自增ID [LogDate] [DATETIME] NULL, --日誌時間 ...
一、數據準備
在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")]