2012-08-25 72 views
0

我正在實現我自己的日誌框架。以下是我的BaseLogger,它接收日誌條目並將其推送到實施abstract Log方法的實際記錄器。異步記錄器。我可以丟失/延遲日誌條目嗎?

我使用C#TPL用於以異步方式記錄。 我使用線程而不是TPL。 (TPL任務不成立一個真正的線程。因此,如果應用端的所有線程,任務將停止爲好,這將導致失去了所有的「等待」的日誌條目。)

public abstract class BaseLogger 
{ 
    // ... Omitted properties constructor .etc. ... // 

public virtual void AddLogEntry(LogEntry entry) 
{ 
    if (!AsyncSupported) 
    { 
     // the underlying logger doesn't support Async. 
     // Simply call the log method and return. 
     Log(entry); 
     return; 
    } 
    // Logger supports Async. 
    LogAsync(entry); 
} 

private void LogAsync(LogEntry entry) 
{ 
    lock (LogQueueSyncRoot) // Make sure we ave a lock before accessing the queue. 
    { 
     LogQueue.Enqueue(entry); 
    } 

    if (LogThread == null || LogThread.ThreadState == ThreadState.Stopped) 
    { // either the thread is completed, or this is the first time we're logging to this logger. 
     LogTask = new new Thread(new ThreadStart(() => 
     { 
      while (true) 
      { 
       LogEntry logEntry; 
       lock (LogQueueSyncRoot) 
       { 
        if (LogQueue.Count > 0) 
        { 
         logEntry = LogQueue.Dequeue(); 
        } 
        else 
        { 
         break; 
         // is it possible for a message to be added, 
         // right after the break and I leanve the lock {} but 
         // before I exit the loop and task gets 'completed' ?? 
        } 
       } 
       Log(logEntry); 
      } 
     })); 
     LogThread.Start(); 
    } 
} 

// Actual logger implimentations will impliment this method. 
protected abstract void Log(LogEntry entry); 
} 

注意AddLogEntry可以同時從多個線程中調用。

我的問題是,這個實現可能會丟失日誌條目嗎? 我很擔心,是否有可能在隊列中添加一個日誌條目,在我的線程存在與break語句的循環並退出鎖定塊之後,以及哪個位於else子句中,並且線程仍處於'運行'狀態。

我也知道,因爲我使用的是隊列,即使我錯過一個條目,下一個請求登錄,將推動無緣進入爲好。但這是不可接受的,特別是如果這發生在應用程序的最後一個日誌條目中。

另外,請讓我知道我是否以及如何能夠實現相同的,但使用新的C#5.0 asyncawait關鍵字一個更乾淨的代碼。我不介意需要.NET 4.5。

在此先感謝。

+0

啊...擰緊它。這是午夜。我刮這個和使用NLog XD ...感謝大家的幫助。 – Madushan

回答

3

我看到兩個比賽條件了我的頭頂部:

  1. 您可以旋轉起來不止一個Thread如果多個線程調用AddLogEntry。這不會導致事件丟失,但效率不高。
  2. 是的,當Thread正在退出時,事件可以排隊,在這種情況下,它會「丟失」。

而且,這裏有一個嚴重的性能問題:除非你經常登錄(上千次),,你會爲每個日誌條目的可紡了一個新的Thread。這將很快變得昂貴。

和詹姆斯一樣,我同意你應該使用一個已建立的日誌庫。記錄並不像看起來那麼簡單,而且已經有很多解決方案。

也就是說,如果你想要一個漂亮的基於.NET 4.5的方法,這是很容易:

public abstract class BaseLogger 
{ 
    private readonly ActionBlock<LogEntry> block; 

    protected BaseLogger(int maxDegreeOfParallelism = 1) 
    { 
    block = new ActionBlock<LogEntry>(
     entry => 
     { 
      Log(entry); 
     }, 
     new ExecutionDataflowBlockOptions 
     { 
      MaxDegreeOfParallelism = maxDegreeOfParallelism, 
     }); 
    } 

    public virtual void AddLogEntry(LogEntry entry) 
    { 
    block.Post(entry); 
    } 

    protected abstract void Log(LogEntry entry); 
} 
4

雖然你可能會得到這個工作,但根據我的經驗,如果可能,我建議,如果可能的話,使用現有的日誌記錄框架:)例如,有不同的log4net日誌/ appender選項,如this async appender wrapper thingy

否則,恕我直言,因爲你會被你的記錄操作過程中阻止反正線程池線程,我反而剛開始爲您記錄一個專門的線程。你似乎已經習慣了這種方法,只需通過Task,在沒有任何日誌記錄的情況下你不會佔用線程池線程。但是,實現中的簡化我認爲只需要專用線程就可以帶來好處。

一旦你有一個專門記錄線程,那麼你只需要有一箇中間ConcurrentQueue。在這一點上,你的日誌方法只是添加到隊列中,而你的專用日誌線程只是在你已經擁有的循環中進行。如果您需要阻止/限制行爲,則可以使用BlockingCollection進行換行。

通過具有專用線程中寫到的唯一的東西,它消除了多線程/任務脫下隊列的條目,並試圖寫在同一時間(痛苦的比賽條件)日誌條目的任何可能性。由於log方法現在只是添加到一個集合中,所以它不需要是異步的,並且你根本不需要處理TPL,這使得它更簡單和更容易推理(並且希望在'顯然是正確的「或大約:)

這種」專用日誌記錄線程「方法是我認爲我鏈接到的log4net appender也可以做到FWIW,以防有助於作爲示例。

+0

那麼....是否有可能讓我的執行錯過日誌條目?以上述方式,在「休息」和任務完成之間? – Madushan

+0

請注意..我更新了代碼。我按照你的建議使用線程,因爲它會在應用程序結束時變得討厭並失去「等待」條目。 – Madushan

0

關於對因爲未處理的異常的應用美眉失去等待的消息,我已經綁定一個處理程序事件AppDomain.CurrentDomain.DomainUnload。像這樣:

protected ManualResetEvent flushing = new ManualResetEvent(true); 
protected AsyncLogger() // ctor of logger 
{ 
    AppDomain.CurrentDomain.DomainUnload += CurrentDomain_DomainUnload; 
} 

protected void CurrentDomain_DomainUnload(object sender, EventArgs e) 
{ 
    if (!IsEmpty) 
    { 
     flushing.WaitOne(); 
    } 
} 

也許不是太乾淨,但工程。