2013-03-25 78 views
0

已經有一個關於這個issue的問題,但它並不告訴我我需要知道什麼: 假設我有一個web應用程序,並且每次往返都有很多日誌記錄。我不想打開關於的辯論爲什麼有這麼多的日誌記錄,或者我該如何做更少的登錄操作。我想知道爲了使日誌記錄問題有什麼可能性高性能乾淨如何最大限度地減少對日誌的影響

到目前爲止,我已經實現了聲明(基於屬性)和必要的記錄,這似乎是做...現在的涼爽和清潔的方式,我能做些什麼有關性能,假設我可以期待那些日誌需要比預期更多的時間。 可以打開一個線程並將其保留下來嗎?

+0

不是說這是一個足夠明確的答案,但我知道如果你做日誌記錄,你應該通過網絡註銷到你的Web應用程序不在的驅動器上。因爲那麼性能不會受到日誌被寫入webapp運行的同一個驅動器的阻礙。 – leeand00 2013-03-25 20:09:36

+0

@ leeand00這是無稽之談。 – 2013-03-25 20:17:12

+1

你可以使用像Gurock Smartinspect之類的東西,它允許跨管道記錄,tcp以及文件記錄。它也允許異步記錄並且易於使用。 http://www.gurock.com/smartinspect/ – 2013-03-25 20:17:21

回答

1

我使用下面的代碼來記錄。它是一個接受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; 
      } 
     } 
    } 
}             
+0

我將使用擴展方法來「覆蓋」日誌記錄並使用該併發隊列技術,如果它獲得更好的時間,我會讓你知道,但這正是我所尋找的,感謝代碼示例。 – 2013-03-31 23:13:19

2

事情我會考慮:

  • 使用一個有效的文件格式,以儘量減少數據量寫入(如XML和文本格式很容易閱讀,但通常非常低效的 - 同樣的信息以更小的空間以二進制格式存儲)。但是,不要花費大量的CPU時間試圖「優化」打包數據。只需要一個簡潔的格式,但它很緊湊,但寫起來很快。

  • 在日誌上測試壓縮的使用情況。這可能不是快速SSD的情況,但在大多數I/O情況下,壓縮數據的開銷小於I/O開銷,因此壓縮會帶來淨增益(儘管這是一種折衷方案 - 提高CPU使用率以降低I/O用法)。

  • 只記錄有用信息。不管你認爲一切都很重要,你很可能會找到一些切斷的東西。

  • 消除重複的數據。例如您是否重複記錄客戶的IP地址或域名?這些可以報告一次會議,然後不再重複?或者,您是否可以將它們存儲在地圖文件中並在需要引用時使用緊湊索引值? etc

  • 測試是否緩存RAM中記錄的數據有助於提高性能(例如,編寫一千個20字節的日誌記錄將意味着1,000個函數調用,並且可能導致大量磁盤搜尋和其他寫入開銷,同時寫入一個20,000字節在一次突發中阻塞意味着只有一個函數調用,並且可以顯着提高性能並最大化您獲取到磁盤的突發速率)。通常將數據塊寫入像(4k,16k,32,64k)數據這樣的大小可以很好地適用於磁盤和I/O體系結構(但請檢查特定體系結構以瞭解哪些大小可以提高效率)。 RAM緩衝區的另一面是,如果發生停電,您將丟失更多數據。所以你可能不得不平衡性能與健壯性。

  • (特別是如果你正在緩衝...)將信息轉儲到內存中的數據結構,並將它傳遞給另一個線程,以便將其流出到磁盤。這將有助於阻止您的主線程被日誌I/O阻止。但請注意線程 - 例如,您可能不得不考慮在創建數據的時候如何處理時間,而不是記錄短的突發數據 - 您是否需要實現隊列等?

  • 你記錄多個流?可以將它們多路複用到一個日誌中以減少磁盤搜索和打開文件的數量?

  • 有沒有一種硬件解決方案可以爲您的降壓帶來巨大的轟動?例如你使用過SSD還是RAID磁盤?將數據轉儲到不同的服務器幫助還是阻礙?如果花費500美元來簡單地升級磁盤,花費10000美元的開發人員時間使性能更好,但並不總是很有意義。

+0

需要注意的一點是,如果在時間敏感的操作中使用RAM緩衝區,則在某些時候緩衝區必須刷新到磁盤,這可能是一個昂貴的操作。在開始時間敏感操作之前,您可以考慮刷新緩衝區,以避免緩衝區變滿並在操作期間強制刷新。 – 2013-03-25 20:40:56

+0

日誌記錄調用後面有日誌文件和Windows事件日誌。我對打開線程和排隊調用感興趣。你認爲這可能是一個很好的解決方案嗎?我基本上想把這個過程從主線程中解放出來。你有關於這個「日誌隊列」stuf的鏈接嗎? – 2013-03-25 20:41:00

+0

@Denise:是的,最好避免小型和大型緩衝區,因爲這兩個極端都可能導致性能問題。 4k-64k通常都是很好的尺寸。 – 2013-03-25 23:00:08

相關問題