C# 如何最大限度地减少日志记录的影响
关于这一点已经有一个问题了,但它并没有告诉我我需要知道什么: 假设我有一个web应用程序,每次往返都有大量日志记录。我不想就为什么会有这么多日志记录,或者如何减少日志记录操作展开辩论。我想知道我有什么可能使这个日志问题性能和干净C# 如何最大限度地减少日志记录的影响,c#,performance,logging,C#,Performance,Logging,关于这一点已经有一个问题了,但它并没有告诉我我需要知道什么: 假设我有一个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}");
- 我会考虑的事情: