在 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; }
}
}
这段代码有两个问题。
- 我不能使用通常用于日志记录的
{}
。
- 非常冗长。
我在 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
方法有两个问题。
- 当您将内部 class 转换为 lambda 表达式时,您删除了编译器的任何提示,即您的 lambda 表达式应该实现
ToStringable
。接收器类型是 Object
(或者好吧,无论编译器在尝试 log4j API 的一种重载方法时可能会尝试什么)。 lambda 表达式需要一个 目标类型 ,换句话说,如果你没有将它赋值给一个适当类型的变量并且参数类型不合适,你必须插入一个类型转换喜欢 (ToStringable) ()->""
- 但这对这里没有帮助,因为您正试图覆盖
java.lang.Object
中声明的方法。 Lambda 表达式不能覆盖从 java.lang.Object
¹. 继承的方法
为了解决这个问题,您需要一个辅助方法,使用好的旧内部 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] 时消息的值=].如果记录了许多惰性消息,这两个事件之间的时间间隔可能会增加。另请注意,您的消息对象需要是线程安全的。您可能已经知道这一点,但我认为值得指出。
希望有用。
我有这个代码:
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; }
}
}
这段代码有两个问题。
- 我不能使用通常用于日志记录的
{}
。 - 非常冗长。
我在 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
方法有两个问题。
- 当您将内部 class 转换为 lambda 表达式时,您删除了编译器的任何提示,即您的 lambda 表达式应该实现
ToStringable
。接收器类型是Object
(或者好吧,无论编译器在尝试 log4j API 的一种重载方法时可能会尝试什么)。 lambda 表达式需要一个 目标类型 ,换句话说,如果你没有将它赋值给一个适当类型的变量并且参数类型不合适,你必须插入一个类型转换喜欢(ToStringable) ()->""
- 但这对这里没有帮助,因为您正试图覆盖
java.lang.Object
中声明的方法。 Lambda 表达式不能覆盖从java.lang.Object
¹. 继承的方法
为了解决这个问题,您需要一个辅助方法,使用好的旧内部 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] 时消息的值=].如果记录了许多惰性消息,这两个事件之间的时间间隔可能会增加。另请注意,您的消息对象需要是线程安全的。您可能已经知道这一点,但我认为值得指出。
希望有用。