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是将文件资源常驻内存的方式写入 优点是写入效率快 缺点是内存一直被占用 如果文件意外被删除 在不重启程序的情况下不会在进行日志写入

 

扩展知识篇

 

 

 

 

  

posted @ 2021-09-09 16:26  刘小吉  阅读(4069)  评论(0编辑  收藏  举报