用Log4j 2记录日志

说明

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("面积为:" + Math.PI * Math.pow(r, 2));
}

上面代码,对日志级别的判断实际进行了两次,第一次是调用isInfoEnabled()方法的时候,第二次是调用info方法记录日志的时候。如果记录日志的地方多,代码就显得庞杂

在log4j 2中的做法为:

// log4j 2中的做法,用替换参数,代码中不需要显式判断对应级别的日志是否打开
logger.info("面积为:{}", Math.PI * Math.pow(r, 2));

上面代码,对日志级别的判断只进行了一次,而且日志字符串只有在对应级别的日志允许输出的情况下才进行构造。代码更加简洁

通过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");
	}

}

输出:

在控制台打印日志,使用替换参数构造日志字符串

为了进行对比,首先给出log4j 1.x中的做法:

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;
				
		// log4j 1.x中的做法,代码中要显式判断对应级别的日志是否打开
		if (logger.isInfoEnabled()) {
			logger.info("面积为:" + Math.PI * Math.pow(r, 2));
		}
	}

}

运行输出:

08:41:01.549 [main] INFO  com.thb.Test - 面积为:12.566370614359172

再给出log4j 2中的做法:

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;
		
		// log4j 2中的做法,使用替换参数,代码中不需要显式判断对应级别的日志是否打开
		logger.info("面积为:{}", Math.PI * Math.pow(r, 2));
	}

}

运行输出:

08:43:14.741 [main] INFO  com.thb.Test - 面积为:12.566370614359172

通过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

两个类都定义了静态的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
相关推荐
守护者1706 分钟前
JAVA学习-练习试用Java实现“使用Arrays.toString方法将数组转换为字符串并打印出来”
java·学习
源码哥_博纳软云7 分钟前
JAVA同城服务场馆门店预约系统支持H5小程序APP源码
java·开发语言·微信小程序·小程序·微信公众平台
禾高网络9 分钟前
租赁小程序成品|租赁系统搭建核心功能
java·人工智能·小程序
学会沉淀。15 分钟前
Docker学习
java·开发语言·学习
如若12316 分钟前
对文件内的文件名生成目录,方便查阅
java·前端·python
初晴~1 小时前
【Redis分布式锁】高并发场景下秒杀业务的实现思路(集群模式)
java·数据库·redis·分布式·后端·spring·
黑胡子大叔的小屋2 小时前
基于springboot的海洋知识服务平台的设计与实现
java·spring boot·毕业设计
ThisIsClark2 小时前
【后端面试总结】深入解析进程和线程的区别
java·jvm·面试
雷神乐乐2 小时前
Spring学习(一)——Sping-XML
java·学习·spring
小林coding3 小时前
阿里云 Java 后端一面,什么难度?
java·后端·mysql·spring·阿里云