0

我正在实现我自己的日志框架。以下是我的BaseLogger,它接收日志条目并将其推送到实现该abstract Log方法的实际 Logger。

我使用 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.0asyncawait关键字以及更简洁的代码。我不介意需要 .NET 4.5。

提前致谢。

4

3 回答 3

4

虽然您可能会使其工作,但根据我的经验,如果可能的话,我建议使用现有的日志框架:) 例如,使用 log4net 的异步日志/附加器有多种选项,例如这个异步附加器包装器 thingy .

否则,恕我直言,因为无论如何您将在日志记录操作期间阻塞线程池线程,我将改为为您的日志记录启动一个专用线程。您似乎已经开始采用这种方法,只需通过 Task ,这样您就不会在没有任何日志记录时持有线程池线程。但是,我认为实现的简化只有拥有专用线程才有好处。

一旦你有了一个专用的日志线程,你就只需要一个中间的ConcurrentQueue。那时,您的日志方法只是添加到队列中,而您的专用日志线程只是执行您已经拥有的 while 循环。如果需要阻塞/有界行为,可以使用BlockingCollection进行包装。

通过将专用线程作为唯一写入内容,它消除了多个线程/任务拉出队列条目并尝试同时写入日志条目的任何可能性(痛苦的竞争条件)。由于 log 方法现在只是添加到一个集合中,它不需要是异步的,你根本不需要处理 TPL,使其更简单、更容易推理(并且希望在 ' 的类别中显然是正确的'或附近:)

这种“专用日志记录线程”方法是我认为我链接到的 log4net appender 也可以做到的,FWIW,以防它作为示例。

于 2012-08-25T05:14:20.323 回答
3

我突然想到了两个比赛条件:

  1. 如果多个Thread线程调用AddLogEntry. 这不会导致丢失事件,但效率低下。
  2. 是的,一个事件可以在Thread退出时排队,在这种情况下它会“丢失”。

此外,这里还有一个严重的性能问题:除非您不断地记录(每秒数千次),否则您Thread将为每个日志条目旋转一个新条目。那会很快变得昂贵。

像 James 一样,我同意您应该使用已建立的日志库。日志记录并不像看起来那么简单,并且已经有很多解决方案。

也就是说,如果您想要一个基于 .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);
}
于 2012-08-25T10:43:17.963 回答
0

关于由于未处理的异常而导致应用程序迷恋的等待消息丢失,我已将处理程序绑定到 event 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();
    }
}

也许不是太干净,但有效。

于 2013-06-18T05:46:53.287 回答