记录框架和多线程兼容性

wag*_*ghe 1 .net logging log4net multithreading nlog

请注意,我在SO上看到了这些问题(讨论了log4net的线程安全性),我怀疑他们回答了我的问题,但我还是会问:

多线程安全日志记录

Log4Net FileAppender不是线程安全的吗?

最近我写了一个用于日志记录的WCF服务.这个想法非常类似于Clog(或者在Calcium下寻找Clog).基本上,我已经实现了一个用于Silverlight客户端(Silverlight类库)的日志API.日志记录API或多或少是我们在应用程序中的其他地方使用的Common.Logging for .NET API的克隆.API的实现将所有日志记录消息转发到WCF日志记录服务,该服务本身是根据Common.Logging实现的.

在看Clog的时候,我在Log4NetStrategy课堂上发现了以下代码让我觉得有些奇怪:

/// <summary> 
/// All Write log calls are done asynchronously because the DanielVaughan.Logging.Log 
/// uses the AppPool to dispatch calls to this method. Therefore we need to ensure 
/// that the call to Log is threadsafe. That is, if an appender such as FileAppender is used, 
/// then we need to ensure it is called from no more than one thread at a time. 
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="loggingEvent">The logging event.</param>
[MethodImpl(MethodImplOptions.Synchronized)]
static void WriteThreadSafe(ILogger logger, LoggingEvent loggingEvent)
{
  logger.Log(loggingEvent);
}
Run Code Online (Sandbox Code Playgroud)

Log4NetStrategy(以及NLog和EnterpriseLibrary的策略)实现了一个具有Write(ILogEntry logEntry)方法的接口.ILogEntry本质上是Clog日志服务从客户端收到的DTO.提取logEntry中的信息并用于创建log4net LoggingEvent.还将根据ILogEntry DTO中的记录器名称检索相应的log4net记录器.创建log4net LoggingEvent后,它和记录器将被发送到上面的WriteThreadSafe方法并通过log4net记录.NLog和EnterpriesLibrary实现类似.

在伪代码中,Clog日志服务上的"Write"方法如下所示:

// Write on the logging service
// Send the input log entry to each configured strategy.
public void Write(ILogEntry logEntry)
{
  foreach (ILoggingStrategy ls in loggingStrategies)
  {
    ls.Write(logEntry);
  }
}

// Write on the Log4NetStrategy
// Convert the logEntry to log4net form and log with log4net
public void Write(ILogEntry logEntry)
{
  log4net.LoggingEvent le = ConvertToLoggingEvent(logEntry);
  ILog logger = log4net.GetLogger(logEntry.Logger);
  WriteThreadSafe(logger, le);
}
Run Code Online (Sandbox Code Playgroud)

所以这是我的问题...通常log4net(和NLog,我认为,EnterpriseLibrary)被认为是多线程兼容的.也就是说,公共API的用户可以简单地调用log.Info,log.Log等,而不用担心在多线程环境中运行.日志记录框架应该确保日志记录调用(以及日志记录调用中的任何处理)都是线程安全的.

如果日志框架是多线程兼容的,那么就是使用了

