当前位置:   article > 正文

Logback输出日志到自定义MySQL数据库(重写DBAppender)_logback日志输出到mysql数据库

logback日志输出到mysql数据库

目录

前言:需求

1  项目总览

2  原始logback输出日志至MySQL数据库配置

3  源码解析

3.1  names 

3.2  script

3.3  DBAppender

3.3.1  数据格式化方法

3.3.2  数据插入以及入口方法

3.4  SQLBuilder

3.4  其他

4  重写源码 

4.1  数据库表设计

4.2  数据库插入语句生成

4.3  数据格式化

4.4  修改后代码 (MyDBAppender.java)

4.5  运行测试

5  总结

附:


前言:需求

在一个系统开发过程中,日志系统和测试系统必不可少,在整个开发流程中扮演着及其重要的角色。在现有的主流日志框架中,有log4j,slf4j和logback等,本着买新不买旧的原则,在系统中采用了logbackslf4j的日志框架。

在配置过程中,可以实现日志实时格式化显示在console中,也可以分时间分大小记录到文件中。因为项目中需要将日志实时记录到MySQL数据库中,查找了很多资料,遇到很多问题,基本都在配置文件中倒下。

以下,将分享两种记录日志至MySQL数据库中的方式,其一是根据logback原始配置,其二是自定义数据库表,记录日志。


1  项目总览


2  原始logback输出日志至MySQL数据库配置

  1. <!-- 输出日志到数据库 -->
  2. <appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
  3. <connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
  4. <driverClass>com.mysql.cj.jdbc.Driver</driverClass>
  5. <url>jdbc:mysql://127.0.0.1:3306/log?serverTimezone=UTC</url>
  6. <user>root</user>
  7. <password>z111111</password>
  8. </connectionSource>
  9. </appender>

此上代码段,为输出日志到MySQL数据库中的配置,其中<appender> 标签中 class的具体值,指向处理日志输出至数据库中的具体类,同时,在数据库连接的配置,没有用到数据库连接池,在查询各种资料并试验后,使用数据库连接池的都无法正常运行。

logback-text.xml 配置文件源码如下:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <configuration debug="false">
  3. <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
  4. <property name="LOG_HOME" value="/home" />
  5. <property name="CONSOLE_LOG_PATTERN"
  6. value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) |- %highlight(%-5level) | %blue(%thread) | %blue(%file:%line) | %green(%logger) - %cyan(%msg%n)"/>
  7. <property name="FILE_LOG_PATTERN"
  8. value="%d{yyyy-MM-dd HH:mm:ss} |- %-5level - %thread - %file:%line - %logger : %msg%n"/>
  9. <!-- 控制台输出 -->
  10. <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
  11. <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
  12. <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
  13. <pattern>${CONSOLE_LOG_PATTERN}</pattern>
  14. </encoder>
  15. </appender>
  16. <!-- 输出日志到数据库 -->
  17. <appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
  18. <connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
  19. <driverClass>com.mysql.cj.jdbc.Driver</driverClass>
  20. <url>jdbc:mysql://127.0.0.1:3306/log?serverTimezone=UTC</url>
  21. <user>root</user>
  22. <password>z111111</password>
  23. </connectionSource>
  24. </appender>
  25. <!-- 日志输出级别 -->
  26. <root level="DEBUG">
  27. <appender-ref ref="STDOUT" />
  28. <appender-ref ref="FILE_ALL" />
  29. <appender-ref ref="FILE_ERROR"/>
  30. <appender-ref ref="DB"/>
  31. </root>
  32. </configuration>

MySQL数据库表创建脚本在 ch.qos.logback.classic.db.scrip 包下,选择mysql.sql 内容如下,运行并创建数据库表:

  1. BEGIN;
  2. DROP TABLE IF EXISTS logging_event_property;
  3. DROP TABLE IF EXISTS logging_event_exception;
  4. DROP TABLE IF EXISTS logging_event;
  5. COMMIT;
  6. BEGIN;
  7. CREATE TABLE logging_event
  8. (
  9. timestmp BIGINT NOT NULL,
  10. formatted_message TEXT NOT NULL,
  11. logger_name VARCHAR(254) NOT NULL,
  12. level_string VARCHAR(254) NOT NULL,
  13. thread_name VARCHAR(254),
  14. reference_flag SMALLINT,
  15. arg0 VARCHAR(254),
  16. arg1 VARCHAR(254),
  17. arg2 VARCHAR(254),
  18. arg3 VARCHAR(254),
  19. caller_filename VARCHAR(254) NOT NULL,
  20. caller_class VARCHAR(254) NOT NULL,
  21. caller_method VARCHAR(254) NOT NULL,
  22. caller_line CHAR(4) NOT NULL,
  23. event_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY
  24. );
  25. COMMIT;
  26. BEGIN;
  27. CREATE TABLE logging_event_property
  28. (
  29. event_id BIGINT NOT NULL,
  30. mapped_key VARCHAR(254) NOT NULL,
  31. mapped_value TEXT,
  32. PRIMARY KEY(event_id, mapped_key),
  33. FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
  34. );
  35. COMMIT;
  36. BEGIN;
  37. CREATE TABLE logging_event_exception
  38. (
  39. event_id BIGINT NOT NULL,
  40. i SMALLINT NOT NULL,
  41. trace_line VARCHAR(254) NOT NULL,
  42. PRIMARY KEY(event_id, i),
  43. FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
  44. );
  45. COMMIT;

