当前位置:   article > 正文

logback重写DBAppenderBase连接达梦数据库实现自定义日志_dbappender代码

dbappender代码

一. 背景

        本来项目组使用的数据库是Oracle,如今要切换到达梦。。。早期的时候就讨论过数据库日志迁移的问题,本来以为是一个小问题可以随便搞定,没想到是踩坑了,记录下:

二. 如果已经实现Oracle自定义日志

        简单来说,如果你现在已经实现了Oracle的自定义日志,数据库迁移到达梦后用同一套代码报错,那么就是达梦没有创建主键,只需要创建一个主键。并在自己实现的 DBAppender 里面重写一下这个方法即可:   

  1. @Override
  2. protected Method getGeneratedKeysMethod() {
  3.         try {
  4.             return PreparedStatement.class.getMethod("getGeneratedKeys", (Class[]) null);
  5.         } catch (Exception ex) {
  6.             return null;
  7.         }
  8. }

        但如果啥都没实现,那么就继续看下去吧。

三. 快速实现

                lomback.xml主要配置信息(只展示数据库连接部分配置)

  1. <!-- 输出日志到数据库-->
  2. <appender name="DB" class="org.cloud.common.config.MyDBAppender">
  3. <connectionSource class="org.cloud.common.config.LogDBRewrite">
  4. </connectionSource>
  5. </appender>
  6. <!-- * 通配符 设置log打印级别 对所有类有效TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF-->
  7. <!--将上面的appender添加到root-->
  8. <root level="INFO">
  9. <!--日志输出到数据库 -->
  10. <appender-ref ref="DB"/>
  11. </root>

        MyDBAppender.java        

  1. import ch.qos.logback.classic.spi.CallerData;
  2. import ch.qos.logback.classic.spi.ILoggingEvent;
  3. import ch.qos.logback.core.db.DBAppenderBase;
  4. import dm.jdbc.driver.DmdbPreparedStatement;
  5. import dm.jdbc.driver.DmdbStatement;
  6. import org.slf4j.MDC;
  7. import java.lang.reflect.Method;
  8. import java.sql.*;
  9. /**
  10. * @author hhh
  11. * @Description lomback日志连接数据库配置
  12. * @create 2021-01-06
  13. */
  14. public class MyDBAppender extends DBAppenderBase<ILoggingEvent> {
  15. private String insertSQL;
  16. private static Method GET_GENERATED_KEYS_METHOD;
  17. private static final int LOGID_INDEX = 1;
  18. private static final int USERID_INDEX = 1;
  19. private static final int CLASS_INDEX = 2;
  20. private static final int MOTHOD_INDEX = 3;
  21. private static final int OPERATIONTIME_INDEX = 4;
  22. private static final int LOGLEVEL_INDEX = 5;
  23. private static final int MSG_INDEX = 6;
  24. private static final int IP_INDEX = 7;
  25. private static final StackTraceElement EMPTY_CALLER_DATA = CallerData.naInstance();
  26. static {
  27. // PreparedStatement.getGeneratedKeys() method was added in JDK 1.4
  28. Method getGeneratedKeysMethod;
  29. try {
  30. getGeneratedKeysMethod = PreparedStatement.class.getMethod("getGeneratedKeys", (Class[]) null);
  31. } catch (Exception ex) {
  32. getGeneratedKeysMethod = null;
  33. }
  34. GET_GENERATED_KEYS_METHOD = getGeneratedKeysMethod;
  35. }
  36. @Override
  37. public void start() {
  38. insertSQL = buildInsertSQL();
  39. super.start();
  40. }
  41. private static String buildInsertSQL() {
  42. //我用的是达梦数据库,test_sequence.nextval是一个自增长的值
  43. return "INSERT INTO test " +
  44. "(event_id, USERID, CLASS, MOTHOD, OPERATIONTIME, LOGLEVEL, MSG, IP)"+
  45. "VALUES (test_sequence.nextval, ?, ? ,?, ?, ?, ?, ?)";
  46. }
  47. private void bindLoggingEventWithInsertStatement(PreparedStatement stmt, ILoggingEvent event) throws SQLException {
  48. stmt.setString(USERID_INDEX, event.getMDCPropertyMap().get("username"));
  49. stmt.setString(IP_INDEX, event.getMDCPropertyMap().get("ip"));
  50. stmt.setTimestamp(OPERATIONTIME_INDEX, new Timestamp(event.getTimeStamp()));
  51. stmt.setString(MSG_INDEX, event.getFormattedMessage());
  52. stmt.setString(LOGLEVEL_INDEX, event.getLevel().toString());
  53. stmt.setString(CLASS_INDEX, event.getLoggerName());
  54. stmt.setString(MOTHOD_INDEX, event.getThreadName());
  55. }
  56. private void bindCallerDataWithPreparedStatement(PreparedStatement stmt, StackTraceElement[] callerDataArray) throws SQLException {
  57. }
  58. @Override
  59. protected void subAppend(ILoggingEvent event, Connection connection, PreparedStatement insertStatement) throws Throwable {
  60. bindLoggingEventWithInsertStatement(insertStatement, event);
  61. // This is expensive... should we do it every time?
  62. bindCallerDataWithPreparedStatement(insertStatement, event.getCallerData());
  63. int updateCount = insertStatement.executeUpdate();
  64. if (updateCount != 1) {
  65. addWarn("Failed to insert loggingEvent");
  66. }
  67. }
  68. private StackTraceElement extractFirstCaller(StackTraceElement[] callerDataArray) {
  69. StackTraceElement caller = EMPTY_CALLER_DATA;
  70. if (hasAtLeastOneNonNullElement(callerDataArray))
  71. caller = callerDataArray[0];
  72. return caller;
  73. }
  74. private boolean hasAtLeastOneNonNullElement(StackTraceElement[] callerDataArray) {
  75. return callerDataArray != null && callerDataArray.length > 0 && callerDataArray[0] != null;
  76. }
  77. @Override
  78. protected Method getGeneratedKeysMethod() {
  79. return GET_GENERATED_KEYS_METHOD;
  80. }
  81. @Override
  82. protected String getInsertSQL() {
  83. return insertSQL;
  84. }
  85. protected void secondarySubAppend(ILoggingEvent event, Connection connection, long eventId){
  86. }
  87. }

