content: /*
   * Copyright 1999-2018 Alibaba Group Holding Ltd.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package com.alibaba.druid.filter.logging;
  
  import com.alibaba.druid.DbType;
  import com.alibaba.druid.filter.FilterChain;
  import com.alibaba.druid.filter.FilterEventAdapter;
  import com.alibaba.druid.pool.DruidDataSource;
  import com.alibaba.druid.pool.DruidPooledConnection;
  import com.alibaba.druid.proxy.jdbc.*;
  import com.alibaba.druid.sql.SQLUtils;
  import com.alibaba.druid.sql.SQLUtils.FormatOption;
  import com.alibaba.druid.util.JdbcUtils;
  import com.alibaba.druid.util.MySqlUtils;
  
  import java.sql.*;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Properties;
  
  /**
   * @author wenshao [szujobs@hotmail.com]
   */
  public abstract class LogFilter extends FilterEventAdapter implements LogFilterMBean {
      protected String dataSourceLoggerName = "druid.sql.DataSource";
      protected String connectionLoggerName = "druid.sql.Connection";
      protected String statementLoggerName = "druid.sql.Statement";
      protected String resultSetLoggerName = "druid.sql.ResultSet";
  
      private boolean connectionConnectBeforeLogEnable = true;
      private boolean connectionConnectAfterLogEnable = true;
      private boolean connectionCommitAfterLogEnable = true;
      private boolean connectionRollbackAfterLogEnable = true;
      private boolean connectionCloseAfterLogEnable = true;
  
      private boolean statementCreateAfterLogEnable = true;
      private boolean statementPrepareAfterLogEnable = true;
      private boolean statementPrepareCallAfterLogEnable = true;
  
      private boolean statementExecuteAfterLogEnable = true;
      private boolean statementExecuteQueryAfterLogEnable = true;
      private boolean statementExecuteUpdateAfterLogEnable = true;
      private boolean statementExecuteBatchAfterLogEnable = true;
      private boolean statementExecutableSqlLogEnable;
  
      private boolean statementCloseAfterLogEnable = true;
  
      private boolean statementParameterClearLogEnable = true;
      private boolean statementParameterSetLogEnable = true;
  
      private boolean resultSetNextAfterLogEnable = true;
      private boolean resultSetOpenAfterLogEnable = true;
      private boolean resultSetCloseAfterLogEnable = true;
  
      private boolean dataSourceLogEnabled = true;
      private boolean connectionLogEnabled = true;
      private boolean connectionLogErrorEnabled = true;
      private boolean statementLogEnabled = true;
      private boolean statementLogErrorEnabled = true;
      private boolean resultSetLogEnabled = true;
      private boolean resultSetLogErrorEnabled = true;
  
      private FormatOption statementSqlFormatOption = new FormatOption(false, true);
      private boolean statementLogSqlPrettyFormat;
  
      protected DataSourceProxy dataSource;
  
      public LogFilter() {
          this(System.getProperties());
      }
  
      public LogFilter(final Properties properties) {
          configFromProperties(properties);
      }
  
      public void configFromProperties(Properties properties) {
          if (properties == null) {
              return;
          }
  
          {
              String prop = properties.getProperty("druid.log.conn");
              if ("false".equals(prop)) {
                  connectionLogEnabled = false;
              } else if ("true".equals(prop)) {
                  connectionLogEnabled = true;
              }
          }
          {
              String prop = properties.getProperty("druid.log.stmt");
              if ("false".equals(prop)) {
                  statementLogEnabled = false;
              } else if ("true".equals(prop)) {
                  statementLogEnabled = true;
              }
          }
          {
              String prop = properties.getProperty("druid.log.rs");
              if ("false".equals(prop)) {
                  resultSetLogEnabled = false;
              } else if ("true".equals(prop)) {
                  resultSetLogEnabled = true;
              }
          }
          {
              String prop = properties.getProperty("druid.log.stmt.executableSql");
              if ("true".equals(prop)) {
                  statementExecutableSqlLogEnable = true;
              } else if ("false".equals(prop)) {
                  statementExecutableSqlLogEnable = false;
              }
          }
          {
              String prop = properties.getProperty("druid.log.conn.logError");
              if ("false".equals(prop)) {
                  connectionLogErrorEnabled = false;
              } else if ("true".equals(prop)) {
                  connectionLogErrorEnabled = true;
              }
          }
          {
              String prop = properties.getProperty("druid.log.stmt.logError");
              if ("false".equals(prop)) {
                  statementLogErrorEnabled = false;
              } else if ("true".equals(prop)) {
                  statementLogErrorEnabled = true;
              }
          }
          {
              String prop = properties.getProperty("druid.log.rs.logError");
              if ("false".equals(prop)) {
                  resultSetLogErrorEnabled = false;
              } else if ("true".equals(prop)) {
                  resultSetLogErrorEnabled = true;
              }
          }
      }
  
      private DbType dbType;
      @Override
      public void init(DataSourceProxy dataSource) {
          this.dataSource = dataSource;
          dbType = DbType.of(dataSource.getDbType());
          configFromProperties(dataSource.getConnectProperties());
          //configFromProperties(System.getProperties());
      }
  
      public boolean isConnectionLogErrorEnabled() {
          return connectionLogErrorEnabled;
      }
  
      public boolean isResultSetCloseAfterLogEnabled() {
          return isResultSetLogEnabled() && resultSetCloseAfterLogEnable;
      }
  
      public void setResultSetCloseAfterLogEnabled(boolean resultSetCloseAfterLogEnable) {
          this.resultSetCloseAfterLogEnable = resultSetCloseAfterLogEnable;
      }
  
      public void setConnectionLogErrorEnabled(boolean connectionLogErrorEnabled) {
          this.connectionLogErrorEnabled = connectionLogErrorEnabled;
      }
  
      public boolean isResultSetLogErrorEnabled() {
          return resultSetLogErrorEnabled;
      }
  
      public void setResultSetLogErrorEnabled(boolean resultSetLogErrorEnabled) {
          this.resultSetLogErrorEnabled = resultSetLogErrorEnabled;
      }
  
      public boolean isConnectionConnectBeforeLogEnabled() {
          return isConnectionLogEnabled() && connectionConnectBeforeLogEnable;
      }
  
      public void setConnectionConnectBeforeLogEnabled(boolean beforeConnectionConnectLogEnable) {
          this.connectionConnectBeforeLogEnable = beforeConnectionConnectLogEnable;
      }
  
      public boolean isConnectionCloseAfterLogEnabled() {
          return isConnectionLogEnabled() && connectionCloseAfterLogEnable;
      }
  
      public boolean isConnectionRollbackAfterLogEnabled() {
          return isConnectionLogEnabled() && connectionRollbackAfterLogEnable;
      }
  
      public void setConnectionRollbackAfterLogEnabled(boolean connectionRollbackAfterLogEnable) {
          this.connectionRollbackAfterLogEnable = connectionRollbackAfterLogEnable;
      }
  
      public void setConnectionCloseAfterLogEnabled(boolean afterConnectionCloseLogEnable) {
          this.connectionCloseAfterLogEnable = afterConnectionCloseLogEnable;
      }
  
      public boolean isConnectionCommitAfterLogEnabled() {
          return isConnectionLogEnabled() && connectionCommitAfterLogEnable;
      }
  
      public void setConnectionCommitAfterLogEnabled(boolean afterConnectionCommitLogEnable) {
          this.connectionCommitAfterLogEnable = afterConnectionCommitLogEnable;
      }
  
      public boolean isConnectionConnectAfterLogEnabled() {
          return isConnectionLogEnabled() && connectionConnectAfterLogEnable;
      }
  
      public void setConnectionConnectAfterLogEnabled(boolean afterConnectionConnectLogEnable) {
          this.connectionConnectAfterLogEnable = afterConnectionConnectLogEnable;
      }
  
      public boolean isResultSetNextAfterLogEnabled() {
          return isResultSetLogEnabled() && resultSetNextAfterLogEnable;
      }
  
      public void setResultSetNextAfterLogEnabled(boolean afterResultSetNextLogEnable) {
          this.resultSetNextAfterLogEnable = afterResultSetNextLogEnable;
      }
  
      public boolean isResultSetOpenAfterLogEnabled() {
          return isResultSetLogEnabled() && resultSetOpenAfterLogEnable;
      }
  
      public void setResultSetOpenAfterLogEnabled(boolean afterResultSetOpenLogEnable) {
          this.resultSetOpenAfterLogEnable = afterResultSetOpenLogEnable;
      }
  
      public boolean isStatementCloseAfterLogEnabled() {
          return isStatementLogEnabled() && statementCloseAfterLogEnable;
      }
  
      public void setStatementCloseAfterLogEnabled(boolean afterStatementCloseLogEnable) {
          this.statementCloseAfterLogEnable = afterStatementCloseLogEnable;
      }
  
      public boolean isStatementCreateAfterLogEnabled() {
          return isStatementLogEnabled() && statementCreateAfterLogEnable;
      }
  
      public void setStatementCreateAfterLogEnabled(boolean afterStatementCreateLogEnable) {
          this.statementCreateAfterLogEnable = afterStatementCreateLogEnable;
      }
  
      public boolean isStatementExecuteBatchAfterLogEnabled() {
          return isStatementLogEnabled() && statementExecuteBatchAfterLogEnable;
      }
  
      public void setStatementExecuteBatchAfterLogEnabled(boolean afterStatementExecuteBatchLogEnable) {
          this.statementExecuteBatchAfterLogEnable = afterStatementExecuteBatchLogEnable;
      }
  
      public boolean isStatementExecuteAfterLogEnabled() {
          return isStatementLogEnabled() && statementExecuteAfterLogEnable;
      }
  
      public void setStatementExecuteAfterLogEnabled(boolean afterStatementExecuteLogEnable) {
          this.statementExecuteAfterLogEnable = afterStatementExecuteLogEnable;
      }
  
      public boolean isStatementExecuteQueryAfterLogEnabled() {
          return isStatementLogEnabled() && statementExecuteQueryAfterLogEnable;
      }
  
      public void setStatementExecuteQueryAfterLogEnabled(boolean afterStatementExecuteQueryLogEnable) {
          this.statementExecuteQueryAfterLogEnable = afterStatementExecuteQueryLogEnable;
      }
  
      public boolean isStatementExecuteUpdateAfterLogEnabled() {
          return isStatementLogEnabled() && statementExecuteUpdateAfterLogEnable;
      }
  
      public void setStatementExecuteUpdateAfterLogEnabled(boolean afterStatementExecuteUpdateLogEnable) {
          this.statementExecuteUpdateAfterLogEnable = afterStatementExecuteUpdateLogEnable;
      }
  
      public boolean isStatementExecutableSqlLogEnable() {
          return statementExecutableSqlLogEnable;
      }
  
      public void setStatementExecutableSqlLogEnable(boolean statementExecutableSqlLogEnable) {
          this.statementExecutableSqlLogEnable = statementExecutableSqlLogEnable;
      }
  
      public boolean isStatementPrepareCallAfterLogEnabled() {
          return isStatementLogEnabled() && statementPrepareCallAfterLogEnable;
      }
  
      public void setStatementPrepareCallAfterLogEnabled(boolean afterStatementPrepareCallLogEnable) {
          this.statementPrepareCallAfterLogEnable = afterStatementPrepareCallLogEnable;
      }
  
      public boolean isStatementPrepareAfterLogEnabled() {
          return isStatementLogEnabled() && statementPrepareAfterLogEnable;
      }
  
      public void setStatementPrepareAfterLogEnabled(boolean afterStatementPrepareLogEnable) {
          this.statementPrepareAfterLogEnable = afterStatementPrepareLogEnable;
      }
  
      public boolean isDataSourceLogEnabled() {
          return dataSourceLogEnabled;
      }
  
      public void setDataSourceLogEnabled(boolean dataSourceLogEnabled) {
          this.dataSourceLogEnabled = dataSourceLogEnabled;
      }
  
      public boolean isConnectionLogEnabled() {
          return connectionLogEnabled;
      }
  
      public void setConnectionLogEnabled(boolean connectionLogEnabled) {
          this.connectionLogEnabled = connectionLogEnabled;
      }
  
      public boolean isStatementLogEnabled() {
          return statementLogEnabled;
      }
  
      public void setStatementLogEnabled(boolean statementLogEnabled) {
          this.statementLogEnabled = statementLogEnabled;
      }
  
      public boolean isStatementLogErrorEnabled() {
          return statementLogErrorEnabled;
      }
  
      public void setStatementLogErrorEnabled(boolean statementLogErrorEnabled) {
          this.statementLogErrorEnabled = statementLogErrorEnabled;
      }
  
      public boolean isResultSetLogEnabled() {
          return resultSetLogEnabled;
      }
  
      public void setResultSetLogEnabled(boolean resultSetLogEnabled) {
          this.resultSetLogEnabled = resultSetLogEnabled;
      }
  
      public boolean isStatementParameterSetLogEnabled() {
          return isStatementLogEnabled() && statementParameterSetLogEnable;
      }
  
      public void setStatementParameterSetLogEnabled(boolean statementParameterSetLogEnable) {
          this.statementParameterSetLogEnable = statementParameterSetLogEnable;
      }
  
      public boolean isStatementParameterClearLogEnable() {
          return isStatementLogEnabled() && statementParameterClearLogEnable;
      }
  
      public void setStatementParameterClearLogEnable(boolean statementParameterClearLogEnable) {
          this.statementParameterClearLogEnable = statementParameterClearLogEnable;
      }
  
      public FormatOption getStatementSqlFormatOption() {
          return this.statementSqlFormatOption;
      }
  
      public void setStatementSqlFormatOption(FormatOption formatOption) {
          this.statementSqlFormatOption = formatOption;
      }
  
      public boolean isStatementSqlPrettyFormat() {
          return this.statementLogSqlPrettyFormat;
      }
  
      public void setStatementSqlPrettyFormat(boolean statementSqlPrettyFormat) {
          this.statementLogSqlPrettyFormat = statementSqlPrettyFormat;
      }
  
      protected abstract void connectionLog(String message);
  
      protected abstract void statementLog(String message);
  
      protected abstract void statementLogError(String message, Throwable error);
  
      protected abstract void resultSetLog(String message);
  
      protected abstract void resultSetLogError(String message, Throwable error);
  
      public void connection_connectAfter(ConnectionProxy connection) {
          if (connection == null) {
              return;
          }
  
          if (connectionConnectAfterLogEnable && isConnectionLogEnabled()) {
              StringBuilder msg = new StringBuilder(34)
                      .append("{conn-")
                      .append(connection.getId());
  
              Connection impl = connection.getRawObject();
              if (DbType.mysql.equals(dbType)) {
                  Long procId = MySqlUtils.getId(impl);
                  if (procId != null) {
                      msg.append(",procId-").append(procId);
                  }
              }
  
              msg.append("} connected");
              connectionLog(msg.toString());
          }
      }
  
      @Override
      public Savepoint connection_setSavepoint(FilterChain chain, ConnectionProxy connection) throws SQLException {
          Savepoint savepoint = chain.connection_setSavepoint(connection);
  
          if (isConnectionLogEnabled()) {
              connectionLog("{conn " + connection.getId() + "} setSavepoint-" + savepointToString(savepoint));
          }
  
          return savepoint;
      }
  
      @Override
      public Savepoint connection_setSavepoint(FilterChain chain, ConnectionProxy connection, String name)
              throws SQLException {
          Savepoint savepoint = chain.connection_setSavepoint(connection, name);
  
          if (isConnectionLogEnabled()) {
              connectionLog("{conn " + connection.getId() + "} setSavepoint-" + name);
          }
  
          return savepoint;
      }
  
      @Override
      public void connection_rollback(FilterChain chain, ConnectionProxy connection) throws SQLException {
          super.connection_rollback(chain, connection);
  
          if (connectionRollbackAfterLogEnable && isConnectionLogEnabled()) {
              connectionLog("{conn " + connection.getId() + "} rollback");
          }
      }
  
      @Override
      public void connection_rollback(FilterChain chain, ConnectionProxy connection, Savepoint savePoint)
              throws SQLException {
          super.connection_rollback(chain, connection, savePoint);
  
          if (connectionRollbackAfterLogEnable && isConnectionLogEnabled()) {
              connectionLog("{conn " + connection.getId() + "} rollback -> " + savepointToString(savePoint));
          }
      }
  
      @Override
      public void connection_commit(FilterChain chain, ConnectionProxy connection) throws SQLException {
          super.connection_commit(chain, connection);
  
          if (connectionCommitAfterLogEnable && isConnectionLogEnabled()) {
              connectionLog("{conn-" + connection.getId() + "} committed");
          }
      }
  
      @Override
      public void connection_setAutoCommit(FilterChain chain, ConnectionProxy connection, boolean autoCommit)
              throws SQLException {
          connectionLog("{conn-" + connection.getId() + "} setAutoCommit " + autoCommit);
          chain.connection_setAutoCommit(connection, autoCommit);
      }
  
      @Override
      public void connection_close(FilterChain chain, ConnectionProxy connection) throws SQLException {
          super.connection_close(chain, connection);
  
          if (connectionCloseAfterLogEnable && isConnectionLogEnabled()) {
              connectionLog("{conn-" + connection.getId() + "} closed");
          }
      }
  
      @Override
      public void statement_close(FilterChain chain, StatementProxy statement) throws SQLException {
          super.statement_close(chain, statement);
  
          if (statementCloseAfterLogEnable && isStatementLogEnabled()) {
              statementLog("{conn-" + statement.getConnectionProxy().getId() + ", " + stmtId(statement) + "} closed");
          }
      }
  
      @Override
      protected void statementExecuteBefore(StatementProxy statement, String sql) {
          statement.setLastExecuteStartNano();
          if (statement instanceof PreparedStatementProxy) {
              logParameter((PreparedStatementProxy) statement);
          }
      }
  
      @Override
      protected void statementExecuteAfter(StatementProxy statement, String sql, boolean firstResult) {
          logExecutableSql(statement, sql);
  
          if (statementExecuteAfterLogEnable && isStatementLogEnabled()) {
              statement.setLastExecuteTimeNano();
              double nanos = statement.getLastExecuteTimeNano();
              double millis = nanos / (1000 * 1000);
  
              statementLog("{conn-" + statement.getConnectionProxy().getId() + ", " + stmtId(statement) + "} executed. "
                      + millis + " millis. " + sql);
          }
      }
  
      @Override
      protected void statementExecuteBatchBefore(StatementProxy statement) {
          statement.setLastExecuteStartNano();
      }
  
      @Override
      protected void statementExecuteBatchAfter(StatementProxy statement, int[] result) {
          String sql;
          if (statement instanceof PreparedStatementProxy) {
              sql = ((PreparedStatementProxy) statement).getSql();
          } else {
              sql = statement.getBatchSql();
          }
  
          logExecutableSql(statement, sql);
  
          if (statementExecuteBatchAfterLogEnable && isStatementLogEnabled()) {
              statement.setLastExecuteTimeNano();
              double nanos = statement.getLastExecuteTimeNano();
              double millis = nanos / (1000 * 1000);
  
              statementLog("{conn-" + statement.getConnectionProxy().getId() + ", " + stmtId(statement)
                      + "} batch executed. " + millis + " millis. " + sql);
          }
      }
  
      @Override
      protected void statementExecuteQueryBefore(StatementProxy statement, String sql) {
          statement.setLastExecuteStartNano();
          if (statement instanceof PreparedStatementProxy) {
              logParameter((PreparedStatementProxy) statement);
          }
      }
  
      @Override
      protected void statementExecuteQueryAfter(StatementProxy statement, String sql, ResultSetProxy resultSet) {
          logExecutableSql(statement, sql);
  
          if (statementExecuteQueryAfterLogEnable && isStatementLogEnabled()) {
              statement.setLastExecuteTimeNano();
              double nanos = statement.getLastExecuteTimeNano();
              double millis = nanos / (1000 * 1000);
  
              statementLog("{conn-" + statement.getConnectionProxy().getId() + ", " + stmtId(statement) + ", rs-"
                      + resultSet.getId() + "} query executed. " + millis + " millis. " + sql);
          }
      }
  
      @Override
      protected void statementExecuteUpdateBefore(StatementProxy statement, String sql) {
          statement.setLastExecuteStartNano();
          if (statement instanceof PreparedStatementProxy) {
              logParameter((PreparedStatementProxy) statement);
          }
      }
  
      @Override
      protected void statementExecuteUpdateAfter(StatementProxy statement, String sql, int updateCount) {
          logExecutableSql(statement, sql);
  
          if (statementExecuteUpdateAfterLogEnable && isStatementLogEnabled()) {
              statement.setLastExecuteTimeNano();
              double nanos = statement.getLastExecuteTimeNano();
              double millis = nanos / (1000 * 1000);
  
              statementLog("{conn-" + statement.getConnectionProxy().getId() + ", " + stmtId(statement)
                      + "} update executed. effort " + updateCount + ". " + millis + " millis. " + sql);
          }
      }
  
      private void logExecutableSql(StatementProxy statement, String sql) {
          if ((!isStatementExecutableSqlLogEnable()) || !isStatementLogEnabled()) {
              return;
          }
  
          int parametersSize = statement.getParametersSize();
          if (parametersSize == 0) {
              statementLog("{conn-" + statement.getConnectionProxy().getId() + ", " + stmtId(statement) + "} executed. "
                      + sql);
              return;
          }
  
          List<Object> parameters = new ArrayList<Object>(parametersSize);
          for (int i = 0; i < parametersSize; ++i) {
              JdbcParameter jdbcParam = statement.getParameter(i);
              parameters.add(jdbcParam != null
                      ? jdbcParam.getValue()
                      : null);
          }
  
          DbType dbType = DbType.of(statement.getConnectionProxy().getDirectDataSource().getDbType());
          String formattedSql = SQLUtils.format(sql, dbType, parameters, this.statementSqlFormatOption);
          statementLog("{conn-" + statement.getConnectionProxy().getId() + ", " + stmtId(statement) + "} executed. "
                  + formattedSql);
      }
  
      @Override
      public void resultSet_close(FilterChain chain, ResultSetProxy resultSet) throws SQLException {
          chain.resultSet_close(resultSet);
  
          StringBuilder buf = new StringBuilder();
          buf.append("{conn-");
          buf.append(resultSet.getStatementProxy().getConnectionProxy().getId());
          buf.append(", ");
          buf.append(stmtId(resultSet));
          buf.append(", rs-");
          buf.append(resultSet.getId());
          buf.append("} closed");
  
          if (isResultSetCloseAfterLogEnabled()) {
              resultSetLog(buf.toString());
          }
      }
  
      @Override
      public boolean resultSet_next(FilterChain chain, ResultSetProxy resultSet) throws SQLException {
          boolean moreRows = super.resultSet_next(chain, resultSet);
  
          if (moreRows) {
              if (resultSetNextAfterLogEnable && isResultSetLogEnabled()) {
                  try {
                      StringBuilder buf = new StringBuilder();
                      buf.append("{conn-");
                      buf.append(resultSet.getStatementProxy().getConnectionProxy().getId());
                      buf.append(", ");
                      buf.append(stmtId(resultSet));
                      buf.append(", rs-");
                      buf.append(resultSet.getId());
                      buf.append("}");
                      buf.append(" Result: [");
  
                      ResultSetMetaData meta = resultSet.getMetaData();
                      for (int i = 0, size = meta.getColumnCount(); i < size; ++i) {
                          if (i != 0) {
                              buf.append(", ");
                          }
                          int columnIndex = i + 1;
                          int type = meta.getColumnType(columnIndex);
  
                          Object value;
                          if (type == Types.TIMESTAMP) {
                              value = resultSet.getTimestamp(columnIndex);
                          } else if (type == Types.BLOB) {
                              value = "<BLOB>";
                          } else if (type == Types.CLOB) {
                              value = "<CLOB>";
                          } else if (type == Types.NCLOB) {
                              value = "<NCLOB>";
                          } else if (type == Types.BINARY) {
                              value = "<BINARY>";
                          } else {
                              value = resultSet.getObject(columnIndex);
                          }
                          buf.append(value);
                      }
  
                      buf.append("]");
  
                      resultSetLog(buf.toString());
                  } catch (SQLException ex) {
                      resultSetLogError("logging error", ex);
                  }
              }
          }
  
          return moreRows;
      }
  
      @Override
      public Object callableStatement_getObject(FilterChain chain, CallableStatementProxy statement, int parameterIndex)
              throws SQLException {
          Object obj = chain.callableStatement_getObject(statement, parameterIndex);
  
          if (obj instanceof ResultSetProxy) {
              resultSetOpenAfter((ResultSetProxy) obj);
          }
  
          return obj;
      }
  
      @Override
      public Object callableStatement_getObject(FilterChain chain, CallableStatementProxy statement, int parameterIndex,
                                                java.util.Map<String, Class<?>> map) throws SQLException {
          Object obj = chain.callableStatement_getObject(statement, parameterIndex, map);
  
          if (obj instanceof ResultSetProxy) {
              resultSetOpenAfter((ResultSetProxy) obj);
          }
  
          return obj;
      }
  
      @Override
      public Object callableStatement_getObject(FilterChain chain, CallableStatementProxy statement, String parameterName)
              throws SQLException {
          Object obj = chain.callableStatement_getObject(statement, parameterName);
  
          if (obj instanceof ResultSetProxy) {
              resultSetOpenAfter((ResultSetProxy) obj);
          }
  
          return obj;
      }
  
      @Override
      public Object callableStatement_getObject(FilterChain chain, CallableStatementProxy statement,
                                                String parameterName, java.util.Map<String, Class<?>> map)
              throws SQLException {
          Object obj = chain.callableStatement_getObject(statement, parameterName, map);
  
          if (obj instanceof ResultSetProxy) {
              resultSetOpenAfter((ResultSetProxy) obj);
          }
  
          return obj;
      }
  
      @Override
      protected void resultSetOpenAfter(ResultSetProxy resultSet) {
          if (resultSetOpenAfterLogEnable && isResultSetLogEnabled()) {
              try {
                  StringBuilder buf = new StringBuilder();
                  buf.append("{conn-");
                  buf.append(resultSet.getStatementProxy().getConnectionProxy().getId());
                  buf.append(", ");
                  buf.append(stmtId(resultSet));
                  buf.append(", rs-");
                  buf.append(resultSet.getId());
                  buf.append("}");
  
                  String resultId = buf.toString();
                  resultSetLog(resultId + " open");
  
                  buf.append(" Header: [");
  
                  ResultSetMetaData meta = resultSet.getMetaData();
                  for (int i = 0, size = meta.getColumnCount(); i < size; ++i) {
                      if (i != 0) {
                          buf.append(", ");
                      }
                      buf.append(meta.getColumnName(i + 1));
                  }
                  buf.append("]");
  
                  resultSetLog(buf.toString());
              } catch (SQLException ex) {
                  resultSetLogError("logging error", ex);
              }
          }
      }
  
      protected void statementCreateAfter(StatementProxy statement) {
          if (statementCreateAfterLogEnable && isStatementLogEnabled()) {
              statementLog("{conn-" + statement.getConnectionProxy().getId() + ", stmt-" + statement.getId()
                      + "} created");
          }
      }
  
      protected void statementPrepareAfter(PreparedStatementProxy statement) {
          if (statementPrepareAfterLogEnable && isStatementLogEnabled()) {
              statementLog("{conn-" + statement.getConnectionProxy().getId() + ", pstmt-" + statement.getId()
                      + "} created. " + statement.getSql());
          }
      }
  
      protected void statementPrepareCallAfter(CallableStatementProxy statement) {
          if (statementPrepareCallAfterLogEnable && isStatementLogEnabled()) {
              statementLog("{conn-" + statement.getConnectionProxy().getId() + ", cstmt-" + statement.getId()
                      + "} created. " + statement.getSql());
          }
      }
  
      @Override
      protected void statement_executeErrorAfter(StatementProxy statement, String sql, Throwable error) {
          if (this.isStatementLogErrorEnabled()) {
              if (!isStatementExecutableSqlLogEnable()) {
                  statementLogError("{conn-" + statement.getConnectionProxy().getId() + ", " + stmtId(statement)
                          + "} execute error. " + sql, error);
              } else {
                  int parametersSize = statement.getParametersSize();
                  if (parametersSize > 0) {
                      List<Object> parameters = new ArrayList<Object>(parametersSize);
                      for (int i = 0; i < parametersSize; ++i) {
                          JdbcParameter jdbcParam = statement.getParameter(i);
                          parameters.add(jdbcParam != null
                                  ? jdbcParam.getValue()
                                  : null);
                      }
                      DbType dbType = DbType.of(statement.getConnectionProxy().getDirectDataSource().getDbType());
                      String formattedSql = SQLUtils.format(sql, dbType, parameters, this.statementSqlFormatOption);
                      statementLogError("{conn-" + statement.getConnectionProxy().getId()
                                      + ", " + stmtId(statement)
                                      + "} execute error. " + formattedSql,
                              error
                      );
                  } else {
                      statementLogError("{conn-" + statement.getConnectionProxy().getId() + ", " + stmtId(statement)
                              + "} execute error. " + sql, error);
                  }
              }
          }
      }
  
      private String stmtId(ResultSetProxy resultSet) {
          return stmtId(resultSet.getStatementProxy());
      }
  
      private String stmtId(StatementProxy statement) {
          StringBuilder buf = new StringBuilder();
          if (statement instanceof CallableStatementProxy) {
              buf.append("cstmt-");
          } else if (statement instanceof PreparedStatementProxy) {
              buf.append("pstmt-");
          } else {
              buf.append("stmt-");
          }
          buf.append(statement.getId());
  
          return buf.toString();
      }
  
      protected void logParameter(PreparedStatementProxy statement) {
          if (isStatementParameterSetLogEnabled()) {
              {
                  StringBuilder buf = new StringBuilder();
                  buf.append("{conn-");
                  buf.append(statement.getConnectionProxy().getId());
                  buf.append(", ");
                  buf.append(stmtId(statement));
                  buf.append("}");
                  buf.append(" Parameters : [");
  
                  for (int i = 0, parametersSize = statement.getParametersSize(); i < parametersSize; ++i) {
                      JdbcParameter parameter = statement.getParameter(i);
                      if (i != 0) {
                          buf.append(", ");
                      }
                      if (parameter == null) {
                          continue;
                      }
  
                      int sqlType = parameter.getSqlType();
                      Object value = parameter.getValue();
                      switch (sqlType) {
                          case Types.NULL:
                              buf.append("NULL");
                              break;
                          default:
                              buf.append(String.valueOf(value));
                              break;
                      }
                  }
                  buf.append("]");
                  statementLog(buf.toString());
              }
              {
                  StringBuilder buf = new StringBuilder();
                  buf.append("{conn-");
                  buf.append(statement.getConnectionProxy().getId());
                  buf.append(", ");
                  buf.append(stmtId(statement));
                  buf.append("}");
                  buf.append(" Types : [");
                  for (int i = 0, parametersSize = statement.getParametersSize(); i < parametersSize; ++i) {
                      JdbcParameter parameter = statement.getParameter(i);
                      if (i != 0) {
                          buf.append(", ");
                      }
                      if (parameter == null) {
                          continue;
                      }
                      int sqlType = parameter.getSqlType();
                      buf.append(JdbcUtils.getTypeName(sqlType));
                  }
                  buf.append("]");
                  statementLog(buf.toString());
              }
          }
      }
  
      @Override
      public void dataSource_releaseConnection(FilterChain chain, DruidPooledConnection conn) throws SQLException {
          long connectionId = -1;
  
          if (conn.getConnectionHolder() != null) {
              ConnectionProxy connection = (ConnectionProxy) conn.getConnectionHolder().getConnection();
              connectionId = connection.getId();
          }
  
          chain.dataSource_recycle(conn);
  
          if (connectionCloseAfterLogEnable && isConnectionLogEnabled()) {
              connectionLog("{conn-" + connectionId + "} pool-recycle");
          }
      }
  
      @Override
      public DruidPooledConnection dataSource_getConnection(FilterChain chain, DruidDataSource dataSource,
                                                            long maxWaitMillis) throws SQLException {
          DruidPooledConnection conn = chain.dataSource_connect(dataSource, maxWaitMillis);
  
          ConnectionProxy connection = (ConnectionProxy) conn.getConnectionHolder().getConnection();
  
          if (connectionConnectAfterLogEnable && isConnectionLogEnabled()) {
              connectionLog("{conn-" + connection.getId() + "} pool-connect");
          }
  
          return conn;
      }
  
      @Override
      public void preparedStatement_clearParameters(FilterChain chain, PreparedStatementProxy statement)
              throws SQLException {
          if (isStatementParameterClearLogEnable()) {
              statementLog("{conn-" + statement.getConnectionProxy().getId() + ", pstmt-" + statement.getId()
                      + "} clearParameters. ");
          }
          chain.preparedStatement_clearParameters(statement);
      }
  
      @Override
      public void statement_clearBatch(FilterChain chain, StatementProxy statement)
              throws SQLException {
          if (isStatementParameterClearLogEnable()) {
              statementLog("{conn-" + statement.getConnectionProxy().getId() + ", stmt-" + statement.getId()
                      + "} clearBatch. ");
          }
          chain.statement_clearBatch(statement);
      }
  
      @Override
      public boolean isWrapperFor(Class<?> iface) {
          return iface == this.getClass() || iface == LogFilter.class;
      }
  
      @SuppressWarnings("unchecked")
      @Override
      public <T> T unwrap(Class<T> iface) {
          if (iface == this.getClass() || iface == LogFilter.class) {
              return (T) this;
          }
          return null;
      }
  
      protected String savepointToString(Savepoint savePoint) {
          String savePointString = null;
          try {
              savePointString = savePoint.getSavepointName();
          } catch (SQLException e) {
              try {
                  savePointString = String.valueOf(savePoint.getSavepointId());
              } catch (SQLException e1) {
                  savePointString = savePoint.toString();
              }
          }
          return savePointString;
      }
  }
  