赞
踩
目录
在一个系统开发过程中,日志系统和测试系统必不可少,在整个开发流程中扮演着及其重要的角色。在现有的主流日志框架中,有log4j,slf4j和logback等,本着买新不买旧的原则,在系统中采用了logback+slf4j的日志框架。
在配置过程中,可以实现日志实时格式化显示在console中,也可以分时间分大小记录到文件中。因为项目中需要将日志实时记录到MySQL数据库中,查找了很多资料,遇到很多问题,基本都在配置文件中倒下。
以下,将分享两种记录日志至MySQL数据库中的方式,其一是根据logback原始配置,其二是自定义数据库表,记录日志。
- <!-- 输出日志到数据库 -->
- <appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
- <connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
- <driverClass>com.mysql.cj.jdbc.Driver</driverClass>
- <url>jdbc:mysql://127.0.0.1:3306/log?serverTimezone=UTC</url>
- <user>root</user>
- <password>z111111</password>
- </connectionSource>
- </appender>
此上代码段,为输出日志到MySQL数据库中的配置,其中<appender> 标签中 class的具体值,指向处理日志输出至数据库中的具体类,同时,在数据库连接的配置,没有用到数据库连接池,在查询各种资料并试验后,使用数据库连接池的都无法正常运行。
logback-text.xml 配置文件源码如下:
- <?xml version="1.0" encoding="UTF-8"?>
- <configuration debug="false">
- <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
- <property name="LOG_HOME" value="/home" />
-
- <property name="CONSOLE_LOG_PATTERN"
- value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) |- %highlight(%-5level) | %blue(%thread) | %blue(%file:%line) | %green(%logger) - %cyan(%msg%n)"/>
- <property name="FILE_LOG_PATTERN"
- value="%d{yyyy-MM-dd HH:mm:ss} |- %-5level - %thread - %file:%line - %logger : %msg%n"/>
-
-
- <!-- 控制台输出 -->
- <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
- <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
- <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
- <pattern>${CONSOLE_LOG_PATTERN}</pattern>
- </encoder>
- </appender>
-
- <!-- 输出日志到数据库 -->
- <appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
- <connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
- <driverClass>com.mysql.cj.jdbc.Driver</driverClass>
- <url>jdbc:mysql://127.0.0.1:3306/log?serverTimezone=UTC</url>
- <user>root</user>
- <password>z111111</password>
- </connectionSource>
- </appender>
-
- <!-- 日志输出级别 -->
- <root level="DEBUG">
- <appender-ref ref="STDOUT" />
- <appender-ref ref="FILE_ALL" />
- <appender-ref ref="FILE_ERROR"/>
- <appender-ref ref="DB"/>
- </root>
- </configuration>
MySQL数据库表创建脚本在 ch.qos.logback.classic.db.scrip 包下,选择mysql.sql 内容如下,运行并创建数据库表:
- 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;
测试类如下:
- public class BlogAction {
-
- private static Logger logger = LoggerFactory.getLogger(BlogAction.class);
-
- public static void main(String[] args) {
- for(int i = 0 ; i < 3 ; i++){
- logger.info("logback So"+i);
- logger.error("logback Down");
- logger.debug("debug");
- }
-
- logger.info("logback So44");
- }
- }
运行后,控制台显示如下
数据库中数据如下所示
在上面第一条试验后,我们发现,在实际项目开发中,项目日志并不需要很多内容,如此繁多的表项反而会耽误我们阅读查看的时间和效率,于是在考虑到实际需要,我想着能不能将数据库表自定义,仅保留下一部分重要信息,以便阅读。
在此之前,反复查看 logback 中 ch.qos.logback.classic.db 包下的所有源代码,结构如下图:
在db包下,有names 、script 、和DBAppender 、DBHelper 、SQLBuilder文件,下面分别介绍其作用:
此包下含有几个文件,包含枚举类和一些方法,总结来说就是,这几个文件的作用为,规定数据库表名和数据库表中各个表项的名字,在枚举类ColumnName中包含三个表中各个表项的名字
- package ch.qos.logback.classic.db.names;
-
- public enum ColumnName {
-
- EVENT_ID,
-
- TIMESTMP, FORMATTED_MESSAGE, LOGGER_NAME, LEVEL_STRING, THREAD_NAME, REFERENCE_FLAG, ARG0, ARG1, ARG2, ARG3, CALLER_FILENAME, CALLER_CLASS, CALLER_METHOD, CALLER_LINE,
-
- // MDC
- MAPPED_KEY, MAPPED_VALUE,
-
- I, TRACE_LINE;
- }
在枚举类TableName中包含三个表的名字
- /**
- * @author Tomasz Nurkiewicz
- * @since 0.9.19
- */
- public enum TableName {
-
- LOGGING_EVENT, LOGGING_EVENT_PROPERTY, LOGGING_EVENT_EXCEPTION
-
- }
其余的类中包含一些必须的方法,例如将枚举类中数据转换为小写字母的表名等方法。
此包下包含的所有文件为SQL脚本,对应不同的数据库,有不同的数据库表创建脚本。
此类,为重点,其继承自 DBAppenderBase<ILoggingEvent> ,实现了将日志数据插入数据库中的方法,源码如下:
- package ch.qos.logback.classic.db;
-
- import static ch.qos.logback.core.db.DBHelper.closeStatement;
-
- 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.names.DBNameResolver;
- import ch.qos.logback.classic.db.names.DefaultDBNameResolver;
- import ch.qos.logback.classic.spi.*;
- import ch.qos.logback.core.CoreConstants;
- import ch.qos.logback.core.db.DBAppenderBase;
-
- /**
- * The DBAppender inserts logging events into three database tables in a format
- * independent of the Java programming language.
- *
- * For more information about this appender, please refer to the online manual
- * at http://logback.qos.ch/manual/appenders.html#DBAppender
- *
- * @author Ceki Gülcü
- * @author Ray DeCampo
- * @author Sébastien Pennec
- */
- public class DBAppender extends DBAppenderBase<ILoggingEvent> {
- 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 {
- // 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;
- }
-
- public void setDbNameResolver(DBNameResolver dbNameResolver) {
- this.dbNameResolver = dbNameResolver;
- }
-
- @Override
- public void start() {
- if (dbNameResolver == null)
- dbNameResolver = new DefaultDBNameResolver();
- insertExceptionSQL = SQLBuilder.buildInsertExceptionSQL(dbNameResolver);
- insertPropertiesSQL = SQLBuilder.buildInsertPropertiesSQL(dbNameResolver);
- insertSQL = SQLBuilder.buildInsertSQL(dbNameResolver);
- super.start();
- }
-
- @Override
- 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<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.setLong(TIMESTMP_INDEX, 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 < 4; i++) {
- stmt.setString(ARG0_INDEX + i, asStringTruncatedTo254(argArray[i]));
- }
- if (arrayLen < 4) {
- for (int i = arrayLen; i < 4; 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>();
- // we add the context properties first, then the event properties, since
- // we consider that event-specific properties should have priority over
- // context-wide properties.
- 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<String> propertiesKeys = mergedMap.keySet();
- if (propertiesKeys.size() > 0) {
- PreparedStatement insertPropertiesStatement = null;
- try {
- insertPropertiesStatement = connection.prepareStatement(insertPropertiesSQL);
-
- for (String key : propertiesKeys) {
- String value = 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();
- }
- } finally {
- closeStatement(insertPropertiesStatement);
- }
- }
- }
-
- /**
- * Add an exception statement either as a batch or execute immediately if
- * batch updates are not supported.
- */
- 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 = null;
- try {
- exceptionStatement = connection.prepareStatement(insertExceptionSQL);
-
- short baseIndex = 0;
- while (tp != null) {
- baseIndex = buildExceptionStatement(tp, baseIndex, exceptionStatement, eventId);
- tp = tp.getCause();
- }
-
- if (cnxSupportsBatchUpdates) {
- exceptionStatement.executeBatch();
- }
- } finally {
- closeStatement(exceptionStatement);
- }
-
- }
- }
因为方法较多,所以我将几个重要的方法单独提出来讲解
首先,查找对应数据库插入的方法:
- void bindLoggingEventWithInsertStatement(PreparedStatement stmt, ILoggingEvent event) throws SQLException {
- stmt.setLong(TIMESTMP_INDEX, 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 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()));
- }
上两个方法,则是将日志所有的参数,格式化为数据库对应的格式,根据类的开始几行数据,如以下
- 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;
可以很明显找到这个关键的方法之一,这也为我们后面重写此类的重点。
我们上面找到格式化数据的方法,下面则将查找插入数据库的具体操作实现方法,不难看出,我们找到以下方法内容:
- @Override
- public void start() {
- if (dbNameResolver == null)
- dbNameResolver = new DefaultDBNameResolver();
- insertExceptionSQL = SQLBuilder.buildInsertExceptionSQL(dbNameResolver);
- insertPropertiesSQL = SQLBuilder.buildInsertPropertiesSQL(dbNameResolver);
- insertSQL = SQLBuilder.buildInsertSQL(dbNameResolver);
- super.start();
- }
-
- @Override
- 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");
- }
- }
以上只是插入logging_event这个表内数据的方法,还有其他两个表,其插入方法在类中,可以自行查找。
不难看出,我们找到了此类中的关键方法,了解这两个方法后,有助于我们自己实现自定义DBAppender。
在上一个类中,找到了插入数据库的具体实现方法,那么还有插入数据库的SQL语句没有查到,于是在这个类SQLBuilder中,便集成了三个表的数据插入语句的格式化。具体内容如下:
- /**
- * Logback: the reliable, generic, fast and flexible logging framework.
- * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
- *
- * This program and the accompanying materials are dual-licensed under
- * either the terms of the Eclipse Public License v1.0 as published by
- * the Eclipse Foundation
- *
- * or (per the licensee's choosing)
- *
- * under the terms of the GNU Lesser General Public License version 2.1
- * as published by the Free Software Foundation.
- */
- package ch.qos.logback.classic.db;
-
- import ch.qos.logback.classic.db.names.*;
-
- /**
- * @author Tomasz Nurkiewicz
- * @since 2010-03-16
- */
- public class 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();
- }
- }
由上不难看出其作用,生成具体的SQL语句,用于数据插入。
此包内还有几个其他的类没有讲解,因为其对我们自定义重写的作用不大,就不多赘述了。
由以上源码的解析过程,我们熟悉了其工作方式和具体实现方法,以下,将开始重写源码的过程。
在之前我们说过,原配置的数据库表有很多我们并不需要的信息,于是,将数据库表中不需要的表项删除,删除不需要的表,只留下一个logging_event表的内容,并加以修改。其SQL脚本如下:
- BEGIN;
- DROP TABLE IF EXISTS log_record;
-
- COMMIT;
-
-
- BEGIN;
- CREATE TABLE log_record
- (
- id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
- time DATETIME(6) NOT NULL,
- message TEXT NOT NULL,
- level_string VARCHAR(254) NOT NULL,
- logger_name VARCHAR(254) NOT NULL,
- thread_name VARCHAR(254),
- reference_flag SMALLINT,
- caller_filename VARCHAR(254) NOT NULL,
- caller_class VARCHAR(254) NOT NULL,
- caller_method VARCHAR(254) NOT NULL,
- caller_line CHAR(4) NOT NULL
-
- );
- COMMIT;
由以上表得知,只留下时间和日志具体位置和信息,同时删除其余两个表。
其中,原数据表中时间的格式为Long型,不便于阅读,本文将时间改为DATETIME形式,方便查询和操作。
因为我们只需要管理一个数据库表,本着简洁的原则,我们将names包下面的所有文件全部删除,将SQLBuilder 文件中简化为一个方法,放在MyDBAppender.java 中,方法代码如下:
- private static String buildInsertSQL() {
- return "INSERT INTO log_record " +
- "(time, message, level_string, logger_name, thread_name," +
- "caller_filename, caller_class, caller_method, caller_line)"+
- "VALUES (?, ?, ? ,?, ?, ?, ?, ?, ?)";
- }
是不是简洁了很多,可以根据自己定义的数据库表自行修改。
因为我们对数据库表的修改只是删除某些表项,其具体数据格式化方法本质没变,代码如下:
- private static final int TIME_INDEX = 1;
- private static final int MESSAGE_INDEX = 2;
- private static final int LEVEL_STRING_INDEX = 3;
- private static final int LOGGER_NAME_INDEX = 4;
- private static final int THREAD_NAME_INDEX = 5;
- private static final int CALLER_FILENAME_INDEX = 6;
- private static final int CALLER_CLASS_INDEX = 7;
- private static final int CALLER_METHOD_INDEX = 8;
- private static final int CALLER_LINE_INDEX = 9;
-
-
- private void bindLoggingEventWithInsertStatement(PreparedStatement stmt, ILoggingEvent event) throws SQLException {
- stmt.setTimestamp(TIME_INDEX, new Timestamp(event.getTimeStamp()));
- stmt.setString(MESSAGE_INDEX, event.getFormattedMessage());
- stmt.setString(LEVEL_STRING_INDEX, event.getLevel().toString());
- stmt.setString(LOGGER_NAME_INDEX, event.getLoggerName());
- stmt.setString(THREAD_NAME_INDEX, event.getThreadName());
- }
-
- private 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()));
- }
- package com.ahdy.logtest.config;
-
- import ch.qos.logback.classic.spi.*;
- import ch.qos.logback.core.db.DBAppenderBase;
-
- import java.lang.reflect.Method;
- import java.sql.Connection;
- import java.sql.PreparedStatement;
- import java.sql.SQLException;
- import java.text.SimpleDateFormat;
- import java.util.Date;
-
- /**
- * @Description:
- * @Author: Rocty
- * @Date: 2019-06-18 16:22
- * @Version: 1.0
- */
- public class MyDBAppender extends DBAppenderBase<ILoggingEvent> {
-
- private String insertSQL;
- private static final Method GET_GENERATED_KEYS_METHOD;
-
- private static final int TIME_INDEX = 1;
- private static final int MESSAGE_INDEX = 2;
- private static final int LEVEL_STRING_INDEX = 3;
- private static final int LOGGER_NAME_INDEX = 4;
- private static final int THREAD_NAME_INDEX = 5;
- private static final int CALLER_FILENAME_INDEX = 6;
- private static final int CALLER_CLASS_INDEX = 7;
- private static final int CALLER_METHOD_INDEX = 8;
- private static final int CALLER_LINE_INDEX = 9;
-
- private static final StackTraceElement EMPTY_CALLER_DATA = CallerData.naInstance();
-
- 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;
- }
-
- @Override
- public void start() {
- insertSQL = buildInsertSQL();
- super.start();
- }
-
- private static String buildInsertSQL() {
- return "INSERT INTO log_record " +
- "(time, message, logger_name, level_string, thread_name," +
- "caller_filename, caller_class, caller_method, caller_line)"+
- "VALUES (?, ?, ? ,?, ?, ?, ?, ?, ?)";
- }
-
- private void bindLoggingEventWithInsertStatement(PreparedStatement stmt, ILoggingEvent event) throws SQLException {
- stmt.setTimestamp(TIME_INDEX, new Timestamp(event.getTimeStamp()));
- stmt.setString(MESSAGE_INDEX, event.getFormattedMessage());
- stmt.setString(LEVEL_STRING_INDEX, event.getLevel().toString());
- stmt.setString(LOGGER_NAME_INDEX, event.getLoggerName());
- stmt.setString(THREAD_NAME_INDEX, event.getThreadName());
- }
-
- private 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()));
- }
-
- @Override
- protected void subAppend(ILoggingEvent event, Connection connection, PreparedStatement insertStatement) throws Throwable {
- bindLoggingEventWithInsertStatement(insertStatement, event);
- // 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");
- }
- }
-
- 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;
- }
-
- @Override
- protected Method getGeneratedKeysMethod() {
- return GET_GENERATED_KEYS_METHOD;
- }
-
- @Override
- protected String getInsertSQL() {
- return insertSQL;
- }
-
- protected void secondarySubAppend(ILoggingEvent event, Connection connection, long eventId) throws Throwable {
- }
- }
首先,修改logback-test.xml 中的 <appender>标签:
<appender name="DB" class="com.ahdy.logtest.config.MyDBAppender">
其次,原测试文件 BlogAction.java不变,运行后控制台输出一致,数据库中数据如下图示:
至此,可以看到数据完全插入数据库中,完全实现自定义功能。结束。
在写这个日志模块的过程中,查了很多资料,几乎所有的文章都是千篇一律,仅有几个重写DBApepender的文章也很不清晰,无法学习。
在实在找不到资料的情况下,阅读学习源码,并重写方法实现自定义功能。一切教程文章,都比不上阅读源码来的实在,为此,写上这么一篇文章,将logback中配置的源码讲解一些,如有不清晰,还请多多包涵。在码农中,还是新人,尚在学习中。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。