在 log4j2 中创建惰性消息

Create lazy messages in log4j2

我有这个代码:

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Test {
  private static final Logger logger = LogManager.getLogger();
  public static void main (String[] args) {
    logger.info("Text: {}", getText());
  }
  static String getText() {
    // Expensive action using IO
    return "";
  }
}

在我的 log4j2.json 中,记录器设置为 ERROR

我希望在不需要时根本不调用 getText()。为此,我使用消息 API 并改为编写以下内容:

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.Message;
public class Test {
  private static final Logger logger = LogManager.getLogger();
  public static void main (String[] args) {
    logger.info(new TextMessage());
  }
  static String getText() {
    // Expensive action using IO
    return "";
  }
  static class TextMessage implements Message {
    @Override public String getFormat() { return null; }
    @Override public String getFormattedMessage() {
        return String.format("text: %s", getText());
    }
    @Override public Object[] getParameters() { return null; }
    @Override public Throwable getThrowable() { return null; }
  }
}

这段代码有两个问题。

  1. 我不能使用通常用于日志记录的{}
  2. 非常冗长。

我在 javadoc 中检查了我可以在 Java 8 的 lambda 表达式(意思是只有一个抽象方法)中使用的任何 Message,但是有 none.

我考虑过创建一个 ToStringable 接口,如下所示。

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Test {
  private static final Logger logger = LogManager.getLogger();
  public static void main (String[] args) {
    logger.info("Text: {}", new ToStringable() { public String toString() { return getText();}});
  }
  static String getText() {
    // Expensive action using IO
    return "";
  }
}
interface ToStringable { @Override String toString(); }

这完成了工作,但它几乎不可读,我不能在这上面使用 Java 8 的 lambda(见下文,注意此代码无法编译)。

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Test {
  private static Logger logger = LogManager.getLogger();
  public static void main (String[] args) {
    logger.info("Text: {}", () -> getText());
  }
  static String getText() {
    // Expensive action using IO
    return "";
  }
}
interface ToStringable {@Override String toString(); }

最后,只有老好人 if (logger.isXxxxEnabled()) { logger.xxxx(...) } 可靠地解决了这个问题,但是使用现代的懒惰记录器有什么意义呢?

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Test {
  private static final Logger logger = LogManager.getLogger();
  public static void main (String[] args) {
    if (logger.isInfoEnabled()) {
      logger.info("Text: {}", getText());
    }
  }
  static String getText() {
    // Expensive action using IO
    return "";
  }
}

有没有人遇到过这个问题,如果有,是怎么解决的?

最后的话:前 3 个代码片段是 short, self-contained, correct examples。这意味着他们只是来这里展示问题。请不要想问题之外(意思是:不要告诉我要务实,放手吧)。

您的 ToStringable 方法有两个问题。

  1. 当您将内部 class 转换为 lambda 表达式时,您删除了编译器的任何提示,即您的 lambda 表达式应该实现 ToStringable。接收器类型是 Object(或者好吧,无论编译器在尝试 log4j API 的一种重载方法时可能会尝试什么)。 lambda 表达式需要一个 目标类型 ,换句话说,如果你没有将它赋值给一个适当类型的变量并且参数类型不合适,你必须插入一个类型转换喜欢 (ToStringable) ()->""
  2. 但这对这里没有帮助,因为您正试图覆盖 java.lang.Object 中声明的方法。 Lambda 表达式不能覆盖从 java.lang.Object ¹.
  3. 继承的方法

为了解决这个问题,您需要一个辅助方法,使用好的旧内部 class 方法来覆盖 Object.toString() 委托给另一个 interface,这可以通过 lambda 表达式实现:

import java.util.function.Supplier;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Log4jTest {

  private static final Logger logger = LogManager.getLogger();
  public static void main (String[] args) {
    logger.info("Text: {}", lazy( ()->getText() ));
    logger.error("Text: {}", lazy(Log4jTest::getText));
  }
  // reusable helper method
  static Object lazy(Supplier<String> s) {
    return new Object() {
      @Override
      public String toString() {
          return s.get();
      }
    };
  }
  static String getText() {
    System.out.println("very long computation");
    return "";
  }
}

此示例使用 lambda 表达式和方法引用展示了两者,当然,行为上的差异源于不同的日志记录级别。只要 log4j 不提供像 the other API 这样的功能接口输入,带有一个内部 class 的辅助方法是不可避免的。但是你只需要实现一次,所有调用者都可以使用 lambda 表达式。

更新(2015 年 8 月 5 日)

考虑投票给这张票 LOG4J2-599 以便为 Log4j 2 带来对 lambda 表达式的支持。


更新(2015 年 8 月 10 日)

Log4J 的下一个版本 2.4 将支持 lambda。用法示例:

// log message is not constructed if DEBUG level is not enabled
logger.debug("Result of some expensive operation is {}", () -> someLongRunningOperation());

问题已经得到解答,这只是一些额外的建议,以防您计划将其与异步记录器或异步附加程序一起使用:

  • 您提到创建消息结果是一项开销很大的操作。我假设您想使用异步 loggers/appenders 来提高应用程序的吞吐量 and/or 响应时间。说得通。请注意,如果创建消息结果非常昂贵,那么(取决于您记录的日志量)您的队列可能会填满;一旦发生这种情况,您实际上是在再次同步登录。您可以调整队列大小以帮助您处理突发事件,但如果持续记录率非常高,您可能 运行 会遇到此问题。 Log4j2 包含 JMX MBeans (1, 2),您可以使用它来查询队列有多满。
  • 输出日志结果将反映后台 I/O 线程调用 Message.getFormattedMessage 时消息的值,而不是应用程序线程调用 [=12] 时消息的值=].如果记录了许多惰性消息,这两个事件之间的时间间隔可能会增加。另请注意,您的消息对象需要是线程安全的。您可能已经知道这一点,但我认为值得指出。

希望有用。