【从零单排】详解 Log4j + Slf4j 等日志框架(上篇)
上篇
- 首先,本文会简单介绍日志框架是什么。
- 其次,本文会讲解 JUL + JCL + Log4j + Slf4j 等如何实际使用。
下篇
- 然后,本文会探讨当项目中不同 jar 包使用不同日志框架时的兼容性问题。
- 最后,本文会介绍一个关于日志框架的实际 Debug 的例子。
日志框架 - 综述
打 log 最原始的方法,就是用 System.out.println(msg)
,直接输出到 console 看。
但是,实际生产实践中,这样有几个问题:
- 日志太大,想看的东西容易被刷掉。在 console 中也不便于分析查询。
- 除了 msg 之外,我还想要知道其它信息,比如:时间,这个 msg 来自哪个类等等。
解决方案是:我们不采用System.out
,而是使用FileOutputStream
,直接输出到一个文件中。并且,默认把 class 名字也当做参数传进去。
以上解决方案,催生出了许多日志框架。主流的日志框架使用有这些:
- Log4j 1/2
- Slf4j + Logback
- Slf4j + Slf4j simple
- Slf4j + Log4j 1/2
- Apache Commons Logging + Log4j 1/2
这里,需要说明的是:
- Slf4j 是 API ,它需要搭配具体的实现类库使用。-> 关于 Slf4j 可以参考这篇文章 Java日志框架:slf4j作用及其实现原理
- Apache Commons Logging (JCL) 和 Slf4j 类似。支持切换不同的日志系统。
日志框架 - 架构
我们使用日志框架的架构如下:
日志框架,相当于一个黑盒子,我们不需要,(大多数情况下)也不关心里面的具体实现,我们和它的交集,主要在3个地方:
- 引入正确的 jar 依赖
- 使用正确的配置文件 (如,log4j.properties)
- 在代码中正确地使用
一个合格的日志框架,需要解决如下问题:
WHAT
日志输出什么内容,是 INFO 还是 ERROR ?WHERE
日志输出到哪里,是 console 还是 file ?HOW
日志输出采用什么格式,比如时间戳怎么控制,一个文件的的最大 size 是多少?
以 Log4j 为例,以上分别对应到其核心模块:
Logger
/Filter
Appender
Layout
这些,都是体现在配置文件中的。
JDK Logging (JUL)
使用 JDK 自带的 java.util.logging
,(不需要额外引入 dependency )
没有配置文件的情况下,会默认输出到控制台。
import java.util.logging.Logger;
public class JdkLogApp {
public static void main(String[] args) {
Logger logger = Logger.getGlobal();
logger.info("JdkLogApp - Info");
logger.warning("JdkLogApp - Warn");
logger.severe("JdkLogApp - Severe");
}
}
输出如下:
四月 24, 2021 10:55:13 上午 JdkLogApp main
信息: JdkLogApp - Info
四月 24, 2021 10:55:13 上午 JdkLogApp main
警告: JdkLogApp - Warn
四月 24, 2021 10:55:13 上午 JdkLogApp main
严重: JdkLogApp - Severe
注:如果有额外的配置文件,可以通过-Djava.util.logging.config.file=<config-file-name>
传入。
(引用自参考1)
Apache Commons Logging (JCL)
Apache 提供的日志模块:org.apache.commons.logging
,可以挂载不同的日志系统。
The Apache Commons Logging (JCL) provides a Log interface that is intended to be both light-weight and an independent abstraction of other logging toolkits.
实战:JCL 搭配 JDK Logging
实际使用中,首先引入依赖
<!-- common logging -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
然后使用LogFactory.getLog
获取Log
对象
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class CommonLogApp {
public static void main(String[] args) {
Log log = LogFactory.getLog(CommonLogApp.class);
log.info("CommonLogApp - Info");
log.warn("CommonLogApp - Warn");
log.error("CommonLogApp - Error");
}
}
结果和使用 JDK Logging 一样,是输出到控制台的。
实战:JCL 搭配 Log4j2
配置 dependency 如下:(另外,还需搭配 log4j2.xml,见下面 Log4j2 章节)
<!-- bridge -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-jcl</artifactId>
<version>2.11.1</version>
</dependency>
<!-- log4j2 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.11.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.11.1</version>
</dependency>
实战:JCL 搭配 Log4j1
配置 dependency 如下:(另外,还需搭配 log4j.properties,见下面 Log4j 章节)
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
(引用自参考1,2)
Log4j 1
依赖
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
配置 log4j.properties
# output path variable
log = D:/codebase/LogTestSystem/logdir
# Define the root logger with appender file
log4j.rootLogger = INFO, testlog
# Define the file appender
log4j.appender.testlog=org.apache.log4j.FileAppender
log4j.appender.testlog.File=${log}/log.out
# Define the layout for file appender
log4j.appender.testlog.layout=org.apache.log4j.PatternLayout
log4j.appender.testlog.layout.conversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] - [%c] %m%n
使用
import org.apache.log4j.Logger;
public class Log4jApp {
static Logger log = Logger.getLogger(Log4jApp.class);
public static void main(String[] args) {
log.debug("Debug");
log.info("Info");
log.error("Error");
}
}
Log4j 2
依赖
<!-- log4j2 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.11.1</version>
</dependency>
<!-- NOTE: log4j-core itself contains log4j-api -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.11.1</version>
</dependency>
配置 log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Properties>
<!-- 定义日志格式 -->
<Property name="log.pattern">%d{MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36}%n%msg%n%n</Property>
<!-- 定义文件名变量 -->
<Property name="file.debug.filename">logdir/log2.out</Property>
<Property name="file.debug.pattern">logdir/log2.out.%i.gz</Property>
</Properties>
<!-- 定义Appender,即目的地 -->
<Appenders>
<!-- 定义输出到屏幕 -->
<Console name="console" target="SYSTEM_OUT">
<!-- 日志格式引用上面定义的log.pattern -->
<PatternLayout pattern="${log.pattern}" />
</Console>
<!-- 定义输出到文件,文件名引用上面定义的file.debug.filename -->
<RollingFile name="debug" bufferedIO="true" fileName="${file.debug.filename}" filePattern="${file.debug.pattern}">
<PatternLayout pattern="${log.pattern}" />
<Policies>
<!-- 根据文件大小自动切割日志 -->
<SizeBasedTriggeringPolicy size="1 MB" />
</Policies>
<!-- 保留最近10份 -->
<DefaultRolloverStrategy max="10" />
</RollingFile>
</Appenders>
<Loggers>
<Root level="info">
<!-- 对info级别的日志,输出到console -->
<AppenderRef ref="console" level="info" />
<!-- 对error级别的日志,输出到err,即上面定义的RollingFile -->
<AppenderRef ref="debug" level="debug" />
</Root>
</Loggers>
</Configuration>
使用
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
public class Log4j2App {
static Logger log = LogManager.getLogger();
public static void main(String[] args) {
log.debug("Debug");
log.info("Info");
log.error("Error");
}
}
Slf4j + Logback
依赖
<!-- slf4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<!-- logback -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.1.7</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.7</version>
</dependency>
配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
<!-- 应用名称 -->
<property name="APP_NAME" value="logbackfile" />
<!--日志文件的保存路径,首先查找系统属性-Dlog.dir,如果存在就使用其;否则,在当前目录下创建名为 logdir 目录做日志存放的目录 -->
<property name="LOG_HOME" value="${log.dir:-logdir}/${APP_NAME}" />
<!-- 日志输出格式 -->
<property name="ENCODER_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{80} - %msg%n" />
<contextName>${APP_NAME}</contextName>
<!-- 控制台日志:输出全部日志到控制台 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<Pattern>${ENCODER_PATTERN}</Pattern> </encoder>
</appender>
<!-- 文件日志:输出全部日志到文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/output.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${ENCODER_PATTERN}</pattern>
</encoder>
</appender>
<!-- 错误日志:用于将错误日志输出到独立文件 -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>7</maxHistory> </rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${ENCODER_PATTERN}</pattern> </encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>WARN</level> </filter>
</appender>
<!-- 独立输出的同步日志 -->
<appender name="SYNC_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/sync.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>7</maxHistory> </rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${ENCODER_PATTERN}</pattern>
</encoder>
</appender>
<logger name="log.sync" level="DEBUG" addtivity="true">
<appender-ref ref="SYNC_FILE" />
</logger>
<root>
<level value="DEBUG" />
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
<appender-ref ref="ERROR_FILE" />
</root>
</configuration>
使用
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Slf4j2LogbackApp {
private static final Logger logger = LoggerFactory.getLogger(Slf4j2LogbackApp.class);
public static void main(String[] args) {
logger.debug("Debug");
logger.info("Info");
logger.error("Error");
}
}
Slf4j + Log4j1
依赖
<!-- slf4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<!-- slf4j to log4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
配置参考 log4j.properties
使用参考 Slf4j + Logback
Slf4j + Log4j2
依赖
<!-- slf4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<!-- slf4j to log4j2 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.11.1</version>
</dependency>
<!-- log4j2 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.11.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.11.1</version>
</dependency>
配置参考 log4j2.xml
使用参考 Slf4j + Logback
参考
- 参考1:廖雪峰博客 https://www.liaoxuefeng.com/wiki/1252599548343744/1264738568571776
- 参考2:Apache Commons Logging 官网 https://commons.apache.org/proper/commons-logging/guide.html
- 参考3:slf4j作用及其实现原理 https://www.cnblogs.com/xrq730/p/8619156.html