`

扩展logback DBAppender

阅读更多
一) logback已经提供了一个DBAppender(ch.qos.logback.classic.db.DBAppender),为何还需自己发明一个轮子?

1.1
ch.qos.logback.classic.db.DBAppender默认只能保存4个参数到数据库里,如下
(slf4j代码)
LOGGER.info("{}{}{}{}{}", 1,2,3,4,5);

参数5不能保存在DB中的单独一个字段,这样并不方便。扩展为可以保存32个参数。

1.2
logback默认的DBAppender不方便配置,不能自由指定表名

1.3
logback默认的DBAppender中保存的时间戳为long,阅读时不方便。

二) 代码片段 (仅新的DBAppender类,其他工具类等为节省篇幅不贴出)
package com.github.yingzhuo.logbackext.db;

import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import ch.qos.logback.classic.db.DBHelper;
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.CoreConstants;
import ch.qos.logback.core.db.DBAppenderBase;
import ch.qos.logback.core.db.dialect.SQLDialectCode;

import com.github.yingzhuo.logbackext.names.DefaultTableAndColumnNameResolver;
import com.github.yingzhuo.logbackext.names.TableAndColumnNameResolver;

/**
 * 参考ch.qos.logback.classic.db.DBAppender
 * 
 * @author yingzhuo
 * 
 */
@SuppressWarnings("rawtypes")
public class DBAppender extends DBAppenderBase<ILoggingEvent> {
	
	private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS");
	
	private boolean printStackTrace = true;
	protected String insertPropertiesSQL;
	protected String insertExceptionSQL;
	protected String insertSQL;
	protected static final Method GET_GENERATED_KEYS_METHOD;

	private TableAndColumnNameResolver nameResolver = new DefaultTableAndColumnNameResolver();

	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 ARG4_INDEX = 11;
	static final int ARG5_INDEX = 12;
	static final int ARG6_INDEX = 13;
	static final int ARG7_INDEX = 14;
	static final int ARG8_INDEX = 15;
	static final int ARG9_INDEX = 16;
	static final int ARG10_INDEX = 17;
	static final int ARG11_INDEX = 18;
	static final int ARG12_INDEX = 19;
	static final int ARG13_INDEX = 20;
	static final int ARG14_INDEX = 21;
	static final int ARG15_INDEX = 22;
	static final int ARG16_INDEX = 23;
	static final int ARG17_INDEX = 24;
	static final int ARG18_INDEX = 25;
	static final int ARG19_INDEX = 26;
	static final int ARG20_INDEX = 27;
	static final int ARG21_INDEX = 28;
	static final int ARG22_INDEX = 29;
	static final int ARG23_INDEX = 30;
	static final int ARG24_INDEX = 31;
	static final int ARG25_INDEX = 32;
	static final int ARG26_INDEX = 33;
	static final int ARG27_INDEX = 34;
	static final int ARG28_INDEX = 35;
	static final int ARG29_INDEX = 36;
	static final int ARG30_INDEX = 37;
	static final int ARG31_INDEX = 38;

	static final int CALLER_FILENAME_INDEX = 39;
	static final int CALLER_CLASS_INDEX = 40;
	static final int CALLER_METHOD_INDEX = 41;
	static final int CALLER_LINE_INDEX = 42;
	static final int EVENT_ID_INDEX = 43;

	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;
	}

	@Override
	public void start() {
		insertExceptionSQL = SQLBuilder.buildInsertExceptionSQL(nameResolver);
		insertPropertiesSQL = SQLBuilder.buildInsertPropertiesSQL(nameResolver);
		insertSQL = SQLBuilder.buildInsertSQL(nameResolver);
		System.out.println(insertSQL);
		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 = 0;
		try {
			updateCount = insertStatement.executeUpdate();
		} catch (Exception e) {
			if (this.printStackTrace) {
				e.printStackTrace();
			}
			throw e;
		}
		if (updateCount != 1) {
			addWarn("Failed to insert loggingEvent");
		}
	}

	protected void secondarySubAppend(ILoggingEvent event, Connection connection, long eventId) throws Throwable {
		Map<String, String> mergedMap = mergePropertyMaps(event);
		insertProperties(mergedMap, connection, eventId);

		if (event.getThrowableProxy() != null) {
			insertThrowable(event.getThrowableProxy(), connection, eventId);
		}
	}

	void bindLoggingEventWithInsertStatement(PreparedStatement stmt, ILoggingEvent event) throws SQLException {
		stmt.setString(TIMESTMP_INDEX, DATE_FORMAT.format(new Date(event.getTimeStamp())));
		stmt.setString(FORMATTED_MESSAGE_INDEX, event.getFormattedMessage());
		stmt.setString(LOGGER_NAME_INDEX, event.getLoggerName());
		stmt.setString(LEVEL_STRING_INDEX, event.getLevel().toString());
		stmt.setString(THREAD_NAME_INDEX, event.getThreadName());
		stmt.setShort(REFERENCE_FLAG_INDEX, DBHelper.computeReferenceMask(event));
	}

	void bindLoggingEventArgumentsWithPreparedStatement(PreparedStatement stmt, Object[] argArray) throws SQLException {
		int arrayLen = argArray != null ? argArray.length : 0;
		for (int i = 0; i < arrayLen && i < 32; i++) {
			stmt.setString(ARG0_INDEX + i, asStringTruncatedTo254(argArray[i]));
		}
		if (arrayLen < 32) {
			for (int i = arrayLen; i < 32; i++) {
				stmt.setString(ARG0_INDEX + 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;
		} else {
			return s.substring(0, 254);
		}
	}

	void bindCallerDataWithPreparedStatement(PreparedStatement stmt, StackTraceElement[] callerDataArray) throws SQLException {

		StackTraceElement caller = extractFirstCaller(callerDataArray);

		stmt.setString(CALLER_FILENAME_INDEX, caller.getFileName());
		stmt.setString(CALLER_CLASS_INDEX, caller.getClassName());
		stmt.setString(CALLER_METHOD_INDEX, caller.getMethodName());
		stmt.setString(CALLER_LINE_INDEX, 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<String, String> mergePropertyMaps(ILoggingEvent event) {
		Map<String, String> mergedMap = new HashMap<String, String>();
		Map<String, String> loggerContextMap = event.getLoggerContextVO().getPropertyMap();
		Map<String, String> mdcMap = event.getMDCPropertyMap();
		if (loggerContextMap != null) {
			mergedMap.putAll(loggerContextMap);
		}
		if (mdcMap != null) {
			mergedMap.putAll(mdcMap);
		}

		return mergedMap;
	}

	@Override
	protected Method getGeneratedKeysMethod() {
		return GET_GENERATED_KEYS_METHOD;
	}

	@Override
	protected String getInsertSQL() {
		return insertSQL;
	}

	protected void insertProperties(Map<String, String> mergedMap, Connection connection, long eventId) throws SQLException {
		Set propertiesKeys = mergedMap.keySet();
		if (propertiesKeys.size() > 0) {
			PreparedStatement insertPropertiesStatement = connection.prepareStatement(insertPropertiesSQL);

			for (Iterator i = propertiesKeys.iterator(); i.hasNext();) {
				String key = (String) i.next();
				String value = (String) mergedMap.get(key);

				insertPropertiesStatement.setLong(1, eventId);
				insertPropertiesStatement.setString(2, key);
				insertPropertiesStatement.setString(3, value);

				if (cnxSupportsBatchUpdates) {
					insertPropertiesStatement.addBatch();
				} else {
					insertPropertiesStatement.execute();
				}
			}

			if (cnxSupportsBatchUpdates) {
				insertPropertiesStatement.executeBatch();
			}

			insertPropertiesStatement.close();
		}
	}

	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 (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);
		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(CoreConstants.TAB);
			ThrowableProxyUtil.subjoinSTEP(sb, stepArray[i]);
			updateExceptionStatement(insertExceptionStatement, sb.toString(), baseIndex++, eventId);
		}

		if (commonFrames > 0) {
			StringBuilder sb = new StringBuilder();
			sb.append(CoreConstants.TAB).append("... ").append(commonFrames)
					.append(" common frames omitted");
			updateExceptionStatement(insertExceptionStatement, sb.toString(),
					baseIndex++, eventId);
		}

		return baseIndex;
	}

	protected void insertThrowable(IThrowableProxy tp, Connection connection, long eventId) throws SQLException {

		PreparedStatement exceptionStatement = connection.prepareStatement(insertExceptionSQL);

		short baseIndex = 0;
		while (tp != null) {
			baseIndex = buildExceptionStatement(tp, baseIndex, exceptionStatement, eventId);
			tp = tp.getCause();
		}

		if (cnxSupportsBatchUpdates) {
			exceptionStatement.executeBatch();
		}
		exceptionStatement.close();
	}

	public boolean isPrintStackTrace() {
		return printStackTrace;
	}

	public void setPrintStackTrace(boolean printStackTrace) {
		this.printStackTrace = printStackTrace;
	}

	public TableAndColumnNameResolver getNameResolver() {
		return nameResolver;
	}

	public void setNameResolver(TableAndColumnNameResolver nameResolver) {
		this.nameResolver = nameResolver;
	}

	@Override
	public void append(ILoggingEvent eventObject) {
		Connection connection = null;
		try {
			connection = connectionSource.getConnection();
			connection.setAutoCommit(false);
			PreparedStatement insertStatement;

			if (cnxSupportsGetGeneratedKeys) {
				String EVENT_ID_COL_NAME = "EVENT_ID";
				if (connectionSource.getSQLDialectCode() == SQLDialectCode.POSTGRES_DIALECT) {
					EVENT_ID_COL_NAME = EVENT_ID_COL_NAME.toLowerCase();
				}
				insertStatement = connection.prepareStatement(getInsertSQL(), new String[] { EVENT_ID_COL_NAME });
			} else {
				insertStatement = connection.prepareStatement(getInsertSQL());
			}

			long eventId;
			synchronized (this) {
				subAppend(eventObject, connection, insertStatement);
				eventId = 
						selectEventId(insertStatement, connection);
			}
			secondarySubAppend(eventObject, connection, eventId);
			insertStatement.close();
			connection.commit();
		} catch (Throwable sqle) {
			if (this.printStackTrace) {
				sqle.printStackTrace();
			}
			addError("problem appending event", sqle);
		} finally {
			try { connection.close();} catch (SQLException e) {}
		}
	}
}


三) 配置 (logback.xml片段)
<appender name="DB" class="com.github.yingzhuo.logbackext.db.DBAppender">
	<connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
		<dataSource class="com.mchange.v2.c3p0.ComboPooledDataSource">
			<driverClass>com.mysql.jdbc.Driver</driverClass>
			<url>jdbc:mysql://127.0.0.1:3306/logback</url>
			<user>root</user>
			<password>root</password>
		</dataSource>
	</connectionSource>
	<nameResolver class="com.github.yingzhuo.logbackext.names.DefaultTableAndColumnNameResolver">
		<loggingEventTableName>last</loggingEventTableName>
		<loggingEventExceptionTableName>last_exception</loggingEventExceptionTableName>
		<loggingEventPropertyTableName>last_property</loggingEventPropertyTableName>
	</nameResolver>
	<printStackTrace>true</printStackTrace>
</appender>


四) 下载安装
这是一个maven项目,源代码已经发布到GitHub
https://github.com/yingzhuo/logback-ext
分享到:
评论

相关推荐

    扩展logback将日志输出到Kafka实例扩展源码

    扩展logback将日志输出到Kafka实例扩展源码,详情参见博文:http://blog.csdn.net/l1028386804/article/details/79136841

    扩展logback将日志输出到Kafka实例源码

    扩展logback将日志输出到Kafka实例源码,详情请参见博文:http://blog.csdn.net/l1028386804/article/details/79135948

    logback-ext:Logback日志库的扩展

    登录扩展 Logback日志库的扩展主要适用于针对Amazon Web Services的附加程序,包括CloudWatch Logs,DynamoDB,Kinesis,SNS和SQS附加程序。 还包含基于LMAX Disrupotr的高性能异步附加器和某些实用程序,例如...

    springmvc log4j2 logback 注解 jackson 日志脱敏实现源码

    几乎是网上 能找到的 日志脱敏的所有实现 1、基于正则表达式的 日志脱敏实现 ,扩展logback 、log4j 2、springmvc 返回报文脱敏。 3、基于注解方式的脱敏。 大家选择使用。

    logback-ext-spring

    spring使用logback的扩展,使用起来非常方便。在web.xml中配置: &lt;param-name&gt;logbackConfigLocation &lt;param-value&gt;/WEB-INF/conf/logback.xml &lt;listener-class&gt;ch.qos.logback.ext.spring.web....

    Logback类库含logback.xml配置文件

    该压缩包包含 logback类库所包含的jar包以及logback.xml配置文件(放到 src 目录),用于开发学习使用。

    logback下载 日志文件jar包

    内置三个jar包 一个配置文件 logback.txt logback-classic-1.2.3.jar logback-core-1.2.3.jar slf4j-api-1.7.26.jar

    Slf4j+logback实现logback测试

    Slf4j+logback实现logback测试,Slf4j+logback实现logback测试

    logback.的jar包

    此zip包含logback-access-1.2.3和logback-classic-1.2.3和logback-core-1.2.3

    logback-core-1.2.10-API文档-中文版.zip

    赠送jar包:logback-core-1.2.10.jar; 赠送原API文档:logback-core-1.2.10-javadoc.jar; 赠送源代码:logback-core-1.2.10-sources.jar; 赠送Maven依赖信息文件:logback-core-1.2.10.pom; 包含翻译后的API文档...

    logback jar包和logback.xml配置文件打包下载 小白新手学Java

    slf4j-api-1.7.26.jar logback-core-1.2.3.jar logback-classic-1.2.3.jar logback.xml

    logback.jar包

    日志组件logback jar包,logback-access-1.1.3.jar logback-classic-1.1.3.jar logback-core-1.1.3.jar slf4j-api-1.7.12.jar

    logback-ext-spring-0.1.1

    logback与spring集成的文件,从官网上找的。上传的文件包括源文件和jar包,以下是连接: https://github.com/qos-ch/logback-extensions/wiki/Spring ...

    logback日志框架所需要的jar包

    包含logback所需:logback-classic-1.1.7.jar、logback-classic-1.1.7-sources.jar、logback-core-1.1.7.jar、logback-core-1.1.7-sources.jar、slf4j-api-1.7.21.jar、slf4j-api-1.7.21-sources.jar

    logback日志的jar包和配置介绍

    logback日志的jar包和配置介绍:logback-classic-1.1.2.jar、logback-core-1.1.2.jar、slf4j-api-1.7.7.jar、logback.xml、rsframework.properties

    logback-1.0.1

    logback当前分成三个模块:logback-core,logback- classic和logback-access。logback-core是其它两个模块的基础模块。logback-classic是log4j的一个 改良版本。此外logback-classic完整实现SLF4J API使你可以很方便...

    LogBack 中文开发手册

    Logback 中文手册,清晰版. 简单地说,Logback 是一个 Java 领域的日志框架。它被认为是 Log4J 的继承人。 Logback 主要由三个模块组成: logback-core logback-classic logback-access

    LogBack配置文件

    LogBack配置文件,主要包括LOGBack的配置文件内容

    logback通用xml配置

    用于logback框架通用xml配置文件

    Logback所需的jar包

    免费获取Logback所需的jar包 打包合集 让你少走弯路 一.logback简介 1.logback: Logback是由log4j创始人设计的另一个开源日志组件。(好的日志记录方式可以提供我们足够多的定位错误的依据)。 2.主要有三个模块...

Global site tag (gtag.js) - Google Analytics