我的log4j过滤器出了什么问题? (它让tomcat挂断!)

时间:2010-02-17 15:03:51

标签: java filter log4j tomcat5.5

我们最近使用自定义过滤器为我们的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: 我们会尝试的东西:

  • 使我们的所有appender嵌套在AsyncAppender

编辑4: 在asyncAppender中包装每个appender都无法解决问题

1 个答案:

答案 0 :(得分:1)

可能的死锁

查看源代码,我看到在记录事件时在Category上获取了锁,然后在分派事件的每个AppenderSkeleton上获得锁。如果两个Category实例使用相同的appender(这是常见的),那么尝试从附加到该appender的Appender(或Filter)进行日志记录肯定会导致死锁。 / p>

例如,有两个Category个对象,C1C2,以及一个AppenderAThread 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;

  }

}

这可能不是您想要的,因为其他记录器上的事件不会“刷新”该类别的“重复”消息。另一方面,它可能更有效地压缩日志,因为相同的消息可能会在同一个记录器上重复。