前言
良好的日志在一个软件中占了非常重要的地位,日志是开发与运维管理之间的桥梁。日志可以帮助运维人员和管理人员快速查找系统的故障和瓶颈,也可以帮助开发人员与运维人员沟通,更好地完成开发和运维任务。但日志的信息量会随着软件运行时间不断变多,所以需要定期汇总和清理,避免影响服务器的正常运行。
在 Java 开发中常用的日志框架有 Log4j、Log4j2、Apache Commons Log、java.util.logging、slf4j等,这些工具对外的接口不尽相同。为了统一这些工具的接口,MyBatis 定义了一套统一的日志接口供上层使用,并为上述常用的日志框架提供了相应的适配器。
日志适配器
多种第三方日志组件都有各自的 Log级别,且都有所不同。例如java.util.logging提供了 All、FINEST、FINER、FINE、CONFIG、INFO、WARNING 等9种级别,而 Log4j2 则只有 trace、debug、info、warn、error、fatal这6种日志级别。MyBatis 统一提供了 trace、debug、warn、enor 四个级别,这基本与主流日志框架的日志级别类似,可以满足绝大多数场景的日志需求。
MyBatis 的日志模块位于 org.apache.ibatis.logging 包中,该模块中通过 Log接口定义了日志模块的功能,当然日志适配器也会实现此接口。
LogFactory 工厂类负责创建对应的日志组件适配器,类结构如下所示:
public final class LogFactory {
/**
* Marker to be used by logging implementations that support markers.
*/
public static final String MARKER = "MYBATIS";
private static Constructor<? extends Log> logConstructor;
static {
tryImplementation(LogFactory::useSlf4jLogging);
tryImplementation(LogFactory::useCommonsLogging);
tryImplementation(LogFactory::useLog4J2Logging);
tryImplementation(LogFactory::useLog4JLogging);
tryImplementation(LogFactory::useJdkLogging);
tryImplementation(LogFactory::useNoLogging);
}
private LogFactory() {
// disable construction
}
public static Log getLog(Class<?> clazz) {
return getLog(clazz.getName());
}
public static Log getLog(String logger) {
try {
return logConstructor.newInstance(logger);
} catch (Throwable t) {
throw new LogException("Error creating logger for logger " + logger + ". Cause: " + t, t);
}
}
public static synchronized void useCustomLogging(Class<? extends Log> clazz) {
setImplementation(clazz);
}
public static synchronized void useSlf4jLogging() {
setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
}
public static synchronized void useCommonsLogging() {
setImplementation(org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.class);
}
/**
* @deprecated Since 3.5.9 - See https://github.com/mybatis/mybatis-3/issues/1223. This method will remove future.
*/
@Deprecated
public static synchronized void useLog4JLogging() {
setImplementation(org.apache.ibatis.logging.log4j.Log4jImpl.class);
}
public static synchronized void useLog4J2Logging() {
setImplementation(org.apache.ibatis.logging.log4j2.Log4j2Impl.class);
}
public static synchronized void useJdkLogging() {
setImplementation(org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl.class);
}
public static synchronized void useStdOutLogging() {
setImplementation(org.apache.ibatis.logging.stdout.StdOutImpl.class);
}
public static synchronized void useNoLogging() {
setImplementation(org.apache.ibatis.logging.nologging.NoLoggingImpl.class);
}
private static void tryImplementation(Runnable runnable) {
if (logConstructor == null) {
try {
runnable.run();
} catch (Throwable t) {
// ignore
}
}
}
private static void setImplementation(Class<? extends Log> implClass) {
try {
Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
Log log = candidate.newInstance(LogFactory.class.getName());
if (log.isDebugEnabled()) {
log.debug("Logging initialized using '" + implClass + "' adapter.");
}
logConstructor = candidate;
} catch (Throwable t) {
throw new LogException("Error setting Log implementation. Cause: " + t, t);
}
}
}
LogFactory通过tryImplementation方法,依次尝试加载日志组件适配器,加载顺序如下:
- useSlf4jLogging :Slf4j
- useCommonsLogging :JCL
- useLog4J2Logging :Log4j2
- useLog4JLogging : Log4j
- useJdkLogging : JUL
- useNoLogging
日志简介
- 日志门面:Slf4j、JCL、Log4j2;只提供日志相关的接口定义,即相应的 API,而不提供具体的接口实现。日志门面在使用时,可以动态或者静态地指定具体的日志框架实现,解除了接口和实现的耦合,使用户可以灵活地选择日志的具体实现框架。
- 日志系统:Logback、Log4j、Log4j2、JUL等;与日志门面相对,它提供了具体的日志接口实现,应用程序通过它执行日志打印的功能。
PS : Log4j2即可以作为日志门面,也可以作为日志的具体实现,一般用作日志的具体实现
Slf4j
Slf4j的日志绑定和日志桥接,如下图所示:
日志绑定
日志桥接
JCL
JCL 全称为Jakarta Commons Logging,是Apache提供的一个通用日志API。它适配的日志实现:Log4j、JDK自带的日志(JUL)
相关官网地址
- Slf4j官网:http://www.slf4j.org/
- Log4j2官网:http://logging.apache.org/log4j/2.x/
- Logback官网:http://logback.qos.ch/
- JCL官网:http://commons.apache.org/proper/commons-logging/
Mybatis使用各种日志
代码准备
创建mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<environments default="test">
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper class="com.ys.mybatis.log.mapper.LogMapper"/>
</mappers>
</configuration>
创建LogMapper
public interface LogMapper {
@Select("select 1 from dual")
Long printLog();
}
执行测试方法
public class LogTest {
private SqlSessionFactory sqlSessionFactory;
@BeforeEach
public void init() throws IOException {
InputStream inputStream = LogTest.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
sqlSessionFactory = factoryBuilder.build(inputStream);
}
@Test
public void testLog() {
SqlSession sqlSession = sqlSessionFactory.openSession();
LogMapper mapper = sqlSession.getMapper(LogMapper.class);
mapper.printLog();
}
}
Slf4j + Logback
添加pom依赖
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.12</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.6</version>
</dependency>
创建logback.xml
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<property name="pattern" value="logback %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${pattern}</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="console"/>
</root>
</configuration>
PS :日志格式以logback开头
执行测试方法
Slf4j + Log4j2
添加pom依赖
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.12</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
<version>3.2.1</version>
</dependency>
创建log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="log4j2 %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="debug">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
PS :日志格式以log4j2开头
执行测试方法
JCL + Log4j
添加pom依赖
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
创建log4j.properties
log4j.rootLogger = debug,console
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.conversionPattern = log4j [%.10p] %t %d{yyyy-MM-dd HH:mm:ss} %m%n
PS :日志格式以log4j开头
执行测试方法
JUL
创建jul.properties
handlers= java.util.logging.ConsoleHandler
.level= ALL
# Limit the message that are printed on the console to INFO and above.
java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
修改@BeforeEach标记的init()方法
@BeforeEach
public void init() throws IOException {
ClassLoader classLoader = LogTest.class.getClassLoader();
InputStream inputStream = classLoader.getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
sqlSessionFactory = factoryBuilder.build(inputStream);
InputStream logInputStream = classLoader.getResourceAsStream("jul.properties");
LogManager logManager = LogManager.getLogManager();
logManager.readConfiguration(logInputStream);
}