测试类如下: 

  1. public class BlogAction {
  2. private static Logger logger = LoggerFactory.getLogger(BlogAction.class);
  3. public static void main(String[] args) {
  4. for(int i = 0 ; i < 3 ; i++){
  5. logger.info("logback So"+i);
  6. logger.error("logback Down");
  7. logger.debug("debug");
  8. }
  9. logger.info("logback So44");
  10. }
  11. }

运行后,控制台显示如下

数据库中数据如下所示


3  源码解析

在上面第一条试验后,我们发现,在实际项目开发中,项目日志并不需要很多内容,如此繁多的表项反而会耽误我们阅读查看的时间和效率,于是在考虑到实际需要,我想着能不能将数据库表自定义,仅保留下一部分重要信息,以便阅读。

在此之前,反复查看 logback 中 ch.qos.logback.classic.db 包下的所有源代码,结构如下图:

在db包下,有names 、script 、和DBAppender 、DBHelper 、SQLBuilder文件,下面分别介绍其作用:

3.1  names 

此包下含有几个文件,包含枚举类和一些方法,总结来说就是,这几个文件的作用为,规定数据库表名和数据库表中各个表项的名字,在枚举类ColumnName中包含三个表中各个表项的名字

  1. package ch.qos.logback.classic.db.names;
  2. public enum ColumnName {
  3. EVENT_ID,
  4. TIMESTMP, FORMATTED_MESSAGE, LOGGER_NAME, LEVEL_STRING, THREAD_NAME, REFERENCE_FLAG, ARG0, ARG1, ARG2, ARG3, CALLER_FILENAME, CALLER_CLASS, CALLER_METHOD, CALLER_LINE,
  5. // MDC
  6. MAPPED_KEY, MAPPED_VALUE,
  7. I, TRACE_LINE;
  8. }

在枚举类TableName中包含三个表的名字

  1. /**
  2. * @author Tomasz Nurkiewicz
  3. * @since 0.9.19
  4. */
  5. public enum TableName {
  6. LOGGING_EVENT, LOGGING_EVENT_PROPERTY, LOGGING_EVENT_EXCEPTION
  7. }

其余的类中包含一些必须的方法,例如将枚举类中数据转换为小写字母的表名等方法。

3.2  script

此包下包含的所有文件为SQL脚本,对应不同的数据库,有不同的数据库表创建脚本。

3.3  DBAppender