LogDBRewrite.java

  1. import ch.qos.logback.core.db.DataSourceConnectionSource;
  2. import org.cloud.bdma.common.util.CryptUtil;
  3. import java.io.IOException;
  4. import java.io.InputStreamReader;
  5. import java.io.UnsupportedEncodingException;
  6. import java.sql.Connection;
  7. import java.sql.DriverManager;
  8. import java.sql.SQLException;
  9. import java.util.Properties;
  10. /**
  11. * @author zjt
  12. * @description logback日志数据库配置
  13. * @updateTime 2023/1/3 14:51
  14. */
  15. public class LogDBRewrite extends DataSourceConnectionSource {
  16. private static String url;
  17. private static String username;
  18. private static String password;
  19. private static String driverClassName = dm.jdbc.driver.DmDriver;
  20. @Override
  21. public void start() {
  22. try {
  23. if (driverClassName != null) {
  24. Class.forName(driverClassName);
  25. discoverConnectionProperties();
  26. } else {
  27. addError("WARNING: No JDBC driver specified for logback DriverManagerConnectionSource.");
  28. }
  29. } catch (final ClassNotFoundException cnfe) {
  30. addError("Could not load JDBC driver class: " + driverClassName, cnfe);
  31. }
  32. }
  33. @Override
  34. public Connection getConnection() throws SQLException {
  35. return DriverManager.getConnection(url, username, password);
  36. }
  37. }

数据库建表语句:(必须有一个主键)

  1. CREATE TABLE test(
  2. "event_id" int PRIMARY KEY,
  3. "userid" VARCHAR2(12),
  4. "class" VARCHAR2(255),
  5. "mothod" VARCHAR2(255),
  6. "operationtime" timestamp,
  7. "loglevel" VARCHAR2(255),
  8. "msg" VARCHAR2(555),
  9. "ip" VARCHAR2(50)
  10. );
  11. CREATE SEQUENCE test_sequence INCREMENT BY 1;

