Logback是一个开源日志组件,为取代log4j而生。
Logback主要分为三个模块:logback-core,logback-classic和logback-access。
Logback有着许多独特而实用的特性,如Marker、参数化记录语句、条件化堆栈跟踪和强大的事件过滤等。在此不详述,有兴趣的朋友可以查看官方文档。
入正题,在很多实际项目开发中,往往会有将日志写入自定义的用户表的需求,或者需要记录特殊的字段。这两者,原理类似。今天给大家分享下基于logback将日志写入Mysql数据库(写入其他数据库或文件同理)的一个字段扩展。
Logback记录日志主要用到三张固定的表:logging_event,logging_event_property,logging_event_exception。结构如下:
BEGIN;
DROP TABLE IF EXISTS logging_event_property;
DROP TABLE IF EXISTS logging_event_exception;
DROP TABLE IF EXISTS logging_event;
COMMIT;
BEGIN;
CREATE TABLE logging_event
(
timestmp BIGINT NOT NULL,
formatted_message TEXT 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 NOT NULL AUTO_INCREMENT PRIMARY KEY
);
COMMIT;
BEGIN;
CREATE TABLE logging_event_property
(
event_id BIGINT NOT NULL,
mapped_key VARCHAR(254) NOT NULL,
mapped_value TEXT,
PRIMARY KEY(event_id, mapped_key),
FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
);
COMMIT;
BEGIN;
CREATE TABLE logging_event_exception
(
event_id BIGINT NOT NULL,
i SMALLINT NOT NULL,
trace_line VARCHAR(254) NOT NULL,
PRIMARY KEY(event_id, i),
FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
);
COMMIT;
细心的读者很容易发现logging_event的设计者提供了4个(arg0,arg1,arg2,arg3)扩展字段,本文就是实现往arg0字段写入内容,写入自定义的表同理。
1.新建测试的java工程,导入相应jar包。项目目录结构如下:
2.修改logback的配置文件logback.xml,引用我们自己编写的Appender类。以下为简化的配置文件,有兴趣的读者可以查阅详细配置文档。
3.编写Appender类和SQLBuilder类。
Appender类源码:
package com.ldl.cn;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import ch.qos.logback.classic.db.DBHelper;
import ch.qos.logback.classic.db.names.DBNameResolver;
import ch.qos.logback.classic.db.names.DefaultDBNameResolver;
import ch.qos.logback.classic.spi.CallerData;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.classic.spi.StackTraceElementProxy;
import ch.qos.logback.classic.spi.ThrowableProxyUtil;
import ch.qos.logback.core.db.DBAppenderBase;
public class MyAppender extends DBAppenderBase
public MyAppender() {}
protected String insertPropertiesSQL;
protected String insertExceptionSQL;
protected String insertSQL;
protected static final Method GET_GENERATED_KEYS_METHOD;
private DBNameResolver dbNameResolver;
static final int TIMESTMP_INDEX = 1;
static final int FORMATTED_MESSAGE_INDEX = 2;
static final int LOGGER_NAME_INDEX = 3;
static final int LEVEL_STRING_INDEX = 4;
static final int THREAD_NAME_INDEX = 5;
static final int REFERENCE_FLAG_INDEX = 6;
static final int ARG0_INDEX = 7;
static final int ARG1_INDEX = 8;
static final int ARG2_INDEX = 9;
static final int ARG3_INDEX = 10;
static final int CALLER_FILENAME_INDEX = 11;
static final int CALLER_CLASS_INDEX = 12;
static final int CALLER_METHOD_INDEX = 13;
static final int CALLER_LINE_INDEX = 14;
static final int EVENT_ID_INDEX = 15;
static final StackTraceElement EMPTY_CALLER_DATA = CallerData.naInstance();
static
{
Method getGeneratedKeysMethod;
try
{
getGeneratedKeysMethod = PreparedStatement.class.getMethod("getGeneratedKeys", (Class[])null);
}
catch (Exception ex) {
getGeneratedKeysMethod = null;
}
GET_GENERATED_KEYS_METHOD = getGeneratedKeysMethod;
}
public void setDbNameResolver(DBNameResolver dbNameResolver) {
this.dbNameResolver = dbNameResolver;
}
public void start()
{
if (this.dbNameResolver == null)
this.dbNameResolver = new DefaultDBNameResolver();
this.insertExceptionSQL = SQLBuilder.buildInsertExceptionSQL(this.dbNameResolver);
this.insertPropertiesSQL = SQLBuilder.buildInsertPropertiesSQL(this.dbNameResolver);
this.insertSQL = SQLBuilder.buildInsertSQL(this.dbNameResolver);
super.start();
}
@Override
protected void subAppend(ILoggingEvent event, Connection connection, PreparedStatement insertStatement)
throws Throwable
{
bindLoggingEventWithInsertStatement(insertStatement, event);
bindLoggingEventArgumentsWithPreparedStatement(insertStatement, event.getArgumentArray());
bindCallerDataWithPreparedStatement(insertStatement, event.getCallerData());
int updateCount = insertStatement.executeUpdate();
if (updateCount != 1) {
addWarn("Failed to insert loggingEvent");
}
}
@Override
protected void secondarySubAppend(ILoggingEvent event, Connection connection, long eventId)
throws Throwable {
// TODO Auto-generated method stub
Map
insertProperties(mergedMap, connection, eventId);
if (event.getThrowableProxy() != null) {
insertThrowable(event.getThrowableProxy(), connection, eventId);
}
}
void bindLoggingEventWithInsertStatement(PreparedStatement stmt, ILoggingEvent event) throws SQLException
{
stmt.setLong(1, event.getTimeStamp());
stmt.setString(2, event.getFormattedMessage());
stmt.setString(3, event.getLoggerName());
stmt.setString(4, event.getLevel().toString());
stmt.setString(5, event.getThreadName());
stmt.setShort(6, DBHelper.computeReferenceMask(event));
stmt.setString(7, "yourString");
}
void bindLoggingEventArgumentsWithPreparedStatement(PreparedStatement stmt, Object[] argArray)
throws SQLException
{
int arrayLen = argArray != null ? argArray.length : 0;
for (int i = 0; (i < arrayLen) && (i < 4); i++) {
stmt.setString(8 + i, asStringTruncatedTo254(argArray[i]));
}
if (arrayLen < 4) {
for (int i = arrayLen; i < 4; i++) {
stmt.setString(8 + i, null);
}
}
}
String asStringTruncatedTo254(Object o) {
String s = null;
if (o != null) {
s = o.toString();
}
if (s == null) {
return null;
}
if (s.length() <= 254) {
return s;
}
return s.substring(0, 254);
}
void bindCallerDataWithPreparedStatement(PreparedStatement stmt, StackTraceElement[] callerDataArray)
throws SQLException
{
StackTraceElement caller = extractFirstCaller(callerDataArray);
stmt.setString(11, caller.getFileName());
stmt.setString(12, caller.getClassName());
stmt.setString(13, caller.getMethodName());
stmt.setString(14, Integer.toString(caller.getLineNumber()));
}
private StackTraceElement extractFirstCaller(StackTraceElement[] callerDataArray) {
StackTraceElement caller = EMPTY_CALLER_DATA;
if (hasAtLeastOneNonNullElement(callerDataArray))
caller = callerDataArray[0];
return caller;
}
private boolean hasAtLeastOneNonNullElement(StackTraceElement[] callerDataArray) {
return (callerDataArray != null) && (callerDataArray.length > 0) && (callerDataArray[0] != null);
}
Map
Map
Map
Map
if (loggerContextMap != null) {
mergedMap.putAll(loggerContextMap);
}
if (mdcMap != null) {
mergedMap.putAll(mdcMap);
}
return mergedMap;
}
@Override
protected Method getGeneratedKeysMethod() {
// TODO Auto-generated method stub
return GET_GENERATED_KEYS_METHOD;
}
@Override
protected String getInsertSQL() {
// TODO Auto-generated method stub
return this.insertSQL;
}
protected void insertProperties(Map
{
Set
if (propertiesKeys.size() > 0) {
PreparedStatement insertPropertiesStatement = null;
try {
insertPropertiesStatement = connection.prepareStatement(this.insertPropertiesSQL);
for (String key : propertiesKeys) {
String value = (String)mergedMap.get(key);
insertPropertiesStatement.setLong(1, eventId);
insertPropertiesStatement.setString(2, key);
insertPropertiesStatement.setString(3, value);
if (this.cnxSupportsBatchUpdates) {
insertPropertiesStatement.addBatch();
} else {
insertPropertiesStatement.execute();
}
}
if (this.cnxSupportsBatchUpdates) {
insertPropertiesStatement.executeBatch();
}
} finally {
ch.qos.logback.core.db.DBHelper.closeStatement(insertPropertiesStatement);
}
}
}
void updateExceptionStatement(PreparedStatement exceptionStatement, String txt, short i, long eventId)
throws SQLException
{
exceptionStatement.setLong(1, eventId);
exceptionStatement.setShort(2, i);
exceptionStatement.setString(3, txt);
if (this.cnxSupportsBatchUpdates) {
exceptionStatement.addBatch();
} else {
exceptionStatement.execute();
}
}
short buildExceptionStatement(IThrowableProxy tp, short baseIndex, PreparedStatement insertExceptionStatement, long eventId)
throws SQLException
{
StringBuilder buf = new StringBuilder();
ThrowableProxyUtil.subjoinFirstLine(buf, tp);
baseIndex = (short)(baseIndex + 1);updateExceptionStatement(insertExceptionStatement, buf.toString(), baseIndex, eventId);
int commonFrames = tp.getCommonFrames();
StackTraceElementProxy[] stepArray = tp.getStackTraceElementProxyArray();
for (int i = 0; i < stepArray.length - commonFrames; i++) {
StringBuilder sb = new StringBuilder();
sb.append('\t');
ThrowableProxyUtil.subjoinSTEP(sb, stepArray[i]);
baseIndex = (short)(baseIndex + 1);updateExceptionStatement(insertExceptionStatement, sb.toString(), baseIndex, eventId);
}
if (commonFrames > 0) {
StringBuilder sb = new StringBuilder();
sb.append('\t').append("... ").append(commonFrames).append(" common frames omitted");
baseIndex = (short)(baseIndex + 1);updateExceptionStatement(insertExceptionStatement, sb.toString(), baseIndex, eventId);
}
return baseIndex;
}
protected void insertThrowable(IThrowableProxy tp, Connection connection, long eventId)
throws SQLException
{
PreparedStatement exceptionStatement = null;
try {
exceptionStatement = connection.prepareStatement(this.insertExceptionSQL);
short baseIndex = 0;
while (tp != null) {
baseIndex = buildExceptionStatement(tp, baseIndex, exceptionStatement, eventId);
tp = tp.getCause();
}
if (this.cnxSupportsBatchUpdates) {
exceptionStatement.executeBatch();
}
} finally {
ch.qos.logback.core.db.DBHelper.closeStatement(exceptionStatement);
}
}
}
SQLBuilder类源码(该类自带,默认为protected类型,外部调用会报错,故贴出):
package com.ldl.cn;
import ch.qos.logback.classic.db.names.ColumnName;
import ch.qos.logback.classic.db.names.DBNameResolver;
import ch.qos.logback.classic.db.names.TableName;
public class SQLBuilder
{
public SQLBuilder()
{
}
static String buildInsertPropertiesSQL(DBNameResolver dbNameResolver)
{
StringBuilder sqlBuilder = new StringBuilder("INSERT INTO ");
sqlBuilder.append(dbNameResolver.getTableName(TableName.LOGGING_EVENT_PROPERTY)).append(" (");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.EVENT_ID)).append(", ");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.MAPPED_KEY)).append(", ");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.MAPPED_VALUE)).append(") ");
sqlBuilder.append("VALUES (?, ?, ?)");
return sqlBuilder.toString();
}
static String buildInsertExceptionSQL(DBNameResolver dbNameResolver)
{
StringBuilder sqlBuilder = new StringBuilder("INSERT INTO ");
sqlBuilder.append(dbNameResolver.getTableName(TableName.LOGGING_EVENT_EXCEPTION)).append(" (");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.EVENT_ID)).append(", ");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.I)).append(", ");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.TRACE_LINE)).append(") ");
sqlBuilder.append("VALUES (?, ?, ?)");
return sqlBuilder.toString();
}
static String buildInsertSQL(DBNameResolver dbNameResolver)
{
StringBuilder sqlBuilder = new StringBuilder("INSERT INTO ");
sqlBuilder.append(dbNameResolver.getTableName(TableName.LOGGING_EVENT)).append(" (");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.TIMESTMP)).append(", ");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.FORMATTED_MESSAGE)).append(", ");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.LOGGER_NAME)).append(", ");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.LEVEL_STRING)).append(", ");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.THREAD_NAME)).append(", ");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.REFERENCE_FLAG)).append(", ");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.ARG0)).append(", ");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.ARG1)).append(", ");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.ARG2)).append(", ");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.ARG3)).append(", ");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.CALLER_FILENAME)).append(", ");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.CALLER_CLASS)).append(", ");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.CALLER_METHOD)).append(", ");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.CALLER_LINE)).append(") ");
sqlBuilder.append("VALUES (?, ?, ? ,?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
return sqlBuilder.toString();
}
}
4.测试
查看数据库:
OK!