我们在做开发的时候,需要把一些信息记录下来,方便问题排查、数据分析和统计。通常我们使用log4net作为logging的工具,但是大部分时候需要加以封装,以便更加方便的使用,并且不妨碍主业务程序的运行。下面就是一个异步logging的例子,关键在于:
- 简洁:不做过度封装,能满足需要的就是做好的,“done is better than perfect”;
- 异步:所有的信息都以异步的方式进行记录,不会对主业务逻辑造成任何的block。
首先,在一个新建的工程里引用log4net.dll,并且进行简单的封装。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 using System; 2 using System.Diagnostics; 3 using System.IO; 4 using log4net; 5 6 [assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.config", Watch = true)] 7 8 namespace Log 9 { 10 public partial class CommonLogging 11 { 12 private const string StringNewLineString = "-------------------------------------------------------"; 13 14 private static ILog log; 15 16 static CommonLogging() 17 { 18 log = LogManager.GetLogger("common"); 19 StartLogThread(); 20 } 21 22 public static void Info(string message) 23 { 24 AddLog(message); 25 } 26 27 public static void Info(string message, Exception ex) 28 { 29 AddLog(message, LogType.Info, ex); 30 } 31 32 public static void InfoLine(string message) 33 { 34 AddLogFormat("{0} {1}", LogType.Info, null, StringNewLineString, message); 35 } 36 37 public static void Warn(string message) 38 { 39 AddLog(message, LogType.Warn); 40 } 41 42 public static void Warn(string message, Exception ex) 43 { 44 AddLog(message, LogType.Warn, ex); 45 } 46 47 public static void Debug(string message) 48 { 49 AddLog(message, LogType.Debug); 50 } 51 52 public static void Debug(string message, Exception ex) 53 { 54 AddLog(message, LogType.Debug, ex); 55 } 56 57 public static void Error(string message) 58 { 59 AddLog(message, LogType.Error); 60 } 61 62 public static void Error(string message, Exception ex) 63 { 64 if (null == ex) 65 { 66 Error(message); 67 return; 68 } 69 70 AddLog(message, LogType.Error, ex); 71 } 72 73 public static void Fatal(string message) 74 { 75 AddLog(message, LogType.Fatal); 76 } 77 78 public static void Fatal(string message, Exception ex) 79 { 80 AddLog(message, LogType.Fatal, ex); 81 } 82 83 public static void InfoFormat(string format, params string[] args) 84 { 85 AddLogFormat(format, LogType.Info, null, args); 86 } 87 88 public static void ErrorFormat(string format, params string[] args) 89 { 90 AddLogFormat(format, LogType.Error, null, args); 91 } 92 93 public static void ErrorFormat(string format, Exception ex, params string[] args) 94 { 95 AddLogFormat(format, LogType.Error, ex, args); 96 } 97 98 public static void WatchToInfoLog(string message, Action action) 99 { 100 Stopwatch sw = Stopwatch.StartNew(); 101 Info(string.Format("start to {0}", message)); 102 action(); 103 sw.Stop(); 104 Info(string.Format("{0} completed..., cost: {1}", message, sw.Elapsed.TotalSeconds)); 105 } 106 107 public static bool CatchLog(Action action, string errorMsg, bool isThrowException = false) 108 { 109 if (null == action) 110 { 111 return true; 112 } 113 114 try 115 { 116 action(); 117 return true; 118 } 119 catch (Exception ex) 120 { 121 Error(errorMsg, ex); 122 123 if (isThrowException) 124 { 125 throw; 126 } 127 128 return false; 129 } 130 } 131 132 private static string GetLogFileName(string tname) 133 { 134 string name; 135 string basedir = AppDomain.CurrentDomain.BaseDirectory; 136 int pos = basedir.IndexOf("\inetpub\"); 137 if (pos < 0) 138 { 139 // we are not running under an inetpub dir, log underneath the base dir 140 string separator = basedir.EndsWith("\") ? null : "\"; 141 name = AppDomain.CurrentDomain.BaseDirectory + separator + @"logs" + "nevmiss" + tname + ".log"; 142 } 143 else 144 { 145 // we're running on an IIS server, so log under the logs directory so we can share it 146 name = basedir.Substring(0, pos + 9) + "logs" + Path.DirectorySeparatorChar + "nevmiss_" + tname + ".log"; 147 } 148 149 return name; 150 } 151 } 152 }
使用一个partial类来进行扩展:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 using System; 2 using System.Collections.Concurrent; 3 using System.Threading; 4 using System.Threading.Tasks; 5 6 namespace Log 7 { 8 public partial class CommonLogging 9 { 10 private static ConcurrentQueue<LoggingModel> messageQueue; 11 12 private static Thread thread; 13 14 private static void StartLogThread() 15 { 16 messageQueue = new ConcurrentQueue<LoggingModel>(); 17 thread = new Thread(InternalWriteLog); 18 thread.SetApartmentState(ApartmentState.STA); 19 thread.IsBackground = true; 20 21 thread.Start(); 22 } 23 24 private static void AddLog(string message, LogType type = LogType.Info, Exception ex = null) 25 { 26 messageQueue.Enqueue(new LoggingModel(message, type, ex)); 27 CommonLogging.Trigger(); 28 } 29 30 private static void AddLogFormat(string format, string arg0, LogType type = LogType.Info, Exception ex = null) 31 { 32 try 33 { 34 messageQueue.Enqueue(new LoggingModel(string.Format(format, arg0), type, ex)); 35 CommonLogging.Trigger(); 36 } 37 catch (Exception exception) 38 { 39 AddLog(string.Format("Add Log Format error, format string:'{0}' , arg0:{1}.", format, arg0), LogType.Error, exception); 40 } 41 } 42 43 private static void AddLogFormat(string format, LogType type = LogType.Info, Exception ex = null, params string[] args) 44 { 45 try 46 { 47 messageQueue.Enqueue(new LoggingModel(string.Format(format, args), type, ex)); 48 CommonLogging.Trigger(); 49 } 50 catch (Exception exception) 51 { 52 AddLog( 53 string.Format("Add Log Format error,format:'{0}', arg:{1}.", format, null == args ? null : string.Join(" , ", args)), 54 LogType.Error, 55 exception); 56 } 57 } 58 59 public static void Trigger() 60 { 61 if (IsProcessing) 62 { 63 return; 64 } 65 else 66 { 67 Task.Factory.StartNew(() => 68 { 69 InternalWriteLog(); 70 }); 71 } 72 } 73 74 private volatile static bool IsProcessing = false; 75 public static void InternalWriteLog() 76 { 77 LoggingModel model; 78 while (messageQueue.TryDequeue(out model)) 79 { 80 IsProcessing = true; 81 82 switch (model.MessageType) 83 { 84 case LogType.Info: 85 { 86 log.Info(model.Message, model.Exception); 87 } 88 break; 89 case LogType.Error: 90 { 91 log.Error(model.Message, model.Exception); 92 } 93 break; 94 case LogType.Warn: 95 { 96 log.Warn(model.Message, model.Exception); 97 } 98 break; 99 case LogType.Debug: 100 { 101 log.Debug(model.Message, model.Exception); 102 } 103 break; 104 default: 105 break; 106 } 107 108 model.Dispose(); 109 } 110 111 IsProcessing = false; 112 } 113 } 114 }
用到的LoggingModel:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 using System; 2 3 namespace Log 4 { 5 internal struct LoggingModel : IDisposable 6 { 7 private string message; 8 private LogType messageType; 9 private Exception exception; 10 11 public Exception Exception 12 { 13 get { return this.exception; } 14 set { this.exception = value; } 15 } 16 17 internal LogType MessageType 18 { 19 get { return this.messageType; } 20 set { this.messageType = value; } 21 } 22 23 public string Message 24 { 25 get { return this.message; } 26 set 27 { 28 this.message = value; 29 } 30 } 31 32 public LoggingModel(string message, bool isError = false, Exception ex = null) 33 { 34 this.message = string.Format("[{0}],{1}", DateTime.UtcNow.ToString("HH:mm:ss,fff"), message); 35 this.messageType = isError ? LogType.Error : LogType.Info; 36 this.exception = ex; 37 } 38 39 public LoggingModel(string message, LogType type = LogType.Info, Exception ex = null) 40 { 41 this.message = string.Format("[{0}] {1}", DateTime.UtcNow.ToString("HH:mm:ss,fff"), message); 42 this.messageType = type; 43 this.exception = ex; 44 } 45 46 public void Dispose() 47 { 48 this.exception = null; 49 this.message = null; 50 } 51 } 52 53 internal enum LogType 54 { 55 Debug = 0, 56 57 Info = 1, 58 59 Warn = 2, 60 61 Error = 3, 62 63 Fatal = 4, 64 } 65 }
其次,在需要使用logging的工程中加入单独的配置文件:
<?xml version="1.0"?> <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="%date [%thread] %-5level %logger %ndc - %message%newline" /> </layout> </appender> <appender name="CommonLogAppender" type="log4net.Appender.RollingFileAppender"> <file value="logscommon_"/> <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>
注意:需要设置log4net.config的属性,使之能够自动拷贝到编译的文件夹中。
并且在app.config或者web.config中指定对应的引用:
<appSettings> <add key="log4net.Config" value="log4net.config" /> <add key="log4net.Config.Watch" value="True" /> </appSettings>
这样就可以方便地使用logging而不用担心性能的问题啦。