gpt4 book ai didi

.net - 日志记录框架和多线程兼容性

转载 作者:行者123 更新时间:2023-12-03 13:16:24 25 4
gpt4 key购买 nike

请注意,我在SO上已经看到了这些问题(讨论了log4net的线程安全性),我怀疑它们回答了我的问题,但是无论如何我都会问:

Multithread safe logging

Log4Net FileAppender not thread safe?

最近,我编写了WCF服务进行日志记录。这个想法与Clog(或look for Clog under Calcium)非常相似。基本上,我已经实现了在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);
}
Log4NetStrategy(以及NLog和EnterpriseLibrary的策略)实现了具有类似Write(ILogEntry logEntry)之类的方法的接口(interface)。 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);
}

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

如果日志记录框架是多线程兼容的,那么使用
[MethodImpl(MethodImplOptions.Synchronized]

确实需要属性吗?即使基础日志框架应该能够在多线程环境中处理日志,通过强制同步处理所有日志消息,这似乎也会(或可能)引起瓶颈。

对于我的日志记录服务(可能是多线程),似乎不需要像这样同步调用。看来我应该能够从服务调用中获取日志记录输入,构造适当的日志记录结构(基于基础日志记录框架),然后对其进行记录。如果我的日志记录服务是多线程的,那么它应该“正常工作”,因为底层的日志记录框架应该支持它(多线程)。

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

最佳答案

为什么要锁?

据我所记得,企业库中使用.NET Tracelistener类,该类具有属性IsThreadSafe。如果监听器是线程安全的,则不执行同步。
对于作为输出设备的文件,没有理由同步来自多个线程的访问,除非内部保存写缓冲区的StreamWriter可能因未同步的写操作而损坏。这就是为什么需要获取synchronized StreamWriter从不同线程写入文件的原因。

但是无论如何,这更具有学术意义。如果一次只允许写入一个线程,则仍然可以生成数百MB的输出,甚至可以减少最快的硬盘。写入文件时,您受IO约束。整个链中最慢的是硬盘,而不是同步写入文件。
但是有时候在一个文件中包含多个进程的输出会很好。 Log4Net例如cannot做到了。

... How do I get multiple process to log to the same file?

The FileAppender holds a write lock on the log file while it is logging. This prevents other processes from writing to the file, therefore it is not possible to have multiple processes log directly to the same log file, even if they are on the same machine. ...



格式化性能

即使您认为锁使您付出了高昂的性能,但通常还会忽略其他成本。可配置的输出格式非常好,但是至少在Enterprise Library中的格式直到4.0才是 very slow。我的速度提高了13倍以上,导致净文件吞吐量提高了2倍。

并发写入文件

借助Win32的一点魔力, possible可以将来自不同进程的可靠数据同时写入同一文件。
但是回到您的问题是否需要同步:取决于输出设备,可能需要同步。

可扩展性

如果您关注的是可伸缩性,则需要有一个每个线程的缓冲区,该缓冲区在定义的时间点合并以允许快速收集数据。然后,您可以实现一个全局环形缓冲区,该缓冲区从所有线程获取格式化的数据,其中最早的数据将被覆盖。当发生有趣的事情时,您可以将整个环形缓冲区转储到光盘上。这种设计比任何文件附加程序快10-20倍。 Windows Event Tracing确实使用了非常快的环形缓冲区技巧。

既然您询问锁定问题,我确实认为 速度是您的主要关注点。如果这个假设是正确的,那么您应该看看 tracing framework ,就像 ApiChange工具使用的那样。它具有一些创新的方法,例如当您的方法带有异常时自动跟踪异常,或者使用 fault injection来对错误路径进行单元测试,而无需修改产品代码。

为了实现可伸缩性,您应该在日志记录和跟踪之间进行区分,如我在此处所述: Logging is not Tracing.

无锁

如果要创建可伸缩的跟踪框架,则需要将一个跟踪调用的所有数据作为不可变数据进行传递,将数据格式化为字符串并将其传递给输出设备。它可以像(C#伪代码)一样简单
  • Trace.Info(“blahh {0}”,数据);
  • traceString = Format(formatString,args,CurrentTime);
  • Write(traceString);

  • 如果数据没有存储在类中,或者类成员在初始化后没有更改,则永远不需要锁。您必须谨慎分享例如线程之间的格式化缓冲区。为了实现可伸缩性,您需要按照以下顺序设置线程安全的设计优先级。
  • 隔离
    到目前为止,这是最好的解决方案。如果可以通过几个线程可以独立运行的方式来解决问题,则无需考虑锁定或共享数据。
  • 不变性
    第二个最佳解决方案是共享数据,但是一个以上的线程可以“看到”它,则它永远不能改变。例如,这可以实现。带有仅具有 setter/getter 的对象,并且所有数据都通过ctor传递。
  • 锁定
    如果您使用无锁或某些锁定策略没有太大关系。您处在痛苦的世界中,必须注意处理器缓存false sharing,如果您很勇敢,您甚至可以尝试一些无锁方法,只是发现它最多比普通锁快两倍。在某些情况下,无锁代码可能会变慢。

  • 如果您想了解有关线程细节的更多信息,建议使用 Joe Duffys blog

    追踪策略

    要编写可维护的软件,您需要能够跟踪您的应用程序和数据流。只有这样,您才能诊断出仅在您的客户现场发生的问题。这样做确实可以使您的代码“充满”痕迹,从而为您提供足够的信息来快速诊断问题。您可以追踪太多或更少或不相关的数据。一条良好的追踪线确实与例如您的XPath查询字符串及其所操作的文件要好得多,因为日志文件中的信息分散了。

    你的
    阿洛伊斯·克劳斯(Alois Kraus)

    关于.net - 日志记录框架和多线程兼容性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5021892/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com