Log4j 1.x JDBCAppender记录日志失效问题详解

官网:http://logging.apache.org/log4j/1.2/manual.html

事件:
最近在项目中使用log4j 1.x JDBCAppender记录管理员操作日志到数据库,在测试时发现系统启动后运行一段时间无法继续记录相关操作日志到数据库。
配置如下:
log4j.properties:

log4j.logger.oplog=INFO, oplog
log4j.appender.oplog=com.lenovo.moc.portal.dao.LogJDBCAppender
log4j.appender.oplog.driver=com.mysql.jdbc.Driver
log4j.appender.oplog.URL=jdbc:mysql://192.168.2.164:3306/oplog?characterEncoding=utf8
log4j.appender.oplog.user=xxx
log4j.appender.oplog.password=xxx
log4j.appender.oplog.sql=insert into operation_loginfo (staff_id, staff_name, user_role, op_type, op_alias, create_time, content, content_alias) values ('%x{login_staff_id}', '%x{login_staff_name}','%x{login_user_role}', '%x{op_type}', '%x{op_alias}', '%d{yyyy-mm-dd hh:mm:ss}','%m', '%x{content_alias}')
log4j.appender.oplog.layout=org.apache.log4j.PatternLayout

java代码:

public class OperationLogService {
  private static final Logger logger = Logger.getLogger(OperationLogService.class);
  private static ExecutorService threadPool = Executors.newFixedThreadPool(3);;

  private static ExecutorService getThreadPool() {
    return threadPool;
  }

  /**
  * 记录操作日志
  * @param login_staff_id 员工id
  * @param login_staff_name 员工姓名
  * @param login_user_role 员工角色
  * @param op_type 操作类型
  * @param op_alias 操作别名
  * @param content_alias 操作内容
  * @param msg 附加信息
  */
  public static void log(String login_staff_id, String login_staff_name, String login_user_role, String op_type,
    String op_alias, String content_alias, final String msg) {
    getThreadPool().execute(new Runnable() {
      @Override
      public void run() {
        MDC.put("login_staff_id", login_staff_id);
        MDC.put("login_staff_name", login_staff_name);
        MDC.put("login_user_role", login_user_role);
        MDC.put("op_type", op_type);
        MDC.put("op_alias", op_alias);
        MDC.put("content_alias", content_alias);
       logger.info(msg);
      }
    });
  }

  public static void main(String[] args) {
    log("1", "zhangsan", "admin", "add_user", "添加用户", "zhangsan添加用户", "test msg");
  }
}

解决办法:
通过查看log4j 1.x JDBCAppender源码发现,并没有对数据库连接的有效性进行判断。即:一旦数据库连接断开,就无法继续写入日志。
故而,通过扩展JDBCAppender的方式,进行数据库连接重连处理:

/**
* 自定义实现Log4j日志组件,将日志记录到数据库<br />.
* 解决问题: 原生组件在系统运行过程中可能会出现数据库连接断开,导致无法正常记录日志信息到数据库.
*
* @desc com.lenovo.moc.portal.dao.LogJDBCAppender
* @author chench9@lenovo.com
* @date 2017年3月15日
*/
public class LogJDBCAppender extends JDBCAppender {
  private static final Logger logger = Logger.getLogger(LogJDBCAppender.class);

  @Override
  protected Connection getConnection() throws SQLException {
    Connection connection = super.getConnection();
    if(connection == null || connection.isClosed()) {
      logger.warn(String.format("reconnect log jdbc appender connection"));
      connection = reconnect();
    }
    return connection;
  }

  /**
  * 重新创建数据库连接
  * @return
  * @throws SQLException
  */
  private Connection reconnect() throws SQLException {
    Connection connection = DriverManager.getConnection(databaseURL, databaseUser,databasePassword);
    return connection;
  }

  /**
  * 重载父类方法,打印错误信息到日志文件 <br />
  * 同时,处理数据库重连并在出错时重试记录日志信息.
  */
  @Override
  protected void execute(String sql) throws SQLException {
    try {
      super.execute(sql);
    } catch (Exception e) {
      logger.error(String.format("log jdbc appender execute sql eror: %s", getSql()), e);
      closeConnectionInterval();
      super.execute(sql);
    }
  }

  // 真正地关闭数据库连接
  private void closeConnectionInterval() {
    if(connection == null) {
      return;
    }

    try {
      connection.close();
    } catch (SQLException e) {
      e.printStackTrace();
    } finally {
      connection = null;
    }
  }
}

log4j 1.x org.apache.log4j.jdbc.JDBCAppender类图:

org.apache.log4j.jdbc.JDBCAppender数据库连接实现:

log4j 2.x org.apache.logging.log4j.core.appender.db.jdbc.JdbcAppender类图:

显然,在log4j 2.x中,使用了数据库连接池,所以建议使用log4j 2.x版本的JdbcAppender。


【参考】
http://stackoverflow.com/questions/3880521/reconnect-to-db-within-log4j Reconnect to DB within log4j

原文地址:https://www.cnblogs.com/nuccch/p/6795918.html