一、概述
最近项目需求中需将重要的用户操作日志和机器人日志保存到自己定义的日志表中。要求该功能需将对业务功能的影响降到最低。
尝试过几种解决方案:Spring Aspect面向切面编程、logback日志组件。效果都不太理想,logback本身支持将日志保存到组件预定义的三张表(logging_event,logging_event_exception,logging_event_property)中,但是我们需要将日志个性化记录到自己的日志表中,网上相关资料比较少,于是决定看logback源码。
二、logback DBAppender详解
我们程序使用logback版本是1.2.3,默认将日志保存至数据库配置如下:
。。。省略
DBAppender中有两个方法subAppend和secondarySubAppend,第一个方法用来向logging_event表中插入日志,第二个方法向logging_event_property和logging_event_exception两个表中插日志
protected void subAppend(ILoggingEvent event, Connection connection, PreparedStatement insertStatement) throws Throwable {
bindLoggingEventWithInsertStatement(insertStatement, event);
bindLoggingEventArgumentsWithPreparedStatement(insertStatement, event.getArgumentArray());
// This is expensive... should we do it every time?
bindCallerDataWithPreparedStatement(insertStatement, event.getCallerData());
int updateCount = insertStatement.executeUpdate();
if (updateCount != 1) {
addWarn("Failed to insert loggingEvent");
}
}
protected void secondarySubAppend(ILoggingEvent event, Connection connection, long eventId) throws Throwable { Map mergedMap = mergePropertyMaps(event);
insertProperties(mergedMap, connection, eventId);
if (event.getThrowableProxy() != null) {
insertThrowable(event.getThrowableProxy(), connection, eventId);
}
}
两个方法都是protected,初步设想我们可以自定义一个类继承自DBAppender,然后重写secondarySubAppend(当然重写subAppend也可以),加入自己的逻辑就行了
三、重写DBAppender
重写后的的DBAppender如下:
package ch.qos.logback.classic.db;
import static ch.qos.logback.core.db.DBHelper.closeStatement;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Map;
import java.util.Properties;
import ch.qos.logback.classic.spi.ILoggingEvent;
public class MyDBAppender extends DBAppender {
public final static String ROBOT_LOG = "RB_LOG";
public final static String OPERATE_LOG = "OP_LOG";
private final static Properties logSqlProperties = new Properties();
static {
InputStream input = MyDBAppender.class.getClassLoader().getResourceAsStream("ch/qos/logback/classic/db/logsql.properties");
try {
logSqlProperties.load(new InputStreamReader(input, "UTF-8"));
} catch (IOException e) {
} finally {
try {
input.close();
} catch (IOException e) {}}
}
protected void secondarySubAppend(ILoggingEvent event, Connection connection, long eventId) throws Throwable { Map mergedMap = mergePropertyMaps(event);
insertProperties(mergedMap, connection, eventId);
if (event.getThrowableProxy() != null) {
insertThrowable(event.getThrowableProxy(), connection, eventId);
}
// insert owner log
insertOwnerLog(event, connection, eventId);
}
private void insertOwnerLog(ILoggingEvent event, Connection connection, long eventId) {
Object[] params = event.getArgumentArray();
// String formattedMessage = event.getFormattedMessage();
// Level level = event.getLevel();
PreparedStatement insertStatement = null;
try {
if(params.length == 0) {
return;
}
String logType = (String)params[0];
if(ROBOT_LOG.equals(logType) && params.length >= 7) {
insertStatement = connection.prepareStatement(logSqlProperties.getProperty(ROBOT_LOG));
this.setLong(insertStatement, 1, eventId)
.setString(insertStatement, 2, (String)params[1]) // robot id
.setInt(insertStatement, 3, (Integer)params[2]) // type
.setString(insertStatement, 4, (String)params[3]) // description
.setString(insertStatement, 5, (String)params[4]) // error message
.setString(insertStatement, 6, (String)params[5]) // operator
.setString(insertStatement, 7, (String)params[6]); // operate time
} else if(OPERATE_LOG.equals(logType) && params.length >= 8) {
insertStatement = connection.prepareStatement(logSqlProperties.getProperty(OPERATE_LOG));
this.setLong(insertStatement, 1, eventId)
.setString(insertStatement, 2, (String)params[1]) // module name
.setString(insertStatement, 3, (String)params[2]) // description
.setInt(insertStatement, 4, (Integer)params[3]) // operate type
.setString(insertStatement, 5, (String)params[4]) // ip address
.setString(insertStatement, 6, (String)params[5]) // operating room
.setString(insertStatement, 7, (String)params[6]) // operator
.setString(insertStatement, 8, (String)params[7]); // operate time
}
if(insertStatement != null) {
insertStatement.execute();
}
} catch(Exception e) {
e.printStackTrace();
} finally {
closeStatement(insertStatement);
}
}
private MyDBAppender setLong(PreparedStatement statement, int parameterIndex, Long value) throws SQLException {
statement.setLong(parameterIndex, value);
return this;
}
private MyDBAppender setInt(PreparedStatement statement, int parameterIndex, Integer value) throws SQLException {
statement.setInt(parameterIndex, value != null ? value : 5); // default operate type is others
return this;
}
private MyDBAppender setString(PreparedStatement statement, int parameterIndex, String value) throws SQLException {
statement.setString(parameterIndex, value);
return this;
}
}
logsql.properties 配置文件如下:
# robot log sql
RB_LOG=insert into m_robot_log(id, robot_id, type, description, error_message, operator, operate_time) values(?, ?, ?, ?, ?, ?, ?)
# operate log sql
OP_LOG=insert into m_operation_log(id, module_name, description, operate_type, ip_address, operating_room, operator, operate_time) values(?, ?, ?, ?, ?, ?, ?, ?)
调整过后的logback.xml关键部分如下:
。。。省略
四、测试
测试日志如下,即可将日志保存到自定义表中
logger.error("{} {} {} {} {} {} {}", MyDBAppender.ROBOT_LOG, robotId, 1, "机器人通讯异常", "", "system", "2018-02-07 16:46:12");