[Asp.net 5] Logging-其他日志系统的实现
Microsoft.Framework.Logging.NLog
使用Nlog扩展日志系统:按照我们上节说的,对于扩展的日志系统都要实现俩个接口ILogger、ILoggerProvider。所以在当前工程中也没例外,NLogLoggerProvider实现了ILoggerProvider、内部类Logger实现了ILogger。源代码如下:
public class NLogLoggerProvider : ILoggerProvider { private readonly LogFactory _logFactory; public NLogLoggerProvider(LogFactory logFactory) { _logFactory = logFactory; } public ILogger CreateLogger(string name) { return new Logger(_logFactory.GetLogger(name)); } private class Logger : ILogger { private readonly global::NLog.Logger _logger; public Logger(global::NLog.Logger logger) { _logger = logger; } public void Log( LogLevel logLevel, int eventId, object state, Exception exception, Func<object, Exception, string> formatter) { var nLogLogLevel = GetLogLevel(logLevel); var message = string.Empty; if (formatter != null) { message = formatter(state, exception); } else { message = LogFormatter.Formatter(state, exception); } if (!string.IsNullOrEmpty(message)) { var eventInfo = LogEventInfo.Create(nLogLogLevel, _logger.Name, message, exception); eventInfo.Properties["EventId"] = eventId; _logger.Log(eventInfo); } } public bool IsEnabled(LogLevel logLevel) { return _logger.IsEnabled(GetLogLevel(logLevel)); } private global::NLog.LogLevel GetLogLevel(LogLevel logLevel) { switch (logLevel) { case LogLevel.Verbose: return global::NLog.LogLevel.Debug; case LogLevel.Information: return global::NLog.LogLevel.Info; case LogLevel.Warning: return global::NLog.LogLevel.Warn; case LogLevel.Error: return global::NLog.LogLevel.Error; case LogLevel.Critical: return global::NLog.LogLevel.Fatal; } return global::NLog.LogLevel.Debug; } public IDisposable BeginScopeImpl([NotNull] object state) { return NestedDiagnosticsContext.Push(state.ToString()); } } }
这段代码很容易读懂,也就不过多解释了,需要注意的一点是:LogFactory是NLog的工厂。
下面我们主要就NLog和大名鼎鼎的Log4net进行比较(道听途说比较多,欢迎指正)
- 都不建议直接使用,使用门面类包装(也就是相当于本处的Microsoft.Framework.Logging.NLog的内部Logger类实际是Nlog的log的门面)
- 如果现在系统使用了日志系统,建议使用原有的日志系统(应用log4net比较多),完全新的项目建议使用Nlog
- 从初始化配置上看Nlog要比log4net简单
- 从功能强大度上,基本可以平分秋色
- 从开源代码更新程度上看:Nlog以及在github上开源(https://github.com/nlog)、log4net在Apache上开源(http://logging.apache.org/log4net/download_log4net.cgi)。但是Nlog还有更新,支持的.net版本也比较多;log4net感觉好久没更新了,核心版本还是.net2.0(此处不是道听途说,是楼猪自己下载源码得倒的结论)
- 据说使用同步记录日志时log4net比Nlog要快,但是Nlog可以开启异步记录,并且速度会反超log4net
下面是楼主参考的一些博文
- log4net vs. Nlog [closed](骄傲的豹子)
- NLog 2.0.0.2000 使用实例 (回复部分为主要参考,主体是介绍Nlog的使用的)
- NLog文章系列——系列文章目录以及简要介绍(Nlog学习的参考)
测试代码:
<?xml version="1.0" encoding="utf-8" ?> <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true"> <targets> <target name="logfile" xsi:type="File" fileName="file.txt" layout="${longdate}|${level:uppercase=true}|${logger}|${event-context:item=EventId}|${message}|${ndc}" /> <target name="console" xsi:type="ColoredConsole" layout="[${level:uppercase=true}:${logger}] ${message}"/> </targets> <rules> <logger name="*" minlevel="Info" writeTo="logfile,console" /> </rules> </nlog>
public Program() { // a DI based application would get ILoggerFactory injected instead var factory = new LoggerFactory(); // getting the logger immediately using the class's name is conventional _logger = factory.CreateLogger(typeof(Program).FullName); // providers may be added to an ILoggerFactory at any time, existing ILoggers are updated #if !DNXCORE50 factory.AddNLog(new global::NLog.LogFactory()); #endif factory.AddConsole(); factory.AddConsole((category, logLevel) => logLevel >= LogLevel.Critical && category.Equals(typeof(Program).FullName)); }
Microsoft.Framework.Logging.Console
实际上用控制台直接写日志的并不多,所以这个日志系统可能用途并不广,不过精炼的代码还是有很多可圈可点的地方:使用IConsole接口以及LogConsole类。我们一般来说肯定只会实现ILogger、ILoggerProvider俩个接口,也就是写实现类ConsoleLogger、ConsoleLoggerProvider。但是我们主要到ConsoleLogger只是负责记录的逻辑操作,具体对于介质的操作属于IConsole的职责,如果将ConsoleLogger与LogConsole柔和在一起,问题也不大;但是明显违反了面向对象设计的单一职责原则。所以麻雀虽小,五脏俱全,这几个小类也能体现面向对象的能力的。
Microsoft.Framework.Logging.TraceSource
这个对于我来说是个陌生的东西,虽然是.net自带的,并且从1.0就有了。最开始使用的时Debug和Trace进行跟踪、记录,到.net2.0时,使用TraceSource作为Debug和Trace的增强版本。
TraceSource的配置:
- Listeners:控制跟踪信息输出的方向(可以是:TextWriterTraceListener,DefaultTraceListener,EventLogTraceListener,WebPageTraceListener等,而TextWriterTraceListener的子类又有ConsoleTraceListener, DelimitedListTraceListener,XmlWriterTraceListener,EventSchemaTraceListener)
- Switch:筛选信息的开关,主要包括BooleanSwitch 类、TraceSwitch 类和 SourceSwitch 类
主要参考的文献:
我们回头看Microsoft.Framework.Logging.TraceSource代码,也就一目了然了.
public class TraceSourceLoggerProvider : ILoggerProvider { private readonly SourceSwitch _rootSourceSwitch; private readonly TraceListener _rootTraceListener; private readonly ConcurrentDictionary<string, TraceSource> _sources = new ConcurrentDictionary<string, TraceSource>(StringComparer.OrdinalIgnoreCase); public TraceSourceLoggerProvider([NotNull]SourceSwitch rootSourceSwitch, [NotNull]TraceListener rootTraceListener) { _rootSourceSwitch = rootSourceSwitch; _rootTraceListener = rootTraceListener; } public ILogger CreateLogger(string name) { return new TraceSourceLogger(GetOrAddTraceSource(name)); } private TraceSource GetOrAddTraceSource(string name) { return _sources.GetOrAdd(name, InitializeTraceSource); } private TraceSource InitializeTraceSource(string traceSourceName) { var traceSource = new TraceSource(traceSourceName); string parentSourceName = ParentSourceName(traceSourceName); if (string.IsNullOrEmpty(parentSourceName)) { if (HasDefaultSwitch(traceSource)) { traceSource.Switch = _rootSourceSwitch; } if (_rootTraceListener != null) { traceSource.Listeners.Add(_rootTraceListener); } } else { if (HasDefaultListeners(traceSource)) { TraceSource parentTraceSource = GetOrAddTraceSource(parentSourceName); traceSource.Listeners.Clear(); traceSource.Listeners.AddRange(parentTraceSource.Listeners); } if (HasDefaultSwitch(traceSource)) { TraceSource parentTraceSource = GetOrAddTraceSource(parentSourceName); traceSource.Switch = parentTraceSource.Switch; } } return traceSource; } private static string ParentSourceName(string traceSourceName) { int indexOfLastDot = traceSourceName.LastIndexOf('.'); return indexOfLastDot == -1 ? null : traceSourceName.Substring(0, indexOfLastDot); } private static bool HasDefaultListeners(TraceSource traceSource) { return traceSource.Listeners.Count == 1 && traceSource.Listeners[0] is DefaultTraceListener; } private static bool HasDefaultSwitch(TraceSource traceSource) { return string.IsNullOrEmpty(traceSource.Switch.DisplayName) == string.IsNullOrEmpty(traceSource.Name) && traceSource.Switch.Level == SourceLevels.Off; } }
internal class TraceSourceLogger : ILogger { private readonly TraceSource _traceSource; public TraceSourceLogger(TraceSource traceSource) { _traceSource = traceSource; } public void Log(LogLevel logLevel, int eventId, object state, Exception exception, Func<object, Exception, string> formatter) { if (!IsEnabled(logLevel)) { return; } var message = string.Empty; if (formatter != null) { message = formatter(state, exception); } else { if (state != null) { message += state; } if (exception != null) { message += Environment.NewLine + exception; } } if (!string.IsNullOrEmpty(message)) { _traceSource.TraceEvent(GetEventType(logLevel), eventId, message); } } public bool IsEnabled(LogLevel logLevel) { var traceEventType = GetEventType(logLevel); return _traceSource.Switch.ShouldTrace(traceEventType); } private static TraceEventType GetEventType(LogLevel logLevel) { switch (logLevel) { case LogLevel.Critical: return TraceEventType.Critical; case LogLevel.Error: return TraceEventType.Error; case LogLevel.Warning: return TraceEventType.Warning; case LogLevel.Information: return TraceEventType.Information; case LogLevel.Verbose: default: return TraceEventType.Verbose; } } public IDisposable BeginScopeImpl(object state) { return new TraceSourceScope(state); } }
[Fact] public static void IsEnabledReturnsCorrectValue() { // Arrange var testSwitch = new SourceSwitch("TestSwitch", "Level will be set to warning for this test"); testSwitch.Level = SourceLevels.Warning; var factory = new LoggerFactory(); var logger = factory.CreateLogger("Test"); // Act factory.AddTraceSource(testSwitch, new ConsoleTraceListener()); // Assert Assert.True(logger.IsEnabled(LogLevel.Critical)); Assert.True(logger.IsEnabled(LogLevel.Error)); Assert.True(logger.IsEnabled(LogLevel.Warning)); Assert.False(logger.IsEnabled(LogLevel.Information)); Assert.False(logger.IsEnabled(LogLevel.Verbose)); } [Theory] [InlineData(SourceLevels.Warning, SourceLevels.Information, true)] [InlineData(SourceLevels.Information, SourceLevels.Information, true)] [InlineData(SourceLevels.Information, SourceLevels.Warning, true)] [InlineData(SourceLevels.Warning, SourceLevels.Warning, false)] public static void MultipleLoggers_IsEnabledReturnsCorrectValue(SourceLevels first, SourceLevels second, bool expected) { // Arrange var firstSwitch = new SourceSwitch("FirstSwitch", "First Test Switch"); firstSwitch.Level = first; var secondSwitch = new SourceSwitch("SecondSwitch", "Second Test Switch"); secondSwitch.Level = second; var factory = new LoggerFactory(); var logger = factory.CreateLogger("Test"); // Act factory.AddTraceSource(firstSwitch, new ConsoleTraceListener()); factory.AddTraceSource(secondSwitch, new ConsoleTraceListener()); // Assert Assert.Equal(expected, logger.IsEnabled(LogLevel.Information)); } }