此类,为重点,其继承自 DBAppenderBase<ILoggingEvent> ,实现了将日志数据插入数据库中的方法,源码如下:

  1. package ch.qos.logback.classic.db;
  2. import static ch.qos.logback.core.db.DBHelper.closeStatement;
  3. import java.lang.reflect.Method;
  4. import java.sql.Connection;
  5. import java.sql.PreparedStatement;
  6. import java.sql.SQLException;
  7. import java.util.HashMap;
  8. import java.util.Map;
  9. import java.util.Set;
  10. import ch.qos.logback.classic.db.names.DBNameResolver;
  11. import ch.qos.logback.classic.db.names.DefaultDBNameResolver;
  12. import ch.qos.logback.classic.spi.*;
  13. import ch.qos.logback.core.CoreConstants;
  14. import ch.qos.logback.core.db.DBAppenderBase;
  15. /**
  16. * The DBAppender inserts logging events into three database tables in a format
  17. * independent of the Java programming language.
  18. *
  19. * For more information about this appender, please refer to the online manual
  20. * at http://logback.qos.ch/manual/appenders.html#DBAppender
  21. *
  22. * @author Ceki G&uuml;lc&uuml;
  23. * @author Ray DeCampo
  24. * @author S&eacute;bastien Pennec
  25. */
  26. public class DBAppender extends DBAppenderBase<ILoggingEvent> {
  27. protected String insertPropertiesSQL;
  28. protected String insertExceptionSQL;
  29. protected String insertSQL;
  30. protected static final Method GET_GENERATED_KEYS_METHOD;
  31. private DBNameResolver dbNameResolver;
  32. static final int TIMESTMP_INDEX = 1;
  33. static final int FORMATTED_MESSAGE_INDEX = 2;
  34. static final int LOGGER_NAME_INDEX = 3;
  35. static final int LEVEL_STRING_INDEX = 4;
  36. static final int THREAD_NAME_INDEX = 5;
  37. static final int REFERENCE_FLAG_INDEX = 6;
  38. static final int ARG0_INDEX = 7;
  39. static final int ARG1_INDEX = 8;
  40. static final int ARG2_INDEX = 9;
  41. static final int ARG3_INDEX = 10;
  42. static final int CALLER_FILENAME_INDEX = 11;
  43. static final int CALLER_CLASS_INDEX = 12;
  44. static final int CALLER_METHOD_INDEX = 13;
  45. static final int CALLER_LINE_INDEX = 14;
  46. static final int EVENT_ID_INDEX = 15;
  47. static final StackTraceElement EMPTY_CALLER_DATA = CallerData.naInstance();
  48. static {
  49. // PreparedStatement.getGeneratedKeys() method was added in JDK 1.4
  50. Method getGeneratedKeysMethod;
  51. try {
  52. // the
  53. getGeneratedKeysMethod = PreparedStatement.class.getMethod("getGeneratedKeys", (Class[]) null);
  54. } catch (Exception ex) {
  55. getGeneratedKeysMethod = null;
  56. }
  57. GET_GENERATED_KEYS_METHOD = getGeneratedKeysMethod;
  58. }
  59. public void setDbNameResolver(DBNameResolver dbNameResolver) {
  60. this.dbNameResolver = dbNameResolver;
  61. }
  62. @Override
  63. public void start() {
  64. if (dbNameResolver == null)
  65. dbNameResolver = new DefaultDBNameResolver();
  66. insertExceptionSQL = SQLBuilder.buildInsertExceptionSQL(dbNameResolver);
  67. insertPropertiesSQL = SQLBuilder.buildInsertPropertiesSQL(dbNameResolver);
  68. insertSQL = SQLBuilder.buildInsertSQL(dbNameResolver);
  69. super.start();
  70. }
  71. @Override
  72. protected void subAppend(ILoggingEvent event, Connection connection, PreparedStatement insertStatement) throws Throwable {
  73. bindLoggingEventWithInsertStatement(insertStatement, event);
  74. bindLoggingEventArgumentsWithPreparedStatement(insertStatement, event.getArgumentArray());
  75. // This is expensive... should we do it every time?
  76. bindCallerDataWithPreparedStatement(insertStatement, event.getCallerData());
  77. int updateCount = insertStatement.executeUpdate();
  78. if (updateCount != 1) {
  79. addWarn("Failed to insert loggingEvent");
  80. }
  81. }
  82. protected void secondarySubAppend(ILoggingEvent event, Connection connection, long eventId) throws Throwable {
  83. Map<String, String> mergedMap = mergePropertyMaps(event);
  84. insertProperties(mergedMap, connection, eventId);
  85. if (event.getThrowableProxy() != null) {
  86. insertThrowable(event.getThrowableProxy(), connection, eventId);
  87. }
  88. }
  89. void bindLoggingEventWithInsertStatement(PreparedStatement stmt, ILoggingEvent event) throws SQLException {
  90. stmt.setLong(TIMESTMP_INDEX, event.getTimeStamp());
  91. stmt.setString(FORMATTED_MESSAGE_INDEX, event.getFormattedMessage());
  92. stmt.setString(LOGGER_NAME_INDEX, event.getLoggerName());
  93. stmt.setString(LEVEL_STRING_INDEX, event.getLevel().toString());
  94. stmt.setString(THREAD_NAME_INDEX, event.getThreadName());
  95. stmt.setShort(REFERENCE_FLAG_INDEX, DBHelper.computeReferenceMask(event));
  96. }
  97. void bindLoggingEventArgumentsWithPreparedStatement(PreparedStatement stmt, Object[] argArray) throws SQLException {
  98. int arrayLen = argArray != null ? argArray.length : 0;
  99. for (int i = 0; i < arrayLen && i < 4; i++) {
  100. stmt.setString(ARG0_INDEX + i, asStringTruncatedTo254(argArray[i]));
  101. }
  102. if (arrayLen < 4) {
  103. for (int i = arrayLen; i < 4; i++) {
  104. stmt.setString(ARG0_INDEX + i, null);
  105. }
  106. }
  107. }
  108. String asStringTruncatedTo254(Object o) {
  109. String s = null;
  110. if (o != null) {
  111. s = o.toString();
  112. }
  113. if (s == null) {
  114. return null;
  115. }
  116. if (s.length() <= 254) {
  117. return s;
  118. } else {
  119. return s.substring(0, 254);
  120. }
  121. }
  122. void bindCallerDataWithPreparedStatement(PreparedStatement stmt, StackTraceElement[] callerDataArray) throws SQLException {
  123. StackTraceElement caller = extractFirstCaller(callerDataArray);
  124. stmt.setString(CALLER_FILENAME_INDEX, caller.getFileName());
  125. stmt.setString(CALLER_CLASS_INDEX, caller.getClassName());
  126. stmt.setString(CALLER_METHOD_INDEX, caller.getMethodName());
  127. stmt.setString(CALLER_LINE_INDEX, Integer.toString(caller.getLineNumber()));
  128. }
  129. private StackTraceElement extractFirstCaller(StackTraceElement[] callerDataArray) {
  130. StackTraceElement caller = EMPTY_CALLER_DATA;
  131. if (hasAtLeastOneNonNullElement(callerDataArray))
  132. caller = callerDataArray[0];
  133. return caller;
  134. }
  135. private boolean hasAtLeastOneNonNullElement(StackTraceElement[] callerDataArray) {
  136. return callerDataArray != null && callerDataArray.length > 0 && callerDataArray[0] != null;
  137. }
  138. Map<String, String> mergePropertyMaps(ILoggingEvent event) {
  139. Map<String, String> mergedMap = new HashMap<String, String>();
  140. // we add the context properties first, then the event properties, since
  141. // we consider that event-specific properties should have priority over
  142. // context-wide properties.
  143. Map<String, String> loggerContextMap = event.getLoggerContextVO().getPropertyMap();
  144. Map<String, String> mdcMap = event.getMDCPropertyMap();
  145. if (loggerContextMap != null) {
  146. mergedMap.putAll(loggerContextMap);
  147. }
  148. if (mdcMap != null) {
  149. mergedMap.putAll(mdcMap);
  150. }
  151. return mergedMap;
  152. }
  153. @Override
  154. protected Method getGeneratedKeysMethod() {
  155. return GET_GENERATED_KEYS_METHOD;
  156. }
  157. @Override
  158. protected String getInsertSQL() {
  159. return insertSQL;
  160. }
  161. protected void insertProperties(Map<String, String> mergedMap, Connection connection, long eventId) throws SQLException {
  162. Set<String> propertiesKeys = mergedMap.keySet();
  163. if (propertiesKeys.size() > 0) {
  164. PreparedStatement insertPropertiesStatement = null;
  165. try {
  166. insertPropertiesStatement = connection.prepareStatement(insertPropertiesSQL);
  167. for (String key : propertiesKeys) {
  168. String value = mergedMap.get(key);
  169. insertPropertiesStatement.setLong(1, eventId);
  170. insertPropertiesStatement.setString(2, key);
  171. insertPropertiesStatement.setString(3, value);
  172. if (cnxSupportsBatchUpdates) {
  173. insertPropertiesStatement.addBatch();
  174. } else {
  175. insertPropertiesStatement.execute();
  176. }
  177. }
  178. if (cnxSupportsBatchUpdates) {
  179. insertPropertiesStatement.executeBatch();
  180. }
  181. } finally {
  182. closeStatement(insertPropertiesStatement);
  183. }
  184. }
  185. }
  186. /**
  187. * Add an exception statement either as a batch or execute immediately if
  188. * batch updates are not supported.
  189. */
  190. void updateExceptionStatement(PreparedStatement exceptionStatement, String txt, short i, long eventId) throws SQLException {
  191. exceptionStatement.setLong(1, eventId);
  192. exceptionStatement.setShort(2, i);
  193. exceptionStatement.setString(3, txt);
  194. if (cnxSupportsBatchUpdates) {
  195. exceptionStatement.addBatch();
  196. } else {
  197. exceptionStatement.execute();
  198. }
  199. }
  200. short buildExceptionStatement(IThrowableProxy tp, short baseIndex, PreparedStatement insertExceptionStatement, long eventId) throws SQLException {
  201. StringBuilder buf = new StringBuilder();
  202. ThrowableProxyUtil.subjoinFirstLine(buf, tp);
  203. updateExceptionStatement(insertExceptionStatement, buf.toString(), baseIndex++, eventId);
  204. int commonFrames = tp.getCommonFrames();
  205. StackTraceElementProxy[] stepArray = tp.getStackTraceElementProxyArray();
  206. for (int i = 0; i < stepArray.length - commonFrames; i++) {
  207. StringBuilder sb = new StringBuilder();
  208. sb.append(CoreConstants.TAB);
  209. ThrowableProxyUtil.subjoinSTEP(sb, stepArray[i]);
  210. updateExceptionStatement(insertExceptionStatement, sb.toString(), baseIndex++, eventId);
  211. }
  212. if (commonFrames > 0) {
  213. StringBuilder sb = new StringBuilder();
  214. sb.append(CoreConstants.TAB).append("... ").append(commonFrames).append(" common frames omitted");
  215. updateExceptionStatement(insertExceptionStatement, sb.toString(), baseIndex++, eventId);
  216. }
  217. return baseIndex;
  218. }
  219. protected void insertThrowable(IThrowableProxy tp, Connection connection, long eventId) throws SQLException {
  220. PreparedStatement exceptionStatement = null;
  221. try {
  222. exceptionStatement = connection.prepareStatement(insertExceptionSQL);
  223. short baseIndex = 0;
  224. while (tp != null) {
  225. baseIndex = buildExceptionStatement(tp, baseIndex, exceptionStatement, eventId);
  226. tp = tp.getCause();
  227. }
  228. if (cnxSupportsBatchUpdates) {
  229. exceptionStatement.executeBatch();
  230. }
  231. } finally {
  232. closeStatement(exceptionStatement);
  233. }
  234. }
  235. }

