[英]How to minimize Logging impact
關於這個問題已經存在一個問題,但是它並沒有告訴我我需要知道的內容:假設我有一個Web應用程序,並且每次往返都有很多日志記錄。 我不想就為何日志量如此之大或如何減少日志記錄操作展開辯論。 我想知道,我在為了使這個記錄問題高性能和清潔什么的可能性。
到目前為止,我已經實現了聲明式(基於屬性)和命令式日志,這似乎是一種很酷的方法……現在,假設我可以預期這些日志將花費更多的時間, 我該如何處理性能?超出預期。 是否可以打開線程並保留該工作?
我會考慮的事情:
使用有效的文件格式來最小化要寫入的數據量(例如,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阻塞。 但是請注意線程的使用-例如,您可能需要考慮如何處理創建數據的時間快於短突發記錄的時間-您是否需要實現隊列等?
您是否正在記錄多個流? 是否可以將它們多路復用到一個日志中,以減少磁盤搜索和打開文件的數量?
是否有硬件解決方案可以為您帶來巨大收益? 例如,您是否使用過SSD或RAID磁盤? 將數據轉儲到其他服務器會有所幫助還是受阻? 如果您可以花費500美元來簡單地升級磁盤,花10,000美元的開發人員時間來使某些性能更好可能並不總是很有意義。
我使用下面的代碼登錄。 它是一個單例,它接受日志記錄並將每條消息放入並發隊列。 每隔兩秒鍾,它將寫入其中的所有內容寫入磁盤。 現在,您的應用僅會延遲將每條消息放入列表所需的時間。 這是我自己的代碼,請隨時使用。
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.