说明
maven工程中增加对Log4j 2的依赖
下面代码示例的maven工程中的pom.xml文件中需要增加对Log4j 2的依赖:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.20.0</version>
</dependency>
log4j 2的配置
配置说明参考文档
https://logging.apache.org/log4j/2.x/manual/configuration.html
配置文件中pattern的详细说明
例如,下面配置文件片段中用到了pattern:
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
<File name="File1" fileName="${filename}">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</File>
</Appenders>
pattern的详细说明请参考:
https://logging.apache.org/log4j/2.x/manual/layouts.html#PatternLayout
通常给每个类创建自己的Logger
为了便于对日志的过滤、搜索、排序等,通常每个类都获取它自己的带名字的Logger,而不是所有类共用一个Logger。
例如通常的做法:
private static final Logger logger = LogManager.getLogger();
创建一个Logger ,名字就是调用类的全限定名。
建议将Logger 声明为static的
Logger 可以声明为static的、或者非static的,但建议声明为static的。这样做的目的是为了节约实例化的成本。
Logger的名字
大多数log实现用层级结构匹配Logger的名字和日志的配置,层级结构用点号“.”表示,跟包名类似。例如,com.thb.register和com.thb.common的父都是com.thb。例如,下面几种写法得到的Logger的名字相同:
package com.thb;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Test {
private static final Logger logger = LogManager.getLogger();
}
package com.thb;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Test {
private static final Logger logger = LogManager.getLogger(Test.class);
}
package com.thb;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Test {
private static final Logger logger = LogManager.getLogger(Test.class.getName());
}
替换参数
在记录日志时,经常需要将一些动态信息放进去,log4j 2和log4j 1.x的做法不同。
在log4j 1.x中的做法为如下:
// log4j 1.x中的做法,代码中要显式判断对应级别的日志是否打开
if (logger.isInfoEnabled()) {
logger.info("名字为:" + name);
}
上面代码,对日志级别的判断实际进行了两次,第一次是调用isInfoEnabled()方法的时候,第二次是调用info方法记录日志的时候。如果记录日志的地方多,代码就显得庞杂
在log4j 2中的做法为:
// log4j 2中的做法,用替换参数,代码中不需要显式判断对应级别的日志是否打开
logger.info("名字为:{}", name);
上面代码,对日志级别的判断只进行了一次,而且日志字符串只有在对应级别的日志允许输出的情况下才进行构造。代码更加简洁
备注:使用替换参数,后面的参数值如果是函数(没有使用Lambda表达式的方式)就达不到提升性能的效果了,因为此时无论该级别的日志是否真正打开,函数都会被执行。
通过Java-8的Lambda表达式支持日志消息懒构造
log4j 2通过Java-8的Lambda表达式支持日志消息懒构造,即日志消息在对应的日志级别打开的情况下才构造:
logger.info("the information is {}", () -> expensiveOperation())
log4j 2的日志级别
下面日志级别按照由高到低列出:
OFF:不记录日志(其实这个本质上不是日志级别,是个关闭所有日志开关)
FATAL
ERROR
WARN
INFO
DEBUG
TRACE
ALL:所有日志都记录(其实这个本质上不是日志级别,是个打开所有日志开关)
在过滤器或者Logger中配置了某个级别的日志,那么实际会记录该级别及该级别以上的日志。例如,如果配置了日志级别为INFO,那么实际会记录FATAL、ERROR、WARN、和INFO几个级别的日志。
代码示例
代码示例公共说明
如果没有特别说明,下面代码示例中maven工程中src/main/resources/log4j2.xml的配置内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
<File name="File1" fileName="${filename}">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</File>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
用LogManager.ROOT_LOGGER_NAME获取root Logger的名字
LogManager.ROOT_LOGGER_NAME是root Logger的名字,这个名字是空字符串""。
package com.thb;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Test {
public static Logger logger = LogManager.getLogger();
public static void main(String[] args) {
System.out.println("root logger name: " + LogManager.ROOT_LOGGER_NAME);
//System.out.println("logger name: " + logger.getName());
}
}
运行结果:
从上面输出结果可以发现,root Logger的名字是空字符串""。
用LogManager的getLogger()获取一个带名字的Logger
LogManager.getLogger()返回一个带名字的Logger,这个Logger的名字就是调用的类的全限定名称。这个方法经常使用。
下面代码中获取Logger的方法是典型的获取方法。
package com.thb;
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) {
System.out.println("logger name: " + logger.getName());
}
}
运行输出:
从上面输出可以看出,Logger的名字是调用类的全限定名称,此处是com.thb.Test。
几种方法获取相同名字的Logger
方法一:
package com.thb;
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) {
System.out.println(logger.getName());
}
}
运行输出:
com.thb.Test
方法二:
package com.thb;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Test {
private static final Logger logger = LogManager.getLogger(Test.class);
public static void main(String[] args) {
System.out.println(logger.getName());
}
}
运行输出:
com.thb.Test
方法三:
package com.thb;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Test {
private static final Logger logger = LogManager.getLogger(Test.class.getName());
public static void main(String[] args) {
System.out.println(logger.getName());
}
}
运行输出:
com.thb.Test
用LogManager的getLogger(String name)创建Logger时指定名字
package com.thb;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Test {
// 创建Logger时明确指定了名字为Thb
private static final Logger logger = LogManager.getLogger("Thb");
public static void main(String[] args) {
double r = 2;
// log4j 2中的做法,代码中不需要显式判断对应级别的日志是否打开
logger.info("面积为:{}", Math.PI * Math.pow(r, 2));
}
}
运行输出:
08:55:31.715 [main] INFO Thb - 面积为:12.566370614359172
在控制台打印一条INFO级别的日志
打印日志的代码:
package com.thb;
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) {
System.out.println("logger name: " + logger.getName());
logger.info("hello");
}
}
输出:
在控制台打印日志,使用替换参数构造日志字符串
package com.thb;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Demo {
/**
* 日志类.
*/
private static final Logger LOGGER = LogManager.getLogger();
/**
* 主函数.
* @param args
*/
public static void main(String[] args) {
final String name = "Mike";
final Demo demo = new Demo();
LOGGER.info("name is {}", name);
}
}
运行输出:
08:30:09.638 [main] INFO com.thb.Demo - name is Mike
不正确地使用替换参数:参数值是函数(不是Lambda表达式)
下面代码中本意是要使用替换参数的方式提升性能(字符串晚构造),但因为参数值是函数(不是Lambda表达式的形式),结果就是即便该级别的日志没有实际打开,函数也会被执行。
package com.thb;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Demo {
/**
* 日志类.
*/
private static final Logger LOGGER = LogManager.getLogger();
/**
* 主函数.
* @param args
*/
public static void main(String[] args) {
final Demo demo = new Demo();
// 即便这个级别日志级别没有打开,函数method2也会被执行,因为逗号后面是函数
LOGGER.trace("name is {}", demo.method2());
}
/**
* 函数2.
* @return 字符串
*/
public String method2() {
System.out.println("in method2");
return "in method2";
}
}
运行输出:
in method2
从上面输出可以看到,尽管trace级别的日志没有被打印,但代替参数值的函数还是被执行了(我们不期望它执行),这样就没有达到提升性能的目的。
通过Java-8的Lambda表达式支持日志消息懒构造
package com.thb;
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) {
double r = 2;
// 通过Java-8的Lambda表达式支持日志消息懒构造
logger.info("面积为:{}", () -> Math.PI * Math.pow(r, 2));
}
}
运行输出:
09:29:48.911 [main] INFO com.thb.Test - 面积为:12.566370614359172
多个参数都使用Lambda表达式
package com.thb;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Demo {
/**
* 日志类.
*/
private static final Logger LOGGER = LogManager.getLogger();
private static final int AGE = 10;
/**
* 主函数.
* @param args
*/
public static void main(String[] args) {
final Demo demo = new Demo();
LOGGER.info("name is {} and age is {}", () -> demo.method1(), () -> demo.method2());
}
/**
* 函数1.
* @return 名字
*/
public String method1() {
return "ok";
}
/**
* 函数2.
* @return 年龄
*/
public int method2() {
return AGE;
}
}
运行输出:
13:34:17.281 [main] INFO com.thb.Demo - name is ok and age is 10
参数使用Lambda表达式,因为日志级别没有打开,所以Lambda表达式不会执行
package com.thb;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Demo {
/**
* 日志类.
*/
private static final Logger LOGGER = LogManager.getLogger();
/**
* 主函数.
* @param args
*/
public static void main(String[] args) {
final Demo demo = new Demo();
// 如果这个级别的日志没有打开,method3不会被执行,因为使用了Lambda表达式
LOGGER.trace("name is {}", () -> demo.method3());
}
/**
* 函数3.
* @return 字符串
*/
public String method3() {
System.out.println("in method3");
return "in method3";
}
}
运行以后没有任何输出,这个是因为trace级别的日志没有被打开,Lambda表达式没有被执行,因此函数method3没有被执行。
两个类都定义了静态的Logger,并且在一个类中调用另外一个类的方法
定义一个类,类中定义了自己的静态Logger:
package com.thb;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class AnotherClass {
private static final Logger logger = LogManager.getLogger();
public void method() {
logger.info("hello from AnotherClass");
}
}
定义一个主类,在主类中也定义了一个静态Logger:
package com.thb;
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("hello from Test");
AnotherClass another = new AnotherClass();
another.method();
}
}
运行结果:
15:10:28.161 [main] INFO com.thb.Test - hello from Test
15:10:28.199 [main] INFO com.thb.AnotherClass - hello from AnotherClass
用traceEntry(String format, Object… params)在函数的入口记录trace日志
maven工程中src/main/resources/log4j2.xml的配置内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
<File name="File1" fileName="${filename}">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</File>
</Appenders>
<Loggers>
<Root level="trace">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
记录日志的代码示例:
package com.thb;
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) {
Test test = new Test();
test.method("hello", 10);
}
public void method(String name, int age) {
logger.traceEntry("name: {} and age: {}", name, age);
}
}
运行输出:
10:26:48.335 [main] TRACE com.thb.Test - Enter name: hello and age: 10
用traceExit(R result)在函数的结尾记录trace日志
maven工程中src/main/resources/log4j2.xml的配置内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
<File name="File1" fileName="${filename}">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</File>
</Appenders>
<Loggers>
<Root level="trace">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
记录日志的代码示例:
package com.thb;
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) {
Test test = new Test();
test.method("hello", 10);
}
public boolean method(String name, int age) {
boolean result = true;
logger.traceExit(result);
return result;
}
}
运行输出:
10:36:38.201 [main] TRACE com.thb.Test - Exit with(true)
用traceEntry()和traceExit()记录函数的进入和离开trace日志
如果函数没有参数、或者我们对参数不感兴趣,可以直接用traceEntry()记录函数的进入日志。
如果函数不返回任何结果、或者我们对返回结果不感兴趣,可以直接用traceExit()记录函数的离开日志。
maven工程中src/main/resources/log4j2.xml的配置内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
<File name="File1" fileName="${filename}">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</File>
</Appenders>
<Loggers>
<Root level="trace">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
输出日志代码:
package com.thb;
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) {
Test test = new Test();
test.method();
}
public void method() {
logger.traceEntry();
logger.traceExit();
}
}
运行结果:
11:09:29.794 [main] TRACE com.thb.Test - Enter
11:09:29.799 [main] TRACE com.thb.Test - Exit
用catching(Throwable throwable)打印ERROR级别的异常
catching(Throwable throwable)用 ERROR级别打印异常。
示例:
package com.thb;
import java.io.IOException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Demo {
private static final Logger LOGGER = LogManager.getLogger();
public static void main(String[] args) {
final Demo demo = new Demo();
demo.method();
}
public void method() {
try {
throw new IOException("illegal input type");
} catch(IOException e) {
LOGGER.catching(e);
// do something
} finally {
// do something
}
}
}
运行输出:
用catching(Level level, Throwable throwable)打印指定级别的异常
catching(Level level, Throwable throwable)可以用指定级别打印异常。
示例:
package com.thb;
import java.io.IOException;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Demo {
private static final Logger LOGGER = LogManager.getLogger();
public static void main(String[] args) {
final Demo demo = new Demo();
demo.method();
}
public void method() {
try {
throw new IOException("illegal input type");
} catch(IOException e) {
LOGGER.catching(Level.FATAL, e);
// do something
} finally {
// do something
}
}
}
运行输出: