Java日志
1基础概念
1.1日志门面
- JCL 一套日志的 API ,在不改变代码的情况下,接入不同的第三方日志框架,切换不同的具体实现,[常用 log4j 作为实现]
- SLF4j
1.2日志框架
- JUL
java util logging - Log4j
- Log4j2
- Logback
日志门面是日志规范接口,日志框架是对日志门面的一种实现,类似JDBC。门面就是标准[specification]
2 JUL
2.1JUL 是什么
JUL 全称 Java Util Logging,它是Java的原生的日志框架,使用时不需要另外引入第三方类库,相对其他的框架使用方便。适用于小型项目应用中。
2.2 JUL 组件
Logger:被成为记录器,应用程序通过获取logger对象,用其 API 来发布日志信息。logger通常被认为是访问日志系统的入口程序。
Handler:处理器,每个Logger都会关联一个或者一组Handler,Logger会将日志交给关联的Handler去处理,有Handler负责将日志做几路。Handler具体实现了是指的输出位置,比如可以输出到控制台或者文件中,等等。
Filter:过滤器,根据需要定制哪些信息回被记录,哪些心事会被略过。
Formatter:格式化组件,让负责对日志中的数据和信息进行转换和格式化,所以他决定了日志最终的输出形式。
Level:日志输出级别,每条日志消息都有一个关联的级别,设置输出级别,设置debug级别信息,用来展现最终所呈现的日志信息,那么info级别的信息就会被忽略。根据不同的需求,取设置不同的级别。
入门案例
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* @author chuan
* @Classname JULTest
* @Description
* @Version 1.0.0
* @Date 2024/11/11 23:21
*/public class JULTest {
@Test
public void testLogging() {
Logger logger = java.util.logging.Logger.getLogger(JULTest.class.getName());
// 直接指定日志级别输出日志
logger.info("This is a test log message.");
// 打印日志,设置级别,输出日志
logger.log(Level.INFO, "This is a test log message with level.");
}
}
2.3 日志级别
public static final Level OFF = new Level("OFF",Integer.MAX_VALUE, defaultBundle);
public static final Level SEVERE = new Level("SEVERE",1000, defaultBundle);
public static final Level WARNING = new Level("WARNING", 900, defaultBundle);
public static final Level INFO = new Level("INFO", 800, defaultBundle);
public static final Level CONFIG = new Level("CONFIG", 700, defaultBundle);
public static final Level FINE = new Level("FINE", 500, defaultBundle);
public static final Level FINER = new Level("FINER", 400, defaultBundle);
public static final Level FINEST = new Level("FINEST", 300, defaultBundle);
public static final Level ALL = new Level("ALL", Integer.MIN_VALUE, defaultBundle);
| 日志级别 | 数值 | 备注 |
|---|---|---|
| OFF | int 最大值 | |
| SEVERE | 1000 | 错误 |
| WARNING | 900 | 警告 |
| INFO | 800 | 默认日志级别 |
| CONFIG | 700 | |
| FINE | 500 | |
| FINER | 400 | |
| FINEST | 300 | |
| ALL | int 最小值 | |
| 日志级别数值的意义,在于设置日志级别时,假设指定默认级别为 CONFIG 那么比 CONFIG 日志级别数值低的日志级别的日志,日志系统不处理[不打印],一半系统默认日志级别是 INFO,这意味着比 INFO 级别数值低的日志,是不会打印的。 |
设置日志级别的方法
logger.setLevel(Levle.CONFIG);
上面的方法虽然设置了日志级别,但并不意味着 CONFIG 这个级别的日志能够打印,需要实现相应的 Handler 才能实现默认以下级别日志的打印。
@Test
public void testLogging2() {
Logger logger = java.util.logging.Logger.getLogger(JULTest.class.getName());
// 设置日志级别为ALL 这里的设置并不能生效,在getLogger会读取配置文件中的设置,如果没有配置文件,则使用默认的配置 INFO logger.setLevel(Level.ALL);
// 输出日志,设置级别,输出日志
logger.log(Level.SEVERE, "This is a test log message with level SEVERE.");
logger.log(Level.WARNING, "This is a test log message with level WARNING.");
logger.log(Level.INFO, "This is a test log message with level INFO.");
logger.log(Level.CONFIG, "This is a test log message with level CONFIG.");
logger.log(Level.FINE, "This is a test log message with level FINE.");
logger.log(Level.FINER, "This is a test log message with level FINER.");
logger.log(Level.FINEST, "This is a test log message with level FINEST.");
logger.log(Level.ALL, "This is a test log message with level ALL.");
}
在代码中Logger.getLogger(T.class),通过阅读源码,代码会读取一个配置文件,如果在配置文件中设置了日志级别,代码会以设置的级别为主,否则设置默认的日志级别。默认的的日志处理器Handler,[会继承级别 / 未验证],所以即使设置了 logger的日志级别,由于Handler不处理相应级别的日志,会导致设置不生效,[日志级别向下设置不生效 / 向上未知]。
自定义日志处理器,并打印所有级别的日志
@Test
public void testLogging3() {
Logger logger = java.util.logging.Logger.getLogger(JULTest.class.getName());
// 不使用父类处理器 [不使用父类处理器,则需要自己设置日志处理器]
logger.setUseParentHandlers(false);
// 自定义日志处理器[控制台输出]
ConsoleHandler customHandler = new ConsoleHandler();
// 创建日志格式化器
SimpleFormatter formatter = new SimpleFormatter();
// 设置日志格式化器
customHandler.setFormatter(formatter);
// 设置日志处理器级别
customHandler.setLevel(Level.ALL);
// 设置日志级别 [日志处理器和日志级别都需要设置,否则不会生效]
logger.setLevel(Level.ALL);
// 添加日志处理器
logger.addHandler(customHandler);
// 输出日志,设置级别,输出日志
logger.log(Level.SEVERE, "This is a test log message with level SEVERE.");
logger.log(Level.WARNING, "This is a test log message with level WARNING.");
logger.log(Level.INFO, "This is a test log message with level INFO.");
logger.log(Level.CONFIG, "This is a test log message with level CONFIG.");
logger.log(Level.FINE, "This is a test log message with level FINE.");
logger.log(Level.FINER, "This is a test log message with level FINER.");
logger.log(Level.FINEST, "This is a test log message with level FINEST.");
logger.log(Level.ALL, "This is a test log message with level ALL.");
}
/**
* 自定义日志处理器 [文件输出] FileHandler 日志格式化器 SimpleFormatter
*/@Test
public void testLogging4() throws IOException {
Logger logger = java.util.logging.Logger.getLogger(JULTest.class.getName());
// 不使用父类处理器 [不使用父类处理器,则需要自己设置日志处理器]
logger.setUseParentHandlers(false);
// 确保 log 文件夹存在
Files.createDirectories(Paths.get("log"));
// 自定义日志处理器 [文件输出]
FileHandler customHandler = new FileHandler("log/JULTest.log",true);
// 创建日志格式化器
SimpleFormatter formatter = new SimpleFormatter();
// 设置日志格式化器
customHandler.setFormatter(formatter);
// 设置日志处理器级别
customHandler.setLevel(Level.ALL);
// 设置日志级别 [日志处理器和日志级别都需要设置,否则不会生效]
logger.setLevel(Level.ALL);
// 添加日志处理器
logger.addHandler(customHandler);
// 输出日志,设置级别,输出日志
logger.log(Level.SEVERE, "This is a test log message with level SEVERE.");
logger.log(Level.WARNING, "This is a test log message with level WARNING.");
logger.log(Level.INFO, "This is a test log message with level INFO.");
logger.log(Level.CONFIG, "This is a test log message with level CONFIG.");
logger.log(Level.FINE, "This is a test log message with level FINE.");
logger.log(Level.FINER, "This is a test log message with level FINER.");
logger.log(Level.FINEST, "This is a test log message with level FINEST.");
logger.log(Level.ALL, "This is a test log message with level ALL.");
}
/**
* 自定义日志处理器 [文件输出 + 控制台输出] FileHandler 日志格式化器 SimpleFormatter
* 两个 Handler 共用一个 Formatter 也可以为不同的Handler 设置不同的Formatter 以差异化输出不同的日志格式
* 不同的 Handler 也可以设置不同的日志级别
*/
@Test
public void testLogging5() throws IOException {
Logger logger = java.util.logging.Logger.getLogger(JULTest.class.getName());
// 不使用父类处理器 [不使用父类处理器,则需要自己设置日志处理器]
logger.setUseParentHandlers(false);
// 确保 log 文件夹存在
Files.createDirectories(Paths.get("log"));
// 自定义日志处理器 [文件输出]
FileHandler customHandler = new FileHandler("log/JULTest.log",true);
// 创建日志格式化器
SimpleFormatter formatter = new SimpleFormatter();
// 设置日志格式化器
customHandler.setFormatter(formatter);
// 设置日志处理器级别
customHandler.setLevel(Level.ALL);
// 设置日志级别 [日志处理器和日志级别都需要设置,否则不会生效]
logger.setLevel(Level.ALL);
// 添加日志处理器
logger.addHandler(customHandler);
// 添加控制台输出
ConsoleHandler consoleHandler = new ConsoleHandler();
consoleHandler.setFormatter(formatter);
consoleHandler.setLevel(Level.ALL);
logger.addHandler(consoleHandler);
// 输出日志,设置级别,输出日志
logger.log(Level.SEVERE, "This is a test log message with level SEVERE.");
logger.log(Level.WARNING, "This is a test log message with level WARNING.");
logger.log(Level.INFO, "This is a test log message with level INFO.");
logger.log(Level.CONFIG, "This is a test log message with level CONFIG.");
logger.log(Level.FINE, "This is a test log message with level FINE.");
logger.log(Level.FINER, "This is a test log message with level FINER.");
logger.log(Level.FINEST, "This is a test log message with level FINEST.");
logger.log(Level.ALL, "This is a test log message with level ALL.");
}
阶段性总结: 用户使用 Logger 来进行日志的记录,Logger 可以持有多个处理器 Handler [日志的记录使用的是 Logger ,日志的输出使用的 Handler ]
2.4 Logger 层级
Logger之间的父子关系 JUL 中的Logger之间的“父子” 关系,非普遍的 [继承] 关系,是通过树形结构存储的上下级关系
@Test
public void testLogging6() throws IOException {
// Logger 的层次结构
// 根 Logger 名称为 "" // 子 Logger 名称为 父 Logger 名称 + "." + 子 Logger 名称
// 例如:cn.qzbook.test 父 Logger 名称为 cn.qzbook 子 Logger 名称为 test
Logger logger0 = java.util.logging.Logger.getLogger("cn");
Logger logger1 = java.util.logging.Logger.getLogger("cn.qzbook");
Logger logger2 = java.util.logging.Logger.getLogger("cn.qzbook.test");
// 通过引用关系判断 Logger 层次结构
System.out.println(logger0 == logger1.getParent());
System.out.println(logger1 == logger2.getParent());
}
在 Logger 的上级的设置,同时能够作用于下级 Logger 对于 上面代码 在logger0 做的设置,会作用于 logger1 和 logger2
@Test
public void testLogging7() throws IOException {
// 日志配置文件
Logger logger0 = java.util.logging.Logger.getLogger("cn");
Logger logger1 = java.util.logging.Logger.getLogger("cn.qzbook");
Logger logger2 = java.util.logging.Logger.getLogger("cn.qzbook.test");
// [上层的 Logger]不使用父类[再上层的 Logger]处理器
logger0.setUseParentHandlers(false);
// 修改上层的 Logger 日志级别为 ALL 输出到控制台
logger0.setLevel(Level.ALL);
ConsoleHandler handler = new ConsoleHandler();
handler.setLevel(Level.ALL);
handler.setFormatter(new SimpleFormatter());
logger0.addHandler(handler);
// 使用下层的 Logger 输出日志 上层的 Logger 设置的日志级别和日志处理器都会生效
logger2.log(Level.SEVERE, "This is a test log message with level SEVERE.");
logger2.log(Level.WARNING, "This is a test log message with level WARNING.");
logger2.log(Level.INFO, "This is a test log message with level INFO.");
logger2.log(Level.CONFIG, "This is a test log message with level CONFIG.");
logger2.log(Level.FINE, "This is a test log message with level FINE.");
logger2.log(Level.FINER, "This is a test log message with level FINER.");
logger2.log(Level.FINEST, "This is a test log message with level FINEST.");
logger2.log(Level.ALL, "This is a test log message with level ALL.");
// 根据上面的日志 配置 直接在 root Logger 上设置日志级别和处理器,则全局生效
}
阶段性总结: JUL 在初始化 Logger 时,会创建一个顶层的 Logger [RootLogger] 作为所有 Logger 最顶级父 Logger
// JUL 获取java Logger 部分源码
// RootLogger 是 LogManger 的内部类
owner.rootLogger = owner.new RootLogger();
RootLogger 对象作为输转结构的根节点存在,自定义的 Logger 父子关系也是通过路径[包路径] 来关联的。
LoggerContent 日志结构上下文,内部不使用 Map 存储 Logger,Key 为路径,value 为 具体的 Logger 对象。
2.5 配置文件
以上所有的配置相关的操作,都是以java硬变化的形式进行的,还可以通过配置文件的方式来配置 Logger 对象,跟方便解耦
如果没有自定义配置文件,则会使用系统默认的配置文件来初始化 Logger,系统默认配置部分源码
owner.readPrimordialConfiguation();
读取的默认配置文件路径 java.home --> 找到 jre 文件夹 --> lib --> logging.properties
配置文件-配置项 在配置文件中配置的是根节点,也就是 RootLogger ,在获取 RootLogger对象时进行设置,默认情况下
# 日志处理器 多个处理器使用 逗号 [,] 分割
handlers=java.util.logging.ConsoleHandler,java.util.logging.FileHandler
# 根节点 日志级别
# 默认情况下,这是全局的日志级别,如果不手动设置其他级别,则默认输出INFO或者比INFO更高的级别
.level= ALL
# 文件日志处理器的 属性配置
# 输出日志文件的路径 %h 代表主机名[用户目录], %u 代表用户名
#java.util.logging.FileHandler.pattern=%h/java%u.log
# 输出日志文件的路径 直接写文件夹名称 相对路径 当前项目的根目录
java.util.logging.FileHandler.pattern=log/java%u.log
# 输出日志文件的显示 50000字节
java.util.logging.FileHandler.limit=50000
# 输出日志文件的数量
java.util.logging.FileHandler.count=1
# 输出日志的格式
# 默认是以 xml 的方式进行输出
#java.util.logging.FileHandler.formatter=java.util.logging.XMLFormatter
# 以文本方式输出日志
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
# 文件日志默认是覆盖模式新增 设置为 - 追加模式
java.util.logging.FileHandler.append=true
# 控制台日志处理器 属性配置
# 控制台输出默认的级别
java.util.logging.ConsoleHandler.level=ALL
# 控制台默认输出的格式
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
# 包名.属性=值 即可设置指定包下的日志配置
# 指定在某个包路径下 指定的日志级别
#cn.qzbook.level = SEVERE
2.6 使用总结
- 初始化 LogManager
- LogManager 加在 logging.properties 配置文件
- 从单例 LogManager 中获取 LogManager [单例工厂]
- 设置日志级别 Level ,在打印的过程中使用到了日志记录的LogRecord类
- Fileter 作为过滤器,提供了日志级别之外,更细粒度的控制器
- Handler 日志处理器,决定日志的输出位置,例如:控制台、文件...
- Formatter 是用来格式化输出的
3 Log4J
3.1 Log4J 简介
Log4J 是 Apache 是一个开源项目,通过使用 Log4j,我们可以控制日志信息的输送目的地是控制台、文件、GUI 组件,甚至是套接口服务器、NT 的事件记录器、UNIX Syslog 守护进程等;也可以控制每一条日志的输出格式,通过定义每一条日志信息的级别,能够更加细致的控制日志的生成过程。
3.2 Log4J 组件
组件概述
Loggers [日志记录器] : 控制日志的输出以及输出级别
Appenders [输出控制器] : 指定日志的输出方式,控制台,文件 ...
Layout [日志格式化器] : 控制日志信息的输出格式
3.2.1 Loggers
日志记录器,负责手机处理日志记录,实例的命名就是类的全限定名,如 cn.qzbook.Test,Logger 的名字大小写敏感,其命名有继承机制,下级包名会继承上级包名。Log4J 有一个特殊的 logger 是 “root”,他是所有的logger的根,也就意味着其他所有的logger都会直接或间接的继承自root。root logger 可以用logger.getRootLogger()方法获取。
日志根节点的名称,为空字符串 "",下一级为以及包名,依次按包名层级结构向下分级。
日志级别: DEBUG、INFO、WARN、ERROR ... 级别分别是从大到小的。
- ALL 最低等级,用于打开所有级别的日志记录
- TRACE 程序推进下的追踪信息,这个追踪信息的日志级别非常低,一般情况下是不会使用的
- DEBUG 指出细粒度信息时间对调试应用程序是非常有帮助的,主要是配合开发,在开发过程中打印一些重要的运行信息
- INFO 消息的粗粒度级别运行信息
- WARN 表示警告,程序运行过程中会出现的有可能会发生隐形的错误,注意,有些信息不是错误,但是这个级别输出的母的就是给程序员提示
- ERROR 系统错误信息,发生的错误不影响系统运行,印版情况下,如果不想输出太多的日志,则使用更改级别即可
- FATAL 表示严重错误,这类型错误发生程序将停止运行
- OFF 最高等级的级别,用户关闭了所有的日志记录
3.2.2 Appenders
常用的 appenders
- ConsoleAppender
- 将日志输出到控制台
- FileAppender
- 将日志输出到文件中
- DailyRollingFileAppender
- 将日志输出到一个日志文件,并且每天输出到一个新的文件 [按照设定的时间来分别创建文件]
- RollingFileAppender
- 将日志信息输出到日志文件,并且指定文件的大小,当文件内容大小超出时,自动修改文件名,并创建的新的文件
- JDBCAppender
- 将日志信息输出到数据库
3.2.3 Layout
格式化日志输出,Log4J可以在Appenders后面附件Layouts来完善这个功能。Layouts提供了四种日志输出样式,如根据HTML样式、自由制定演示、包含日志级别与信息的样式,包含日志时间、现成、类别等信息的样式。
常用的 Layout
- HTMLLayout
- 格式化日志输出为 HTML
- SimpleLayout
- 简单的日志输出格式化,打印日志格式如默认INFO级别的消息
- PatternLayout
- 最强大的格式组件,可以根据自定义格式输出日志,如果没有指定转换格式,就是用默认的转换格式。
启动流程
BasicConfigurator.configure();
// 1. 创建了根节点对象
Logger root = Logger.getRootLogger();
// 2. 根节点添加了 ConsoleAppender 对象 输出日志到工作台 使用默认的输出格式
root.addAppender(new ConsoleAppender(new PatternLayout("%r [%t] %p %c %x - %m%n")));
3.3 基本使用
引入依赖
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
// log4j 包路径
// import org.apache.log4j.BasicConfigurator;
// import org.apache.log4j.Logger;
@Test
public void test() {
// 加载初始化配置 如果不初始化 会报警告信息
BasicConfigurator.configure();
Logger logger = Logger.getLogger(AppLog4JTest.class);
logger.info("This is an info message.");
}
3.4 配置文件
使用配置文件防止硬编码在程序中不方便维护,使用配置文件的方式可以解耦,方便维护
Log4J 提供了多种配置文件的配置方式,在 LogManager 中可以看到
/** @deprecated */
public static final String DEFAULT_CONFIGURATION_FILE = "log4j.properties";
static final String DEFAULT_XML_CONFIGURATION_FILE = "log4j.xml";
/** @deprecated */
public static final String DEFAULT_CONFIGURATION_KEY = "log4j.configuration";
/** @deprecated */
public static final String CONFIGURATOR_CLASS_KEY = "log4j.configuratorClass";
/** @deprecated */
public static final String DEFAULT_INIT_OVERRIDE_KEY = "log4j.defaultInitOverride";
private static Object guard = null;
private static RepositorySelector repositorySelector;
常用的配置方式,使用 log4j.properties 配置文件,它在 LogManager 初始化的时候通过静态代码块加载。 配置文件默认加载源码:
static {
Hierarchy h = new Hierarchy(new RootLogger(Level.DEBUG));
repositorySelector = new DefaultRepositorySelector(h);
String override = OptionConverter.getSystemProperty("log4j.defaultInitOverride", (String)null);
if (override != null && !"false".equalsIgnoreCase(override)) {
LogLog.debug("Default initialization of overridden by log4j.defaultInitOverrideproperty.");
} else {
String configurationOptionStr = OptionConverter.getSystemProperty("log4j.configuration", (String)null);
String configuratorClassName = OptionConverter.getSystemProperty("log4j.configuratorClass", (String)null);
URL url = null;
if (configurationOptionStr == null) {
url = Loader.getResource("log4j.xml");
if (url == null) {
url = Loader.getResource("log4j.properties");
}
} else {
try {
url = new URL(configurationOptionStr);
} catch (MalformedURLException var7) {
url = Loader.getResource(configurationOptionStr);
}
}
if (url != null) {
LogLog.debug("Using URL [" + url + "] for automatic log4j configuration.");
try {
OptionConverter.selectAndConfigure(url, configuratorClassName, getLoggerRepository());
} catch (NoClassDefFoundError var6) {
NoClassDefFoundError e = var6;
LogLog.warn("Error during default initialization", e);
}
} else {
LogLog.debug("Could not find resource: [" + configurationOptionStr + "].");
}
}
}
配置文件在maven项目中resource目录下,可以使用多种格式配置,以下示例基本都是以 log4j.properties 文件形式配置
以下数据是通过 OptionConverter.selectAndConfigure(url, configuratorClassName, getLoggerRepository()); 代码加载配置文件转换为配置 PropertyConfigurator 对象,其中的常量就是在配置文件中配置的配置项
static final String CATEGORY_PREFIX = "log4j.category.";
static final String LOGGER_PREFIX = "log4j.logger.";
static final String FACTORY_PREFIX = "log4j.factory";
static final String ADDITIVITY_PREFIX = "log4j.additivity.";
static final String ROOT_CATEGORY_PREFIX = "log4j.rootCategory";
static final String ROOT_LOGGER_PREFIX = "log4j.rootLogger";
static final String APPENDER_PREFIX = "log4j.appender.";
static final String RENDERER_PREFIX = "log4j.renderer.";
static final String THRESHOLD_PREFIX = "log4j.threshold";
private static final String THROWABLE_RENDERER_PREFIX = "log4j.throwableRenderer";
private static final String LOGGER_REF = "logger-ref";
private static final String ROOT_REF = "root-ref";
private static final String APPENDER_REF_TAG = "appender-ref";
public static final String LOGGER_FACTORY_KEY = "log4j.loggerFactory";
private static final String RESET_KEY = "log4j.reset";
private static final String INTERNAL_ROOT_NAME = "root";
配置方式:
log4j.appender.自定义名称=org.apache.log4j.ConsoleAppender[log4J提供的Appenders] log4j.logger.自定义名称=[log4J提供的logger]
不同的前缀配置不同的组件,前缀后面跟自定义名称为组件命名
简单配置
# 配置 rootLogger 输出级别为 DEBUG,输出到控制台
# 配置项:第一个参数为日志级别, 后续参数为 Appender 名称[线面配置的 log4j.appender.自定义名称]
log4j.rootLogger=DEBUG, console
# 配置 appender 输出方式 输出到控制台
log4j.appender.console=org.apache.log4j.ConsoleAppender
# 配置输出格式
log4j.appender.console.layout=org.apache.log4j.SimpleLayout
@Test
public void test3() {
// 使用配置文件
Logger logger = Logger.getLogger(AppLog4JTest.class);
logger.fatal("This is a fatal message.");
logger.error("This is an error message.");
logger.warn("This is a warning message.");
logger.info("This is an info message.");
logger.debug("This is a debug message.");
logger.trace("This is a trace message.");
}
// LogLog 未生效 在idea启动中添加参数 VM options: -Dlog4j.debug
@Test
public void test4() {
// 开启内部调试模式 打印 logger 的内部日志信息
LogLog.setInternalDebugging(true);
// 确保日志级别为 DEBUG 或更低
Logger.getRootLogger().setLevel(Level.DEBUG);
// 检查标准错误流是否正常工作
System.err.println("This should appear in the standard error stream.");
Logger logger = Logger.getLogger(AppLog4JTest.class);
logger.fatal("This is a fatal message.");
logger.error("This is an error message.");
logger.warn("This is a warning message.");
logger.info("This is an info message.");
logger.debug("This is a debug message.");
logger.trace("This is a trace message.");
}
自定义 日志格式输出,log4j layout配置,其中 PatternLayout 是日常使用最多的方式,支持自定义输出格式,根据配置定义个性化输出。 关于 PatternLayout 在源码中的位置
setConversionPattern(String conversionPattern)
PatternLayout 配置项
- 这种格式化输出,采用类似于 C 语言的 printf 函数的打印格式来格式化日志信息,具体的占位符以及其含义
- %m 输出代码中指定的日志信息
- %p 输出优先级,及 DEBUG、INFO 等
- %n 换行符
- %r 输出自应用启动到输出该 Log 信息的毫秒数
- %c 输出打印语句所属类的全名
- %t 输出产生该日志的线程全名
- %d 输出服务器当前时间,默认为 ISO8601,也可以指定格式,如:%d{yyyy年MM月dd日 HH:mm:ss}
- %1 输出日志时间发生的位置,包括类名、线程、及在代码中的行数。如:Test.main(Test.java:10)
- %F 输出日志消息产生式所在的文件名称
- %L 输出代码中的行号
- %% 输出一个 “%” 字符
- %5c 输出 category 名称,最小宽度是5,category<<5,默认的情况下右对齐
配置文件配置-示例 [控制台输出]
# 配置 rootLogger 输出级别为 DEBUG,输出到控制台
# 配置项:第一个参数为日志级别, 后续参数为 Appender 名称[下面配置的 log4j.appender.自定义名称]
log4j.rootLogger=DEBUG, aa
# 配置 自定义名称的 appender 输出方式 输出到控制台 同一个自定义名称的 Appenders 各个组件配置为一组
log4j.appender.aa=org.apache.log4j.ConsoleAppender
# 配置输出格式 HTML 格式输出 简单输出格式
#log4j.appender.aa.layout=org.apache.log4j.HTMLLayout
#log4j.appender.aa.layout=org.apache.log4j.SimpleLayout
# 自定义输出格式化器
log4j.appender.aa.layout=org.apache.log4j.PatternLayout
# 自定义输出格式 [conversionPattern 是 PatternLayout类的一个属性]
log4j.appender.aa.layout.conversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} %p %c{1}:%L %t - %m%n
配置文件配置-示例 [控制台输出+文件输出]
# 配置 rootLogger 输出级别为 DEBUG,输出到控制台
# 配置项:第一个参数为日志级别, 后续参数为 Appender 名称[下面配置的 log4j.appender.自定义名称]
log4j.rootLogger=DEBUG, aa,outFile
# 配置 appender 输出方式 输出到控制台
log4j.appender.aa=org.apache.log4j.ConsoleAppender
# 配置输出格式 HTML 格式输出 简单输出格式
#log4j.appender.aa.layout=org.apache.log4j.HTMLLayout
#log4j.appender.aa.layout=org.apache.log4j.SimpleLayout
log4j.appender.aa.layout=org.apache.log4j.PatternLayout
log4j.appender.aa.layout.conversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} %p %c{1}:%L %t - %m%n
# 配置 appender 输出方式 输出到文件 [FileAppender 中的属性 在下面的配置中添加配置]
log4j.appender.outFile=org.apache.log4j.FileAppender
# 如: File 是 FileAppender 的一个属性,表示指定文件位置,下面属性同理
log4j.appender.outFile.File=log/log4j.log
log4j.appender.outFile.MaxFileSize=10MB
log4j.appender.outFile.layout=org.apache.log4j.PatternLayout
log4j.appender.outFile.layout.conversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} %p %c{1}:%L %t - %m%n
输出中文设置 查看FileAppender的父类,有一个 encoding 字符集编码属性,配置为 UTF-8,可正确的输出中文
log4j.appender.outFile.layout.encoding=UTF-8
文件输出Appender类:所有的配置,都是类的属性,为属性设置不同的值,达到个性化输出的目的,可配置的项是当前类的属性和父类的属性都可以配置
- FileAppender
- 类的属性按需要配置
- RollingFileAppender
- 文件拆分 [ maxFileSize 文件最大字节 / maxBackupIndex 最大文件数量] 文件数量超过指定数量,从最早的日志数据开始覆盖
- DailyRollingFileAppender
- 按照时间来拆分 [具体配置项查看类属性]
- ExternallyRolledFileAppender
- 外部程序控制拆分,比如:日志文件达到一定大小后,调用外部程序,由外部程序来拆分日志文件,再将日志数据写入新的文件中
# 配置 rootLogger 输出级别为 DEBUG,输出到控制台 ,输出到文件 ,输出到文件[可拆分] 如果下面配置了自定义的Appenders 但在 log4j.rootLogger 中没有配置,是不会生效的
# 配置项:第一个参数为日志级别, 后续参数为 Appender 名称[下面配置的 log4j.appender.自定义名称]
log4j.rootLogger=DEBUG, aa,outFile,outFileRolling
# 配置 appender 输出方式 输出到控制台
log4j.appender.aa=org.apache.log4j.ConsoleAppender
# 配置输出格式 HTML 格式输出 简单输出格式
#log4j.appender.aa.layout=org.apache.log4j.HTMLLayout
#log4j.appender.aa.layout=org.apache.log4j.SimpleLayout
log4j.appender.aa.layout=org.apache.log4j.PatternLayout
log4j.appender.aa.layout.conversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} %p %c{1}:%L %t - %m%n
# 配置 appender 输出方式 输出到文件
log4j.appender.outFile=org.apache.log4j.FileAppender
log4j.appender.outFile.encoding=UTF-8
log4j.appender.outFile.Append=true
log4j.appender.outFile.File=log/log4j.log
log4j.appender.outFile.layout=org.apache.log4j.PatternLayout
log4j.appender.outFile.layout.conversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} %p %c{1}:%L %t - %m%n
# 配置 appender 输出方式 输出到文件
log4j.appender.outFileRolling=org.apache.log4j.RollingFileAppender
log4j.appender.outFileRolling.encoding=UTF-8
log4j.appender.outFileRolling.Append=true
log4j.appender.outFileRolling.File=log/log4j1.log
log4j.appender.outFileRolling.layout=org.apache.log4j.PatternLayout
log4j.appender.outFileRolling.layout.conversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} %p %c{1}:%L %t - %m%n
3.5 持久化日志[JDBC]
自定义数据库字段,按照 PatternLayout 配置项 取值,最终输出到数据库
引入 JDBC 依赖
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>
配置文件
log4j.rootLogger=DEBUG,db
# 配置 输出到数据库
log4j.appender.db=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.db.driver=com.mysql.cj.jdbc.Driver
log4j.appender.db.URL=jdbc:mysql://localhost:3306/log4j
log4j.appender.db.user=root
log4j.appender.db.password=123456
log4j.appender.db.sql=INSERT INTO log4j_log (log_time, log_level, log_class, log_method, log_line, log_message) VALUES (NOW(), '%p', '%c{1}', '%M', '%L', '%m')
log4j.appender.db.layout=org.apache.log4j.PatternLayout
log4j.appender.db.layout.conversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} %p %c{1}:%L %t - %m%n
解释
日志消息格式化: log4j.appender.db.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} %p %c{1}:%L %t - %m%n 定义了日志消息的格式。 例如,一条日志消息可能会被格式化为 2023-10-01 12:34:56.789 DEBUG MyClass:42 main - This is a log message。
SQL 语句中的占位符: log4j.appender.db.sql=INSERT INTO LOGS (TIMESTAMP, LEVEL, LOGGER, THREAD, MESSAGE) VALUES ('%d{yyyy-MM-dd HH:mm:ss.SSS}', '%p', '%c{1}', '%t', '%m') 定义了将日志消息插入数据库的 SQL 语句。
占位符 %d{yyyy-MM-dd HH:mm:ss.SSS}、%p、%c{1}、%t 和 %m 会被 PatternLayout 格式化后的日志消息内容替换。 例如,上述格式化的日志消息会被插入到数据库中,对应的 SQL 语句可能是:
INSERT INTO LOGS (TIMESTAMP, LEVEL, LOGGER, THREAD, MESSAGE)
VALUES ('2023-10-01 12:34:56.789', 'DEBUG', 'MyClass', 'main', 'This is a log message')
总结
- log4j.appender.db.layout 和 log4j.appender.db.layout.conversionPattern 用于定义日志消息的格式。
- log4j.appender.db.sql 用于定义将日志消息插入数据库的 SQL 语句。
- conversionPattern 中的占位符会被 sql 语句中的相应占位符替换,从而将格式化后的日志消息插入到数据库中。
3.6 自定义 Logger
前面所有的配置都是基于 rootLogger 来进行配置的,log4j 可以通过自定Logger的形式来细化log记录的差异化配置。自定义的 Logger 也可以被其他的 Logger 所继承。无论是 rootLogger 还是自定的 Logger 它们的关系依然是树形结构的关系,上下级由包名的的层级关系来组成,也就是说,所有 Logger 的根节点最终还是 rootLogger, 所以全局配置还是配置在rootLogger上,自定义的配置,可以按照业务模块[包结构]做差异化的配置.
自定义 Logger 在配置文件中的前缀为 [log4j.logger.]+包名,包名到哪一级,当前自定义Logger作用到哪一级。它的参数配置和[log4j.rootLogger]是一致的,第一个参数为日志级别, 后续参数为 定义的 Appender 名称,可以与 rootLogger 引用相同的 Appender。
# 自定义 logger 输出级别为 DEBUG,输出到文件
log4j.logger.cn.qzbook=DEBUG, outFile
# 配置 appender 输出方式 输出到文件
log4j.appender.outFile=org.apache.log4j.FileAppender
log4j.appender.outFile.encoding=UTF-8
log4j.appender.outFile.Append=true
log4j.appender.outFile.File=log/log4j.log
log4j.appender.outFile.layout=org.apache.log4j.PatternLayout
log4j.appender.outFile.layout.conversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} %p %c{1}:%L %t - %m%n
此包cn.qzbook级别的日志,采用 outFile 的 appender 将日志文件输出到文件,其他包级别的日志,默认走 rootLogger 的配置。当前自定义的 cn.qzbook 的 logger 自身的配置生效,且还会集成rootLogger,两者的配置都会生效,简单来说,下一级的日志配置本身生效的同时,还会在继承上级日志配置。
4 JCL
全称为 Jakarta commons logging,是 Apache 提供的一个通用日志 API。
用户可以自由选择第三方的日志组件作为具体的实现,像 Log4j,或者 Jdk自带的jul,common-logging 会通过动态查找的机制,在程序运行时自动找出真真使用的日志库。
当然,common-logging 内部是有一个 Simple Logger 的简单实现,但实现较为简单,所以使用 common-logging 的时候,通常配置 log4j 以及其他日志框架来使用。
使用它的好处就是,代码依赖的是 common-logging 而非具体的日志框架,避免了和具体的日志框架 API 直接耦合,在有必要时,可以更改日志实现的第三方库。
JCL 有两个基本的抽象类:
- Log:日志记录器
- LogFactory:日志工厂[负责创建 Log 实例]
4.1 JCL 入门案例
引入依赖
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
hello word 示例
package cn.qzbook;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
public class AppTest{
@Test
public void test1(){
Log log = LogFactory.getLog(AppTest.class);
log.info("Hello, world!");
}
}
控制台打印信息
十一月 19, 2024 11:00:42 下午 cn.qzbook.AppTest test1
信息: Hello, world!
在hello World 示例中,JCL 使用的是 JDK 自带的 JUL 日志框架,因为项目中没有导入任何第三方日志框架
JCL 使用原则:如果有项目中导入了 Log4j框架,JCL 优先使用 Log4j 作为日志实现,在项目没有使用任何第三方日志框架的时候,JCL 默认使用 jdk 自带的 JUL 日志框架。
继续上面的案例,在导入 log4j 日志框架之后,控制台输出
log4j:WARN No appenders could be found for logger (cn.qzbook.AppTest).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
从警告信息中可以看出,JCL 使用了 log4j 日志框架,由于没有项目中没有找到 log4j 的配置文件,发出警告了 ⚠️
4.1.2 LogFactory
LogFactory 是加在日志框架具体实现的构造工厂,但它是一个接口,具体的实现类 LogFactoryImpl
5 SLF4J
SLF4J 日志门面。
门面模式 (facade pattern),也称为外观模式,核心概念:外部与一个子系统的通信西部通过一个统一的外观对象进行,是的子系统更易于使用。
外观模式主要是体现了 Java 中的一种好的封装性。更简单的说,就是对外提供的接口要尽可能的简单。
简单日志门面(simple Logging Facade For Java) SLF4J 主要是为了给 Java 日志访问提供一套标准、规范的 API 框架,其主要意义在于提供接口,具体的视线可以交由其他的日志框架,例如 Log4j 和 Logback等。当然 slf4j 自己也提供了功能较为简单的实现,但是一般很少用到。对于一般的 java 项目而言,日志框架会选择 slf4j-api 作为门面,配上具体的实现框架 (log4j、logback等),中间使用桥接器完成桥接。 对于开发者而言,SLF4J 最重要的两个功能就是对于日志框架的绑定以及日志框架的桥接。
5.1 SLF4J 适配器
通常,项目依赖的某些组件中依赖于 SLF4J 的日志 API。可能还假设这些组件在将来会切换到 SLF4J。为了处理这种情况,SLFJ 附带了几个桥接模块,这些模块会将对 Log4j,JCL 和 java.util.logging API 的调用重定向行为,就好像对 SLF4J API 进行的操作一样。
SLF4J 官网介绍图
![[concrete-bindings.png]] SJF4J 日志门面,共有三种情况对日志实现进行绑定
- 在没有绑定任何日志实现的基础之上,日志门面本身无法提供具体功能,此时,日志功能相当于没有,是上图中第一种情况。
- 值得注意的是,下面的几个示例,是导入了 slf4j-simple 这个简单实现来提供的具体功能。导入了 slf4j-simple 依赖 SLF4J 自动绑定,并且提供日志功能,是上图中的第五中情况。
- logback 和 simple (报错 nop)
- 都是 slf4j 门面时间线后面提供的日志实现,所以 API 完全遵循 slf4j 进行设计,需要导入相关的日志实现依赖,即可与 slf4j 无缝衔接,值得一提的是 nop 虽然也划分到视线中了,但它只是一个默认的抽象实现,即没有任何具体的功能(相当于项目有日志依赖,但没有日志功能)。
- log4j 和 JUL
- 都是 slf4j 门面出现之前的日志实现,所以 API 不遵循 slf4j 进行设计
- 通过适配桥接的技术,完成与日志门面的衔接
5.2 入门示例
导包: SLF4J 的包分两部分,slf4j-api 是接口定义(门面),slf4j-simple SLF4J 的一个简单实现。
<!-- slf4j API -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<!-- slf4j simple implementation -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</dependency>
5.2.1 示例一:简单打印 info 日志
package cn.qzbook;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// 注意 logger 的日志包 org.slf4j.*
public class AppTest {
/**
* 入门示例
*/
@Test
public void testLog1() {
Logger logger = LoggerFactory.getLogger(AppTest.class);
logger.info("This is a test log message.");
}
}
注意: 在导入 slf4j-api 后没有导入任何一个具体的日志实现框架,项目运行会抛出异常
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
5.2.12 示例二:占位符拼接日志信息
@Test
public void testLog2() {
String name = "qzbook";
int age = 25;
double salary = 5000.0;
Logger logger = LoggerFactory.getLogger(AppTest.class);
// 参数站位符{}
logger.info("{} is {} years old and earns {}.",name,age,salary);
}
5.2.3 示例三:打印异常堆栈信息
@Test
public void testLog3() {
Logger logger = LoggerFactory.getLogger(AppTest.class);
// 异常栈信息
try {
int i = 1 / 0;
} catch (Exception e) {
// e 是错误对象,可以打印堆栈信息 不需要字符串占位符{}
// 堆栈信息会自动输出到日志文件中 方便追踪错误
// logger 会自动输出异常信息和堆栈信息 不需要调用e.printStackTrace()
// 此处调用的是重载方法,参数类型不同 void error(String var1, Throwable var2);
logger.error("Exception occurred.", e);
}
}
5.3 日志级别
SLF4J 的日志级别:
- trace 日志追踪信息
- debug 日志详细信息
- info 日志的关键信息 默认打印级别
- warn 日志的警告信息
- error 日志的错误信息
5.4 集成实现
5.4.1 集成Logback
导入依赖
<!-- slf4j API -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<!-- logback implementation -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
注意在下面示例中: logback-classic 在导入的时候,并没有去掉上面示例的 slf4j-simple 导入完整导入如下:
<!-- slf4j API -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<!-- slf4j simple implementation -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
</dependency>
<!-- logback implementation -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
通过调换导入 slf4j-simple 和 logback-classic 的顺序,slf4j 会加载先导入的实现框架。 在实际应用中,不需要引入多个日志实现框架,只需要选择其中一个。这里反应出 SLF4J 的一个便利之处,在更换不同实现框架时,代码不需要做任何变更,这就是日志门面的优势,低耦合,降低学习成本,只需要学习 SLF4J 的 API ,而不用每一个日志框架都学习(虽然还是需要了解一下,理论上是可以的)。
/**
* 日志实现 导入顺序测试
* 1. 在 maven 中导入顺序 slf4j-simple -> logback-classic
* 在一下示例中 打印日志的实现框架 是 sl4j-simple 实现的
* 2. 在 pom.xml 中导入顺序 logback-classic -> slf4j-simple
* 在一下示例中 打印日志的实现框架 是 logback-classic 实现的
* 日志实现框架的选择,需要根据实际情况选择,如果项目中已经有其他日志实现框架,可以选择不使用 slf4j-simple 实现。
* 一旦导入了多个日志实现框架,正常情况下,会按照导入顺序进行初始化,但是如果存在冲突,可能会导致日志输出异常。
*/
@Test
public void testLog4() {
Logger logger = LoggerFactory.getLogger(AppTest.class);
// 异常栈信息
try {
int i = 1 / 0;
} catch (Exception e) {
logger.error("Exception occurred.", e);
}
}
5.4.2 集成 Log4J
Log4j 日志框架出现在 slf4j 之前,所以 log4j 不适配 slf4j 的标准,如果需要在项目中使用 slf4j 并且以 log4j 作为实现,需要引入一个额外的依赖,添加一个适配器的依赖,通过桥接器让 slf4j 兼容 log4j。
<!-- slf4j API -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<!-- slf4j to log4j bridge 桥接器 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
<!-- log4j 依赖 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
示例代码:
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Test
public void testLog4() {
Logger logger = LoggerFactory.getLogger(AppTest.class);
// 异常栈信息
try {
int i = 1 / 0;
} catch (Exception e) {
logger.error("Exception occurred.", e);
}
}
控制台输出: 控制台打印警告信息找不到 appenders 的配置(找不到 log4j 的配置文件),这说明 slf4j 已经成功的桥接了 log4j 作为实现框架,到此为止,代码中导入的包,仍然是 slft4j 的,也就是说 logger 仍然是 slft4j 的类对象。
log4j:WARN No appenders could be found for logger (cn.qzbook.AppTest).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
5.4.3 集成 JUL
导入依赖
<!-- slf4j API -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<!-- slf4j jdk14 bridge JCL 桥接器 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
</dependency>
代码示例
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Test
public void testLog4() {
Logger logger = LoggerFactory.getLogger(AppTest.class);
// 异常栈信息
try {
int i = 1 / 0;
} catch (Exception e) {
logger.error("Exception occurred.", e);
}
}
5.5 集成异常
当项目中使用了 SLF4J ,且导入了多个日志实现框架如:logback log4j ... 控制台会输出警告信息
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/Users/chuan/local_directory/repository/org/slf4j/slf4j-simple/1.7.25/slf4j-simple-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/Users/chuan/local_directory/repository/org/slf4j/slf4j-jdk14/1.7.25/slf4j-jdk14-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.slf4j.impl.SimpleLoggerFactory]
控制台输出:可以看出日志格式为 JUL 的默认格式,即表示 slf4j 桥接 JCL 作为日志实现框架成功,并且代码示例和上面的 log4j 的示例没有却别,这意味着切换日志的底层实现框架,不需要做任何代码层面上的修改。
十一月 23, 2024 11:59:04 下午 cn.qzbook.AppTest testLog4
严重: Exception occurred.
java.lang.ArithmeticException: / by zero
at cn.qzbook.AppTest.testLog4(AppTest.java:62)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
.... 省略
5.6 日志重构
当项目中已经使用了 log4j 的日志框架,这是项目需要切换日志框架,将 log4j 切换到 logback 由于代码中已经使用了 log4j 的代码,修改
slf4j官网提供了 Bridging legacy APIs 桥接旧版 API ,可以在不改变原始代码的情况下切换到 slf4j 的日志框架
![[log4j-slf4j.png]]
示例:从 log4j 切换到 logback
使用 log4j 的情况: 添加依赖
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
代码示例:此时日志对象的依赖包为 log4j org.apache.log4j.Logger
package cn.qzbook;
import org.apache.log4j.Logger;
import org.junit.Test;
public class AppTest{
@Test
public void test1() {
// 使用配置文件
Logger logger = Logger.getLogger(AppTest.class);
logger.fatal("This is a fatal message.");
logger.error("This is an error message.");
logger.warn("This is a warning message.");
logger.info("This is an info message.");
logger.debug("This is a debug message.");
logger.trace("This is a trace message.");
}
}
配置文件如下:
log4j.rootLogger=DEBUG, aa
log4j.logger.cn.qzbook=DEBUG, outFile
log4j.appender.aa=org.apache.log4j.ConsoleAppender
log4j.appender.aa.layout=org.apache.log4j.PatternLayout
log4j.appender.aa.layout.conversionPattern=%d{yyyy-MM-dd HH:mm:ss} %p %c{1}:%L %t - %m%n
控制台打印结果如下:
2024-11-25 23:05:36 FATAL AppTest:11 main - This is a fatal message.
2024-11-25 23:05:36 ERROR AppTest:12 main - This is an error message.
2024-11-25 23:05:36 WARN AppTest:13 main - This is a warning message.
2024-11-25 23:05:36 INFO AppTest:14 main - This is an info message.
2024-11-25 23:05:36 DEBUG AppTest:15 main - This is a debug message.
切换到 logback 日志输出
注释或删除 log4j 的依赖,并导入 slf4j 和 logback 的依赖
<!-- <dependency>-->
<!-- <groupId>log4j</groupId>-->
<!-- <artifactId>log4j</artifactId>-->
<!-- <version>1.2.17</version>-->
<!-- </dependency>-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
代码不变,控制台打印
23:08:45.282 [main] ERROR cn.qzbook.AppTest - This is a fatal message.
23:08:45.290 [main] ERROR cn.qzbook.AppTest - This is an error message.
23:08:45.290 [main] WARN cn.qzbook.AppTest - This is a warning message.
23:08:45.290 [main] INFO cn.qzbook.AppTest - This is an info message.
23:08:45.290 [main] DEBUG cn.qzbook.AppTest - This is a debug message.
此时,打印日志风格已经变为了 logback 的默认的风格,这表示日志框架已经无缝切换到了 logback,并且全程只修改了 maven 依赖,并没有对代码做任何调整。
需要注意的是 log4j 打印的日志因为自定义配置的关系,与 logback 的默认风格有点类似,但差异还是存在的。
实现无缝切换日志框架的核心是,桥接器 log4j-over-slf4j 依赖。
注意:log4j-over-slf4j 和 slf4j-log4j12 不能同时导入,会导致异常。 slf4j-log4j12 让 log4j 适配 slf4j 的 API,在代码中使用 slf4j 的 API。 log4j-over-slf4j 与上面功能类似,也是让 让 log4j 适配 slf4j ,但是在代码中仍然可以使用 log4j 的 API,适合项目框架重构。
6 Logback
Logback 是由 log4j 创始人设计的又一个开源日志组件。
Logback 当前分为三个模块: logback-core , logback-classic 和 logback-access
logback-access 访问模块与 servlet 容器集成提供通过 htpp 来访问日志的功能。
6.1 Logback 组件
6.1.1 Logger
日志记录器,主要用于存放日志对象,也可以定义日志类型,级别。
6.1.2 Appender
用于指定日志输出目的地,目的地可以使控制台、文件、数据库等等。
6.1.3 Layout
负责把事件转换成字符串,格式化的日志信息输出。在 Logback 中 Layout 对象被封装在 encoder 中。也就是说未来使用的 encoder 其实就是 Layout。
6.1.4 日志级别
日志级别 trace < debug < info < warn < error
6.2 Logback 配置文件
Logback 提供了 3 中配置文件
- Logback.groovy
- Logback-test.xml
- Logback.xml 如果都不配置,则采用默认配置
6.3 日志输出格式
%-10level 级别,案例为设置 10 个字符,左对齐
%d{yyyy-MM-dd HH:mm:ss.SSS} 日期
%c 当前类的全限定名
%M 当前执行日志的方法
%L 行号
%thread 线程名称
%m 或者 %msg 信息
%n 换行
6.4 示例[XML 配置文件]
使用 SLF4J 日志门面的方式来使用 logback,代码示例
package cn.qzbook;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AppTest {
@Test
public void testApp1() {
// 日志级别 trace < debug < info < warn < error
Logger logger = LoggerFactory.getLogger(AppTest.class);
logger.trace("This is a trace message");
logger.debug("This is a debug message");
logger.info("This is an info message");
logger.warn("This is a warn message");
logger.error("This is an error message");
}
xml 配置文件命名为 logback.xml 放在 maven 项目的 resource 目录即可,Logback 默认会读取这个位置的以 logback.xml 命名的配置文件。
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<!-- <property name="LOG_HOME" value="${user.home}/logs"/>-->
<!-- property 可以定义变量 name="变量名" value="变量值",在 logback.xml 中引用 -->
<!-- 日志输出路径-->
<property name="LOG_HOME" value="logs"/>
<!-- 日志 输出文本格式 -->
<property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger{36} - %msg%n"/>
<!-- <property name="pattern" value="%msg%n"/>-->
<!-- 输出日志到控制台 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<!-- 日志 输出格式 -->
<target>System.err</target>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!-- 引用pattern变量 -->
<pattern>${pattern}</pattern>
</encoder>
</appender>
<!-- 输出日志到文件 -->
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 日志 输出路径 -->
<file>${LOG_HOME}/app.log</file>
<!-- 日志 输出格式 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!-- 引用pattern变量 -->
<pattern>${pattern}</pattern>
</encoder>
<!-- 日志 输出滚动策略 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志 输出文件名 -->
<fileNamePattern>${LOG_HOME}/app.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志 保留天数 -->
<maxHistory>30</maxHistory>
</rollingPolicy>
<!-- 日志 输出文件大小 -->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<!-- 日志 输出文件大小 50MB --> <maxFileSize>50MB</maxFileSize>
</triggeringPolicy>
</appender>
<!-- 注意 logger 的配置 要在 appender 之后 否则不会生效 [在当前案例中是这样] -->
<logger name="cn.qzbook">
<level value="ALL"/>
<appender-ref ref="console"/>
<appender-ref ref="file"/>
</logger>
<!-- 自定义的 logger 顺序在 root logger 之前 -->
<!-- root logger 的顺序在配置文件的最后,所以它将接收所有日志事件 -->
<!-- root logger 日志级别为INFO,输出到控制台 -->
<root level="ALL">
<appender-ref ref="console"/>
</root>
</configuration>
Appender 的可配置项,可以查看具体的类的属性名称,以 RollingFileAppender 为例,以下为部分源码:
public class RollingFileAppender<E> extends FileAppender<E> {
File currentlyActiveFile;
TriggeringPolicy<E> triggeringPolicy;
RollingPolicy rollingPolicy;
// ... 省略其他源码
}
可以看到 rollingPolicy 和 triggeringPolicy 两个属性,对应 xml 文件中的标签 和 这两个标签,同理,这两个标签下可配置项也是其类中的属性,层层嵌套。
6.5 过滤器
参见示例配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<!-- 日志 输出文本格式 -->
<property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger{36} - %msg%n"/>
<!-- 输出日志到控制台 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<!-- 日志 输出格式 -->
<target>System.err</target>
<!-- 日志级别过滤器 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 日志级别过滤 -->
<level>TRACE</level>
<!-- 日志级别匹配 若匹配则输出 -->
<onMatch>ACCEPT</onMatch>
<!-- 日志级别不匹配 若不匹配则不输出 -->
<onMismatch>DENY</onMismatch>
</filter>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!-- 引用pattern变量 -->
<pattern>${pattern}</pattern>
</encoder>
</appender>
<root level="ALL">
<appender-ref ref="console"/>
</root>
</configuration>
6.6 异步日志
java代码示例
@Test
public void testApp2() {
// 日志级别 trace < debug < info < warn < error Logger logger = LoggerFactory.getLogger(AppTest.class);
for (int i = 0; i < 1000000; i++) {
logger.trace("This is a trace message");
logger.debug("This is a debug message");
logger.info("This is an info message");
logger.warn("This is a warn message");
logger.error("This is an error message");
}
System.out.println("------------ This is a normal message 1");
System.out.println("------------ This is a normal message 2");
System.out.println("------------ This is a normal message 3");
System.out.println("------------ This is a normal message 4");
}
xml 配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<!-- 日志 输出文本格式 -->
<property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger{36} - %msg%n"/>
<!-- 输出日志到控制台 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<!-- 日志 输出格式 -->
<target>System.err</target>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!-- 引用pattern变量 -->
<pattern>${pattern}</pattern>
</encoder>
</appender>
<root level="ALL">
<!-- 引用async 输出器 -->
<appender-ref ref="console"/>
</root>
</configuration>
正常情况下日志打印完成才会指定到 System.out.println 语句,代码是按照顺序同步执行的。引申出一个问题,日志的打影响到了业务代码的执行,如果日志的记录数据量较大,会直接影响业务代码的执行速度,导致服务迟滞,这就本末倒置了。 logback 提供了异步执行日志的方式来打印日志,日志的打印不影响业务流程的执行。
使用异步,新增一个Appender,实现类为 AsyncAppender ,在 AsyncAppender 标签中可以直接引用其他的 Appender 配置,在 root 或者自定 logger 配置中将引用 AsyncAppender logger 即可实现异步打印日志。 配置如下:
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<!-- 日志 输出文本格式 -->
<property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger{36} - %msg%n"/>
<!-- 输出日志到控制台 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<!-- 日志 输出格式 -->
<target>System.err</target>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!-- 引用pattern变量 -->
<pattern>${pattern}</pattern>
</encoder>
</appender>
<!-- 日志 异步 -->
<appender name="async" class="ch.qos.logback.classic.AsyncAppender">
<!-- 引用console 输出器 -->
<appender-ref ref="console"/>
</appender>
<root level="ALL">
<!-- 引用async 输出器 -->
<appender-ref ref="async"/>
</root>
</configuration>
注意: AsyncAppender 引用的 Appender 不能使用过滤器,使用过滤器,看控制台的数据顺序,还是顺序[同步]执行的,异步没有生效。
6.7 自定义 Logger
配置实例
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<!-- 日志 输出文本格式 -->
<property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger{36} - %msg%n"/>
<!-- 输出日志到控制台 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<!-- 日志 输出格式 -->
<target>System.err</target>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!-- 引用pattern变量 -->
<pattern>${pattern}</pattern>
</encoder>
</appender>
<root level="ERROR">
<appender-ref ref="console"/>
</root>
<!-- 自定义 logger additivity="false" 关闭父类的日志输出[不继承父类的日志配置] -->
<logger name="cn.qzbook" level="DEBUG" additivity="false">
<!-- 引用console appender -->
<appender-ref ref="console"/>
</logger>
</configuration>
6.7 补充
异步日志:可配置属性,配置了一个阈值,但队列的剩余容量小于这个阈值的时候,当前日志级别的 trace、debug、info这个三个级别的日志将被丢弃。设置为 0 ,说明永不丢弃 trace、debug、info 这三个级别的日志。
<discardingThreshold>0</discardingThreshold>
配置队列的深度,这个值会影响记录日志的性能,默认值是 256
<queueSize>256</queueSize>
一般使用默认配置即可,配置为其他值可能会影响性能,了解功能即可
关于不同日志的实现,配置文件也是不通的,例如 log4j 经常使用的是 properties 属性文件,logback 使用的是 xml 配置文件,将项目日志记录框架从 log4j 切换到 logback 的时候,可以借助 logback 官网工具,将 log4j 的 properties 配置文件,转换为 logback 的 xml 格式配置文件 网址:https://logback.qos.ch/translator/
转换工具有限翻译,log4j 与 logback 都支持的配置才会被翻译
7 Log4j2
性能提升: log4j2 包含基于 LAMX Disruptor 库的下一代异步记录器。在多线程场景中,异步记录器的吞吐比 log4j 1.x 和 Logback 高18倍,延迟低。
自动重新加载配置 与 Logback 一样,Log4j2 可以再修改时自动重新加载器配置。与 Logback 不同,会在重新配置发生时不会丢失日志事件。
高级过滤 与 Logback 一样,Log4j2 支持基于 Log 事件上下文数据,标记,正则表达式和其他组件进行过滤。 此外,过滤器还可以与记录器关联。与 Logback 不同,Log4j2 可以在任何这些情况下使用通用的 Filter 类。
插件架构 Log4j2 使用插件模式配置组件。因此,无需编写代码来创建和配置 appender ,Layout , Pattern Converter 等。在配置了的情况下, Log4j2 会自动识别插件并使用它们。
无垃圾机制 在稳态日志记录期间 Log4j2 在独立应用程序中是无垃圾的,在 web 引用程序中是低垃圾。这减少了垃圾收集器的压力,并且可以提供更好的响应性能。
7.1 入门示例
导入依赖
<!-- log4j2 的日志门面 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.12.1</version>
</dependency>
<!-- log4j2 的日志实现 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.12.1</version>
</dependency>
代码
package cn.qzbook;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.Test;
public class AppTest {
@Test
public void testApp() {
Logger logger = LogManager.getLogger(AppTest.class);
logger.fatal("fatal message");
logger.error("error message");
logger.warn("warn message");
logger.info("info message");
logger.debug("debug message");
logger.trace("trace message");
/**
* ERROR StatusLogger No Log4j 2 configuration file found. Using default configuration (logging only errors to the console), or user programmatically provided configurations. Set system property 'log4j2.debug' to show Log4j 2 internal initialization logging. See https://logging.apache.org/log4j/2.x/manual/configuration.html for instructions on how to configure Log4j 2 * 23:02:04.085 [main] FATAL cn.qzbook.AppTest - fatal message * 23:02:04.087 [main] ERROR cn.qzbook.AppTest - error message * *
* log4j2 默认日志级别为 ERROR,因此以上日志级别为 ERROR 及以上级别的日志才会被输出。
*
*
* ERROR StatusLogger No Log4j 2 configuration file found. Using default configuration
* 表示没有找到 log4j2 的配置文件,使用默认配置(只输出错误日志到控制台),或者用户通过编程方式提供的配置。
*/
}
}
控制台输出
ERROR StatusLogger No Log4j 2 configuration file found. Using default configuration (logging only errors to the console), or user programmatically provided configurations. Set system property 'log4j2.debug' to show Log4j 2 internal initialization logging. See https://logging.apache.org/log4j/2.x/manual/configuration.html for instructions on how to configure Log4j 2
23:02:04.085 [main] FATAL cn.qzbook.AppTest - fatal message
23:02:04.087 [main] ERROR cn.qzbook.AppTest - error message
log4j2 与 log4j 的日志级别一致: fatal > error > warn > info > debug > trace
log4j2 默认日志级别为 ERROR,因此以上日志级别为 ERROR 及以上级别的日志才会被输出。
控制台错误信息:ERROR StatusLogger No Log4j 2 configuration file found. Using default configuration 表示没有找到 log4j2 的配置文件,使用默认配置(只输出错误日志到控制台),或者用户通过编程方式提供的配置。
7.2 使用配置文件
log4j2 默认加载类路径(resource)下的 log4j2.xml 文件中的配置。
<?xml version="1.0" encoding="UTF-8" ?>
<Configuration>
<!-- 定义 appender 输出到控制台 -->
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<!-- 日志输出格式 -->
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<!-- 定义日志器 -->
<Loggers>
<!-- 定义日志级别 -->
<Root level="info">
<!-- 定义日志输出到控制台 -->
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
控制台输出
23:11:53.391 [main] FATAL cn.qzbook.AppTest - fatal message
23:11:53.393 [main] ERROR cn.qzbook.AppTest - error message
23:11:53.393 [main] WARN cn.qzbook.AppTest - warn message
23:11:53.393 [main] INFO cn.qzbook.AppTest - info message
根标签,所有日志相关信息,都在根标签中进行配置. 根标签中,可添加属性 status="debug" 日志框架根深的日志输出级别 monitorInterval="数值" 自动加载配置文件的间隔时间,单位秒(自动重新加载)
<Configuration status="debug" monitorInterval="1">
</Configuration>
7.3 使用 SLF4J门面
教程 URL : https://www.bilibili.com/video/BV1Mb4y1Z74W?spm_id_from=333.788.player.switch&vd_source=6c6e9b25d282f47b0d3022737f870fce&p=99