简体   繁体   English

log4net BufferingForwardingAppender性能问题

[英]log4net BufferingForwardingAppender performance issue

EDIT 2 : I have solved the problem (see answer below) Please note that the problem potentially affects all appenders decorated with BufferingForwardingAppender as well as all appenders inheriting from BufferingAppenderSkeleton (respectively : AdoNetAppender, RemotingAppender, SmtpAppender and SmtpPickupDirAppender) * 编辑2:我已经解决了问题(请参阅下面的答案)请注意,该问题可能会影响所有使用BufferingForwardingAppender修饰的appender以及从BufferingAppenderSkeleton继承的所有appender(分别为:AdoNetAppender,RemotingAppender,SmtpAppender和SmtpPickupDirAppender)*

I was doing some very basic benchs of log4net and I tried to decorate a RollingFileAppender with a BufferingForwardingAppender. 我正在做一些非常基本的log4net工作台,我尝试用BufferingForwardingAppender修饰RollingFileAppender。

I experience terrible performance going through the BufferingForwardingAppender instead of directly through the RollingFileAppender and I really don't get the reason. 我通过BufferingForwardingAppender而不是直接通过RollingFileAppender遇到了糟糕的性能,我真的没有理由。

Here is my configuration: 这是我的配置:

<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
  <file value="c:\" />
  <appendToFile value="false" />
  <rollingStyle value="Composite" />
  <datePattern value="'.'MMdd-HH'.log'" />
  <maxSizeRollBackups value="168" />
  <staticLogFileName value="false" />
  <layout type="log4net.Layout.PatternLayout">      
    <conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
  </layout>
</appender>

<appender name="BufferingForwardingAppender" type="log4net.Appender.BufferingForwardingAppender">
  <bufferSize value="512" />
  <appender-ref ref="RollingLogFileAppender" />
</appender>

<root>
  <level value="DEBUG" />
  <appender-ref ref="BufferingForwardingAppender" />    
</root>

And here is the benchmark (very simple code): 这是基准(非常简单的代码):

var stopWatch = new Stopwatch();
stopWatch.Start();            
for (int i = 0; i < 100000; i++)            
{
   Log.Debug("Hello");
}
stopWatch.Stop();
Console.WriteLine("Done in {0} ms", stopWatch.ElapsedMilliseconds);

Going directly through RollingFileAppender the output is: 直接通过RollingFileAppender输出的是:

Done in 511 ms 完成时间为511毫秒

Whereas going through the BufferingForwardingAppender decorating the RollingFileAppender : 而通过BufferingForwardingAppender装饰RollingFileAppender:

Done in 14261 ms 订单日期为14261毫秒

That's approx 30 times slower. 这大约慢了30倍。

I thought I would gain some speed by buffering a certain amount of log before writing them to the file, however for some reason it gets things much worse. 我认为通过在将它们写入文件之前缓冲一定量的日志来获得一些速度,但是由于某种原因它会使事情变得更糟。

Seems to me like the configuration is OK, so this is really weird. 在我看来配置是好的,所以这真的很奇怪。

Anyone got a clue? 有人知道吗?

Thanks! 谢谢!

EDIT 1 : 编辑1:

The behavior is strictly the same by wrapping/decorating a FileAppender or even ConsoleAppender (still there is an example of basic BufferingForwardingAppender wrapping/decorating ConsoleAppender in log4net official config samples .. and nothing specific mentioned dealing with performance). 通过包装/装饰FileAppender甚至是ConsoleAppender,行为完全相同(在log4net官方配置示例中仍然有一个基本的BufferingForwardingAppender包装/装饰ConsoleAppender的例子......并且没有具体提到处理性能)。

After some investigation/profiling, I can see that the majority of the time is spoiled inside the BufferingForwardingAppender more specifically in a call to WindowsIdentity.GetCurrent() ... being called EACH time we make a call to Log.Debug() .. in the previous sample (100K times in the sample source above). 经过一些调查/分析后,我可以看到大部分时间都在BufferingForwardingAppender中被破坏,更具体地说是在调用WindowsIdentity.GetCurrent()...被调用时我们调用Log.Debug() 。在上一个样本中(上面的示例源中100K次)。

