基于log4net日志组件的使用


一、前言

  系统运行的时候,生产上需要使用文件来记录一些系统运行中的日志,比如系统的对接日志、错误信息、警告信息或者特定调试信息,通过对记录的信息回溯系统执行过程,查找系统问题,帮助开发者解决线上问题等。这个系统都要使用日志组件,一般都会有相应的开源组件,比如log4net。

二、使用

1、在项目引用中引入log4net组件,然后对日志配置文件进行配置,配置信息涉及到文件夹位置、文件名称格式、文件最大大小、文件的内容格式、日志级别等信息,比如Log4Net.config。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
  configSections>
  <log4net>
    <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="[%p %t] [%date{yyyy-MM-dd HH:mm:ss,fff}] %-5l - %m%n"/>
      layout>
    appender>
    <appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
      
      <param name= "File" value= "logs\Info\log_"/>
      
      <param name= "AppendToFile" value= "true"/>
      
      <param name= "MaxSizeRollBackups" value= "10"/>
      
      <param name= "StaticLogFileName" value= "false"/>
      
      <param name= "DatePattern" value= "yyyy-MM-dd".log""/>
      
      <param name= "RollingStyle" value= "Date"/>
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="记录时间:%date 线程ID:[%thread] 日志级别:  %-5level 类:%logger  - 描述:%message %n"/>
      layout>
      <filter type="log4net.Filter.LevelRangeFilter">
        <param name="LevelMin" value="Info" />
        <param name="LevelMax" value="WARN" />
      filter>
    appender>
    <appender name="RollingFile" type="log4net.Appender.RollingFileAppender">
      <param name= "File" value= "logs\Error\Log_"/>
      <param name= "AppendToFile" value= "true"/>
      <param name= "MaxSizeRollBackups" value= "10"/>
      <param name= "StaticLogFileName" value= "false"/>
      <param name= "DatePattern" value= "yyyy-MM-dd".log""/>
      <param name= "RollingStyle" value= "Date"/>
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="记录时间:%date 线程ID:[%thread] 日志级别:  %-5level 类:%logger  - 描述:%message %n"/>
      layout>
      <filter type="log4net.Filter.LevelRangeFilter">
        <param name="LevelMin" value="ERROR" />
        <param name="LevelMax" value="FATAL" />
      filter>
    appender>
    <root>
      
      <level value="ALL" />
      <appender-ref ref="RollingLogFileAppender" />
      <appender-ref ref="RollingFile" />
    root>
  log4net>
configuration>

通过这个日志文件定义基本信息,可以在打印日志生成想要的日志文件。