因为方法较多,所以我将几个重要的方法单独提出来讲解

3.3.1  数据格式化方法

首先,查找对应数据库插入的方法:

  1. void bindLoggingEventWithInsertStatement(PreparedStatement stmt, ILoggingEvent event) throws SQLException {
  2. stmt.setLong(TIMESTMP_INDEX, event.getTimeStamp());
  3. stmt.setString(FORMATTED_MESSAGE_INDEX, event.getFormattedMessage());
  4. stmt.setString(LOGGER_NAME_INDEX, event.getLoggerName());
  5. stmt.setString(LEVEL_STRING_INDEX, event.getLevel().toString());
  6. stmt.setString(THREAD_NAME_INDEX, event.getThreadName());
  7. stmt.setShort(REFERENCE_FLAG_INDEX, DBHelper.computeReferenceMask(event));
  8. }
  9. void bindCallerDataWithPreparedStatement(PreparedStatement stmt, StackTraceElement[] callerDataArray) throws SQLException {
  10. StackTraceElement caller = extractFirstCaller(callerDataArray);
  11. stmt.setString(CALLER_FILENAME_INDEX, caller.getFileName());
  12. stmt.setString(CALLER_CLASS_INDEX, caller.getClassName());
  13. stmt.setString(CALLER_METHOD_INDEX, caller.getMethodName());
  14. stmt.setString(CALLER_LINE_INDEX, Integer.toString(caller.getLineNumber()));
  15. }