Calls to this method are known to be very costly and should be avoided or minimized, I really don't get why it gets called for each log event. 已知对此方法的调用非常昂贵,应该避免或最小化,我真的不明白为什么它会被调用每个日志事件。 Am I really completely misconfiguring something / not seeing something evident, or is that a bug somehow somewhere, this is what I am trying to figure out now... 我真的完全错误地配置了某些东西/没有看到明显的东西,或者某种错误是某种错误,这就是我现在想要解决的问题......

The partial call stack is : 部分调用堆栈是:

  • AppenderSkeleton.DoAppend AppenderSkeleton.DoAppend
  • BufferingAppenderSkeleton.Append BufferingAppenderSkeleton.Append
  • LoggingEvent.FixVolatileData LoggingEvent.FixVolatileData
  • LoggingEvent.get_UserName() LoggingEvent.get_UserName()

A call to get_LocationInformation() is also done in FixVolatileData, incurring an high perf cost as well (capture the stack trace each time). 调用get_LocationInformation()也在FixVolatileData中完成,也会导致高性能(每次捕获堆栈跟踪)。

I am now trying to understand why this extremely costly FixVolatileData call (at least for the fix asked) happens for each log event in this context whereas going directly through the wrapped appender (directly through ConsoleAppender/FileAppender ..) does not perform this kind of operation. 我现在试图理解为什么这个极其昂贵的FixVolatileData调用(至少对于修复问题)在这个上下文中发生的每个日志事件,而直接通过包装的appender(直接通过ConsoleAppender / FileAppender ..)不执行这种操作。

Upcoming update to follow, unless someone got an answer to all of this ;) 即将到来的更新,除非有人得到了所有这些的答案;)

Thanks! 谢谢!

I found out the issue. 我发现了这个问题。

The BufferingForwardingAppender is inheriting from BufferingAppenderSkeleton (as are other appenders making use of logging events buffering such as AdoNetAppender , RemotingAppender , SmtpAppender ..). BufferingForwardingAppender继承自BufferingAppenderSkeleton (与使用记录事件缓冲的其他appender一样,如AdoNetAppenderRemotingAppenderSmtpAppender ......)。

The BufferingAppenderSkeleton is actually buffering logging events before actually delivering them to the target appender once a certain condition is met (buffer full for example). BufferingAppenderSkeleton实际上是在满足特定条件(例如缓冲区已满)之前将日志事件实际传送到目标appender之前缓冲日志事件。

According to documentation of the LoggingEvent class (representing a logging event, and containing all values (message, threadid ...) of the event) : 根据LoggingEvent类的文档(表示日志记录事件,并包含事件的所有值(message,threadid ...)):

Some logging events properties are considered "volatile", that is the values are correct at the time the event is delivered to appenders, but will not be consistent at any time afterwards. 某些日志记录事件属性被视为“易失性”,即在事件传递给追加者时值是正确的,但之后的任何时间都不一致。 If an event is to be stored and the processed at a later time, these volatile values must be fixed bycalling FixVolatileData. 如果要存储事件并在以后处理,则必须通过调用FixVolatileData来修复这些易失性值。 There is a performance penalty incurred by calling FixVolatileData but is is essential to maintain data consistency 调用FixVolatileData会导致性能下降,但这对于维护数据一致性至关重要

These "volatile" properties are represented by the FixFlags enumeration containing flags such as Message, ThreadName, UserName, Identity ... so all volatile properties. 这些“volatile”属性由包含Message,ThreadName,UserName,Identity等标志的FixFlags枚举表示,因此所有volatile属性。 It also contains the flag "None" (fix no properties), "All" (fix all properties) and "Partial" (fix only a certain predefine dset of properties). 它还包含标志“None”(不修复属性),“All”(修复所有属性)和“Partial”(仅修复某个预定义的dset属性)。

Whem the BufferingAppenderSkeleton is instanciated, by DEFAULT it sets the fixing to "All" meaning that all "volatile" properties should be fixed. 通过DEFAULT将BufferingAppenderSkeleton为实例,它将修复设置为“All”,这意味着应该修复所有“volatile”属性。

In that context, for each LoggingEvent appended into the BufferingAppenderSkeleton, ALL "volatile" properties will be fixed before the event is inserted in the buffer. 在该上下文中,对于附加到BufferingAppenderSkeleton中的每个LoggingEvent,在将事件插入缓冲区之前,将修复所有“volatile”属性。 This includes the properties Identity (username) and LocationInformation (stack trace) even if these properties are not included in the layout (but I guess it makes some kind of sense if the layout is changed to include these properties at a later time while a buffer has been already been filled with LoggingEvents). 这包括属性Identity(用户名)和LocationInformation(堆栈跟踪),即使这些属性不包含在布局中(但我想如果布局被更改为稍后在缓冲区中包含这些属性时会有某种意义已经填充了LoggingEvents)。

