From 837066d40dc1db9ebf161b351551ae5f08b7faba Mon Sep 17 00:00:00 2001 From: Francesco Nigro Date: Thu, 12 Jan 2017 08:39:55 +0100 Subject: [PATCH] ARTEMIS-920 Log SQL Exceptions and Warnings --- .../store/drivers/AbstractJDBCDriver.java | 76 +++++++++++++++---- .../artemis/jdbc/store/drivers/JDBCUtils.java | 57 ++++++++++++++ .../store/file/JDBCSequentialFileFactory.java | 2 +- .../jdbc/store/journal/JDBCJournalImpl.java | 2 +- 4 files changed, 122 insertions(+), 15 deletions(-) diff --git a/artemis-jdbc-store/src/main/java/org/apache/activemq/artemis/jdbc/store/drivers/AbstractJDBCDriver.java b/artemis-jdbc-store/src/main/java/org/apache/activemq/artemis/jdbc/store/drivers/AbstractJDBCDriver.java index 7f8c58f1fa..d75ea21207 100644 --- a/artemis-jdbc-store/src/main/java/org/apache/activemq/artemis/jdbc/store/drivers/AbstractJDBCDriver.java +++ b/artemis-jdbc-store/src/main/java/org/apache/activemq/artemis/jdbc/store/drivers/AbstractJDBCDriver.java @@ -22,9 +22,12 @@ import java.sql.Driver; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.SQLWarning; import java.sql.Statement; import java.util.Arrays; 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.journal.ActiveMQJournalLogger; @@ -61,7 +64,7 @@ public abstract class AbstractJDBCDriver { this.sqlProvider = provider; } - public void start() throws Exception { + public void start() throws SQLException { connect(); createSchema(); prepareStatements(); @@ -69,7 +72,12 @@ public abstract class AbstractJDBCDriver { public void stop() throws SQLException { 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 void createTable(String... schemaSqls) throws SQLException { + protected final void createTable(String... schemaSqls) throws SQLException { createTableIfNotExists(connection, sqlProvider.getTableName(), schemaSqls); } - protected void connect() throws Exception { + private void connect() throws SQLException { if (dataSource != null) { - connection = dataSource.getConnection(); + try { + connection = dataSource.getConnection(); + } catch (SQLException e) { + logger.error(JDBCUtils.appendSQLExceptionDetails(new StringBuilder(), e)); + throw e; + } } else { 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()); + if (connection == null) { + throw new IllegalStateException("the driver: " + jdbcDriverClass + " isn't able to connect to the requested url: " + jdbcConnectionUrl); + } } catch (SQLException e) { + logger.error(JDBCUtils.appendSQLExceptionDetails(new StringBuilder(), e)); 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 { + final String dropTableSql = "DROP TABLE " + sqlProvider.getTableName(); try { connection.setAutoCommit(false); try (Statement statement = connection.createStatement()) { - statement.executeUpdate("DROP TABLE " + sqlProvider.getTableName()); + statement.executeUpdate(dropTableSql); } connection.commit(); } 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; } } - 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); try { connection.setAutoCommit(false); try (ResultSet rs = connection.getMetaData().getTables(null, null, tableName, null)) { 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()) { for (String sql : sqls) { statement.executeUpdate(sql); + final SQLWarning statementSqlWarning = statement.getWarnings(); + if (statementSqlWarning != null) { + logger.warn(JDBCUtils.appendSQLExceptionDetails(new StringBuilder(), statementSqlWarning, sql)); + } } } } } connection.commit(); } 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 { Driver driver = (Driver) Class.forName(className).newInstance(); diff --git a/artemis-jdbc-store/src/main/java/org/apache/activemq/artemis/jdbc/store/drivers/JDBCUtils.java b/artemis-jdbc-store/src/main/java/org/apache/activemq/artemis/jdbc/store/drivers/JDBCUtils.java index 418fd4324f..2d85b2951a 100644 --- a/artemis-jdbc-store/src/main/java/org/apache/activemq/artemis/jdbc/store/drivers/JDBCUtils.java +++ b/artemis-jdbc-store/src/main/java/org/apache/activemq/artemis/jdbc/store/drivers/JDBCUtils.java @@ -16,6 +16,8 @@ */ 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.mysql.MySQLSQLProvider; import org.apache.activemq.artemis.jdbc.store.drivers.postgres.PostgresSQLProvider; @@ -63,4 +65,59 @@ public class JDBCUtils { return factory.create(tableName); } + /** + * Append to {@code errorMessage} a detailed description of the provided {@link SQLException}.
+ * The information appended are: + * + * + * @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}.
+ * The information appended are: + * + * + * @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); + } } diff --git a/artemis-jdbc-store/src/main/java/org/apache/activemq/artemis/jdbc/store/file/JDBCSequentialFileFactory.java b/artemis-jdbc-store/src/main/java/org/apache/activemq/artemis/jdbc/store/file/JDBCSequentialFileFactory.java index 66f00ec665..008e00052a 100644 --- a/artemis-jdbc-store/src/main/java/org/apache/activemq/artemis/jdbc/store/file/JDBCSequentialFileFactory.java +++ b/artemis-jdbc-store/src/main/java/org/apache/activemq/artemis/jdbc/store/file/JDBCSequentialFileFactory.java @@ -80,7 +80,7 @@ public class JDBCSequentialFileFactory implements SequentialFileFactory, ActiveM started = true; } } 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; } } diff --git a/artemis-jdbc-store/src/main/java/org/apache/activemq/artemis/jdbc/store/journal/JDBCJournalImpl.java b/artemis-jdbc-store/src/main/java/org/apache/activemq/artemis/jdbc/store/journal/JDBCJournalImpl.java index ba3dd0d3d5..f7187854b6 100644 --- a/artemis-jdbc-store/src/main/java/org/apache/activemq/artemis/jdbc/store/journal/JDBCJournalImpl.java +++ b/artemis-jdbc-store/src/main/java/org/apache/activemq/artemis/jdbc/store/journal/JDBCJournalImpl.java @@ -107,7 +107,7 @@ public class JDBCJournalImpl extends AbstractJDBCDriver implements Journal { } @Override - public void start() throws Exception { + public void start() throws SQLException { super.start(); syncTimer = new JDBCJournalSync(scheduledExecutorService, completeExecutor, SYNC_DELAY, TimeUnit.MILLISECONDS, this); started = true;