日志框架和多线程兼容性
请注意,我在 SO 上看到了这些问题(讨论了 log4net 的线程安全性),我怀疑它们回答了我的问题,但无论如何我还是要问:
最近我编写了一个用于日志记录的 WCF 服务。这个想法与 Clog (或 在钙下寻找 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);
}
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);
}
所以这是我的问题...通常 log4net(以及 NLog 和我假设的 EnterpriseLibrary)被认为是多线程兼容的。也就是说,公共API的用户可以简单地调用log.Info、log.Log等,而不必担心在多线程环境中运行。日志记录框架应确保日志记录调用(以及日志记录调用中的任何处理)是线程安全的。
如果日志框架是多线程兼容的,那么
[MethodImpl(MethodImplOptions.Synchronized]
真的需要使用该属性吗?看起来这会(或可能)通过强制同步处理所有日志消息而导致瓶颈,即使底层日志框架应该能够处理多线程环境中的日志记录。
对于我的日志记录服务(可能是多线程的),似乎没有必要像这样同步调用。看来我应该能够从服务调用中获取日志输入,构建适当的日志结构(基于底层日志框架),然后记录它。如果我的日志记录服务是多线程的,它应该“正常工作”,因为底层日志记录框架应该支持它(多线程)。
这有道理吗?日志记录调用的显式同步(至少对于 log4net 和 NLog)真的有必要吗?
Note that I have seen these questions here on SO (which discuss the thread safety of log4net) and I suspect that they answer my question, but I am going to ask anyway:
Log4Net FileAppender not thread safe?
Recently I have written a WCF service for logging. The idea is very similar to Clog (or look for Clog under Calcium). Basically, I have implemented a logging API for use in a Silverlight client (a Silverlight class library). The logging API is, more or less, a clone of the Common.Logging for .NET API which we are using elsewhere in our application. The implementation of the API forwards all logging messages to a WCF logging service that, itself, is implemented in terms of Common.Logging.
While looking at Clog I found the following code in the Log4NetStrategy
class that struck me as a little bit odd:
/// <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
(and the strategies for NLog and EnterpriseLibrary as well) implements an interface that has a method like Write(ILogEntry logEntry). ILogEntry is essentially the DTO that the Clog logging service received from the client. The information from the logEntry is extracted and used to create a log4net LoggingEvent. The appropriate log4net logger is also retrieved based on the logger name in the ILogEntry DTO. After the log4net LoggingEvent is created, it and the logger are sent to the WriteThreadSafe method above and logged via log4net. The NLog and EnterpriesLibrary implementations are similar.
In pseudocode, the "Write" method on the Clog logging service looks something like this:
// 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);
}
So here is my question... Generally log4net (and NLog and, I assume, EnterpriseLibrary) are considered to be multithread compatible. That is, the user of the public API can simply call log.Info, log.Log, etc and not be concerned about running in a multithreaded environment. The logging framework should take care of ensuring that the logging calls (and any processing within the logging calls) are threadsafe.
If the logging frameworks are multithread compatible, then is the use of the
[MethodImpl(MethodImplOptions.Synchronized]
attribute really needed? It seems like this would (or could) just cause a bottleneck by forcing all logging messages to be handled synchronously, even though the underlying logging framework should be able to handle logging in a multhithreaded environment.
In the case of my logging service (which could be multithreaded), it doesn't seem like it would be necessary to synchronize the calls like this. It seems like I should be able to take my logging input from the service call, construct the appropriate logging structure (based on the underlying logging framework), and just log it. If my logging service is multithreaded, it should "just work" because the underlying logging framework should support it (multithreading).
Does this make sense? Is the explicit synchronization of logging calls (at least for log4net and NLog) really necessary?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
为什么是锁?
据我记得在企业库中使用.NET Tracelistener 类,它具有 IsThreadSafe 属性。如果侦听器是线程安全的,则不会执行同步。
对于作为输出设备的文件,没有理由同步多个线程的访问,除非您的 StreamWriter 内部保存写入缓冲区,可能会因不同步写入而损坏。这就是为什么您需要获取 同步 StreamWriter 从不同线程写入文件。
但无论如何,这更具有学术兴趣。当您一次只允许一个线程写入时,您仍然可以生成数百 MB 的输出,这甚至会导致最快的硬盘出现故障。当您写入文件时,您会受到 IO 限制。整个链中最慢的是硬盘,而不是同步写入文件。
但有时将多个进程的输出放在一个文件中是件好事。例如 Log4Net 不能做到这一点。
格式化性能
即使您认为锁会降低性能,但通常还会忽略其他成本。可配置的输出格式非常好,但至少在 4.0 之前,企业库中的格式是
并发写入文件
借助一点 Win32 的魔力,它是 可能将来自不同进程的可靠数据同时写入同一个文件。
但回到你的问题是否需要同步:根据输出设备,可能需要同步。
可扩展性
如果您关心可扩展性,那么您需要每个线程缓冲区,该缓冲区在定义的时间点合并以允许快速数据收集。然后,您可以实现一个全局环形缓冲区,它从所有线程获取格式化数据,其中最旧的数据被覆盖。当发生有趣的事情时,您可以将整个环形缓冲区转储到磁盘。此设计比任何文件附加器快约 10-20 倍。 Windows 事件跟踪确实采用了速度极快的环形缓冲区技巧。
既然您询问了锁定问题,我确实认为速度是您主要关心的问题。如果这个假设是正确的,你应该看看跟踪框架类似于ApiChange工具使用的框架。它有一些创新的方法,例如当您的方法出现异常时自动异常跟踪或 故障注入对错误路径进行单元测试,无需修改您的产品代码。
为了可扩展,您应该区分日志记录和跟踪,正如我在这里所解释的: 日志记录不是跟踪。。
无锁
如果要创建一个可扩展的跟踪框架,您需要将一次跟踪调用的所有数据作为不可变数据传递,将数据格式化为字符串并将其传递到输出设备。 一样简单
当数据未存储在类中或类成员在初始化后未更改时,您将永远不需要锁。您必须小心,不要在线程之间共享例如格式化缓冲区。为了可扩展,您需要按以下顺序设置线程安全的设计优先级。
这是迄今为止最好的解决方案。如果问题可以以多个线程可以独立运行的方式进行分割,那么您就永远不需要考虑锁定或共享数据。
第二个最佳解决方案是共享数据,但一旦多个线程可以“看到”它,它就永远不会改变。例如,这可以通过仅具有 getter 并且所有数据都通过 ctor 传递的对象来实现。
如果您使用无锁或某些锁定策略并没有多大关系。您正处于痛苦的世界中,必须注意处理器缓存,虚假共享 如果你足够勇敢,你甚至可以尝试一些无锁方法,结果却发现它最多比普通锁快两倍。在某些情况下,无锁代码可能会更慢。
如果您想了解有关线程详细信息的更多信息,我推荐 Joe Duffys 博客。
跟踪策略
要编写可维护的软件,您需要能够跟踪您的应用程序和数据流。只有这样,您才能诊断仅在您的客户站点发生的问题。用痕迹“混乱”你的代码确实是有回报的,这些痕迹确实为你提供了足够的信息来快速诊断问题。您可能会跟踪太多、太少或不相关的数据。一条好的跟踪线确实将您的 XPath 查询字符串与其所操作的文件相关联,这比日志文件中分散的信息要好得多。
你的,
阿洛伊斯·克劳斯
Why The Locks?
As far as I remember in Enterprise Library uses the .NET Tracelistener class which has a property IsThreadSafe. If the listener is thread safe no synchronization is performed.
For a file as output device there is no reason to synchronize access from several threads unless your StreamWriter which internally holds the write buffer could become corrupted by unsynchronized writes. This is the reason why you need to get a synchronized StreamWriter to write to a file from different threads.
But at any rate this is more of academic interest. When you allow only one thread to write at a time you still can generate several hundred MB of output which brings even the fastest hard disc down. You are IO bound when you write to file. The slowest thing in the whole chain is the hard disc and not your synchronized write to a file.
But sometimes it is nice to have the output of several processes in one file. Log4Net for example cannot do it.
Formatting Performance
Even if you think that the lock is costing you performance there are often other costs overlooked. The configurable output format is very nice but at least the formatting in Enterprise Library until 4.0 was very slow. I have made it over 13 times faster which resulted in a net file throughput increase of a factor 2.
Concurrent Writes To File
With a little Win32 magic it is possible to write reliable from different processes into the same file concurrently.
But to come back to your question if synchronization is needed: It may be needed depending on the output device.
Scalabilty
If your concern is scalability then you need to have a per thread buffer which is merged at a defined point in time to allow fast data gathering. Then you can implement a global ring buffer which gets from all threads the formatted data where the oldest data is overwritten. When something interesting happens you can dump the whole ring buffer to disc. This design is ~10-20 times faster than any file appender. Windows Event Tracing does employ the ring buffer trick which is blazingly fast.
Since you asked about locking I do think that speed is your main concern. If that assumption is correct you should have a look at a tracing framework like the one the ApiChange tool uses. It has some innovative approaches like automatic exception tracing when your method is left with an exception or fault injection to unit test the error path without the need to modify your product code.
To be scalable you should differentiate between logging and tracing as I have explained here: Logging is not Tracing..
Lock Free
If you want to create a scalable tracing framework you need to pass all your data for one trace call as immutable data, format the data into a string and pass it on to your output device. It can be as simple as (C# Pseudo Code)
When the data is not stored in a class or the class members do not change after it has been initialized you will never need a lock. You have to be careful that you do share e.g. the formatting buffer between threads. To be scalable you need to set your design priorities for thread safety in the following order.
This is by far the best solution. If the problem can be sliced in a way that several threads can operate independently you never need to think about locking or shared data.
The second best solution is to share data but once more than one thread can "see" it it must never change. This can be achieved e.g. with objects which do have only getters and all data is passed via the ctor.
If you use lock free or some locking strategy does not matter much. You are in the world of pain where you must pay attention to processor caches, false sharing and if you are brave you even try some lock free method only to find out that it at best gets twice as fast as a normal lock. In some cases lock free code can be slower.
If you want to read more about threading details I recommend Joe Duffys blog.
Tracing Strategy
To write maintainable software you need to be able to follow your application and data flow. Only then you can diagnose problems which happen only at your customer site. It does pay off to "clutter" your code with traces which do give you enough information to diagnos a problem quickly. You can trace too much or too less or not relevant data. One good trace line which does correlate e.g. your XPath query string with the file it operates on is much better as scattered pieces of information in your log file.
Yours,
Alois Kraus