Nlog 和 Serilog 比较
安装nuget包
NLog安装
"NLog.Extensions.Logging"
包含 "NLog"
Serilog
"Serilog.AspNetCore"
包含"Serilog" ,"Serilog.Extensions.Hosting", "Serilog.Formatting.Compact", "Serilog.Settings.Configuration", "Serilog.Sinks.Console", "Serilog.Sinks.Debug", "Serilog.Sinks.File"
"Serilog.Sinks.Async"
包含""Serilog"
注入容器和配置文件
NLog
private static void ConfigureLogging(ServiceConfigurationContext context) { // 添加日志 context.Services.AddLogging(logBuilder => { logBuilder.AddNLog("NLog.config"); }); }
配置文件 NLog.config
示例模版展示 详情配置官网 : https://nlog-project.org/config/
nlog 基于layout配置日志内容格式
<?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"> <targets async="true"> <target name="asyncFile" xsi:type="File" layout="[${longdate}] [${level}] [${logger}] [${message}] ${newline} ${exception:format=tostring}" fileName="${basedir}/Logs/${shortdate}.log" archiveFileName="${basedir}/Logs/archives/log.{#####}.log" archiveAboveSize="102400000" archiveNumbering="Sequence" concurrentWrites="true" keepFileOpen="false" encoding="utf-8" /> <target name="console" xsi:type="console"/> </targets> <rules> <!--Info,Error,Warn,Debug,Fatal--> <logger name="*" levels="Info,Error,Warn,Debug,Fatal" writeTo="asyncFile" /> <logger name="*" minlevel="Error" writeTo="console" /> </rules> </nlog>
Serilog
internal static IHostBuilder CreateHostBuilder(string[] args) { return Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }) .UseAutofac() .UseSerilog((ctx, config) => { var logFilePath = $"{AppContext.BaseDirectory}Logs/log.log"; config.ReadFrom.Configuration(ctx.Configuration); config.WriteTo.Async(c => c.File(logFilePath, rollingInterval: RollingInterval.Day)); }); }
官方未提供对应的配置文件 上面均以代码的方式注入配置
具体的配置均以File()方法配置注入
serilog 也有类似的 layout 配置日志格式
public static LoggerConfiguration File( this LoggerSinkConfiguration sinkConfiguration, string path, LogEventLevel restrictedToMinimumLevel = LogEventLevel.Verbose, string outputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}", IFormatProvider formatProvider = null, long? fileSizeLimitBytes = 1073741824, LoggingLevelSwitch levelSwitch = null, bool buffered = false, bool shared = false, TimeSpan? flushToDiskInterval = null, RollingInterval rollingInterval = RollingInterval.Infinite, bool rollOnFileSizeLimit = false, int? retainedFileCountLimit = 31, Encoding encoding = null) { if (sinkConfiguration == null) throw new ArgumentNullException(nameof (sinkConfiguration)); if (path == null) throw new ArgumentNullException(nameof (path)); MessageTemplateTextFormatter templateTextFormatter = outputTemplate != null ? new MessageTemplateTextFormatter(outputTemplate, formatProvider) : throw new ArgumentNullException(nameof (outputTemplate)); return sinkConfiguration.File((ITextFormatter) templateTextFormatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, levelSwitch, buffered, shared, flushToDiskInterval, rollingInterval, rollOnFileSizeLimit, retainedFileCountLimit, encoding); }
outputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}"
从源码分析可知道serilog的格式是通过输出模板而来
具体配置可参考官网: https://serilog.net/
最后在比对一下File文件写入形式
Nlog源码分析 实现代码在 NLog 包中
protected override void InitializeTarget() { base.InitializeTarget(); var appenderFactory = GetFileAppenderFactory(); if (InternalLogger.IsTraceEnabled) { InternalLogger.Trace("{0}: Using appenderFactory: {1}", this, appenderFactory.GetType()); } _fileAppenderCache = new FileAppenderCache(OpenFileCacheSize, appenderFactory, this); if ((OpenFileCacheSize > 0 || EnableFileDelete) && (OpenFileCacheTimeout > 0 || OpenFileFlushTimeout > 0)) { int openFileAutoTimeoutSecs = (OpenFileCacheTimeout > 0 && OpenFileFlushTimeout > 0) ? Math.Min(OpenFileCacheTimeout, OpenFileFlushTimeout) : Math.Max(OpenFileCacheTimeout, OpenFileFlushTimeout); InternalLogger.Trace("{0}: Start autoClosingTimer", this); _autoClosingTimer = new Timer( (state) => AutoClosingTimerCallback(this, EventArgs.Empty), null, openFileAutoTimeoutSecs * 1000, openFileAutoTimeoutSecs * 1000); } }
此源吗表示NLog 使用了一个计时器的方式来做一个异步写入
FileTarget.cs 写入到文件 实现类
具体实现方法:
protected override void Write(LogEventInfo logEvent) { var logFileName = GetFullFileName(logEvent); if (string.IsNullOrEmpty(logFileName)) { throw new ArgumentException("The path is not of a legal form."); } using (var targetStream = _reusableFileWriteStream.Allocate()) { using (var targetBuilder = ReusableLayoutBuilder.Allocate()) using (var targetBuffer = _reusableEncodingBuffer.Allocate()) { RenderFormattedMessageToStream(logEvent, targetBuilder.Result, targetBuffer.Result, targetStream.Result); } ProcessLogEvent(logEvent, logFileName, new ArraySegment<byte>(targetStream.Result.GetBuffer(), 0, (int)targetStream.Result.Length)); } }
这里使用了 Using 来释放资源
Serilog
实现方法类在Serilog.Sinks.File这个包中
RollingFileSink实现类分析
写入方法
public void Emit(LogEvent logEvent) { if (logEvent == null) throw new ArgumentNullException(nameof(logEvent)); lock (_syncRoot) { if (_isDisposed) throw new ObjectDisposedException("The log file has been disposed."); var now = Clock.DateTimeNow; AlignCurrentFileTo(now); while (_currentFile?.EmitOrOverflow(logEvent) == false && _rollOnFileSizeLimit) { AlignCurrentFileTo(now, nextSequence: true); } } }
找到当前文件
void AlignCurrentFileTo(DateTime now, bool nextSequence = false) { if (!_nextCheckpoint.HasValue) { OpenFile(now); } else if (nextSequence || now >= _nextCheckpoint.Value) { int? minSequence = null; if (nextSequence) { if (_currentFileSequence == null) minSequence = 1; else minSequence = _currentFileSequence.Value + 1; } CloseFile(); OpenFile(now, minSequence); }
}
打开文件
void OpenFile(DateTime now, int? minSequence = null) { var currentCheckpoint = _roller.GetCurrentCheckpoint(now); // We only try periodically because repeated failures // to open log files REALLY slow an app down. _nextCheckpoint = _roller.GetNextCheckpoint(now) ?? now.AddMinutes(30); var existingFiles = Enumerable.Empty<string>(); try { if (Directory.Exists(_roller.LogFileDirectory)) { existingFiles = Directory.GetFiles(_roller.LogFileDirectory, _roller.DirectorySearchPattern) .Select(f => Path.GetFileName(f)); } } catch (DirectoryNotFoundException) { } var latestForThisCheckpoint = _roller .SelectMatches(existingFiles) .Where(m => m.DateTime == currentCheckpoint) .OrderByDescending(m => m.SequenceNumber) .FirstOrDefault(); var sequence = latestForThisCheckpoint?.SequenceNumber; if (minSequence != null) { if (sequence == null || sequence.Value < minSequence.Value) sequence = minSequence; } const int maxAttempts = 3; for (var attempt = 0; attempt < maxAttempts; attempt++) { _roller.GetLogFilePath(now, sequence, out var path); try { _currentFile = _shared ? #pragma warning disable 618 (IFileSink)new SharedFileSink(path, _textFormatter, _fileSizeLimitBytes, _encoding) : #pragma warning restore 618 new FileSink(path, _textFormatter, _fileSizeLimitBytes, _encoding, _buffered, _hooks); _currentFileSequence = sequence; } catch (IOException ex) { if (IOErrors.IsLockedFile(ex)) { SelfLog.WriteLine("File target {0} was locked, attempting to open next in sequence (attempt {1})", path, attempt + 1); sequence = (sequence ?? 0) + 1; continue; } throw; } ApplyRetentionPolicy(path, now); return; } }
SharedFileSink方式写入
地开文件方式
public SharedFileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBytes, Encoding? encoding = null) { if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 1) throw new ArgumentException("Invalid value provided; file size limit must be at least 1 byte, or null"); _path = path ?? throw new ArgumentNullException(nameof(path)); _textFormatter = textFormatter ?? throw new ArgumentNullException(nameof(textFormatter)); _fileSizeLimitBytes = fileSizeLimitBytes; var directory = Path.GetDirectoryName(path); if (!string.IsNullOrWhiteSpace(directory) && !Directory.Exists(directory)) { Directory.CreateDirectory(directory); } // FileSystemRights.AppendData sets the Win32 FILE_APPEND_DATA flag. On Linux this is O_APPEND, but that API is not yet // exposed by .NET Core. _fileOutput = new FileStream( path, FileMode.Append, FileSystemRights.AppendData, FileShare.ReadWrite, _fileStreamBufferLength, FileOptions.None); _writeBuffer = new MemoryStream(); _output = new StreamWriter(_writeBuffer, encoding ?? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); }
可以看到并未使用using 释放资源
bool IFileSink.EmitOrOverflow(LogEvent logEvent) { if (logEvent == null) throw new ArgumentNullException(nameof(logEvent)); lock (_syncRoot) { try { _textFormatter.Format(logEvent, _output); _output.Flush(); var bytes = _writeBuffer.GetBuffer(); var length = (int) _writeBuffer.Length; if (length > _fileStreamBufferLength) { var oldOutput = _fileOutput; _fileOutput = new FileStream( _path, FileMode.Append, FileSystemRights.AppendData, FileShare.ReadWrite, length, FileOptions.None); _fileStreamBufferLength = length; oldOutput.Dispose(); } if (_fileSizeLimitBytes != null) { try { if (_fileOutput.Length >= _fileSizeLimitBytes.Value) return false; } catch (FileNotFoundException) { } // Cheaper and more reliable than checking existence } _fileOutput.Write(bytes, 0, length); _fileOutput.Flush(); return true; } catch { // Make sure there's no leftover cruft in there. _output.Flush(); throw; } finally { _writeBuffer.Position = 0; _writeBuffer.SetLength(0); } } }
最终实现 可以看出只是将文件流缓存区清空了
FileSink 方式写入
打开文件方式
internal FileSink( string path, ITextFormatter textFormatter, long? fileSizeLimitBytes, Encoding? encoding, bool buffered, FileLifecycleHooks? hooks) { if (path == null) throw new ArgumentNullException(nameof(path)); if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 1) throw new ArgumentException("Invalid value provided; file size limit must be at least 1 byte, or null."); _textFormatter = textFormatter ?? throw new ArgumentNullException(nameof(textFormatter)); _fileSizeLimitBytes = fileSizeLimitBytes; _buffered = buffered; var directory = Path.GetDirectoryName(path); if (!string.IsNullOrWhiteSpace(directory) && !Directory.Exists(directory)) { Directory.CreateDirectory(directory); } Stream outputStream = _underlyingStream = System.IO.File.Open(path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read); outputStream.Seek(0, SeekOrigin.End); if (_fileSizeLimitBytes != null) { outputStream = _countingStreamWrapper = new WriteCountingStream(_underlyingStream); } // Parameter reassignment. encoding = encoding ?? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); if (hooks != null) { outputStream = hooks.OnFileOpened(path, outputStream, encoding) ?? throw new InvalidOperationException($"The file lifecycle hook `{nameof(FileLifecycleHooks.OnFileOpened)}(...)` returned `null`."); } _output = new StreamWriter(outputStream, encoding); }
也可以看到并未使用using 释放资源
最终实现
bool IFileSink.EmitOrOverflow(LogEvent logEvent) { if (logEvent == null) throw new ArgumentNullException(nameof(logEvent)); lock (_syncRoot) { if (_fileSizeLimitBytes != null) { if (_countingStreamWrapper!.CountedLength >= _fileSizeLimitBytes.Value) return false; } _textFormatter.Format(logEvent, _output); if (!_buffered) _output.Flush(); return true; } }
也能看到只是清空了缓冲区
总结
通过两种组件的最大区别就在于文件写入到磁盘的处理上面的区别
1.Nlog是写完立即释放 优点是 不会占用多余资源 每次写完既释放 缺是IO操作消耗高为了解决这个问题 Nlog 使用了 时间控件 采用 定时 定量写入策略
2.serilog是将文件资源常驻内存的方式写入 优点是写入效率快 缺点是内存一直被占用 如果文件意外被删除 在不重启程序的情况下不会在进行日志写入
扩展知识篇