ARTEMIS-920 Log SQL Exceptions and Warnings

This commit is contained in:
Francesco Nigro 2017-01-12 08:39:55 +01:00
parent c441625e5a
commit 837066d40d
4 changed files with 122 additions and 15 deletions

View File

@ -22,9 +22,12 @@ import java.sql.Driver;
import java.sql.DriverManager; import java.sql.DriverManager;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement; import java.sql.Statement;
import java.util.Arrays; import java.util.Arrays;
import java.util.Properties; import java.util.Properties;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.activemq.artemis.jdbc.store.sql.SQLProvider; import org.apache.activemq.artemis.jdbc.store.sql.SQLProvider;
import org.apache.activemq.artemis.journal.ActiveMQJournalLogger; import org.apache.activemq.artemis.journal.ActiveMQJournalLogger;
@ -61,7 +64,7 @@ public abstract class AbstractJDBCDriver {
this.sqlProvider = provider; this.sqlProvider = provider;
} }
public void start() throws Exception { public void start() throws SQLException {
connect(); connect();
createSchema(); createSchema();
prepareStatements(); prepareStatements();
@ -69,7 +72,12 @@ public abstract class AbstractJDBCDriver {
public void stop() throws SQLException { public void stop() throws SQLException {
if (sqlProvider.closeConnectionOnShutdown()) { if (sqlProvider.closeConnectionOnShutdown()) {
connection.close(); try {
connection.close();
} catch (SQLException e) {
logger.error(JDBCUtils.appendSQLExceptionDetails(new StringBuilder(), e));
throw e;
}
} }
} }
@ -77,58 +85,100 @@ public abstract class AbstractJDBCDriver {
protected abstract void createSchema() throws SQLException; protected abstract void createSchema() throws SQLException;
protected void createTable(String... schemaSqls) throws SQLException { protected final void createTable(String... schemaSqls) throws SQLException {
createTableIfNotExists(connection, sqlProvider.getTableName(), schemaSqls); createTableIfNotExists(connection, sqlProvider.getTableName(), schemaSqls);
} }
protected void connect() throws Exception { private void connect() throws SQLException {
if (dataSource != null) { if (dataSource != null) {
connection = dataSource.getConnection(); try {
connection = dataSource.getConnection();
} catch (SQLException e) {
logger.error(JDBCUtils.appendSQLExceptionDetails(new StringBuilder(), e));
throw e;
}
} else { } else {
try { try {
Driver dbDriver = getDriver(jdbcDriverClass); if (jdbcDriverClass == null || jdbcDriverClass.isEmpty()) {
throw new IllegalStateException("jdbcDriverClass is null or empty!");
}
if (jdbcConnectionUrl == null || jdbcConnectionUrl.isEmpty()) {
throw new IllegalStateException("jdbcConnectionUrl is null or empty!");
}
final Driver dbDriver = getDriver(jdbcDriverClass);
connection = dbDriver.connect(jdbcConnectionUrl, new Properties()); connection = dbDriver.connect(jdbcConnectionUrl, new Properties());
if (connection == null) {
throw new IllegalStateException("the driver: " + jdbcDriverClass + " isn't able to connect to the requested url: " + jdbcConnectionUrl);
}
} catch (SQLException e) { } catch (SQLException e) {
logger.error(JDBCUtils.appendSQLExceptionDetails(new StringBuilder(), e));
ActiveMQJournalLogger.LOGGER.error("Unable to connect to database using URL: " + jdbcConnectionUrl); ActiveMQJournalLogger.LOGGER.error("Unable to connect to database using URL: " + jdbcConnectionUrl);
throw new RuntimeException("Error connecting to database", e); throw e;
} }
} }
} }
public void destroy() throws Exception { public void destroy() throws Exception {
final String dropTableSql = "DROP TABLE " + sqlProvider.getTableName();
try { try {
connection.setAutoCommit(false); connection.setAutoCommit(false);
try (Statement statement = connection.createStatement()) { try (Statement statement = connection.createStatement()) {
statement.executeUpdate("DROP TABLE " + sqlProvider.getTableName()); statement.executeUpdate(dropTableSql);
} }
connection.commit(); connection.commit();
} catch (SQLException e) { } catch (SQLException e) {
connection.rollback(); logger.error(JDBCUtils.appendSQLExceptionDetails(new StringBuilder(), e, dropTableSql));
try {
connection.rollback();
} catch (SQLException rollbackEx) {
logger.error(JDBCUtils.appendSQLExceptionDetails(new StringBuilder(), rollbackEx, dropTableSql));
throw rollbackEx;
}
throw e; throw e;
} }
} }
private static void createTableIfNotExists(Connection connection, String tableName, String... sqls) throws SQLException { private static void createTableIfNotExists(Connection connection,
String tableName,
String... sqls) throws SQLException {
logger.tracef("Validating if table %s didn't exist before creating", tableName); logger.tracef("Validating if table %s didn't exist before creating", tableName);
try { try {
connection.setAutoCommit(false); connection.setAutoCommit(false);
try (ResultSet rs = connection.getMetaData().getTables(null, null, tableName, null)) { try (ResultSet rs = connection.getMetaData().getTables(null, null, tableName, null)) {
if (rs != null && !rs.next()) { if (rs != null && !rs.next()) {
logger.tracef("Table %s did not exist, creating it with SQL=%s", tableName, Arrays.toString(sqls)); if (logger.isTraceEnabled()) {
logger.tracef("Table %s did not exist, creating it with SQL=%s", tableName, Arrays.toString(sqls));
}
final SQLWarning sqlWarning = rs.getWarnings();
if (sqlWarning != null) {
logger.warn(JDBCUtils.appendSQLExceptionDetails(new StringBuilder(), sqlWarning));
}
try (Statement statement = connection.createStatement()) { try (Statement statement = connection.createStatement()) {
for (String sql : sqls) { for (String sql : sqls) {
statement.executeUpdate(sql); statement.executeUpdate(sql);
final SQLWarning statementSqlWarning = statement.getWarnings();
if (statementSqlWarning != null) {
logger.warn(JDBCUtils.appendSQLExceptionDetails(new StringBuilder(), statementSqlWarning, sql));
}
} }
} }
} }
} }
connection.commit(); connection.commit();
} catch (SQLException e) { } catch (SQLException e) {
connection.rollback(); final String sqlStatements = Stream.of(sqls).collect(Collectors.joining("\n"));
logger.error(JDBCUtils.appendSQLExceptionDetails(new StringBuilder(), e, sqlStatements));
try {
connection.rollback();
} catch (SQLException rollbackEx) {
logger.error(JDBCUtils.appendSQLExceptionDetails(new StringBuilder(), rollbackEx, sqlStatements));
throw rollbackEx;
}
throw e;
} }
} }
private Driver getDriver(String className) throws Exception { private Driver getDriver(String className) {
try { try {
Driver driver = (Driver) Class.forName(className).newInstance(); Driver driver = (Driver) Class.forName(className).newInstance();

View File

@ -16,6 +16,8 @@
*/ */
package org.apache.activemq.artemis.jdbc.store.drivers; package org.apache.activemq.artemis.jdbc.store.drivers;
import java.sql.SQLException;
import org.apache.activemq.artemis.jdbc.store.drivers.derby.DerbySQLProvider; import org.apache.activemq.artemis.jdbc.store.drivers.derby.DerbySQLProvider;
import org.apache.activemq.artemis.jdbc.store.drivers.mysql.MySQLSQLProvider; import org.apache.activemq.artemis.jdbc.store.drivers.mysql.MySQLSQLProvider;
import org.apache.activemq.artemis.jdbc.store.drivers.postgres.PostgresSQLProvider; import org.apache.activemq.artemis.jdbc.store.drivers.postgres.PostgresSQLProvider;
@ -63,4 +65,59 @@ public class JDBCUtils {
return factory.create(tableName); return factory.create(tableName);
} }
/**
* Append to {@code errorMessage} a detailed description of the provided {@link SQLException}.<br/>
* The information appended are:
* <ul>
* <li>SQL STATEMENTS</li>
* <li>SQL EXCEPTIONS details ({@link SQLException#getSQLState},
* {@link SQLException#getErrorCode} and {@link SQLException#getMessage}) of the linked list ({@link SQLException#getNextException}) of exceptions</li>
* </ul>
*
* @param errorMessage the target where append the exceptions details
* @param exception the SQL exception (or warning)
* @param sqlStatements the SQL statements related to the {@code exception}
* @return {@code errorMessage}
*/
public static StringBuilder appendSQLExceptionDetails(StringBuilder errorMessage,
SQLException exception,
CharSequence sqlStatements) {
errorMessage.append("\nSQL STATEMENTS: \n").append(sqlStatements);
return appendSQLExceptionDetails(errorMessage, exception);
}
/**
* Append to {@code errorMessage} a detailed description of the provided {@link SQLException}.<br/>
* The information appended are:
* <ul>
* <li>SQL EXCEPTIONS details ({@link SQLException#getSQLState},
* {@link SQLException#getErrorCode} and {@link SQLException#getMessage}) of the linked list ({@link SQLException#getNextException}) of exceptions</li>
* </ul>
*
* @param errorMessage the target where append the exceptions details
* @param exception the SQL exception (or warning)
* @return {@code errorMessage}
*/
public static StringBuilder appendSQLExceptionDetails(StringBuilder errorMessage, SQLException exception) {
errorMessage.append("\nSQL EXCEPTIONS: ");
SQLException nextEx = exception;
int level = 0;
do {
errorMessage.append('\n');
for (int i = 0; i < level; i++) {
errorMessage.append(' ');
}
formatSqlException(errorMessage, nextEx);
nextEx = exception.getNextException();
level++;
} while (nextEx != null);
return errorMessage;
}
private static StringBuilder formatSqlException(StringBuilder errorMessage, SQLException exception) {
final String sqlState = exception.getSQLState();
final int errorCode = exception.getErrorCode();
final String message = exception.getMessage();
return errorMessage.append("SQLState: ").append(sqlState).append(" ErrorCode: ").append(errorCode).append(" Message: ").append(message);
}
} }

View File

@ -80,7 +80,7 @@ public class JDBCSequentialFileFactory implements SequentialFileFactory, ActiveM
started = true; started = true;
} }
} catch (Exception e) { } catch (Exception e) {
ActiveMQJournalLogger.LOGGER.error("Could not start file factory, unable to connect to database"); ActiveMQJournalLogger.LOGGER.error("Could not start file factory, unable to connect to database", e);
started = false; started = false;
} }
} }

View File

@ -107,7 +107,7 @@ public class JDBCJournalImpl extends AbstractJDBCDriver implements Journal {
} }
@Override @Override
public void start() throws Exception { public void start() throws SQLException {
super.start(); super.start();
syncTimer = new JDBCJournalSync(scheduledExecutorService, completeExecutor, SYNC_DELAY, TimeUnit.MILLISECONDS, this); syncTimer = new JDBCJournalSync(scheduledExecutorService, completeExecutor, SYNC_DELAY, TimeUnit.MILLISECONDS, this);
started = true; started = true;