项目上需要将ERROR级别的日志写入数据库中,以便维护人员在页面上可以快速查看到错误日志,于是考虑采用Logback的DBAppender来实现
https://yhsblog.cn/archives/logback-pei-zhi#dbappender-日志入库
高版本的logback中移除了数据库的相关代码,需要手动进行引入
<dependency>
<groupId>ch.qos.logback.db</groupId>
<artifactId>logback-classic-db</artifactId>
<version>1.2.11.1</version>
</dependency>
<appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
<connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
<driverClass>dm.jdbc.driver.DmDriver</driverClass>
<url>jdbc:dm://localhost:5237</url>
<user>APPLOG</user>
<password>xxxxxx</password>
</connectionSource>
</appender>
直接使用ch.qos.logback.classic.db.DBAppender
时,项目启动报错,报错信息为找不到getGeneratedKeys
方法
查看DBAppender的源码
static {
// PreparedStatement.getGeneratedKeys() method was added in JDK 1.4
Method getGeneratedKeysMethod;
try {
// the
getGeneratedKeysMethod = PreparedStatement.class.getMethod("getGeneratedKeys", (Class[]) null);
} catch (Exception ex) {
getGeneratedKeysMethod = null;
}
GET_GENERATED_KEYS_METHOD = getGeneratedKeysMethod;
}
这里写的是从PreparedStatement中获取getGeneratedKeys方法,查看PreparedStatement源码,发现其继承了Statement,在Statement中声明了getGeneratedKeys方法。
查看PreparedStatement的实现
public class DmdbPreparedStatement extends DmdbStatement implements PreparedStatement
public class DmdbStatement extends Filterable implements Statement
于是考虑将DBAppender复制出来,通过Statemen获取getGeneratedKeys方法
static {
// PreparedStatement.getGeneratedKeys() method was added in JDK 1.4
Method getGeneratedKeysMethod;
try {
getGeneratedKeysMethod = Statement.class.getMethod("getGeneratedKeys", (Class[]) null);
// the
String driverClassName = LogbackConnectionSource.dataSource.getDriverClassName();
if (driverClassName.contains("DmDriver")) {
getGeneratedKeysMethod = Statement.class.getMethod("getGeneratedKeys", (Class[]) null);
} else {
getGeneratedKeysMethod = PreparedStatement.class.getMethod("getGeneratedKeys", (Class[]) null);
}
} catch (Exception ex) {
getGeneratedKeysMethod = null;
}
GET_GENERATED_KEYS_METHOD = getGeneratedKeysMethod;
}
修改logback的配置
<appender name="DB" class="x.y.z.DmDBAppender">
<connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
<driverClass>dm.jdbc.driver.DmDriver</driverClass>
<url>jdbc:dm://localhost:5237</url>
<user>APPLOG</user>
<password>xxxxxx</password>
</connectionSource>
</appender>
然而这样一来,数据库链接就暴露了,项目中也未使用JNDI,因此考虑采用重写数据源链接的方式进行操作
public class DriverManagerConnectionSource extends ConnectionSourceBase
DriverManagerConnectionSource继承了ConnectionSourceBase 接口
于是我也继承此接口,进行重写,ConnectionSourceBase 需要重写获取数据库链接的方法
public Connection getConnection() throws SQLException
此处考虑使用数据源连接池,数据库配置从配置文件中或者环境变量中读取,同时,考虑到数据库可能会进行加密操作,因此也需要增加解密操作。
public class LogbackConnectionSource extends ConnectionSourceBase implements ApplicationContextAware {
private String driverClass = null;
private String url = null;
private ApplicationContext applicationContext;
static volatile HikariDataSource dataSource;
private String jasyptPwd = "12345";
private static volatile PooledPBEStringEncryptor encryptor;
private static volatile HikariConfig hikariConfig;
public void start() {
try {
init();
if (driverClass != null) {
Class.forName(driverClass);
discoverConnectionProperties();
} else {
addError("WARNING: No JDBC driver specified for logback DriverManagerConnectionSource.");
}
} catch (final ClassNotFoundException cnfe) {
addError("Could not load JDBC driver class: " + driverClass, cnfe);
}
}
private void init() {
if (encryptor == null) {
synchronized (LogbackConnectionSource.class) {
if (encryptor == null) {
encryptor = new PooledPBEStringEncryptor();
encryptor.setPassword(jasyptPwd);
encryptor.setAlgorithm("PBEWITHHMACSHA512ANDAES_256");
encryptor.setIvGenerator(new org.jasypt.iv.RandomIvGenerator());
}
}
}
if (dataSource == null) {
synchronized (LogbackConnectionSource.class) {
if (dataSource == null) {
if (applicationContext != null) {
Environment environment = applicationContext.getBean(Environment.class);
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setUsername(environment.getProperty("log.datasource.username"));
hikariConfig.setPassword(environment.getProperty("log.datasource.password"));
hikariConfig.setDriverClassName(environment.getProperty("log.datasource.driverClassName"));
hikariConfig.setJdbcUrl(environment.getProperty("log.datasource.url"));
hikariConfig.setMinimumIdle(environment.getProperty("log.datasource.minimumIdle", Integer.class,10));
hikariConfig.setMaximumPoolSize(environment.getProperty("log.datasource.maximumPoolSize", Integer.class,100));
hikariConfig.setKeepaliveTime(60_000);
hikariConfig.setConnectionTimeout(30_000);
decode(hikariConfig);
setter(hikariConfig);
dataSource = new HikariDataSource(hikariConfig);
} else {
String activeProfile = System.getenv("spring.profiles.active");
if (!StringUtils.hasLength(activeProfile)) {
activeProfile = "local";
}
Yaml yaml = new Yaml();
try (InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("application-" + activeProfile + ".yml")) {
Map<String, Object> load = yaml.load(resourceAsStream);
Map<String, Object> log = (Map<String, Object>) load.get("log");
Map<String, Object> datasource = (Map<String, Object>) log.get("datasource");
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setUsername(String.valueOf(datasource.get("username")));
hikariConfig.setPassword(String.valueOf(datasource.get("password")));
hikariConfig.setDriverClassName(String.valueOf(datasource.get("driverClassName")));
hikariConfig.setJdbcUrl(String.valueOf(datasource.get("url")));
hikariConfig.setMinimumIdle(datasource.get("minimumIdle") == null ? 10 : Integer.parseInt(datasource.get("minimumIdle").toString()));
hikariConfig.setMaximumPoolSize(datasource.get("maximumPoolSize") == null ? 100 : Integer.parseInt(datasource.get("maximumPoolSize").toString()));
hikariConfig.setKeepaliveTime(60_000);
hikariConfig.setConnectionTimeout(30_000);
decode(hikariConfig);
setter(hikariConfig);
dataSource = new HikariDataSource(hikariConfig);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
}
if (this.driverClass == null) {
setter(hikariConfig);
}
}
private void setter(HikariConfig hikariConfig) {
this.setUrl(hikariConfig.getJdbcUrl());
this.setDriverClass(hikariConfig.getDriverClassName());
this.setPassword(hikariConfig.getPassword());
this.setUser(hikariConfig.getUsername());
}
private void decode(HikariConfig config) {
String name = config.getUsername();
if (name.startsWith("ENC(")) {
name = encryptor.decrypt(name);
}
config.setUsername(name);
String pwd = config.getPassword();
if (pwd.startsWith("ENC(")) {
pwd = encryptor.decrypt(pwd);
}
config.setPassword(pwd);
String jdbcUrl = config.getJdbcUrl();
if (jdbcUrl.startsWith("ENC(")) {
jdbcUrl = encryptor.decrypt(jdbcUrl);
}
config.setJdbcUrl(jdbcUrl);
String driverClassName = config.getDriverClassName();
if (driverClassName.startsWith("ENC(")) {
driverClassName = encryptor.decrypt(driverClassName);
}
config.setDriverClassName(driverClassName);
hikariConfig = config;
}
@Override
public Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public String getUrl() {
return url;
}
/**
* Sets the url.
*
* @param url The url to set
*/
public void setUrl(String url) {
this.url = url;
}
/**
* Returns the name of the driver class.
*
* @return String
*/
public String getDriverClass() {
return driverClass;
}
/**
* Sets the driver class.
*
* @param driverClass The driver class to set
*/
public void setDriverClass(String driverClass) {
this.driverClass = driverClass;
}
}
最后采用异步的方式进行日志的入库操作,避免对业务产生影响
<appender name="DB" class="x.y.z.DMLogbackDBAppender">
<connectionSource class="x.y.z.LogbackConnectionSource">
</connectionSource>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>WARN</level>
</filter>
</appender>
<appender name="ASYNC-DB" class="ch.qos.logback.classic.AsyncAppender">
<discardingThreshold>0</discardingThreshold>
<queueSize>256</queueSize>
<appender-ref ref="DB"/>
</appender>
官方未提供达梦的初始化SQL,在此记录一下
CREATE TABLE logging_event
(
timestmp NUMBER(20) NOT NULL,
formatted_message VARCHAR2(4000) NOT NULL,
logger_name VARCHAR(254) NOT NULL,
level_string VARCHAR(254) NOT NULL,
thread_name VARCHAR(254),
reference_flag SMALLINT,
arg0 VARCHAR(254),
arg1 VARCHAR(254),
arg2 VARCHAR(254),
arg3 VARCHAR(254),
caller_filename VARCHAR(254) NOT NULL,
caller_class VARCHAR(254) NOT NULL,
caller_method VARCHAR(254) NOT NULL,
caller_line CHAR(4) NOT NULL,
event_id BIGINT identity(1, 1)
)storage(initial 1, next 1, minextents 1, fillfactor 0)
;
alter table logging_event add constraint "PK_Logging_event" primary key(event_id);
create table logging_event_property
(
event_id BIGINT not null ,
mapped_key VARCHAR2(254) not null ,
mapped_value VARCHAR2(1024)
)
storage(initial 1, next 1, minextents 1, fillfactor 0)
;
alter table logging_event_property add constraint primary key(event_id,mapped_key);
alter table logging_event_property add constraint foreign key(event_id) references logging_event(event_id) ;
CREATE TABLE logging_event_exception
(
event_id BIGINT NOT NULL,
i SMALLINT NOT NULL,
trace_line VARCHAR2(254) NOT NULL
);
alter table logging_event_exception add constraint primary key(event_id,i);
alter table logging_event_exception add constraint foreign key(event_id) references logging_event(event_id) ;
参看资料:https://blog.csdn.net/qq_37003223/article/details/128579835