HHH-14217 Add syntax highlighting to the logged SQL

Using ANSI escape codes

Must be explicitly enabled using hibernate.highlight_sql
This commit is contained in:
Gavin King 2020-09-15 23:11:02 +02:00 committed by Sanne Grinovero
parent 9ac29ab4dc
commit cf995a1571
7 changed files with 149 additions and 14 deletions

View File

@ -621,6 +621,9 @@ Write all SQL statements to the console. This is an alternative to setting the l
`*hibernate.format_sql*` (e.g. `true` or `false` (default value))::
Pretty-print the SQL in the log and console.
`*hibernate.highlight_sql*` (e.g. `true` or `false` (default value))::
Colorize the SQL in the console using ANSI escape codes.
`*hibernate.use_sql_comments*` (e.g. `true` or `false` (default value))::
If true, Hibernate generates comments inside the SQL, for easier debugging.

View File

@ -742,6 +742,11 @@ public interface AvailableSettings extends org.hibernate.jpa.AvailableSettings {
*/
String FORMAT_SQL ="hibernate.format_sql";
/**
* Enable highlighting of SQL logged to the console using ANSI escape codes
*/
String HIGHLIGHT_SQL ="hibernate.highlight_sql";
/**
* Add comments to the generated SQL
*/

View File

@ -20,6 +20,10 @@ public enum FormatStyle {
* Formatting for DDL (CREATE, ALTER, DROP, etc) statements
*/
DDL( "ddl", DDLFormatterImpl.INSTANCE ),
/**
* Syntax highlighting via ANSI escape codes
*/
HIGHLIGHT( "highlight", HighlightingFormatter.INSTANCE ),
/**
* No formatting
*/

View File

@ -0,0 +1,105 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.engine.jdbc.internal;
import org.hibernate.engine.jdbc.env.spi.AnsiSqlKeywords;
import org.hibernate.internal.util.StringHelper;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;
/**
* Performs basic syntax highlighting for SQL using ANSI escape codes.
*
* @author Gavin King
*/
public class HighlightingFormatter implements Formatter {
private static final Set<String> KEYWORDS = new HashSet<>( AnsiSqlKeywords.INSTANCE.sql2003() );
static {
// additional keywords not reserved by ANSI SQL 2003
KEYWORDS.addAll( Arrays.asList( "KEY", "SEQUENCE", "CASCADE", "INCREMENT" ) );
}
public static final Formatter INSTANCE =
new HighlightingFormatter(
"34", // blue
"36", // cyan
"32"
);
private static String escape(String code) {
return "\u001b[" + code + "m";
};
private final String keywordEscape;
private final String stringEscape;
private final String quotedEscape;
private final String normalEscape;
/**
* @param keywordCode the ANSI escape code to use for highlighting SQL keywords
* @param stringCode the ANSI escape code to use for highlighting SQL strings
*/
public HighlightingFormatter(String keywordCode, String stringCode, String quotedCode) {
keywordEscape =escape(keywordCode);
stringEscape = escape(stringCode);
quotedEscape = escape(quotedCode);
normalEscape = escape("0");
}
@Override
public String format(String sql) {
String symbolsAndWs = "=><!+-*/()',.|&`\"?" + StringHelper.WHITESPACE;
StringBuilder result = new StringBuilder();
boolean inString = false;
boolean inQuoted = false;
for (StringTokenizer tokenizer = new StringTokenizer( sql, symbolsAndWs, true );
tokenizer.hasMoreTokens(); ) {
String token = tokenizer.nextToken();
switch (token) {
case "\"":
case "`": // for MySQL
if (inString) {
result.append(token);
}
else if (inQuoted) {
inQuoted = false;
result.append(token).append(normalEscape);
}
else {
inQuoted = true;
result.append(quotedEscape).append(token);
}
break;
case "'":
if (inQuoted) {
result.append("'");
}
else if (inString) {
inString = false;
result.append("'").append(normalEscape);
}
else {
inString = true;
result.append(stringEscape).append("'");
}
break;
default:
if ( KEYWORDS.contains( token.toUpperCase() ) ) {
result.append(keywordEscape).append(token).append(normalEscape);
}
else {
result.append(token);
}
}
}
return result.toString();
}
}

View File

@ -55,9 +55,10 @@ public class JdbcServicesImpl implements JdbcServices, ServiceRegistryAwareServi
final boolean showSQL = ConfigurationHelper.getBoolean( Environment.SHOW_SQL, configValues, false );
final boolean formatSQL = ConfigurationHelper.getBoolean( Environment.FORMAT_SQL, configValues, false );
final boolean highlightSQL = ConfigurationHelper.getBoolean( Environment.HIGHLIGHT_SQL, configValues, false );
final long logSlowQuery = ConfigurationHelper.getLong( Environment.LOG_SLOW_QUERY, configValues, 0 );
this.sqlStatementLogger = new SqlStatementLogger( showSQL, formatSQL, logSlowQuery );
this.sqlStatementLogger = new SqlStatementLogger( showSQL, formatSQL, highlightSQL, logSlowQuery );
resultSetWrapper = new ResultSetWrapperImpl( serviceRegistry );
}

View File

@ -6,16 +6,15 @@
*/
package org.hibernate.engine.jdbc.spi;
import java.sql.Statement;
import java.util.concurrent.TimeUnit;
import org.hibernate.engine.jdbc.internal.FormatStyle;
import org.hibernate.engine.jdbc.internal.Formatter;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.build.AllowSysOut;
import org.jboss.logging.Logger;
import java.sql.Statement;
import java.util.concurrent.TimeUnit;
/**
* Centralize logging for SQL statements.
*
@ -27,6 +26,7 @@ public class SqlStatementLogger {
private boolean logToStdout;
private boolean format;
private final boolean highlight;
/**
* Configuration value that indicates slow query. (In milliseconds) 0 - disabled.
@ -37,29 +37,42 @@ public class SqlStatementLogger {
* Constructs a new SqlStatementLogger instance.
*/
public SqlStatementLogger() {
this( false, false );
this( false, false, false );
}
/**
* Constructs a new SqlStatementLogger instance.
*
* @param logToStdout Should we log to STDOUT in addition to our internal logger.
* @param format Should we format the statements prior to logging
* @param format Should we format the statements in the console and log
*/
public SqlStatementLogger(boolean logToStdout, boolean format) {
this( logToStdout, format, 0 );
this( logToStdout, format, false );
}
/**
* Constructs a new SqlStatementLogger instance.
*
* @param logToStdout Should we log to STDOUT in addition to our internal logger.
* @param format Should we format the statements prior to logging
* @param format Should we format the statements in the console and log
* @param highlight Should we highlight the statements in the console
*/
public SqlStatementLogger(boolean logToStdout, boolean format, boolean highlight) {
this( logToStdout, format, highlight, 0 );
}
/**
* Constructs a new SqlStatementLogger instance.
*
* @param logToStdout Should we log to STDOUT in addition to our internal logger.
* @param format Should we format the statements in the console and log
* @param highlight Should we highlight the statements in the console
* @param logSlowQuery Should we logs query which executed slower than specified milliseconds. 0 - disabled.
*/
public SqlStatementLogger(boolean logToStdout, boolean format, long logSlowQuery) {
public SqlStatementLogger(boolean logToStdout, boolean format, boolean highlight, long logSlowQuery) {
this.logToStdout = logToStdout;
this.format = format;
this.highlight = highlight;
this.logSlowQuery = logSlowQuery;
}
@ -120,14 +133,18 @@ public class SqlStatementLogger {
*/
@AllowSysOut
public void logStatement(String statement, Formatter formatter) {
if ( format ) {
if ( logToStdout || LOG.isDebugEnabled() ) {
if ( logToStdout || LOG.isDebugEnabled() ) {
if ( format ) {
statement = formatter.format( statement );
}
if ( highlight ) {
statement = FormatStyle.HIGHLIGHT.getFormatter().format( statement );
}
}
LOG.debug( statement );
if ( logToStdout ) {
System.out.println( "Hibernate: " + statement );
String prefix = highlight ? "\u001b[35m[Hibernate]\u001b[0m " : "Hibernate: ";
System.out.println( prefix + statement );
}
}

View File

@ -57,7 +57,7 @@ public class BasicTestingJdbcServiceImpl implements JdbcServices, ServiceRegistr
public void prepare(boolean allowAggressiveRelease) throws SQLException {
dialect = ConnectionProviderBuilder.getCorrespondingDialect();
connectionProvider = ConnectionProviderBuilder.buildConnectionProvider( allowAggressiveRelease );
sqlStatementLogger = new SqlStatementLogger( true, false );
sqlStatementLogger = new SqlStatementLogger( true, false, false );
Connection jdbcConnection = connectionProvider.getConnection();
try {