转载: 基于log4net自定义异步logging组件

854 阅读4分钟

我们在做开发的时候,需要把一些信息记录下来,方便问题排查、数据分析和统计。通常我们使用log4net作为logging的工具,但是大部分时候需要加以封装,以便更加方便的使用,并且不妨碍主业务程序的运行。下面就是一个异步logging的例子,关键在于:

  1. 简洁:不做过度封装,能满足需要的就是做好的,“done is better than perfect”;
  2. 异步:所有的信息都以异步的方式进行记录,不会对主业务逻辑造成任何的block。            

首先,在一个新建的工程里引用log4net.dll,并且进行简单的封装。

using System;
using System.Diagnostics;
using System.IO;
using log4net;

[assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.config", Watch = true)]

namespace Log
{
    public partial class CommonLogging
    {
        private const string StringNewLineString = "-------------------------------------------------------";

        private static ILog log;

        static CommonLogging()
        {
            log = LogManager.GetLogger("common");
            StartLogThread();
        }

        public static void Info(string message)
        {
            AddLog(message);
        }

        public static void Info(string message, Exception ex)
        {
            AddLog(message, LogType.Info, ex);
        }

        public static void InfoLine(string message)
        {
            AddLogFormat("{0}\r\n{1}", LogType.Info, null, StringNewLineString, message);
        }

        public static void Warn(string message)
        {
            AddLog(message, LogType.Warn);
        }

        public static void Warn(string message, Exception ex)
        {
            AddLog(message, LogType.Warn, ex);
        }

        public static void Debug(string message)
        {
            AddLog(message, LogType.Debug);
        }

        public static void Debug(string message, Exception ex)
        {
            AddLog(message, LogType.Debug, ex);
        }

        public static void Error(string message)
        {
            AddLog(message, LogType.Error);
        }

        public static void Error(string message, Exception ex)
        {
            if (null == ex)
            {
                Error(message);
                return;
            }

            AddLog(message, LogType.Error, ex);
        }

        public static void Fatal(string message)
        {
            AddLog(message, LogType.Fatal);
        }

        public static void Fatal(string message, Exception ex)
        {
            AddLog(message, LogType.Fatal, ex);
        }

        public static void InfoFormat(string format, params string[] args)
        {
            AddLogFormat(format, LogType.Info, null, args);
        }

        public static void ErrorFormat(string format, params string[] args)
        {
            AddLogFormat(format, LogType.Error, null, args);
        }

        public static void ErrorFormat(string format, Exception ex, params string[] args)
        {
            AddLogFormat(format, LogType.Error, ex, args);
        }

        public static void WatchToInfoLog(string message, Action action)
        {
            Stopwatch sw = Stopwatch.StartNew();
            Info(string.Format("start to {0}", message));
            action();
            sw.Stop();
            Info(string.Format("{0} completed..., cost: {1}", message, sw.Elapsed.TotalSeconds));
        }

        public static bool CatchLog(Action action, string errorMsg, bool isThrowException = false)
        {
            if (null == action)
            {
                return true;
            }

            try
            {
                action();
                return true;
            }
            catch (Exception ex)
            {
                Error(errorMsg, ex);

                if (isThrowException)
                {
                    throw;
                }

                return false;
            }
        }

        private static string GetLogFileName(string tname)
        {
            string name;
            string basedir = AppDomain.CurrentDomain.BaseDirectory;
            int pos = basedir.IndexOf("\\inetpub\\");
            if (pos < 0)
            {
                // we are not running under an inetpub dir, log underneath the base dir
                string separator = basedir.EndsWith("\\") ? null : "\\";
                name = AppDomain.CurrentDomain.BaseDirectory + separator + @"logs\" + "nevmiss" + tname + ".log";
            }
            else
            {
                // we're running on an IIS server, so log under the logs directory so we can share it
                name = basedir.Substring(0, pos + 9) + "logs" + Path.DirectorySeparatorChar + "nevmiss_" + tname + ".log";
            }

            return name;
        }
    }
}

使用一个partial类来进行扩展:

using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;

namespace Log
{
    public partial class CommonLogging
    {
        private static ConcurrentQueue<LoggingModel> messageQueue;

        private static Thread thread;

        private static void StartLogThread()
        {
            messageQueue = new ConcurrentQueue<LoggingModel>();
            thread = new Thread(InternalWriteLog);
            thread.SetApartmentState(ApartmentState.STA);
            thread.IsBackground = true;

            thread.Start();
        }

        private static void AddLog(string message, LogType type = LogType.Info, Exception ex = null)
        {
            messageQueue.Enqueue(new LoggingModel(message, type, ex));
            CommonLogging.Trigger();
        }

        private static void AddLogFormat(string format, string arg0, LogType type = LogType.Info, Exception ex = null)
        {
            try
            {
                messageQueue.Enqueue(new LoggingModel(string.Format(format, arg0), type, ex));
                CommonLogging.Trigger();
            }
            catch (Exception exception)
            {
                AddLog(string.Format("Add Log Format error, format string:'{0}' , arg0:{1}.", format, arg0), LogType.Error, exception);
            }
        }

        private static void AddLogFormat(string format, LogType type = LogType.Info, Exception ex = null, params string[] args)
        {
            try
            {
                messageQueue.Enqueue(new LoggingModel(string.Format(format, args), type, ex));
                CommonLogging.Trigger();
            }
            catch (Exception exception)
            {
                AddLog(
                    string.Format("Add Log Format error,format:'{0}', arg:{1}.", format, null == args ? null : string.Join(" , ", args)),
                    LogType.Error,
                    exception);
            }
        }

        public static void Trigger()
        {
            if (IsProcessing)
            {
                return;
            }
            else
            {
                Task.Factory.StartNew(() =>
                {
                    InternalWriteLog();
                });
            }
        }

        private volatile static bool IsProcessing = false;
        public static void InternalWriteLog()
        {
            LoggingModel model;
            while (messageQueue.TryDequeue(out model))
            {
                IsProcessing = true;

                switch (model.MessageType)
                {
                    case LogType.Info:
                        {
                            log.Info(model.Message, model.Exception);
                        }
                        break;
                    case LogType.Error:
                        {
                            log.Error(model.Message, model.Exception);
                        }
                        break;
                    case LogType.Warn:
                        {
                            log.Warn(model.Message, model.Exception);
                        }
                        break;
                    case LogType.Debug:
                        {
                            log.Debug(model.Message, model.Exception);
                        }
                        break;
                    default:
                        break;
                }

                model.Dispose();
            }

            IsProcessing = false;
        }
    }
}

用到的LoggingModel:

using System;

namespace Log
{
    internal struct LoggingModel : IDisposable
    {
        private string message;
        private LogType messageType;
        private Exception exception;

        public Exception Exception
        {
            get { return this.exception; }
            set { this.exception = value; }
        }

        internal LogType MessageType
        {
            get { return this.messageType; }
            set { this.messageType = value; }
        }

        public string Message
        {
            get { return this.message; }
            set
            {
                this.message = value;
            }
        }

        public LoggingModel(string message, bool isError = false, Exception ex = null)
        {
            this.message = string.Format("[{0}],{1}", DateTime.UtcNow.ToString("HH:mm:ss,fff"), message);
            this.messageType = isError ? LogType.Error : LogType.Info;
            this.exception = ex;
        }

        public LoggingModel(string message, LogType type = LogType.Info, Exception ex = null)
        {
            this.message = string.Format("[{0}] {1}", DateTime.UtcNow.ToString("HH:mm:ss,fff"), message);
            this.messageType = type;
            this.exception = ex;
        }

        public void Dispose()
        {
            this.exception = null;
            this.message = null;
        }
    }

    internal enum LogType
    {
        Debug = 0,

        Info = 1,

        Warn = 2,

        Error = 3,

        Fatal = 4,
    }
}

其次,在需要使用logging的工程中加入单独的配置文件:

<?xml version="1.0"?>
<configuration>
  <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
  </configSections>
  <log4net>
    
    <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
      <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date [%thread] %-5level %logger %ndc - %message%newline" />
      </layout>
    </appender>

    <appender name="CommonLogAppender" type="log4net.Appender.RollingFileAppender">
      <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
      <file value="logs\common_"/>
      <encoding value="utf-8"/>
      <appendToFile value="true"/>
      <rollingStyle value="Date"/>
      <datePattern value="yyyyMMdd'.log'"/>
      <maxSizeRollBackups value="10"/>
      <maximumFileSize value="50MB"/>
      <staticLogFileName value="false"/>
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date [%thread] %-5level- %message%newline"/>
      </layout>
    </appender>
    
    <!-- Setup the root category, add the appenders and set the default level -->
    <root>
      <level value="ALL" />
      <appender-ref ref="ConsoleAppender" />
    </root>

    <logger name="common">
      <level value="INFO" />
      <appender-ref ref="CommonLogAppender" />
    </logger>
  </log4net>
</configuration>

并且在app.config或者web.config中指定对应的引用:

<appSettings>
    <add key="log4net.Config" value="log4net.config" />
    <add key="log4net.Config.Watch" value="True" />
  </appSettings>

注意:

为防止多线程写入文件,从而导致出日志文件名叠加的现象,在配置文件中添加了

 <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />


出处:

www.cnblogs.com/allanli/p/5…