简体   繁体   English

如何最大程度地减少日志记录影响

[英]How to minimize Logging impact

There's already a question about this issue , but it's not telling me what I need to know: Let's assume I have a web application, and there's a lot of logging on every roundtrip. 关于这个问题已经存在一个问题,但是它并没有告诉我我需要知道的内容:假设我有一个Web应用程序,并且每次往返都有很多日志记录。 I don't want to open a debate about why there's so much logging, or how can I do less loggin operations. 我不想就为何日志量如此之大或如何减少日志记录操作展开辩论。 I want to know what possibilities I have in order to make this logging issue performant and clean . 我想知道,我在为了使这个记录问题高性能清洁什么的可能性。

So far, I've implemented declarative (attribute based) and imperative logging, which seems to be a cool and clean way of doing it... now, what can I do about performance , assuming I can expect those logs to take more time than expected. 到目前为止,我已经实现了声明式(基于属性)和命令式日志,这似乎是一种很酷的方法……现在,假设我可以预期这些日志将花费更多的时间, 我该如何处理性能?超出预期。 Is it ok to open a thread and leave that work to it? 是否可以打开线程并保留该工作?

Things I would consider: 我会考虑的事情:

  • Use an efficient file format to minimise the quantity of data to be written (eg XML and text formats are easy to read but usually terribly inefficient - the same information can be stored in a binary format in a much smaller space). 使用有效的文件格式来最小化要写入的数据量(例如,XML和文本格式易于阅读,但通常效率极低-相同的信息可以二进制格式存储在较小的空间中)。 But don't spend lots of CPU time trying to pack data "optimally". 但是,不要花费大量CPU时间尝试“最佳”打包数据。 Just go for a simple format that is compact but fast to write. 只需选择一种紧凑但可以快速编写的简单格式。

  • Test use of compression on the logs. 在日志上测试压缩的使用。 This may not be the case with a fast SSD but in most I/O situations the overhead of compressing data is less than the I/O overhead, so compression gives a net gain (although it is a compromise - raising CPU usage to lower I/O usage). 快速SSD可能不是这种情况,但在大多数I / O情况下,压缩数据的开销小于I / O开销,因此压缩可带来净收益(尽管这是一个折衷方案-提高CPU使用率以降低I / O用法)。

  • Only log useful information. 仅记录有用的信息。 No matter how important you think everything is, it's likely you can find something to cut out. 无论您认为一切有多么重要,都有可能找到一些切入点。

  • Eliminate repeated data. 消除重复的数据。 eg Are you logging a client's IP address or domain name repeatedly? 例如,您是否反复记录客户端的IP地址或域名? Can these be reported once for a session and then not repeated? 可以为一个会话报告一次,然后不再重复吗? Or can you store them in a map file and use a compact index value whenever you need to reference them? 还是可以将它们存储在地图文件中,并在需要引用它们时使用紧凑的索引值? etc 等等

  • Test whether buffering the logged data in RAM helps improve performance (eg writing a thousand 20 byte log records will mean 1,000 function calls and could cause a lot of disk seeking and other write overheads, while writing a single 20,000 byte block in one burst means only one function call and could give significant performance increase and maximise the burst rate you get out to disk). 测试将记录的数据缓存在RAM中是否有助于提高性能(例如,写入一千个20字节的日志记录将意味着1,000个函数调用,并且可能会导致大量磁盘搜索和其他写入开销,而在一个突发中写入单个20,000字节的块仅意味着一个函数调用,可以显着提高性能,并最大程度地提高您进入磁盘的突发速率。 Often writing blocks in sizes like (4k, 16k, 32, 64k) of data works well as it tends to fit the disk and I/O architecture (but check your specific architecture for clues about what sizes might improve efficiency). 通常,写大小为(4k,16k,32、64k)的数据块效果很好,因为它倾向于适合磁盘和I / O架构(但请检查您的特定架构,以获取有关哪种尺寸可能会提高效率的线索)。 The down side of a RAM buffer is that if there is a power outage you will lose more data. RAM缓冲区的缺点是,如果断电,您将丢失更多数据。 So you may have to balance performance against robustness. 因此,您可能必须在性能与健壮性之间取得平衡。

  • (Especially if you are buffering...) Dump the information to an in-memory data structure and pass it to another thread to stream it out to disk. (特别是如果正在缓冲...),将信息转储到内存中的数据结构中,并将其传递给另一个线程,以将其流式传输到磁盘。 This will help stop your primary thread being held up by log I/O. 这将有助于防止您的主线程被日志I / O阻塞。 Take care with threads though - for example, you may have to consider how you will deal with times when you are creating data faster than it can be logged for short bursts - do you need to implement a queue, etc? 但是请注意线程的使用-例如,您可能需要考虑如何处理创建数据的时间快于短突发记录的时间-您是否需要实现队列等?

  • Are you logging multiple streams? 您是否正在记录多个流? Can these be multiplexed into a single log to possibly reduce disk seeking and the number of open files? 是否可以将它们多路复用到一个日志中,以减少磁盘搜索和打开文件的数量?

  • Is there a hardware solution that will give a large bang for your buck? 是否有硬件解决方案可以为您带来巨大收益? eg Have you used SSD or RAID disks? 例如,您是否使用过SSD或RAID磁盘? Will dumping the data to a different server help or hinder? 将数据转储到其他服务器会有所帮助还是受阻? It may not always make much sense to spend $10,000 of developer time making something perform better if you can spend $500 to simply upgrade the disk. 如果您可以花费500美元来简单地升级磁盘,花10,000美元的开发人员时间来使某些性能更好可能并不总是很有意义。

I use the code below to Log. 我使用下面的代码登录。 It is a singleton that accepts Logging and puts every message into a concurrentqueue. 它是一个单例,它接受日志记录并将每条消息放入并发队列。 Every two seconds it writes all that has come in to the disk. 每隔两秒钟,它将写入其中的所有内容写入磁盘。 Your app is now only delayed by the time it takes to put every message in the list. 现在,您的应用仅会延迟将每条消息放入列表所需的时间。 It's my own code, feel free to use it. 这是我自己的代码,请随时使用。

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;
            }
        }
    }
}                                                

在调用logger.debug之前,请检查记录器是否已启用调试功能,这意味着在关闭调试时,您的代码不必评估消息字符串。

if (_logger.IsDebugEnabled) _logger.Debug($"slow old string {this.foo} {this.bar}");

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM