C# 如何最大限度地减少日志记录的影响

C# 如何最大限度地减少日志记录的影响,c#,performance,logging,C#,Performance,Logging,关于这一点已经有一个问题了,但它并没有告诉我我需要知道什么: 假设我有一个web应用程序,每次往返都有大量日志记录。我不想就为什么会有这么多日志记录,或者如何减少日志记录操作展开辩论。我想知道我有什么可能使这个日志问题性能和干净 到目前为止,我已经实现了声明式(基于属性)和命令式日志记录,这似乎是一种非常酷和干净的方法。。。现在,假设我可以预期那些日志花费的时间比预期的要长,那么我可以对性能做些什么打开一个线程并把工作留给它可以吗?我会考虑的事情: 使用高效的文件格式以尽量减少要写入的数据量(

关于这一点已经有一个问题了,但它并没有告诉我我需要知道什么: 假设我有一个web应用程序,每次往返都有大量日志记录。我不想就为什么会有这么多日志记录,或者如何减少日志记录操作展开辩论。我想知道我有什么可能使这个日志问题性能干净


到目前为止,我已经实现了声明式(基于属性)和命令式日志记录,这似乎是一种非常酷和干净的方法。。。现在,假设我可以预期那些日志花费的时间比预期的要长,那么我可以对性能做些什么打开一个线程并把工作留给它可以吗?

