关于这个issue已经存在一个问题,但它没有告诉我需要知道的事情: 假设我有一个Web应用程序,并且每次往返都有很多日志记录。我不想打开关于为什么这么多日志记录的争论,或者我怎样才能减少登录操作。我想知道我有什么可能性来解决这个日志问题高性能和干净。
到目前为止,我已经实现了声明式(基于属性)和命令式日志记录,这似乎是一种很酷且干净的方式...现在,我该如何处理性能,假设我可以预期这些日志比预期花费更多时间。 打开一个帖子并将其保留给它可以吗?
答案 0 :(得分:2)
我会考虑的事情:
使用有效的文件格式来最小化要写入的数据量(例如,XML和文本格式易于阅读但通常非常低效 - 相同的信息可以在更小的空间中以二进制格式存储)。但是,不要花费大量的CPU时间来“最佳地”打包数据。只需要一个紧凑但写得快的简单格式。
在日志上测试使用压缩。对于快速SSD可能不是这种情况,但在大多数I / O情况下,压缩数据的开销小于I / O开销,因此压缩会产生净增益(尽管这是一种妥协 - 提高CPU使用率以降低I / O用法)。
仅记录有用信息。无论你认为一切都是多么重要,你很可能会找到一些可以删除的东西。
消除重复数据。例如您是否重复记录客户端的IP地址或域名?这些可以报告一次,然后不重复吗?或者,您可以将它们存储在地图文件中,并在需要引用时使用紧凑索引值吗?等
测试缓冲RAM中记录的数据是否有助于提高性能(例如,写入一千个20字节的日志记录将意味着1,000个函数调用,并且可能导致大量磁盘搜索和其他写入开销,同时写入单个20,000字节一次突发中的块意味着只有一个函数调用,并且可以显着提高性能,并最大化您获得磁盘的突发速率)。通常编写像(4k,16k,32,64k)这样的数据块的块很有效,因为它往往适合磁盘和I / O体系结构(但请检查您的特定体系结构,以获取有关哪些大小可能提高效率的线索)。 RAM缓冲区的缺点是,如果断电,您将丢失更多数据。因此,您可能必须在性能与稳健性之间取得平衡。
(特别是如果你正在缓冲...)将信息转储到内存数据结构并将其传递给另一个线程以将其流式传输到磁盘。这将有助于阻止主线程被日志I / O阻止。尽管如此,请注意线程 - 例如,您可能必须考虑如何处理创建数据的时间,而不是短时间记录的数据 - 您是否需要实现队列等?
< / LI>您是否记录了多个流?这些可以多路复用到一个日志中,以减少磁盘搜索和打开文件的数量吗?
是否有硬件解决方案可以为您带来巨大的收益?例如你使用过SSD或RAID磁盘吗?将数据转储到不同的服务器有帮助还是阻碍?如果花费500美元来简单地升级磁盘,花费10,000美元的开发人员时间让某些东西表现更好可能并不总是有意义。
答案 1 :(得分:2)
我使用下面的代码来记录。它是一个接受Logging并将每条消息放入并发队列的单例。它每两秒写一次所有进入磁盘的内容。您的应用现在只延迟了将每条消息放入列表所需的时间。这是我自己的代码,随意使用它。
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;
}
}
}
}
答案 2 :(得分:0)
在调用logger.debug之前检查记录器是否已启用调试,这意味着在关闭调试时,代码不必评估消息字符串。
if (_logger.IsDebugEnabled) _logger.Debug($"slow old string {this.foo} {this.bar}");