`

扩展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实例源码"涉及的技术点主要集中在如何将Logback与Kafka集成,使得日志可以被有效地发送到Kafka集群。这个过程通常涉及到以下几个步骤: 1. **添加依赖**:首先,你需要在项目的...

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

    标题中的“扩展logback将日志输出到Kafka实例扩展源码”指的是在Java应用程序中,使用logback作为日志框架,并通过自定义appender(输出模块)将日志信息发送到Apache Kafka的消息队列中。Logback是SLF4J(Simple ...

    SpringBoot Logback日志记录到数据库的实现方法

    我们可以使用Logback的DBAppender来实现日志记录到数据库。下面是一个示例代码: ```java import ch.qos.logback.classic.db.DBAppender; import ch.qos.logback.classic.db.DBHelper; public class ...

    logback-ext:Logback日志库的扩展

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

    Logback类库含logback.xml配置文件

    Logback 是为了提供更高效、更灵活的日志记录解决方案而设计的,它不仅继承了 Log4j 的优点,还解决了一些性能和可扩展性问题。在Java应用程序中,日志系统扮演着至关重要的角色,它帮助开发者调试代码,记录错误,...

    Logback框架需要的3个jar包和logback.xml文件

    Logback 是一个在Java应用程序中广泛使用的日志记录框架,它是对早期的log4j框架的一个升级和扩展。Logback 提供了高效、灵活的日志记录解决方案,支持多种日志级别,如DEBUG、INFO、WARN、ERROR等,帮助开发者调试...

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

    `logback-classic-1.2.3.jar`是Logback的经典实现,它扩展了`logback-core`,实现了SLF4J接口,从而使得Logback可以作为SLF4J的日志实现。这个库还引入了`ch.qos.logback.classic.Logger`,它是`org.slf4j.Logger`的...

    logback下载 日志文件jar包

    这是 Logback 的经典实现,扩展了核心库的功能,并且兼容 SLF4J(Simple Logging Facade for Java)。SLF4J 提供了一个抽象层,允许开发者在不同的日志框架之间切换,如 Log4j 或 Logback。`ch.qos.logback.classic...

    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.的jar包

    **日志框架Logback简介** Logback 是一个用于日志记录的开源框架,由 Ceki Gülcü(也是 Log4j 的创始人)设计并开发。它作为 Log4j 的升级版,提供了更高的性能和更丰富的功能。Logback 分为三个主要组件:...

    logback所需jar包

    **日志框架Logback** 日志是任何软件系统中至关重要的组成部分,它为开发者提供了运行时的调试信息、错误报告以及性能分析数据。在Java世界里,Logback是一款高效、可配置的日志记录框架,由Ceki Gülcü(也是log4...

    logback日志记录写入kafka

    本主题将详细介绍如何利用Logback和SLF4J来将日志记录到Kafka队列中,以及支持日志解析和过滤等扩展功能。 首先,我们需要理解SLF4J的工作原理。SLF4J提供了一组API,允许我们在应用程序中插入日志语句,而具体的...

    logback 1.2.3.zip

    10. **可扩展性**: Logback设计时考虑了扩展性,可以通过自定义Appender和Layout来满足特定的日志记录需求。 通过这个压缩包,用户可以获得Logback框架的核心组件和经典实现,以及用于处理HTTP访问日志的模块,从而...

    logback-1.1.2源码包

    **logback-1.1.2源码包详解** **一、logback介绍** Logback 是一个用于日志记录的开源框架,由 Ceki Gülcü(也创建了广泛使用的 log4j)开发。它是对 log4j 的升级,旨在提供更高的性能、更灵活的配置以及更好的...

    Logback所需的jar包

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

    logback类库.rar

    `logback-classic-1.2.3.jar`是logback-classic的库文件,它扩展了logback的功能,如MDC(Mapped Diagnostic Context)、自定义布局和过滤器等。 3. **SLF4J API**: SLF4J作为一个抽象层,允许开发者选择不同的日志...

    Java开发-日志管理-logback框架日志系统基础

    在日常工程开发中,日志是非常重要的一部分,通过...本资源基于maven,使用logback搭建日志结构,实现多种级别日志的输出控制,并封装各个级别的日志输出方法,可以作为日志管理基础进行扩展以满足其他场景的日志管理。

    logback1.2.3

    标题“logback1.2.3”和描述中的文件名暗示了这是一个关于Logback日志框架的版本1.2.3的资源包,其中包含了Logback的经典实现(logback-classic)和核心库(logback-core),以及Simple Logging Facade for Java ...

    logback官方中文版文档.pdf

    Logback 是一个专门为Java应用程序设计的日志框架,由log4j的创始人Ceki Gülcü设计,旨在提高日志处理的效率和灵活性。它在性能和资源消耗方面优于log4j和其他日志系统,提供了许多独特的特性,如Marker、参数化...

    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....

Global site tag (gtag.js) - Google Analytics