上两个方法,则是将日志所有的参数,格式化为数据库对应的格式,根据类的开始几行数据,如以下

  1. static final int TIMESTMP_INDEX = 1;
  2. static final int FORMATTED_MESSAGE_INDEX = 2;
  3. static final int LOGGER_NAME_INDEX = 3;
  4. static final int LEVEL_STRING_INDEX = 4;
  5. static final int THREAD_NAME_INDEX = 5;
  6. static final int REFERENCE_FLAG_INDEX = 6;
  7. static final int ARG0_INDEX = 7;
  8. static final int ARG1_INDEX = 8;
  9. static final int ARG2_INDEX = 9;
  10. static final int ARG3_INDEX = 10;
  11. static final int CALLER_FILENAME_INDEX = 11;
  12. static final int CALLER_CLASS_INDEX = 12;
  13. static final int CALLER_METHOD_INDEX = 13;
  14. static final int CALLER_LINE_INDEX = 14;
  15. static final int EVENT_ID_INDEX = 15;

可以很明显找到这个关键的方法之一,这也为我们后面重写此类的重点。

3.3.2  数据插入以及入口方法

我们上面找到格式化数据的方法,下面则将查找插入数据库的具体操作实现方法,不难看出,我们找到以下方法内容:

  1. @Override
  2. public void start() {
  3. if (dbNameResolver == null)
  4. dbNameResolver = new DefaultDBNameResolver();
  5. insertExceptionSQL = SQLBuilder.buildInsertExceptionSQL(dbNameResolver);
  6. insertPropertiesSQL = SQLBuilder.buildInsertPropertiesSQL(dbNameResolver);
  7. insertSQL = SQLBuilder.buildInsertSQL(dbNameResolver);
  8. super.start();
  9. }
  10. @Override
  11. protected void subAppend(ILoggingEvent event, Connection connection, PreparedStatement insertStatement) throws Throwable {
  12. bindLoggingEventWithInsertStatement(insertStatement, event);
  13. bindLoggingEventArgumentsWithPreparedStatement(insertStatement, event.getArgumentArray());
  14. // This is expensive... should we do it every time?
  15. bindCallerDataWithPreparedStatement(insertStatement, event.getCallerData());
  16. int updateCount = insertStatement.executeUpdate();
  17. if (updateCount != 1) {
  18. addWarn("Failed to insert loggingEvent");
  19. }
  20. }