[MethodImpl(MethodImplOptions.Synchronized]
Run Code Online (Sandbox Code Playgroud)

属性真的需要吗?似乎这会(或可能)通过强制同步处理所有日志记录消息而导致瓶颈,即使底层日志记录框架应该能够处理多线程环境中的日志记录.

在我的日志服务(可能是多线程)的情况下,似乎不需要像这样同步调用.看起来我应该能够从服务调用中获取日志记录输入,构建适当的日志记录结构(基于底层日志记录框架),然后记录它.如果我的日志服务是多线程的,它应该"正常工作",因为底层日志框架应该支持它(多线程).

这有意义吗?是否真的需要显式同步日志记录调用(至少对于log4net和NLog)?

Alo*_*aus 5

为什么锁?

据我所知,在Enterprise Library中使用.NET Tracelistener类,它具有属性IsThreadSafe.如果侦听器是线程安全的,则不执行同步.对于作为输出设备的文件,没有理由同步来自多个线程的访问,除非内部保存写缓冲区的StreamWriter可能被非同步写入损坏.这就是为什么需要让同步的StreamWriter从不同的线程写入文件的原因.

但无论如何,这更符合学术兴趣.当你一次只允许一个线程写入时,你仍然可以产生几百MB的输出,这甚至可以使最快的硬盘失效.您在写入文件时受到IO限制.整个链中最慢的是硬盘而不是你对文件的同步写入.但有时在一个文件中输出多个进程是很好的.例如Log4Net 无法做到这一点.

...如何让多个进程登录到同一个文件?

FileAppender在日志文件记录时保持写锁定.这可以防止其他进程写入文件,因此不可能让多个进程直接记录到同一个日志文件中,即使它们位于同一台计算机上也是如此....

格式化性能

即使您认为锁定会损害您的性能,通常也会忽略其他成本.可配置的输出格式非常好,但至少在4.0之前的Enterprise Library中的格式非常慢.我的速度提高了13倍,导致净文件吞吐量增加了2倍.

并发写入文件

使用一点Win32魔法,可以同时将来自不同进程的可靠写入同一文件.但是,如果需要同步,请回到您的问题:根据输出设备可能需要它.

可伸缩性

如果您关注的是可扩展性,那么您需要拥有一个每个线程缓冲区,该缓冲区在定义的时间点合并,以便快速收集数据.然后,您可以实现一个全局环形缓冲区,它从所有线程获取格式化数据,其中最旧的数据被覆盖.当有趣的事情发生时,您可以将整个环形缓冲区转储到光盘.这种设计比任何文件追加器快〜10-20倍.Windows事件跟踪确实采用了极速快速的环形缓冲技巧.

既然你问过锁定我确实认为速度是你最关心的问题.如果这个假设是正确的,你应该看看像ApiChange工具使用的跟踪框架.它有一些创新的方法,如自动异常跟踪,当您的方法留下异常或故障注入单元测试错误路径而无需修改您的产品代码.

为了实现可扩展性,您应该区分日志记录和跟踪,如我在此处所述:日志记录不是跟踪..

锁定免费

如果你想创建你需要通过所有的数据,一个跟踪呼叫作为不可改变的数据,将数据格式化为一个字符串,并把它传递给你的输出设备的可扩展的跟踪框架.它可以像(C#伪代码)一样简单

  1. Trace.Info("blahh {0}",数据);
  2. traceString = Format(formatString,args,CurrentTime);
  3. 写(traceString);

当数据未存储在类中或类成员在初始化后不会更改时,您将永远不需要锁定.您必须小心,您确实共享例如线程之间的格式化缓冲区.要实现可扩展性,您需要按以下顺序设置线程安全的设计优先级.

  1. 隔离 这是迄今为止最好的解决方案.如果问题可以通过多个线程可以独立运行的方式进行切片,那么您永远不需要考虑锁定或共享数据.
  2. 不可变性 第二个最佳解决方案是共享数据,但是一旦多个线程可以"看到"它,就必须永远不会改变.这可以通过例如仅具有吸气剂且所有数据通过ctor传递的物体来实现.
  3. 锁定 如果您使用无锁或某些锁定策略无关紧要.你处在痛苦的世界里,你必须注意处理器缓存,虚假共享,如果你很勇敢,你甚至可以尝试一些无锁方法,发现它最多只能获得普通锁定速度的两倍.在某些情况下,无锁代码可能会更慢.

如果你想了解更多关于线程细节的信息,我推荐Joe Duffys博客.

追踪策略

要编写可维护的软件,您需要能够遵循您的应用程序和数据流.只有这样,您才能诊断仅在您的客户现场发生的问题.使用跟踪"混乱"代码可以获得足够的回报,这些跟踪可以为您提供足够的信息来快速诊断问题.您可以追踪太多或太少或不相关的数据.一个好的跟踪线确实将您的XPath查询字符串与其运行的文件相关联,这在日志文件中作为分散的信息片段要好得多.

你的,Alois Kraus