C# 使用Log4net记录日志
1.Log4net.config详解
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net"/> </configSections> <log4net> <!-- ConversionPattern 解释 %m(message):输出的日志消息,如ILog.Debug(…)输出的一条消息 %n(new line):換行 %d(datetime):输出当前语句运行的时刻 %r(run time):输出程序从运行到执行到当前语句时消耗的毫秒数 %t(thread id):当前语句所在的线程ID %p(priority): 日志的当前优先级别,即DEBUG、INFO、WARN…等 %c(class):当前日志对象的名称 %L:输出语句所在的行号 %F:输出语句所在的文件名 %-数字:表示该项的最小长度,如果不够,则用空格填充 --> <root> <!--控制级别,由低到高: ALL|DEBUG|INFO|WARN|ERROR|FATAL|OFF--> <!--比如定义级别为INFO,则INFO级别向下的级别,比如DEBUG日志将不会被记录--> <!--如果没有定义LEVEL的值,则缺省为DEBUG--> <level value="ALL"/> <appender-ref ref="AppLogAppender"/> </root> <!--定义输出到控制台命令行中--> <logger name="SysLogger"> <level value="ALL"/> <appender-ref ref="TextAppender" /> </logger> <logger name="AppLogger"> <level value="ALL"/> <appender-ref ref="AppLogAppender" /> </logger> <!--定义输出到控制台命令行中--> <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender"> <layout type="RMFramework.Common.Logger.MyLayout"> <param name="ConversionPattern" value="日志时间:%d %n日志级别:%-5p %n用 户 ID:%Property{UserID} %n用户姓名:%Property{UserName} %n日志信息:%Property{Message} %n异常信息:%exception %n%n" /> </layout> </appender> <!--定义输出到windows事件中--> <appender name="WindowsAppender" type="log4net.Appender.EventLogAppender"> <layout type="RMFramework.Common.Logger.MyLayout"> <param name="ConversionPattern" value="日志时间:%d %n日志级别:%-5p %n用 户 ID:%Property{UserID} %n用户姓名:%Property{UserName} %n日志信息:%Property{Message} %n异常信息:%exception %n%n" /> </layout> </appender> <!--定义输出到文件中 用于记录系统级--> <appender name="TextAppender" type="log4net.Appender.RollingFileAppender"> <param name="File" value="Logs\\SysLog\\" /> <param name="AppendToFile" value="true" /> <param name="MaxFileSize" value="10240" /> <param name="MaxSizeRollBackups" value="100" /> <param name="StaticLogFileName" value="false" /> <param name="DatePattern" value="yyyyMMdd".log"" /> <param name="RollingStyle" value="Date" /> <layout type="RMFramework.Common.Logger.MyLayout"> <param name="ConversionPattern" value="日志时间:%d %n日志级别:%-5p %n用 户 ID:%Property{UserID} %n用户姓名:%Property{UserName} %n日志信息:%Property{Message} %n异常信息:%exception %n%n" /> </layout> </appender> <!--定义输出文本,用于记录业务级--> <appender name="AppLogAppender" type="log4net.Appender.RollingFileAppender"> <file value="Logs/AppLog/" /> <appendToFile value="true" /> <rollingStyle value="Date" /> <staticLogFileName value="false" /> <datePattern value="yyyyMMdd".log"" /> <layout type="log4net.Layout.PatternLayout"> <!--输出格式--> <conversionPattern value="%date %-5level %message%newline" /> </layout> <!--<filter type="log4net.Filter.LoggerMatchFilter"> <loggerToMatch value="SystemLog" /> </filter>--> </appender> <!--定义输出到数据库--> <appender name="DataBaseAppender" type="log4net.Appender.AdoNetAppender"> <!--日志缓存写入条数--> <bufferSize value="1" /> <!--日志数据库连接串--> <connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> <connectionString value="data source=.\SQL2008;initial catalog=Demo;integrated security=false;persist security info=True;User ID=sa;Password=1qaz" /> <!--日志数据库脚本--> <commandText value="INSERT INTO LogInfo ([LogDate],[LogLevel],[UserId],[UserName],[Message],[Exception]) VALUES (@LogDate, @LogLevel,@UserId,@UserName, @Message, @Exception)" /> <!--日志时间LogDate --> <parameter> <parameterName value="@LogDate" /> <dbType value="String" /> <size value="30" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%date{yyyy-MM-dd HH:mm:ss}" /> </layout> </parameter> <!--日志类型LogLevel --> <parameter> <parameterName value="@LogLevel" /> <dbType value="String" /> <size value="10" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%level" /> </layout> </parameter> <!--自定义UserId --> <parameter> <parameterName value="@UserId" /> <dbType value="String" /> <size value="20" /> <layout type="RMFramework.Common.Logger.MyLayout"> <conversionPattern value="%Property{UserID}" /> </layout> </parameter> <!--自定义UserName --> <parameter> <parameterName value="@UserName" /> <dbType value="String" /> <size value="50" /> <layout type="RMFramework.Common.Logger.MyLayout"> <conversionPattern value="%Property{UserName}" /> </layout> </parameter> <!--自定义Message --> <parameter> <parameterName value="@Message" /> <dbType value="String" /> <size value="200" /> <layout type="RMFramework.Common.Logger.MyLayout"> <conversionPattern value="%Property{Message}" /> </layout> </parameter> <!--异常信息Exception --> <parameter> <parameterName value="@Exception" /> <dbType value="String" /> <size value="4000" /> <layout type="log4net.Layout.ExceptionLayout" /> </parameter> </appender> </log4net> </configuration>
2.使用前准备工作
使用nuget引入Log4net;
在引入类库中的AssemblyInfo.cs加入
[assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.config", ConfigFileExtension = "config", Watch = true)]
3.调用方法
3.1 原始调用
public static readonly log4net.ILog loginfo = log4net.LogManager.GetLogger("AppLogger");
实例化ILog后,即可进行调用。LogManager.GetLogger传入参数与log4net.config内的Logger要匹配。
当只有一个配置时,也可以使用如下代码。若多个logger配置时,仅读取第一个输出配置。
LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
完整代码如下:
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using log4net; namespace RMFramework.Common.Logger { public class Log4System { public static readonly log4net.ILog loginfo = log4net.LogManager.GetLogger("AppLogger"); /// <summary> /// 业务日志记录 /// </summary> /// <param name="info"></param> public void WriteLog(string info) { if (loginfo.IsInfoEnabled) { loginfo.Info(info); } } /// <summary> /// 报错日志记录 /// </summary> /// <param name="info"></param> /// <param name="ex"></param> public void WriteLog(string info, Exception ex) { if (loginfo.IsErrorEnabled) { loginfo.Error(info, ex); } } /// <summary> /// 调试日志记录 /// </summary> /// <param name="info"></param> /// <param name="values"></param> public void WriteLog(string info, params object[] values) { if (loginfo.IsDebugEnabled) { loginfo.Debug(string.Format(info, values)); } } } }
3.2 自定义输出
自定义输出对象,映射到配置文件内的layout标签
支持输出到控制台,支持输出到文本,支持输出到数据库。
using log4net; using log4net.Config; using log4net.Core; using log4net.Layout; using log4net.Layout.Pattern; using System; using System.IO; using System.Reflection; namespace RMFramework.Common.Logger { public class LogHelper { /// <summary> /// LoggerName /// </summary> public static string LoggerName = string.Empty; /// <summary> /// 用户ID /// </summary> public static string UserID = string.Empty; /// <summary> /// 用户名称 /// </summary> public static string UserName = string.Empty; private static ILog iLog; private static LogEntity logEntity; /// <summary> /// 接口 /// </summary> private static ILog log { get { var configFile = new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log4net.config")); if (!configFile.Exists) { throw new Exception("未配置log4net配置文件!"); } // 设置日志配置文件路径 XmlConfigurator.Configure(configFile); //string path = AppDomain.CurrentDomain.SetupInformation.ApplicationBase + @"\Log4Net.config";//AppDomain.CurrentDomain.BaseDirectory //XmlConfigurator.Configure(new FileInfo(path)); if (iLog == null) { iLog = LogManager.GetLogger(LoggerName); } else { if (iLog.Logger.Name != LoggerName) { iLog = LogManager.GetLogger(LoggerName); } } return iLog; } } /// <summary> /// 构造消息实体 /// </summary> /// <param name="message"></param> /// <returns></returns> private static LogEntity BuildMessageMode(string message) { if (logEntity == null) { logEntity = new LogEntity(); logEntity.UserID = UserID; logEntity.UserName = UserName; logEntity.Message = message; } else logEntity.Message = message; return logEntity; } /// <summary> /// 调试 /// </summary> /// <param name="message">消息</param> public static void Debug(string message) { if (log.IsDebugEnabled) log.Debug(BuildMessageMode(message)); } /// <summary> /// 调试 /// </summary> /// <param name="message">消息</param> /// <param name="exception">异常</param> public static void Debug(string message, Exception ex) { if (log.IsDebugEnabled) log.Debug(BuildMessageMode(message), ex); } /// <summary> /// 信息 /// </summary> /// <param name="message">消息</param> public static void Info(string message) { if (log.IsInfoEnabled) log.Info(BuildMessageMode(message)); } /// <summary> /// 信息 /// </summary> /// <param name="message">消息</param> /// <param name="exception">异常</param> public static void Info(string message, Exception ex) { if (log.IsInfoEnabled) log.Info(BuildMessageMode(message), ex); } /// <summary> /// 一般错误 /// </summary> /// <param name="message">消息</param> public static void Error(string message) { if (log.IsErrorEnabled) log.Error(BuildMessageMode(message)); } /// <summary> /// 一般错误 /// </summary> /// <param name="message">消息</param> /// <param name="exception">异常</param> public static void Error(string message, Exception exception) { if (log.IsErrorEnabled) log.Error(BuildMessageMode(message), exception); } /// <summary> /// 警告 /// </summary> /// <param name="message">消息</param> public static void Warn(string message) { if (log.IsWarnEnabled) log.Warn(BuildMessageMode(message)); } /// <summary> /// 警告 /// </summary> /// <param name="message">消息</param> /// <param name="exception">异常</param> public static void Warn(string message, Exception ex) { if (log.IsWarnEnabled) log.Warn(BuildMessageMode(message), ex); } /// <summary> /// 严重 /// </summary> /// <param name="message">消息</param> public static void Fatal(string message) { if (log.IsFatalEnabled) log.Fatal(BuildMessageMode(message)); } /// <summary> /// 严重 /// </summary> /// <param name="message">消息</param> /// <param name="exception">异常</param> public static void Fatal(string message, Exception ex) { if (log.IsFatalEnabled) log.Fatal(BuildMessageMode(message), ex); } } public class MyLayout : PatternLayout { public MyLayout() { this.AddConverter("Property", typeof(MyPatternConverter)); } } public class LogEntity { public string UserID { get; set; } public string UserName { get; set; } public string Message { get; set; } public Exception Exception { get; set; } } public class MyPatternConverter : PatternLayoutConverter { protected override void Convert(TextWriter writer, LoggingEvent loggingEvent) { if (Option != null) { WriteObject(writer, loggingEvent.Repository, LookupProperty(Option, loggingEvent)); } else { WriteDictionary(writer, loggingEvent.Repository, loggingEvent.GetProperties()); } } /// <summary> /// 通过反射获取传入的日志对象的某个属性的值 /// </summary> /// <param name="property"></param> /// <returns></returns> private object LookupProperty(string property, log4net.Core.LoggingEvent loggingEvent) { object propertyValue = string.Empty; PropertyInfo propertyInfo = loggingEvent.MessageObject.GetType().GetProperty(property); if (propertyInfo != null) propertyValue = propertyInfo.GetValue(loggingEvent.MessageObject, null); return propertyValue; } } }
3.3 队列输出
将日志添加到队列,队列输出到磁盘,效率高。
using log4net; using log4net.Config; using System; using System.Collections.Concurrent; using System.IO; using System.Threading; namespace RMFramework.Common.Logger { public sealed class FlashLogger { /// <summary> /// 记录消息Queue /// </summary> private readonly ConcurrentQueue<FlashLogMessage> _que; /// <summary> /// 信号 /// </summary> private readonly ManualResetEvent _mre; /// <summary> /// 日志 /// </summary> private readonly ILog _log; /// <summary> /// 日志 /// </summary> private static FlashLogger _flashLog = new FlashLogger(); private FlashLogger() { var configFile = new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log4net.config")); if (!configFile.Exists) { throw new Exception("未配置log4net配置文件!"); } // 设置日志配置文件路径 XmlConfigurator.Configure(configFile); _que = new ConcurrentQueue<FlashLogMessage>(); _mre = new ManualResetEvent(false); _log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); } /// <summary> /// 实现单例 /// </summary> /// <returns></returns> public static FlashLogger Instance() { return _flashLog; } /// <summary> /// 另一个线程记录日志,只在程序初始化时调用一次 /// </summary> public void Register() { Thread t = new Thread(new ThreadStart(WriteLog)); t.IsBackground = false; t.Start(); } /// <summary> /// 从队列中写日志至磁盘 /// </summary> private void WriteLog() { while (true) { // 等待信号通知 _mre.WaitOne(); FlashLogMessage msg; // 判断是否有内容需要如磁盘 从列队中获取内容,并删除列队中的内容 while (_que.Count > 0 && _que.TryDequeue(out msg)) { // 判断日志等级,然后写日志 switch (msg.Level) { case FlashLogLevel.Debug: _log.Debug(msg.Message, msg.Exception); break; case FlashLogLevel.Info: _log.Info(msg.Message, msg.Exception); break; case FlashLogLevel.Error: _log.Error(msg.Message, msg.Exception); break; case FlashLogLevel.Warn: _log.Warn(msg.Message, msg.Exception); break; case FlashLogLevel.Fatal: _log.Fatal(msg.Message, msg.Exception); break; } } // 重新设置信号 _mre.Reset(); Thread.Sleep(1); } } /// <summary> /// 写日志 /// </summary> /// <param name="message">日志文本</param> /// <param name="level">等级</param> /// <param name="ex">Exception</param> public void EnqueueMessage(string message, FlashLogLevel level, Exception ex = null) { if ((level == FlashLogLevel.Debug && _log.IsDebugEnabled) || (level == FlashLogLevel.Error && _log.IsErrorEnabled) || (level == FlashLogLevel.Fatal && _log.IsFatalEnabled) || (level == FlashLogLevel.Info && _log.IsInfoEnabled) || (level == FlashLogLevel.Warn && _log.IsWarnEnabled)) { _que.Enqueue(new FlashLogMessage { Message = "[" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff") + "]\r\n" + message, Level = level, Exception = ex }); // 通知线程往磁盘中写日志 _mre.Set(); } } public static void Debug(string msg, Exception ex = null) { Instance().EnqueueMessage(msg, FlashLogLevel.Debug, ex); } public static void Error(string msg, Exception ex = null) { Instance().EnqueueMessage(msg, FlashLogLevel.Error, ex); } public static void Fatal(string msg, Exception ex = null) { Instance().EnqueueMessage(msg, FlashLogLevel.Fatal, ex); } public static void Info(string msg, Exception ex = null) { Instance().EnqueueMessage(msg, FlashLogLevel.Info, ex); } public static void Warn(string msg, Exception ex = null) { Instance().EnqueueMessage(msg, FlashLogLevel.Warn, ex); } } /// <summary> /// 日志等级 /// </summary> public enum FlashLogLevel { Debug, Info, Error, Warn, Fatal } /// <summary> /// 日志内容 /// </summary> public class FlashLogMessage { public string Message { get; set; } public FlashLogLevel Level { get; set; } public Exception Exception { get; set; } } }
4.需要注意的地方
- 实例化后的日志是被独占的,日志类库应集中管理,否则会导致输出为空