官網: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