我们最近使用自定义过滤器为我们的log4j配置添加了一个过滤器。 目标是一次又一次地停止重复相同的日志,但将其替换为:
log +“最后一行重复x次”
我们编写了以下过滤器,谁工作正常。但是,我们已经开始注意到奇怪的死锁并挂断了tomcat5.5。
当我们删除此过滤器时,会发生错误停止。 (是的,我们非常肯定)。
除了这个经验观察之外,JVM的Heap Stack还有许多log4j线程被阻塞并等待tomcat监视器被重新发布。
以下是我们过滤器的代码。很基本的。怎么了?
public class RepeatFilter extends Filter {
String lastMessage;
Category lastLogger;
int repeatCount = 0;
@Override
public int decide(LoggingEvent event) {
// get the rendered (String) form of the message
String msg = event.getRenderedMessage();
if(msg == null || msg.startWith("Last message repeated "){
return Filter.NEUTRAL;
}
if(msg.equals(lastMessage)) {
repeatCount++;
return Filter.DENY;
} else {
if(repeatCount>0){
String msgToLog = "Last message repeated " + repeatCount + " time(s).";
repeatCount = 0;
lastLogger.log(event.getLevel(), msgToLog);
}
}
lastLogger = event.getLogger();
lastMessage = msg;
return Filter.NEUTRAL;
}
}
编辑: 是的,当我们在过滤器中使用记录器时,theire是递归。实际上,服务器在lastLogger.log(...)行之后挂起。 但我们真的需要写一个自定义消息(重复的x时间)。我们尽量不在过滤器中使用记录器,但我们还没找到方法。
编辑2: 我使用log4j的1.2.15版本。
编辑3: 我们会尝试的东西:
编辑4: 在asyncAppender中包装每个appender都无法解决问题
答案 0 :(得分:1)
查看源代码,我看到在记录事件时在Category
上获取了锁,然后在分派事件的每个AppenderSkeleton
上获得锁。如果两个Category
实例使用相同的appender(这是常见的),那么尝试从附加到该appender的Appender
(或Filter
)进行日志记录肯定会导致死锁。 / p>
例如,有两个Category
个对象,C1
和C2
,以及一个Appender
,A
。 Thread
T1
获取C1
然后A
的锁定,然后开始处理Filter
上的A
链。
同时,Thread
T2
获取C2
上的锁定。它无法获取A
上的锁定,因为它由T1
保留,因此等待。
现在假设T1
被指示(通过过滤器)将消息记录到C2
。它无法获取C2
上的锁定,因为它由T2
保留。死锁。
如果您从线程转储中发布了更多信息,则应该可以判断它是否是真正的死锁,如果是,则说明哪些对象存在争用。
如果确实发生了这种情况,似乎可以通过仅将“重复”消息记录到“当前”记录器(即过滤器评估的事件的记录器)来避免问题。一种方法是在每个记录器的基础上跟踪重复。
public class RepeatFilter
extends Filter
{
private final Map<Category, Repeat> repeats =
new HashMap<Category, Repeat>();
@Override
public int decide(LoggingEvent event)
{
String message = event.getRenderedMessage();
if ((message == null) || message.startsWith("Last message repeated "))
return Filter.NEUTRAL;
Category logger = event.getLogger();
Repeat r = repeats.get(logger);
if (r == null)
repeats.put(logger, r = new Repeat());
if (message.equals(r.message)) {
++r.count;
return Filter.DENY;
}
if (r.count > 0) {
logger.log(r.level, "Last message repeated " + r.count + " time(s).");
r.count = 0;
}
r.message = message;
r.level = event.getLevel();
return Filter.NEUTRAL;
}
private final class Repeat
{
Priority level;
String message;
int count;
}
}
这可能不是您想要的,因为其他记录器上的事件不会“刷新”该类别的“重复”消息。另一方面,它可能更有效地压缩日志,因为相同的消息可能会在同一个记录器上重复。