However in my case this really HURTS performance. 但在我的情况下,这真的是HURTS表现。 I am not including the Identity and LocationInformation in my layout and don't plan to (mainly for performance issues) 我没有在我的布局中包含Identity和LocationInformation而且不​​打算(主要是针对性能问题)

Now for the solution ... 现在为解决方案......

There are two properties in BufferingAppenderSkeleton which can be used to control the FixFlags flag value of the BufferingAppenderSkeleton (once again by default it is set to "ALL" which is not very nice !). 有两种性质BufferingAppenderSkeleton可以用来控制FixFlags中的标志值BufferingAppenderSkeleton (默认情况下再次将其设置为“ALL”,这是不是很漂亮!)。 These two properties are Fix (FixFlags type) and OnlyFixPartialEventData (bool type). 这两个属性是Fix (FixFlags类型)和OnlyFixPartialEventData (bool类型)。

For a fine tune of the flag value or to disable all fix, the Fix property should be used. 要对标志值进行微调或禁用所有修复,应使用Fix属性。 For a specific partial predefined combination of flags (not including Identity or LocationInfo), the OnlyFixPartialEventData can be used instead by setting it to "true". 对于特定的部分预定义标志组合(不包括Identity或LocationInfo),可以通过将其设置为“true”来使用OnlyFixPartialEventData

If I reuse the configuration sample above (in my question), the only change made to the configuration to unleash performance is indicated below: 如果我重用上面的配置示例(在我的问题中),则对配置进行的唯一更改以释放性能如下所示:

<appender name="BufferingForwardingAppender" type="log4net.Appender.BufferingForwardingAppender">
  <bufferSize value="512" />
  <appender-ref ref="RollingLogFileAppender" />
  <Fix value="0"/> <!-- Set Fix flag to NONE -->
</appender>

Using this modified configuration, the benchmark code execution presented in my question above, is dropping from approx 14000ms to 230ms (60X faster) ! 使用这个修改过的配置,我在上面的问题中提出的基准代码执行从大约14000ms下降到230ms(快60倍)! And if I use <OnlyFixPartialEventData value="true"/> instead of disabling all fix it is taking approx 350ms. 如果我使用<OnlyFixPartialEventData value="true"/>而不是禁用所有修复它需要大约<OnlyFixPartialEventData value="true"/>

Sadly, this flag is not very well documented (except in the SDK documentation, a little bit) .. so I had to dig deep into log4net sources to find the issue. 遗憾的是,这个标志没有很好的记录(除了SDK文档,一点点)..所以我不得不深入挖掘log4net来源以找到问题。

This is particularly problematic especially in the "reference" configuration samples, this flag appears nowhere (http://logging.apache.org/log4net/release/config-examples.html). 这特别成问题,特别是在“参考”配置示例中,此标志无处可见(http://logging.apache.org/log4net/release/config-examples.html)。 So the samples provided for BufferingForwardingAppender, and AdoNetAppender (and other appenders inheriting from BufferingAppenderSkeleton) will give TERRIBLE performance to users, even if the layout they are using is pretty minimal. 因此,为BufferingForwardingAppender和AdoNetAppender(以及继承自BufferingAppenderSkeleton的其他appender)提供的示例将为用户提供可怕的性能,即使他们使用的布局非常小。

Is it possible that it's because you aren't specifying a layout pattern in the BufferingForwardingAppender but you are in the RollingLogFileAppender therefore the BufferingForwardingAppender is including everything in it's output including the username (%username) 是否有可能是因为你没有在BufferingForwardingAppender指定布局模式但是你在RollingLogFileAppender因此BufferingForwardingAppender包含其输出中的所有内容,包括用户名(%username)

Below is an interesting blog article that has a list of the options in the pattern layouts and it looks like he has several of them marked as slow. 下面是一篇有趣的博客文章,其中列出了模式布局中的选项,看起来他有几个标记为慢速。

http://www.beefycode.com/post/Log4Net-Tutorial-pt-4-Layouts-and-Patterns.aspx http://www.beefycode.com/post/Log4Net-Tutorial-pt-4-Layouts-and-Patterns.aspx

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM