Spring-boot2使用log4j2中JDBCAppender将日志写入数据库(MySql/HikariCP/yml)
- 如何将log4j2集成到Spring-boot
- 1 导入依赖
- 2 配置log4j2
- 2.1配置log4j2.xml
- 2.2 添加log4j2.yml识别依赖
- 2.3 验证是否为log4j2输出
- 2.4 可以修改日志记录颜色(仅限Idea)
- 3 将日志写入数据库(MySql+Hikari)
- 3.1 配置Hikari数据库连接池
- 3.2 通过JDBCApperder将日志写入数据库
- 1)新建数据库表
- 2)初始化连接池
- 3)配置log4j2.yml
- 结语
如何将log4j2集成到Spring-boot
1 导入依赖
Spring-boot2 中Starters包含log4j2,所以进入log4j2只要引入以下依赖性进入pom.xml
<!--日志管理-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
但是,Spring-boot默认是使用Logback来进行日志管理,所以你需要将自带的log包从Spring-boot中去除。
<!--去掉web中自带的logging-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
如果你在之后遇到了以下 多重绑定错误:
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/E:/maven-repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/E:/maven-repository/org/apache/logging/log4j/log4j-slf4j-impl/2.11.2/log4j-slf4j-impl-2.11.2.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 [ch.qos.logback.classic.util.ContextSelectorStaticBinder]
你需要检查你其他依赖中是否也引入了日志包而造成多重绑定,常见的是spring-boot-starter-thymeleaf
和Zookeeper
两个依赖包。你可以也将其中的日志包去除掉,这里以thymeleaf为例:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<!--排除这个默认日志记录依赖-->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
2 配置log4j2
所有配置都按照log4j2官方文档-配置进行配置。
官方提供了4中配置方式,分别是配置文件、ConfigurationFactory
、使用APIs和调用内部日志类方法。
以下是官方说明:
Configuration of Log4j 2 can be accomplished in 1 of 4 ways:
1.Through a configuration file written in XML, JSON, YAML, or properties format.
2.Programmatically, by creating a ConfigurationFactory and Configuration implementation.
3.Programmatically, by calling the APIs exposed in the Configuration interface to add components to the default configuration. 4.Programmatically, by calling methods on the internal Logger class.
2.1配置log4j2.xml
本文采用YAML格式配置,其余方式可以自行去官方文档查看。我这里采用YAML配置的原因是,这次Spring-boot的配置文件采用YAML进行配置,觉得格式清晰,操作方便。
我们在application.yml
或者application.properties
同级目录下,创建配置文件log4j2.yml
,代码如下:
Configuration:
status: warn
Properties: # 定义全局变量
Property: # 缺省配置(用于开发环境)。其他环境需要在VM参数中指定,如下:
#测试:-Dlog.level.console=warn -Dlog.level.sdk=trace
#生产:-Dlog.level.console=warn -Dlog.level.sdk=info
- name: log.level.console
value: trace
- name: log.level.sdk.mapper
value: debug
- name: log.path
value: E:/logs
- name: project.name
value: sdk_manage
Appenders:
Console: #输出到控制台
name: CONSOLE
target: SYSTEM_OUT
ThresholdFilter:
level: ${sys:log.level.console} # “sys:”表示:如果VM参数中没指定这个变量值,则使用本文件中定义的缺省全局变量值
onMatch: ACCEPT
onMismatch: DENY
PatternLayout:
pattern: "%d{yyyy-MM-dd HH:mm:ss,SSS}:%4p %t (%F:%L) - %m%n"
RollingFile: # 输出到文件,超过128MB归档
- name: ROLLING_FILE #此处添加 “-” 是因为可为多个包配置多个级别,这个数组只有一个数据,你不添加也可以。
ignoreExceptions: false
fileName: ${log.path}/${project.name}.log
filePattern: "${log.path}/$${date:yyyy-MM}/${project.name}-%d{yyyy-MM-dd}-%i.log.gz"
PatternLayout:
pattern: "%d{yyyy-MM-dd HH:mm:ss,SSS}:%4p %t (%F:%L) - %m%n"
Policies:
SizeBasedTriggeringPolicy:
size: "128 MB"
DefaultRolloverStrategy:
max: 1000
Loggers:
Root:
level: info
AppenderRef:
- ref: CONSOLE
- ref: ROLLING_FILE
Logger: # 为com.sdk.management包配置特殊的Log级别,方便调试
- name: com.sdk.management
additivity: false
level: ${sys:log.level.sdk.mapper}
AppenderRef:
- ref: CONSOLE
- ref: ROLLING_FILE
注意:数组类型需要使用-
进行标识
其中,Property
中存放的是后面使用的变量,Appenders
是配置log4j2 LogEvents存放路径的,包括Console
输出到控制台、RollingFile
输出到文件和JDBC
输出到数据库,你可以根据自己需求取舍。Loggers
中存放关于日志输出控制,可以为Appender设置不同的输出级别,这里设置info级别日志输出到控制台和文件,为sdk.management包单独设置Log级别。
2.2 添加log4j2.yml识别依赖
此时,log4j2.yml
文件是不可以识别的,是因为没有添加额外依赖,而不是在配置文件中少了路径:
# logging设置
logging:
config: classpath:log4j2.yml
上面这个不用加在配置文件中,你加上当然也不会报错。
对此官方的描述如下:
Some Log4J features depend on external libraries.
Feature:Configure YAML Layout
Requirements:Jackson core, databind and YAML data format
其中前两个spring-boot自带不用添加,所以需要在pom.xml
中添加yaml-data-format
依赖项:
<dependency> <!-- 加上这个才能辨认到log4j2.yml文件 -->
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
到这里,就已经可以使用log4j2进行日志输出了。
2.3 验证是否为log4j2输出
如果你用的是Idea的话,本来的日志输出是有颜色的,log4j2会变的全是白色,同样,你也可以在配置文件中添加字段来验证是否为Log4j2.
Console: #输出到控制台
name: CONSOLE
target: SYSTEM_OUT
ThresholdFilter:
level: ${sys:log.level.console} # “sys:”表示:如果VM参数中没指定这个变量值,则使用本文件中定义的缺省全局变量值
onMatch: ACCEPT
onMismatch: DENY
PatternLayout:
pattern: "%d{yyyy-MM-dd HH:mm:ss,SSS}:%4p %t (%F:%L) - %m%n log4j2" # 你可以在这里添加log4j2字段,然后测试
添加后,输出结果如下:
ok,没有问题。然后我们可以改回来了。
2.4 可以修改日志记录颜色(仅限Idea)
如果你觉得没有颜色不够清晰分别不同级别日志,可以在idea插件中搜索Grep Console
进行设置控制台日志输出颜色。
安装完插件,重启后,进入Settings进行设置,设置如下:
然后我们可以添加测试类,来看具体输出情况:
@Test
public void loggerTest(){
log.error("ERROR测试");
log.warn("WARN测试");
log.info("INFO测试");
log.debug("DEBUG测试");
}
输出如下:
3 将日志写入数据库(MySql+Hikari)
log4j2 要求将日志写入数据库需要通过连接池操作,不然效率太低,所以我们首先要去配置连接池,这里用的是Spring-boot默认的HikariCP,你也可以使用其他的比如Driud等。
首先,我们来配置数据库连接池:
3.1 配置Hikari数据库连接池
如果你在Spring-boot中,并且已经添加了spring-boot-starter-jdbc
或者spring-boot-starter-data-jpa
‘starters’ 将会自动将Hikari依赖包导入。
我们只需要设置数据源就可以了
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
具体的yml配置如下:
# 主数据源,默认的
spring:
# 数据库访问配置
datasource:
name: mydb
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/xxxxx?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
username: xxxxxxx
password: xxxxxxx
# Hikari will use the above plus the following to setup connection pooling
type: com.zaxxer.hikari.HikariDataSource
hikari:
minimum-idle: 5
maximum-pool-size: 15
idle-timeout: 30000
pool-name: DatebookHikariCP
# 此属性控制从池返回的连接的默认自动提交行为,默认值:true
auto-commit: true
# 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟
max-lifetime: 1800000
# 数据库连接超时时间,默认30秒,即30000
connection-timeout: 30000
connection-test-query: SELECT 1
配置完毕,就可以使用Hikari作为你的数据库连接池了。
启动项目,日志如下:
3.2 通过JDBCApperder将日志写入数据库
这里需要进行三步操作:
- 新建数据库表
- 初始化数据库池连接类,设置方法需返回类型为Connection
- 配置log4j2.yml文件中Appender->JDBCAppender
1)新建数据库表
创建数据库用以存放日志文件:
-- auto-generated definition
create table log
(
id int auto_increment
primary key,
sdkName varchar(64) null,
sdkUploadTime datetime null
);
2)初始化连接池
在config路径下创建新的类ConnectionFactoryConfig
,初始化Hikari
的数据库连接池,返回一个Connection
.
此处可以参照HikariCP官方文档。
实现代码如下:
package com.sdk.management.config;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* @author caohongyun
*/
public class ConnectionFactoryConfig{
private static interface Singleton{
final ConnectionFactoryConfig INSTANCE = new ConnectionFactoryConfig();
}
private HikariDataSource dataSource;
private ConnectionFactoryConfig(){
//也可以使用配置文件直接加载
// HikariConfig config = new HikariConfig("application.properties");
// this.dataSource = new HikariDataSource(config);
String user = "xxxxx";
String password = "xxxxxx";
String url = "jdbc:mysql://127.0.0.1:3306/xxxxxx?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai";
String driverClassName = "com.mysql.cj.jdbc.Driver";
HikariConfig config = new HikariConfig();
config.setDriverClassName(driverClassName);
config.setJdbcUrl(url);
config.setUsername(user);
config.setPassword(password);
config.addDataSourceProperty("cachePrepStmts","true");
config.addDataSourceProperty("prepstmtCacheSize","250");
config.addDataSourceProperty("prepstmtCacheSqlLimit","2048");
//设置连接超时为8小时
config.setConnectionTimeout(8*60*60);
this.dataSource = new HikariDataSource(config);
}
public static Connection getDatabaseConnection() throws SQLException{
return Singleton.INSTANCE.dataSource.getConnection();
}
}
其中,通过将配置set到HikariConfig中,初始化得到数据源,然后通过getDatabaseConnection
方法返回一个Connection
对象。
3)配置log4j2.yml
可以参考log4j2官方文档-JDBCAppender进行配置,其中只有基于.properties的配置方方法,没有.yml的配置方法。
首先是.properties的官方实例:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="error">
<Appenders>
<JDBC name="databaseAppender" tableName="LOGGING.APPLICATION_LOG">
<ConnectionFactory class="net.example.db.ConnectionFactory" method="getDatabaseConnection" />
<Column name="EVENT_ID" literal="LOGGING.APPLICATION_LOG_SEQUENCE.NEXTVAL" />
<Column name="EVENT_DATE" isEventTimestamp="true" />
<Column name="LEVEL" pattern="%level" />
<Column name="LOGGER" pattern="%logger" />
<Column name="MESSAGE" pattern="%message" />
<Column name="THROWABLE" pattern="%ex{full}" />
</JDBC>
</Appenders>
<Loggers>
<Root level="warn">
<AppenderRef ref="databaseAppender"/>
</Root>
</Loggers>
</Configuration>
其次是我自己写的.yml配置方法:
Configuration:
status: warn
Properties: # 定义全局变量
Property: # 缺省配置(用于开发环境)。其他环境需要在VM参数中指定,如下:
#测试:-Dlog.level.console=warn -Dlog.level.sdk=trace
#生产:-Dlog.level.console=warn -Dlog.level.sdk=info
- name: log.level.console
value: trace
- name: log.level.sdk.mapper
value: debug
- name: log.path
value: E:/logs
- name: project.name
value: sdk_manage
Appenders:
Console: #输出到控制台
name: CONSOLE
target: SYSTEM_OUT
ThresholdFilter:
level: ${sys:log.level.console} # “sys:”表示:如果VM参数中没指定这个变量值,则使用本文件中定义的缺省全局变量值
onMatch: ACCEPT
onMismatch: DENY
PatternLayout:
pattern: "%d{yyyy-MM-dd HH:mm:ss,SSS}:%4p %t (%F:%L) - %m%n"
RollingFile: # 输出到文件,超过128MB归档
- name: ROLLING_FILE
ignoreExceptions: false
fileName: ${log.path}/${project.name}.log
filePattern: "${log.path}/$${date:yyyy-MM}/${project.name}-%d{yyyy-MM-dd}-%i.log.gz"
PatternLayout:
pattern: "%d{yyyy-MM-dd HH:mm:ss,SSS}:%4p %t (%F:%L) - %m%n"
Policies:
SizeBasedTriggeringPolicy:
size: "128 MB"
DefaultRolloverStrategy:
max: 1000
JDBC: #输出到数据库
name: DATABASE
tableName: log
ConnectionFactory:
class: com.sdk.management.config.ConnectionFactoryConfig
method: getDatabaseConnection
Column:
- name: sdkName
pattern: "%X{sdkName}"
- name: sdkUploadTime
pattern: "%d{yyyy-MM-dd hh:mm:ss}"
Loggers:
Root:
level: info
AppenderRef:
- ref: CONSOLE
- ref: ROLLING_FILE
Logger: # 为com.sdk.management包配置特殊的Log级别,方便调试
name: com.sdk.management
additivity: false
level: ${sys:log.level.sdk.mapper}
AppenderRef:
- ref: CONSOLE
- ref: ROLLING_FILE
- ref: DATABASE
其中比较起来有两处变动,
第一处是:Appenders
下多了一个JDBC
输出到数据库,其中几个比较重要的参数是:
参数 | 描述 |
name | 必须,Appender名称 |
tableName | 必须,连接数据库中数据表名 |
ConnectionFactory.class | 必须,指定连接池获取数据库连接类名 |
ConnectionFactory.method | 必须,指定连接池获取数据库连接方法名 |
Column | 必须,数据库存储字段以及来源 |
Note:数据库字段Pattern格式可以参考Pattern layout官方文档。
第二处:
你需要设置你的日志输出到数据库的级别,我使用- ref: DATABASE
将其设置为单独为包添加日志写入数据库,当然你也可以自定义数据库写入日志级别。
这时你就可以写一个简单的类去尝试将日志写入数据库了
这里简单写一个测试类,代码如下:
这时你可以尝试启动项目,
@Test
public void logger2DatabaseTest(){
String sdkName = "logByLog4j2";
String sdkUploadTime = new Timestamp(System.currentTimeMillis()).toString();
ThreadContext.put("sdkName",sdkName);
ThreadContext.put("sdkUploadTime",sdkUploadTime);
log.debug("日志已经写入数据库,sdk名称为:" + sdkName+ ",上传时间为:"+ sdkUploadTime);
}
然后执行测试类:
查看数据库表(log):
写入成功。
结语
通过log4j2将日志写入数据库,感觉用到的地方还是比较小的,一般完全可以通过日志文件存储这些日志,
也可以使用AOP切面,对所有的操作进行日志记录,然后自己手动写入数据库。
本来想接着写如何AOP切面编程记录日志,感觉网上资料挺多的,我就不写了。