测试类:

  1. import lombok.extern.slf4j.Slf4j;
  2. import org.junit.Test;
  3. import org.slf4j.MDC;
  4. import org.springframework.boot.test.context.SpringBootTest;
  5. import java.util.HashMap;
  6. import java.util.Map;
  7. /**
  8. * @ClassName MyDBAppenderTest
  9. * @authtr zjt
  10. * @Description TODO
  11. * @createTime 2023年01月06日 16:03:00
  12. */
  13. @SpringBootTest
  14. @Slf4j
  15. public class MyDBAppenderTest {
  16. @Test
  17. public void testDB(){
  18. //自定义插入数据库的数据,在 MyDBAppender#bindLoggingEventWithInsertStatement 方法中的event.getMDCPropertyMap().get("ip")和event.getMDCPropertyMap().get("username") 中可以获取到这些值
  19. Map context = new HashMap();
  20. context.put("username", "test");
  21. context.put("ip", "127.0.0.1");
  22. MDC.setContextMap(context);
  23. log.info("测试");
  24. }
  25. }

执行后查询数据库,即可看到测试数据结果

四. 原理

 4.1 为何在达梦执行就需要加上主键

         去看 DBAppenderBase 的启动类,可以看到这行代码

表示如果不支持主键又没有方言的数据库, 那么就不支持连接到数据库写日志,而达梦lomback不支持达梦的方言,所以只能走主键这条路了。

        为何需要重写 getGeneratedKeysMethod

        如上图,因为达梦没有方言,所以如果 cnxSupportsGetGeneratedKeys 也为false,那么就会直接报错导致无法启动,所以需要让这个值为true,那么肯定要让这个条件为true        

if (this.getGeneratedKeysMethod() != null)

        所以重写这个方法,让这段代码成功执行,这样项目就可以启动啦 !        

        至于这个方法干嘛的,后面也有介绍,如果不深究原理就不用看下去啦。

4.2 执行流程(建议自己打断点看,图为参考)

继承了 DBAppenderBase 这个类,让我们可以重写logback写入数据库的规则: 

        1.LOGID_INDEX = 1 这些是为了方便修改定义出来的,可以随意修改,主要对应的是 buildInsertSQL()方法中的占位符(例如第一个?,就代表1)

        2. bindLoggingEventWithInsertStatement(PreparedStatement stmt, ILoggingEvent event) 这个方法是绑定sql中的 ? 对应的参数的

        3. bindCallerDataWithPreparedStatement(PreparedStatement stmt, StackTraceElement[] callerDataArray) :与存储过程相关,可以不实现

        4.subAppend(ILoggingEvent event, Connection connection, PreparedStatement insertStatement) :调用 2和3 然后对数据库进行插入(insertStatement.executeUpdate())

 这里插入成功后会继续往下跑:

执行完 subAppend 后,还会继续执行 selectEventId(PreparedStatement insertStatement, Connection connection) 这个方法,继续深究:

 看红框中的代码,这是反射的调用,insertStatement这个参数打个断点就能知道它的真正类型是 DmdbPreparedStatement,回到 我们定义的 MyDBAppender:getGeneratedKeysMethod() 中可以看到返回了这个Method对象 GET_GENERATED_KEYS_METHOD ,而在静态代码块中我们对它进行了初始化:        

PreparedStatement.class.getMethod("getGeneratedKeys", (Class[]) null)

所以这里的rs是调用了这个方法: DmdbPreparedStatement:getGeneratedKeys,再点进去可以看到实际调用的是这个方法: do_getGenerateKeys(),如下图

看红框,我就是在这里栽倒了,这里需要你的日志表里面需要有主键,而我没有定义主键,但报错的话会报空指针异常,所以在数据库增加一个主键即可!!!

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

闽ICP备14008679号