以上只是插入logging_event这个表内数据的方法,还有其他两个表,其插入方法在类中,可以自行查找。

不难看出,我们找到了此类中的关键方法,了解这两个方法后,有助于我们自己实现自定义DBAppender。

3.4  SQLBuilder

在上一个类中,找到了插入数据库的具体实现方法,那么还有插入数据库的SQL语句没有查到,于是在这个类SQLBuilder中,便集成了三个表的数据插入语句的格式化。具体内容如下:

  1. /**
  2. * Logback: the reliable, generic, fast and flexible logging framework.
  3. * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
  4. *
  5. * This program and the accompanying materials are dual-licensed under
  6. * either the terms of the Eclipse Public License v1.0 as published by
  7. * the Eclipse Foundation
  8. *
  9. * or (per the licensee's choosing)
  10. *
  11. * under the terms of the GNU Lesser General Public License version 2.1
  12. * as published by the Free Software Foundation.
  13. */
  14. package ch.qos.logback.classic.db;
  15. import ch.qos.logback.classic.db.names.*;
  16. /**
  17. * @author Tomasz Nurkiewicz
  18. * @since 2010-03-16
  19. */
  20. public class SQLBuilder {
  21. static String buildInsertPropertiesSQL(DBNameResolver dbNameResolver) {
  22. StringBuilder sqlBuilder = new StringBuilder("INSERT INTO ");
  23. sqlBuilder.append(dbNameResolver.getTableName(TableName.LOGGING_EVENT_PROPERTY)).append(" (");
  24. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.EVENT_ID)).append(", ");
  25. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.MAPPED_KEY)).append(", ");
  26. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.MAPPED_VALUE)).append(") ");
  27. sqlBuilder.append("VALUES (?, ?, ?)");
  28. return sqlBuilder.toString();
  29. }
  30. static String buildInsertExceptionSQL(DBNameResolver dbNameResolver) {
  31. StringBuilder sqlBuilder = new StringBuilder("INSERT INTO ");
  32. sqlBuilder.append(dbNameResolver.getTableName(TableName.LOGGING_EVENT_EXCEPTION)).append(" (");
  33. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.EVENT_ID)).append(", ");
  34. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.I)).append(", ");
  35. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.TRACE_LINE)).append(") ");
  36. sqlBuilder.append("VALUES (?, ?, ?)");
  37. return sqlBuilder.toString();
  38. }
  39. static String buildInsertSQL(DBNameResolver dbNameResolver) {
  40. StringBuilder sqlBuilder = new StringBuilder("INSERT INTO ");
  41. sqlBuilder.append(dbNameResolver.getTableName(TableName.LOGGING_EVENT)).append(" (");
  42. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.TIMESTMP)).append(", ");
  43. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.FORMATTED_MESSAGE)).append(", ");
  44. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.LOGGER_NAME)).append(", ");
  45. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.LEVEL_STRING)).append(", ");
  46. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.THREAD_NAME)).append(", ");
  47. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.REFERENCE_FLAG)).append(", ");
  48. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.ARG0)).append(", ");
  49. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.ARG1)).append(", ");
  50. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.ARG2)).append(", ");
  51. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.ARG3)).append(", ");
  52. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.CALLER_FILENAME)).append(", ");
  53. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.CALLER_CLASS)).append(", ");
  54. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.CALLER_METHOD)).append(", ");
  55. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.CALLER_LINE)).append(") ");
  56. sqlBuilder.append("VALUES (?, ?, ? ,?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
  57. return sqlBuilder.toString();
  58. }
  59. }

由上不难看出其作用,生成具体的SQL语句,用于数据插入。

3.4  其他

此包内还有几个其他的类没有讲解,因为其对我们自定义重写的作用不大,就不多赘述了。


4  重写源码 

由以上源码的解析过程,我们熟悉了其工作方式和具体实现方法,以下,将开始重写源码的过程。

4.1  数据库表设计

