我们在做开发的时候,需要把一些信息记录下来,方便问题排查、数据分析和统计。通常我们使用log4net作为logging的工具,但是大部分时候需要加以封装,以便更加方便的使用,并且不妨碍主业务程序的运行。下面就是一个异步logging的例子,关键在于:
- 简洁:不做过度封装,能满足需要的就是做好的,“done is better than perfect”;
- 异步:所有的信息都以异步的方式进行记录,不会对主业务逻辑造成任何的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" />复制代码
出处:
转载于:https://juejin.im/post/5cf78addf265da1ba9156be4