Log4j 2 API
概述
Log4j 2 API 提供了应用程序应该编码的接口,并提供了实现者创建日志记录实现所需的适配器组件。虽然 Log4j 2 在 API 和实现之间进行了划分,但这样做的主要目的不是允许多个实现,尽管这当然是可以的,而是明确定义哪些类和方法在“正常”应用程序代码中是安全的。
你好,世界!
没有介绍会完整,没有传统的 Hello, World 示例。这是我们的。首先,从 LogManager 获取名为“HelloWorld”的记录器。接下来,使用记录器写入“Hello, World!”消息,但是只有当记录器配置为允许信息消息时才会写入消息。
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class HelloWorld { private static final Logger logger = LogManager.getLogger("HelloWorld"); public static void main(String[] args) { logger.info("Hello, World!"); } }
对 logger.info() 的调用的输出将根据使用的配置而有很大差异。有关更多详细信息,请参见 配置 部分。
替换参数
通常,日志记录的目的是提供有关系统中正在发生的事情的信息,这需要包含有关正在操作的对象的信息。在 Log4j 1.x 中,这可以通过执行以下操作来完成
if (logger.isDebugEnabled()) { logger.debug("Logging in user " + user.getName() + " with birthday " + user.getBirthdayCalendar()); }
重复执行此操作会导致代码感觉更像是关于日志记录而不是实际的任务。此外,它会导致日志级别被检查两次;一次在调用 isDebugEnabled 时,一次在调试方法上。更好的选择是
logger.debug("Logging in user {} with birthday {}", user.getName(), user.getBirthdayCalendar());
使用上面的代码,日志级别只会检查一次,并且只有在启用调试日志记录时才会进行字符串构造。
此外,您可以将 Throwable 作为最后一个参数传递。在这种情况下,无需添加额外的替换参数,因为它将由日志记录 API 自动处理。例如
logger.debug("Logging in user {} with birthday {}", user.getName(), user.getBirthdayCalendar(), exception);
格式化参数
如果 toString()
不是您想要的,格式化程序记录器会将格式化留给您。为了便于格式化,您可以使用与 Java 的 Formatter 相同的格式字符串。例如
public static Logger logger = LogManager.getFormatterLogger("Foo"); logger.debug("Logging in user %s with birthday %s", user.getName(), user.getBirthdayCalendar()); logger.debug("Logging in user %1$s with birthday %2$tm %2$te,%2$tY", user.getName(), user.getBirthdayCalendar()); logger.debug("Integer.MAX_VALUE = %,d", Integer.MAX_VALUE); logger.debug("Long.MAX_VALUE = %,d", Long.MAX_VALUE);
要使用格式化程序记录器,您必须调用 LogManager 的 getFormatterLogger 方法之一。此示例的输出显示 Calendar toString() 与自定义格式相比很冗长
2012-12-12 11:56:19,633 [main] DEBUG: User John Smith with birthday java.util.GregorianCalendar[time=?,areFieldsSet=false,areAllFieldsSet=false,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/New_York",offset=-18000000,dstSavings=3600000,useDaylight=true,transitions=235,lastRule=java.util.SimpleTimeZone[id=America/New_York,offset=-18000000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=?,YEAR=1995,MONTH=4,WEEK_OF_YEAR=?,WEEK_OF_MONTH=?,DAY_OF_MONTH=23,DAY_OF_YEAR=?,DAY_OF_WEEK=?,DAY_OF_WEEK_IN_MONTH=?,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=?,ZONE_OFFSET=?,DST_OFFSET=?] 2012-12-12 11:56:19,643 [main] DEBUG: User John Smith with birthday 05 23, 1995 2012-12-12 11:56:19,643 [main] DEBUG: Integer.MAX_VALUE = 2,147,483,647 2012-12-12 11:56:19,643 [main] DEBUG: Long.MAX_VALUE = 9,223,372,036,854,775,807
混合记录器与格式化程序记录器
格式化程序记录器可以对输出格式进行细粒度控制,但缺点是必须指定正确的类型(例如,为 %d 格式参数传递除十进制整数以外的任何内容都会导致异常)。
如果您的主要用途是使用 {} 样式参数,但偶尔需要对输出格式进行细粒度控制,则可以使用 printf
方法
public static Logger logger = LogManager.getLogger("Foo"); logger.debug("Opening connection to {}...", someDataSource); logger.printf(Level.INFO, "Logging in user %1$s with birthday %2$tm %2$te,%2$tY", user.getName(), user.getBirthdayCalendar());
Java 8 lambda 支持延迟日志记录
在 2.4 版本中,Logger
接口添加了对 lambda 表达式的支持。这允许客户端代码延迟记录消息,而无需显式检查是否启用了请求的日志级别。例如,以前您会编写
// pre-Java 8 style optimization: explicitly check the log level // to make sure the expensiveOperation() method is only called if necessary if (logger.isTraceEnabled()) { logger.trace("Some long-running operation returned {}", expensiveOperation()); }
使用 Java 8,您可以使用 lambda 表达式实现相同的效果。您不再需要显式检查日志级别
// Java-8 style optimization: no need to explicitly check the log level: // the lambda expression is not evaluated if the TRACE level is not enabled logger.trace("Some long-running operation returned {}", () -> expensiveOperation());
记录器名称
大多数日志记录实现使用分层方案来匹配记录器名称与日志记录配置。在此方案中,记录器名称层次结构由记录器名称中的 '.'
字符表示,其方式与用于 Java 包名称的层次结构非常相似。例如,org.apache.logging.appender
和 org.apache.logging.filter
都有 org.apache.logging
作为其父级。在大多数情况下,应用程序通过将当前类的名称传递给 LogManager.getLogger(...)
来命名其记录器。由于此用法非常普遍,因此 Log4j 2 在记录器名称参数被省略或为 null 时将其作为默认值。例如,在下面的所有示例中,记录器将具有 "org.apache.test.MyTest"
的名称。
package org.apache.test; public class MyTest { private static final Logger logger = LogManager.getLogger(MyTest.class); }
package org.apache.test; public class MyTest { private static final Logger logger = LogManager.getLogger(MyTest.class.getName()); }
package org.apache.test; public class MyTest { private static final Logger logger = LogManager.getLogger(); }