在之前我们说过,原配置的数据库表有很多我们并不需要的信息,于是,将数据库表中不需要的表项删除,删除不需要的表,只留下一个logging_event表的内容,并加以修改。其SQL脚本如下:

  1. BEGIN;
  2. DROP TABLE IF EXISTS log_record;
  3. COMMIT;
  4. BEGIN;
  5. CREATE TABLE log_record
  6. (
  7. id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  8. time DATETIME(6) NOT NULL,
  9. message TEXT NOT NULL,
  10. level_string VARCHAR(254) NOT NULL,
  11. logger_name VARCHAR(254) NOT NULL,
  12. thread_name VARCHAR(254),
  13. reference_flag SMALLINT,
  14. caller_filename VARCHAR(254) NOT NULL,
  15. caller_class VARCHAR(254) NOT NULL,
  16. caller_method VARCHAR(254) NOT NULL,
  17. caller_line CHAR(4) NOT NULL
  18. );
  19. COMMIT;

由以上表得知,只留下时间和日志具体位置和信息,同时删除其余两个表。

其中,原数据表中时间的格式为Long型,不便于阅读,本文将时间改为DATETIME形式,方便查询和操作。

4.2  数据库插入语句生成

因为我们只需要管理一个数据库表,本着简洁的原则,我们将names包下面的所有文件全部删除,将SQLBuilder 文件中简化为一个方法,放在MyDBAppender.java 中,方法代码如下:

  1. private static String buildInsertSQL() {
  2. return "INSERT INTO log_record " +
  3. "(time, message, level_string, logger_name, thread_name," +
  4. "caller_filename, caller_class, caller_method, caller_line)"+
  5. "VALUES (?, ?, ? ,?, ?, ?, ?, ?, ?)";
  6. }

是不是简洁了很多,可以根据自己定义的数据库表自行修改。

4.3  数据格式化

因为我们对数据库表的修改只是删除某些表项,其具体数据格式化方法本质没变,代码如下:

  1. private static final int TIME_INDEX = 1;
  2. private static final int MESSAGE_INDEX = 2;
  3. private static final int LEVEL_STRING_INDEX = 3;
  4. private static final int LOGGER_NAME_INDEX = 4;
  5. private static final int THREAD_NAME_INDEX = 5;
  6. private static final int CALLER_FILENAME_INDEX = 6;
  7. private static final int CALLER_CLASS_INDEX = 7;
  8. private static final int CALLER_METHOD_INDEX = 8;
  9. private static final int CALLER_LINE_INDEX = 9;
  10. private void bindLoggingEventWithInsertStatement(PreparedStatement stmt, ILoggingEvent event) throws SQLException {
  11. stmt.setTimestamp(TIME_INDEX, new Timestamp(event.getTimeStamp()));
  12. stmt.setString(MESSAGE_INDEX, event.getFormattedMessage());
  13. stmt.setString(LEVEL_STRING_INDEX, event.getLevel().toString());
  14. stmt.setString(LOGGER_NAME_INDEX, event.getLoggerName());
  15. stmt.setString(THREAD_NAME_INDEX, event.getThreadName());
  16. }
  17. private void bindCallerDataWithPreparedStatement(PreparedStatement stmt, StackTraceElement[] callerDataArray) throws SQLException {
  18. StackTraceElement caller = extractFirstCaller(callerDataArray);
  19. stmt.setString(CALLER_FILENAME_INDEX, caller.getFileName());
  20. stmt.setString(CALLER_CLASS_INDEX, caller.getClassName());
  21. stmt.setString(CALLER_METHOD_INDEX, caller.getMethodName());
  22. stmt.setString(CALLER_LINE_INDEX, Integer.toString(caller.getLineNumber()));
  23. }