我会考虑的事情:

  • 使用高效的文件格式以尽量减少要写入的数据量(例如,XML和文本格式易于读取,但通常效率极低-相同的信息可以以二进制格式存储在更小的空间中)。但不要花费大量CPU时间试图“最佳”打包数据。只需选择一种简单的格式,它紧凑但编写速度快

  • 在日志上测试压缩的使用。快速SSD的情况可能并非如此,但在大多数I/O情况下,压缩数据的开销小于I/O开销,因此压缩带来了净收益(尽管这是一种折衷方案,即提高CPU使用率以降低I/O使用率)

  • 只记录有用的信息。无论你认为每件事都有多么重要,你都有可能找到一些可以删掉的东西

  • 消除重复数据。e、 g.您是否重复记录客户的IP地址或域名?是否可以为一个会话报告一次,然后不再重复?或者,您可以将它们存储在映射文件中,并在需要引用它们时使用压缩索引值吗?等

  • 测试在RAM中缓冲记录的数据是否有助于提高性能(例如,写入1000个20字节的日志记录将意味着1000个函数调用,可能会导致大量的磁盘查找和其他写入开销,而在一次突发中写入一个20000字节的块只意味着一次函数调用,可以显著提高性能,并最大限度地提高磁盘的突发速率)。通常情况下,以(4k、16k、32、64k)大小写入数据块效果良好,因为它往往适合磁盘和I/O体系结构(但请检查您的特定体系结构,以了解哪些大小可以提高效率)。RAM缓冲区的缺点是,如果断电,您将丢失更多数据。因此,您可能必须平衡性能和健壮性

  • (特别是在缓冲时…)将信息转储到内存中的数据结构中,并将其传递给另一个线程以将其流到磁盘。这将有助于阻止主线程被日志I/O所占用。不过,请注意线程。例如,当创建数据时,您可能需要考虑如何处理比短日志记录快的数据。您是否需要实现队列等

  • 您正在记录多个流吗?是否可以将这些流多路复用到单个日志中,以尽可能减少磁盘查找和打开的文件数

  • 是否有一种硬件解决方案可以让您的成本大幅度提高?例如,您是否使用了SSD或RAID磁盘?将数据转储到不同的服务器会有帮助还是会有阻碍?如果您可以花500美元升级磁盘,那么花10000美元的开发人员时间来提高某些性能可能并不总是很有意义的


    • 我会考虑的事情:

      • 使用高效的文件格式以尽量减少要写入的数据量(例如,XML和文本格式易于读取,但通常效率极低-相同的信息可以以二进制格式存储在更小的空间中)。但不要花费大量CPU时间尝试“最佳”打包数据.只需选择一种简洁但书写速度快的简单格式即可

      • 在日志上测试压缩的使用情况。快速SSD可能不是这种情况,但在大多数I/O情况下,压缩数据的开销小于I/O开销,因此压缩会带来净收益(尽管这是一种折衷方案,即提高CPU使用率以降低I/O使用率)

      • 只记录有用的信息。无论你认为每件事有多重要,你都有可能找到一些可以删掉的东西

      • 消除重复数据。例如,您是否重复记录客户端的IP地址或域名?这些数据是否可以在会话中报告一次,然后不再重复?或者您是否可以将它们存储在映射文件中,并在需要引用它们时使用压缩索引值?等等

      • 测试在RAM中缓冲记录的数据是否有助于提高性能(例如,写入1000个20字节的日志记录将意味着1000个函数调用,可能会导致大量的磁盘查找和其他写入开销,而在一次突发中写入一个20000字节的块只意味着一次函数调用,可以显著提高性能,并最大限度地提高磁盘的突发速率)。通常情况下,以(4k、16k、32、64k)大小写入数据块效果良好,因为它往往适合磁盘和I/O体系结构(但请检查您的特定体系结构,以了解哪些大小可以提高效率)。RAM缓冲区的缺点是,如果断电,您将丢失更多数据。因此,您可能必须平衡性能和健壮性

      • (特别是在缓冲时…)将信息转储到内存中的数据结构中,并将其传递给另一个线程以将其流到磁盘。这将有助于阻止主线程被日志I/O所占用。不过,请注意线程。例如,当创建数据时,您可能需要考虑如何处理比短日志记录快的数据。您是否需要实现队列等

      • 您正在记录多个流吗?Ca
        using System;
        using System.Collections.Concurrent;
        using System.Collections.Generic;
        using System.Linq;
        using System.Threading;
        using System.Windows.Forms;
        
        namespace FastLibrary
        {
            public enum Severity : byte
            {
                Info = 0,
                Error = 1,
                Debug = 2
            }
        
            public class Log
            {
                private struct LogMsg
                {
                    public DateTime ReportedOn;
                    public string Message;
                    public Severity Seriousness;
                }
        
                // Nice and Threadsafe Singleton Instance
                private static Log _instance;
        
                public static Log File
                {
                    get { return _instance; }
                }
        
                static Log()
                {
                    _instance = new Log();
                    _instance.Message("Started");
                    _instance.Start("");
                }
        
                 ~Log()
                {
                    Exit();
                }
        
                public static void Exit()
                {
                    if (_instance != null)
                    {
                        _instance.Message("Stopped");
                        _instance.Stop();
                        _instance = null;
                    }
                }
        
                private ConcurrentQueue<LogMsg> _queue = new ConcurrentQueue<LogMsg>();
                private Thread _thread;
                private string _logFileName;
                private volatile bool _isRunning;
        
                public void Message(string msg)
                {
                    _queue.Enqueue(new LogMsg { ReportedOn = DateTime.Now, Message = msg, Seriousness = Severity.Info });
                }
        
                public void Message(DateTime time, string msg)
                {
                    _queue.Enqueue(new LogMsg { ReportedOn = time, Message = msg, Seriousness = Severity.Info });
                }
        
                public void Message(Severity seriousness, string msg)
                {
                    _queue.Enqueue(new LogMsg { ReportedOn = DateTime.Now, Message = msg, Seriousness = seriousness });
                }
        
                public void Message(DateTime time, Severity seriousness, string msg)
                {
                    _queue.Enqueue(new LogMsg { ReportedOn = time, Message = msg, Seriousness = seriousness });
                }
        
                private void Start(string fileName = "", bool oneLogPerProcess = false)
                {
                    _isRunning = true;
                    // Unique FileName with date in it. And ProcessId so the same process running twice will log to different files
                    string lp = oneLogPerProcess ? "_" + System.Diagnostics.Process.GetCurrentProcess().Id : "";
                    _logFileName = fileName == ""
                                       ? DateTime.Now.Year.ToString("0000") + DateTime.Now.Month.ToString("00") +
                                         DateTime.Now.Day.ToString("00") + lp + "_" +
                                         System.IO.Path.GetFileNameWithoutExtension(Application.ExecutablePath) + ".log"
                                       : fileName;
                    _thread = new Thread(LogProcessor);
                    _thread.IsBackground = true;
                    _thread.Start();
                }
        
                public void Flush()
                {
                    EmptyQueue();
                }
        
                private void EmptyQueue()
                {
                    while (_queue.Any())
                    {
                        var strList = new List<string>();
                        //
                        try
                        {
                            // Block concurrent writing to file due to flush commands from other context
                            lock (_queue)
                            {
                                LogMsg l;
                                while (_queue.TryDequeue(out l)) strList.Add(l.ReportedOn.ToLongTimeString() + "|" + l.Seriousness + "|" + l.Message);
                                if (strList.Count > 0)
                                {
                                    System.IO.File.AppendAllLines(_logFileName, strList);
                                    strList.Clear();
                                }
                            }
                        }
                        catch
                        {
                            //ignore errors on errorlogging ;-)
                        }
                    }
                }
        
                public void LogProcessor()
                {
                    while (_isRunning)
                    {
                        EmptyQueue();
                        // Sleep while running so we write in efficient blocks
                        if (_isRunning) Thread.Sleep(2000);
                        else break;
                    }
                }
        
                private void Stop()
                {
                    // This is never called in the singleton.
                    // But we made it a background thread so all will be killed anyway
                    _isRunning = false;
                    if (_thread != null)
                    {
                        _thread.Join(5000);
                        _thread.Abort();
                        _thread = null;
                    }
                }
            }
        }                                                
        
        if (_logger.IsDebugEnabled) _logger.Debug($"slow old string {this.foo} {this.bar}");