2、在项目代码中使用配置文件,并且提供写入日志的方法,定义一个日志接口(使用文件或者数据库的方式),通过log4net组件来实现文件形式记录日志信息。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TQF.Logger.Logger
{
    /// 
    /// 定义日志接口类
    /// 
    public interface ILogger
    {
        /// 
        /// 调试日志输出
        /// 
        /// 输出内容
        void Debug(string msg);

        /// 
        /// 调试日志输出
        /// 
        /// 输出内容
        /// 输出异常
        void Debug(string msg, Exception ex);

        /// 
        /// 信息日志输出
        /// 
        /// 输出内容
        void Info(string msg);

        /// 
        /// 信息日志输出
        /// 
        /// 输出内容
        /// 输出异常
        void Info(string msg, Exception ex);

        /// 
        /// 警告日志输出
        /// 
        /// 输出内容
        void Warn(string msg);

        /// 
        /// 警告日志输出
        /// 
        /// 输出内容
        /// 输出异常
        void Warn(string msg, Exception ex);

        /// 
        /// 错误日志输出
        /// 
        /// 输出内容
        void Error(string msg);

        /// 
        /// 错误日志输出
        /// 
        /// 输出内容
        /// 输出异常
        void Error(string msg, Exception ex);

        /// 
        /// 致命日志输出
        /// 
        /// 输出内容
        void Fatal(string msg);

        /// 
        /// 致命日志输出
        /// 
        /// 输出内容
        /// 输出异常
        void Fatal(string msg, Exception ex);
    }
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using log4net;
using log4net.Appender;
using log4net.Config;

namespace TQF.Logger.Logger
{
    /// 
    /// 日志接口实现类
    /// 
    public class Log4Helper : ILogger
    {
        /// 
        /// 考虑使用线程安全的字典
        /// 
        private Dictionary<string, ILog> LogDic = new Dictionary<string, ILog>();
        
        /// 
        /// 定义锁对象
        /// 
        private object _islock = new object();

        /// 
        /// 定义文件名
        /// 
        private string fileName = string.Empty;

        
        /// 
        /// 使用构造函数日志调用初始化
        /// 
        /// 日志文件保存路径[若路径为空,则默认程序根目录Logger文件夹;]
        /// 日志文件名[若文件名为空,则默认文件名:Default]
        public Log4Helper(string fileSavePath, string fileName, string logSuffix = ".log")
        {
            try
            {
                Init();
                if (string.IsNullOrEmpty(fileSavePath))
                    fileSavePath = "Logger";
                if (string.IsNullOrEmpty(fileName))
                    fileName = "Default";

                this.fileName = fileName;
                var repository = LogManager.GetRepository();
                var appenders = repository.GetAppenders();

                if (appenders.Length == 0) return;
                var targetApder = appenders.First(p => p.Name == "FileInfoAppender") as RollingFileAppender;
                targetApder.File = Path.Combine(fileSavePath, this.fileName + logSuffix);
                targetApder.ActivateOptions();
            }
            catch (Exception ex) { }
        }

        /// 
        /// 缓存日志对象(通过日志文件名缓存日志对象,减少日志对象的创建工作)
        /// 
        /// 
        /// 
        private ILog GetLog(string name)
        {
            try
            {
                if (LogDic == null)
                {
                    LogDic = new Dictionary<string, ILog>();
                }
                lock (_islock)
                {
                    if (!LogDic.ContainsKey(name))
                    {
                        LogDic.Add(name, LogManager.GetLogger(name));
                    }
                }
                return LogDic[name];
            }
            catch
            {
                return LogManager.GetLogger("Default");
            }
        }

        /// 
        /// 日志记录初始化
        /// 
        private void Init()
        {
            // 获取日志配置文件
            var file = new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Log4net.config"));
            // 检测配置文件是否存在
            if (file.Exists)
            {
                //加载配置文件
                XmlConfigurator.Configure(file);
            }
            else
            {
                new Exception("找不到日志配置文件!");
            }
        }


        /// 
        /// 调试日志输出
        /// 
        /// 内容
        public void Debug(string msg)
        {
            var log = GetLog(this.fileName);
            if (log == null)
            {
                return;
            }
            log.Debug(msg);
        }

        /// 
        /// 调试日志输出
        /// 
        /// 输出内容
        /// 输出异常
        public void Debug(string msg, Exception ex)
        {
            var log = GetLog(this.fileName);
            if (log == null)
            {
                return;
            }
            log.Debug(msg, ex);
        }

        /// 
        /// 错误日志输出
        /// 
        /// 输出内容
        public void Error(string msg)
        {
            var log = GetLog(this.fileName);
            if (log == null)
            {
                return;
            }
            log.Error(msg);
        }

        /// 
        /// 错误日志输出
        /// 
        /// 输出内容
        /// 输出异常
        public void Error(string msg, Exception ex)
        {
            var log = GetLog(this.fileName);
            if (log == null)
            {
                return;
            }
            log.Error(msg, ex);
        }

        /// 
        /// 致命日志输出
        /// 
        /// 输出内容
        public void Fatal(string msg)
        {
            var log = GetLog(this.fileName);
            if (log == null)
            {
                return;
            }
            log.Fatal(msg);
        }

        /// 
        /// 致命日志输出
        /// 
        /// 输出内容
        /// 输出异常
        public void Fatal(string msg, Exception ex)
        {
            var log = GetLog(this.fileName);
            if (log == null)
            {
                return;
            }
            log.Fatal(msg, ex);
        }

        /// 
        /// 信息日志输出
        /// 
        /// 输出内容
        public void Info(string msg)
        {
            var log = GetLog(this.fileName);
            if (log == null)
            {
                return;
            }
            log.Info(msg);
        }

        /// 
        /// 信息日志输出
        /// 
        /// 输出内容
        /// 输出异常
        public void Info(string msg, Exception ex)
        {
            var log = GetLog(this.fileName);
            if (log == null)
            {
                return;
            }
            log.Info(msg, ex);
        }

        /// 
        /// 警告日志输出
        /// 
        /// 输出内容
        public void Warn(string msg)
        {
            var log = GetLog(this.fileName);
            if (log == null)
            {
                return;
            }
            log.Warn(msg);
        }

        /// 
        /// 警告日志输出
        /// 
        /// 输出内容
        /// 输出异常
        public void Warn(string msg, Exception ex)
        {
            var log = GetLog(this.fileName);
            if (log == null)
            {
                return;
            }
            log.Warn(msg, ex);
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TQF.Logger.Logger;

namespace TQF.Logger
{
    /// 
    /// 配置文件如果不设置属性“复制到输出目录”不会在debug文件中存在
    /// 
    class Program
    {
        static void Main(string[] args)
        {
            var log4Helper = new Log4Helper(string.Empty, "program");
            log4Helper.Info("info");
        }
    }
}

通过上述代码,定义日志接口(写入方法,不同层级的写入方法)、日志帮助类实现接口,使用组件读取配置文件、获取日志对象写入日志。

3、上述是按照配置文件格式定义好的文件夹、文件名称、文件内容格式,相对标准化,如果系统要特殊的记录单个日志信息,比如不确定文件夹名称、不确定的的文件名称等,就要开发者进行自定义。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.Concurrent;
using System.Configuration;

using log4net;
using log4net.Appender;
using log4net.Core;
using log4net.Layout;
using log4net.Repository;
using log4net.Repository.Hierarchy;

[assembly: log4net.Config.XmlConfigurator(Watch = true)]
namespace DataPullAPI.Utility
{
    /// 
    /// log4net 自定义日志类
    /// 
    public static class LogCustomHelper
    {
        /// 
        /// 类型安全的字典对象
        /// 
        private static readonly ConcurrentDictionary<string, ILog> loggerContainer = new ConcurrentDictionary<string, ILog>();

        //默认配置 
        private const int MAX_SIZE_ROLL_BACKUPS = 20;
        private const string LAYOUT_PATTERN = "%message";
        private const string DATE_PATTERN = "yyyyMMdd";
        private const string MAXIMUM_FILE_SIZE = "10MB";
        private const string LEVEL = "ALL";

        /// 
        /// 获取日志对象
        /// 
        /// 
        /// 
        /// 
        /// 
        public static ILog GetCustomLogger(string loggerName, string category = null, bool additivity = false)
        {
            return loggerContainer.GetOrAdd(loggerName, delegate (string name)
            {
                RollingFileAppender newAppender = GetNewFileApender(loggerName, GetFile(category, loggerName), MAX_SIZE_ROLL_BACKUPS, true, true, MAXIMUM_FILE_SIZE, RollingFileAppender.RollingMode.Composite,
                    DATE_PATTERN, LAYOUT_PATTERN);

                Hierarchy repository = (Hierarchy)LogManager.GetRepository();
                Logger logger = repository.LoggerFactory.CreateLogger(repository, loggerName);
                logger.Hierarchy = repository;
                logger.Parent = repository.Root;
                logger.Level = GetLoggerLevel(LEVEL);
                logger.Additivity = additivity;
                logger.AddAppender(newAppender);
                logger.Repository.Configured = true;
                return new LogImpl(logger);
            });
        }

        /// 
        /// 自定日志文件夹和文件路径
        /// 
        /// 
        /// 
        /// 
        private static string GetFile(string category, string loggerName)
        {
            if (string.IsNullOrEmpty(category))
            {
                return string.Format(@"Logs\{0}\{1}.txt", DateTime.Now.ToString("yyyy-MM-dd"), loggerName);
            }
            else
            {
                return string.Format(@"Logs\{0}\{1}\{2}.txt", category,DateTime.Now.ToString("yyyy-MM-dd"), loggerName);
            }
        }

        /// 
        /// 获取日志级别
        /// 
        /// 
        /// 
        private static Level GetLoggerLevel(string level)
        {
            if (!string.IsNullOrEmpty(level))
            {
                switch (level.ToLower().Trim())
                {
                    case "debug":
                        return Level.Debug;

                    case "info":
                        return Level.Info;

                    case "warn":
                        return Level.Warn;

                    case "error":
                        return Level.Error;

                    case "fatal":
                        return Level.Fatal;
                }
            }
            return Level.Debug;
        }

        /// 
        /// 获取配置信息
        /// 
        /// 
        /// 
        /// 
        /// 
        /// 
        /// 
        /// 
        /// 
        /// 
        /// 
        private static RollingFileAppender GetNewFileApender(string appenderName, string file, int maxSizeRollBackups, bool appendToFile = true, bool staticLogFileName = false, string maximumFileSize = "2MB", RollingFileAppender.RollingMode rollingMode = RollingFileAppender.RollingMode.Size, string datePattern = "yyyyMMdd\".txt\"", string layoutPattern = "%d [%t] %-5p %c  - %m%n")
        {
            RollingFileAppender appender = new RollingFileAppender
            {
                LockingModel = new FileAppender.MinimalLock(),
                Name = appenderName,
                File = file,
                AppendToFile = appendToFile,
                MaxSizeRollBackups = maxSizeRollBackups,
                MaximumFileSize = maximumFileSize,
                StaticLogFileName = staticLogFileName,
                RollingStyle = rollingMode,
                DatePattern = datePattern
            };
            PatternLayout layout = new PatternLayout(layoutPattern);
            appender.Layout = layout;
            layout.ActivateOptions();
            appender.ActivateOptions();
            return appender;
        }

        /// 
        /// 写入日志信息
        /// 
        /// 文件夹名称
        /// 文件名称
        /// 日志信息
        public static void Info(string folderName, string fileName,string message)
        {
            ILog logger = GetCustomLogger(fileName, folderName);
            logger.Info(message);
        }
    }
}

通过在写入日志的时候,传入文件夹和文件名称来自定义日志存储,对于特殊的日志信息可以通过该方法达到特定存储要求。

总结

1、在系统生产环境记录日志可以帮助用户定位系统问题,解决系统问题,对系统执行过程中可能出现异常错误进行捕获记录,对执行全过程进行回溯查看等。

2、系统日志不仅可以使用文件方式进行存储,还可以使用数据库的方式进行存储。