4.4  修改后代码 (MyDBAppender.java)

  1. package com.ahdy.logtest.config;
  2. import ch.qos.logback.classic.spi.*;
  3. import ch.qos.logback.core.db.DBAppenderBase;
  4. import java.lang.reflect.Method;
  5. import java.sql.Connection;
  6. import java.sql.PreparedStatement;
  7. import java.sql.SQLException;
  8. import java.text.SimpleDateFormat;
  9. import java.util.Date;
  10. /**
  11. * @Description:
  12. * @Author: Rocty
  13. * @Date: 2019-06-18 16:22
  14. * @Version: 1.0
  15. */
  16. public class MyDBAppender extends DBAppenderBase<ILoggingEvent> {
  17. private String insertSQL;
  18. private static final Method GET_GENERATED_KEYS_METHOD;
  19. private static final int TIME_INDEX = 1;
  20. private static final int MESSAGE_INDEX = 2;
  21. private static final int LEVEL_STRING_INDEX = 3;
  22. private static final int LOGGER_NAME_INDEX = 4;
  23. private static final int THREAD_NAME_INDEX = 5;
  24. private static final int CALLER_FILENAME_INDEX = 6;
  25. private static final int CALLER_CLASS_INDEX = 7;
  26. private static final int CALLER_METHOD_INDEX = 8;
  27. private static final int CALLER_LINE_INDEX = 9;
  28. private static final StackTraceElement EMPTY_CALLER_DATA = CallerData.naInstance();
  29. static {
  30. // PreparedStatement.getGeneratedKeys() method was added in JDK 1.4
  31. Method getGeneratedKeysMethod;
  32. try {
  33. // the
  34. getGeneratedKeysMethod = PreparedStatement.class.getMethod("getGeneratedKeys", (Class[]) null);
  35. } catch (Exception ex) {
  36. getGeneratedKeysMethod = null;
  37. }
  38. GET_GENERATED_KEYS_METHOD = getGeneratedKeysMethod;
  39. }
  40. @Override
  41. public void start() {
  42. insertSQL = buildInsertSQL();
  43. super.start();
  44. }
  45. private static String buildInsertSQL() {
  46. return "INSERT INTO log_record " +
  47. "(time, message, logger_name, level_string, thread_name," +
  48. "caller_filename, caller_class, caller_method, caller_line)"+
  49. "VALUES (?, ?, ? ,?, ?, ?, ?, ?, ?)";
  50. }
  51. private void bindLoggingEventWithInsertStatement(PreparedStatement stmt, ILoggingEvent event) throws SQLException {
  52. stmt.setTimestamp(TIME_INDEX, new Timestamp(event.getTimeStamp()));
  53. stmt.setString(MESSAGE_INDEX, event.getFormattedMessage());
  54. stmt.setString(LEVEL_STRING_INDEX, event.getLevel().toString());
  55. stmt.setString(LOGGER_NAME_INDEX, event.getLoggerName());
  56. stmt.setString(THREAD_NAME_INDEX, event.getThreadName());
  57. }
  58. private void bindCallerDataWithPreparedStatement(PreparedStatement stmt, StackTraceElement[] callerDataArray) throws SQLException {
  59. StackTraceElement caller = extractFirstCaller(callerDataArray);
  60. stmt.setString(CALLER_FILENAME_INDEX, caller.getFileName());
  61. stmt.setString(CALLER_CLASS_INDEX, caller.getClassName());
  62. stmt.setString(CALLER_METHOD_INDEX, caller.getMethodName());
  63. stmt.setString(CALLER_LINE_INDEX, Integer.toString(caller.getLineNumber()));
  64. }
  65. @Override
  66. protected void subAppend(ILoggingEvent event, Connection connection, PreparedStatement insertStatement) throws Throwable {
  67. bindLoggingEventWithInsertStatement(insertStatement, event);
  68. // This is expensive... should we do it every time?
  69. bindCallerDataWithPreparedStatement(insertStatement, event.getCallerData());
  70. int updateCount = insertStatement.executeUpdate();
  71. if (updateCount != 1) {
  72. addWarn("Failed to insert loggingEvent");
  73. }
  74. }
  75. private StackTraceElement extractFirstCaller(StackTraceElement[] callerDataArray) {
  76. StackTraceElement caller = EMPTY_CALLER_DATA;
  77. if (hasAtLeastOneNonNullElement(callerDataArray))
  78. caller = callerDataArray[0];
  79. return caller;
  80. }
  81. private boolean hasAtLeastOneNonNullElement(StackTraceElement[] callerDataArray) {
  82. return callerDataArray != null && callerDataArray.length > 0 && callerDataArray[0] != null;
  83. }
  84. @Override
  85. protected Method getGeneratedKeysMethod() {
  86. return GET_GENERATED_KEYS_METHOD;
  87. }
  88. @Override
  89. protected String getInsertSQL() {
  90. return insertSQL;
  91. }
  92. protected void secondarySubAppend(ILoggingEvent event, Connection connection, long eventId) throws Throwable {
  93. }
  94. }

4.5  运行测试

首先,修改logback-test.xml 中的 <appender>标签:

<appender name="DB" class="com.ahdy.logtest.config.MyDBAppender">

其次,原测试文件 BlogAction.java不变,运行后控制台输出一致,数据库中数据如下图示:

至此,可以看到数据完全插入数据库中,完全实现自定义功能。结束。

5  总结

 在写这个日志模块的过程中,查了很多资料,几乎所有的文章都是千篇一律,仅有几个重写DBApepender的文章也很不清晰,无法学习。

在实在找不到资料的情况下,阅读学习源码,并重写方法实现自定义功能。一切教程文章,都比不上阅读源码来的实在,为此,写上这么一篇文章,将logback中配置的源码讲解一些,如有不清晰,还请多多包涵。在码农中,还是新人,尚在学习中。

源码:提取码:1k8c

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/312765
推荐阅读
相关标签
  

闽ICP备14008679号