使用 log4j 的条件日志记录

2022-09-02 01:23:38

我正在使用的Web应用程序偶尔会为某些用户开发数据完整性问题。我想打开跟踪级别日志记录,但是由于我们每秒处理 100 个请求跟踪日志记录,因此每个请求都是不可能的。

有没有办法使用log4j能够有条件地记录?换句话说,我希望仅在特定用户发出请求时才能够获取跟踪日志。由于我事先不知道哪些用户会受到影响,所以我不能简单地暂时硬编码用户名。

编辑:

我想我需要更清楚一点。我可以轻松地向日志语句添加条件。例如

Logger logger = Logger.getLogger("foo");
String usernameFilter = "piglet";
String username = request.getParameter("username");
logger.setLevel(usernameFilter.equals(username) ? Level.TRACE : Level.INFO);
if (logger.isTraceEnabled()) {
   logger.trace("blah blah blah");
}

困难在于动态更改设置日志级别的条件。换句话说,在上面的例子中,除了硬编码之外,我如何设置usernameFilter的值。


答案 1

您希望在 log4j 或 slf4j 中查看嵌套诊断上下文或映射的诊断上下文。NDC/MDC 允许您将数据插入到会话中,这些数据可由 log4j 进行筛选。

因此,您可以将用户名定义为位于NDC中,然后可以更改log4j.properties以更改特定用户的日志记录级别。

MDC 使用映射,而 NDC 基于堆栈原理。如果您使用的是 slf4j,您甚至可以根据 MDC 中的信息创建单独的日志文件。

例如,当用户登录网站时,我们这样做。我们想要跟踪特定用户正在执行的操作(追溯性),因此我们将用户名和会话ID添加到NDC,然后我们可以发布过滤器。

代码类似于以下内容:

public class LoggingFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        MDC.put("username", session.getParameter("username")); // or where ever t is stored
        chain.doFilter(request, response);
    }
}

在您的log4j.xml中,此过滤器基于用户:

  <appender name="UserDebug" class="org.apache.log4j.RollingFileAppender">
    <param name="File" value="userdebug.log"/>
    <param name="Append" value="true"/>
    <param name="MaxFileSize" value="5000KB"/>
    <param name="maxBackupIndex" value="5"/> 
          <layout class="org.apache.log4j.PatternLayout">
                  <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} [%t] user:%X{username} %-5p - %m%n" />
          </layout>

          <filter class="org.apache.log4j.varia.StringMatchFilter">
                  <param name="StringToMatch" value="user:fred" />
                  <param name="AcceptOnMatch" value="true" />
          </filter>

      <filter class="org.apache.log4j.varia.DenyAllFilter"/>
  </appender>

%X{key} 输出 MDC 中的 MDC.get(key) 的值。如果需要更复杂的筛选器,可以自行扩展它,并自行查看 MDC 中的值。


答案 2

Matthew Farwell的答案(使用MDC)对我有用,他提到编写自己的过滤器。在某些情况下,我需要隐藏日志记录消息。具体来说,我们有一个运行状况检查调用,该调用的命中频率比典型的用户使用频率高得多,并且它不必要地填充了日志。Matthew 的解决方案不适合我的情况,因为它需要您将 MDC 添加到实际的日志输出中。我只想使用MDC进行过滤,所以我使用以下类进行扩展:org.apache.log4j.spi.Filter

/**
 * Log4J filter that stops certain log messages from being logged, based on a
 * value in the MDC (See Log4J docs).
 */
public class Log4JMDCFilter extends Filter
{

private String keyToMatch;
private String valueToMatch;
private boolean denyOnMatch = true;

/**
 * {@inheritDoc}
 */
public int decide(LoggingEvent event)
{
    if (keyToMatch != null && valueToMatch != null
        && valueToMatch.equals(event.getMDC(keyToMatch)))
    {
        return denyOnMatch ? DENY : ACCEPT;
    }

    return denyOnMatch ? ACCEPT : DENY;
}

/**
 * The key on which to filter.
 * 
 * @return key on which to filter
 */
public String getKeyToMatch()
{
    return keyToMatch;
}

/**
 * Sets the key on which to filter.
 * 
 * @param keyToMatch key on which to filter
 */
public void setKeyToMatch(String keyToMatch)
{
    this.keyToMatch = keyToMatch;
}

/**
 * Gets the value to match.
 * 
 * @return the value to match.
 */
public String getValueToMatch()
{
    return valueToMatch;
}

/**
 * Sets the value to match.
 * 
 * @param valueToMatch the value to match.
 */
public void setValueToMatch(String valueToMatch)
{
    this.valueToMatch = valueToMatch;
}

/**
 * Returns true if the log message should not be logged if a match is found.
 * 
 * @return true if the log message should not be logged if a match is found.
 */
public boolean isDenyOnMatch()
{
    return denyOnMatch;
}

/**
 * Set this to "true" if you do not want log messages that match the given
 * key/value to be logged. False if you only want messages that match to be
 * logged.
 * 
 * @param denyOnMatch "true" if you do not want log messages that match the
 *        given key/value to be logged. False if you only want messages that
 *        match to be logged.
 */
public void setDenyOnMatch(String denyOnMatch)
{
    this.denyOnMatch = Boolean.valueOf(denyOnMatch).booleanValue();
}

}

使用以下 log4j.xml 代码段激活筛选器(“HEALTHCHECK”是键,“true”是我正在筛选的值):

    <filter class="com.copart.hh.core.utils.Log4JMDCFilter">
        <param name="keyToMatch" value="HEALTHCHECK" />
        <param name="valueToMatch" value="true" />
        <param name="denyOnMatch" value="true" />
    </filter>

然后,在要标记以进行筛选的任何地方,输入如下代码:

MDC.put("HEALTHCHECK", "true");
try
{    
      // do healthcheck stuff that generates unnecessary logs
}
finally
{
    MDC.remove("HEALTHCHECK"); // not sure this is strictly necessary
}

推荐