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

    扩展知识篇

      

  • 相关阅读:
    LeetCode题解——冗余连接(并查集)——java实现
    两数之和的问题
    强引用、软引用、弱引用、虚引用——4中引用的理解
    手写死锁程序实例
    使用阻塞队列实现生产者消费者问题
    ABC三个线程交替打印10遍,要求A打印5次,B打印10次,C打印15次
    使用jstack查看线程情况解决cpu飙高问题
    ES 【elasticsearch】
    C# 正则
    领域驱动设计 浅析VO、DTO、DO、PO的概念、区别和用处等资料链接(草稿)
  • 原文地址:https://www.cnblogs.com/liuxiaoji/p/15247583.html
Copyright © 2020-2023  润新知