Skip to content

Commit

Permalink
Add a new configuration that will format the SQL (#89)
Browse files Browse the repository at this point in the history
* Add an option to format the SQL before logging

* revert unwanted code formatting changes for a cleaner diff

* apply PR comments
  • Loading branch information
rvullriede authored Dec 1, 2023
1 parent 33292e8 commit 5a3f370
Show file tree
Hide file tree
Showing 9 changed files with 125 additions and 13 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,12 @@ decorator.datasource.datasource-proxy.slow-query.logger-name=
decorator.datasource.datasource-proxy.slow-query.threshold=300

decorator.datasource.datasource-proxy.multiline=true

# Formats the SQL for better readability. Uses Hibernate's formatter if present on the class path. If you opted in for a different JPA provider you need to add https://github.com/vertical-blank/sql-formatter as a runtime dependency to your app to enable this.
# Mutually exclusive with json-format=true
decorator.datasource.datasource-proxy.format-sql=true
decorator.datasource.datasource-proxy.json-format=false

# Enable Query Metrics
decorator.datasource.datasource-proxy.count-query=false
```
Expand Down Expand Up @@ -293,4 +298,4 @@ public DataSourceDecorator customDecorator() {

If you want to disable decorating set `decorator.datasource.exclude-beans` with bean names you want to exclude.
Also, you can disable decorating for `AbstractRoutingDataSource` setting property `decorator.datasource.ignore-routing-data-sources` to `true`
Set `decorator.datasource.enabled` to `false` if you want to disable all decorators for all datasources.
Set `decorator.datasource.enabled` to `false` if you want to disable all decorators for all datasources.
4 changes: 2 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ subprojects {

extra["springBootVersion"] = "3.0.2"
extra["p6SpyVersion"] = "3.9.0"
extra["datasourceProxyVersion"] = "1.8.1"
extra["datasourceProxyVersion"] = "1.9"
extra["flexyPoolVersion"] = "2.2.3"

extra["release"] = listOf(
Expand Down Expand Up @@ -155,4 +155,4 @@ tasks {
dependsOn(it.tasks.build)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ dependencies {

compileOnly("org.springframework.boot:spring-boot-starter-actuator:${project.extra["springBootVersion"]}")

// optional (compileOnly) dependencies for SQL formatting
compileOnly("org.hibernate:hibernate-core:6.1.6.Final") // should match the version managed by spring-boot
compileOnly("com.github.vertical-blank:sql-formatter:2.0.4")

testImplementation("com.h2database:h2:2.1.214")
testImplementation("org.springframework.boot:spring-boot-starter-test:${project.extra["springBootVersion"]}")

Expand Down Expand Up @@ -57,4 +61,4 @@ tasks {
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,4 @@ public static DataSourceDecoratorBeanPostProcessor dataSourceDecoratorBeanPostPr
public DataSourceNameResolver dataSourceNameResolver(ApplicationContext applicationContext) {
return new DataSourceNameResolver(applicationContext);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

import com.github.gavlyukovskiy.boot.jdbc.decorator.DataSourceDecoratorProperties;
import com.github.gavlyukovskiy.boot.jdbc.decorator.DataSourceNameResolver;
import com.github.gavlyukovskiy.boot.jdbc.decorator.flexypool.FlexyPoolConfiguration;
import com.github.gavlyukovskiy.boot.jdbc.decorator.p6spy.P6SpyConfiguration;
import net.ttddyy.dsproxy.listener.QueryCountStrategy;
import net.ttddyy.dsproxy.listener.QueryExecutionListener;
import net.ttddyy.dsproxy.listener.SingleQueryCountHolder;
Expand All @@ -30,6 +32,7 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;

/**
* Configuration for integration with datasource-proxy, allows to use define custom {@link QueryExecutionListener},
Expand All @@ -40,6 +43,10 @@
// Datasource-Proxy is automatically configured by Spring Cloud Sleuth if exists
@ConditionalOnMissingBean(type = "org.springframework.cloud.sleuth.autoconfig.instrument.jdbc.DataSourceProxyConfiguration")
@ConditionalOnClass(ProxyDataSource.class)
@Import({
HibernateFormatterConfiguration.class,
SqlFormatterConfiguration.class,
})
public class DataSourceProxyConfiguration {

@Autowired
Expand Down Expand Up @@ -67,4 +74,4 @@ public ProxyDataSourceDecorator proxyDataSourceDecorator(ProxyDataSourceBuilderC
public QueryCountStrategy queryCountStrategy() {
return new SingleQueryCountHolder();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ public class DataSourceProxyProperties {
* @see ProxyDataSourceBuilder#multiline()
*/
private boolean multiline = true;


/**
* Use formatted SQL for logging query.
*
* @see ProxyDataSourceBuilder#formatQuery(ProxyDataSourceBuilder.FormatQueryCallback)
*/
private boolean formatSql = false;

/**
* Use json output for logging query.
*
Expand Down Expand Up @@ -76,6 +85,10 @@ public boolean isMultiline() {
return this.multiline;
}

public boolean isFormatSql() {
return this.formatSql;
}

public boolean isJsonFormat() {
return this.jsonFormat;
}
Expand All @@ -100,6 +113,10 @@ public void setMultiline(boolean multiline) {
this.multiline = multiline;
}

public void setFormatSql(boolean formatSql) {
this.formatSql = formatSql;
}

public void setJsonFormat(boolean jsonFormat) {
this.jsonFormat = jsonFormat;
}
Expand Down Expand Up @@ -220,4 +237,4 @@ public enum DataSourceProxyLogging {
COMMONS,
JUL
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.github.gavlyukovskiy.boot.jdbc.decorator.dsproxy;

import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
import org.hibernate.engine.jdbc.internal.BasicFormatterImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(BasicFormatterImpl.class)
@ConditionalOnProperty(name = "decorator.datasource.datasource-proxy.format-sql", havingValue = "true")
class HibernateFormatterConfiguration {

private static final Logger log = LoggerFactory.getLogger(HibernateFormatterConfiguration.class);

@Bean
@ConditionalOnMissingBean // let users define their own
public ProxyDataSourceBuilder.FormatQueryCallback hibernateFormatQueryCallback() {
log.debug("{} will be used as formatter", BasicFormatterImpl.class.getName());
BasicFormatterImpl hibernateFormatter = new BasicFormatterImpl();
return hibernateFormatter::format;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,8 @@
/**
* Configurer for {@link ProxyDataSourceBuilder} based on the application context.
*
* @see ProxyDataSourceBuilder
*
* @author Arthur Gavlyukovskiy
* @see ProxyDataSourceBuilder
* @since 1.3.1
*/
public class ProxyDataSourceBuilderConfigurer {
Expand Down Expand Up @@ -68,6 +67,9 @@ public class ProxyDataSourceBuilderConfigurer {
@Autowired(required = false)
private ConnectionIdManagerProvider connectionIdManagerProvider;

@Autowired(required = false)
ProxyDataSourceBuilder.FormatQueryCallback formatQueryCallback;

public void configure(ProxyDataSourceBuilder proxyDataSourceBuilder, DataSourceProxyProperties datasourceProxy) {
switch (datasourceProxy.getLogging()) {
case SLF4J: {
Expand Down Expand Up @@ -110,15 +112,30 @@ public void configure(ProxyDataSourceBuilder proxyDataSourceBuilder, DataSourceP
break;
}
}

if (datasourceProxy.isMultiline() && datasourceProxy.isJsonFormat()) {
log.warn("Found opposite multiline and json format, multiline will be used (may depend on library version)");
}
if (datasourceProxy.isFormatSql() && datasourceProxy.isJsonFormat()) {
log.warn("Found opposite format-sql and json format, json format will be used (may depend on library version)");
}

if (datasourceProxy.isMultiline()) {
proxyDataSourceBuilder.multiline();
}
if (datasourceProxy.isJsonFormat()) {

if (!datasourceProxy.isMultiline() && datasourceProxy.isJsonFormat()) {
proxyDataSourceBuilder.asJson();
}

if (!datasourceProxy.isJsonFormat() && datasourceProxy.isFormatSql()) {
if (formatQueryCallback != null) {
proxyDataSourceBuilder.formatQuery(formatQueryCallback);
} else {
log.warn("formatSql requested but cannot be enabled because no formatter is present (neither Hibernate nor SqlFormatter).");
}
}

if (datasourceProxy.isCountQuery()) {
proxyDataSourceBuilder.countQuery(queryCountStrategy);
}
Expand Down Expand Up @@ -161,8 +178,7 @@ private Level toJULLogLevel(String logLevel) {
}
try {
return Level.parse(logLevel);
}
catch (IllegalArgumentException e) {
} catch (IllegalArgumentException e) {
if (logLevel.equalsIgnoreCase("DEBUG")) {
return Level.FINE;
}
Expand All @@ -185,4 +201,14 @@ private CommonsLogLevel toCommonsLogLevel(String logLevel) {
throw new IllegalArgumentException("Unresolved log level " + logLevel + " for apache commons logger, " +
"known levels " + Arrays.toString(CommonsLogLevel.values()));
}
}

private static boolean classExists(String fullQualifiedClassName) {

try {
Class.forName(fullQualifiedClassName);
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.github.gavlyukovskiy.boot.jdbc.decorator.dsproxy;

import com.github.vertical_blank.sqlformatter.SqlFormatter;
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(SqlFormatter.class)
@ConditionalOnProperty(name = "decorator.datasource.datasource-proxy.format-sql", havingValue = "true")
class SqlFormatterConfiguration {

private static final Logger log = LoggerFactory.getLogger(SqlFormatterConfiguration.class);

@Bean
@ConditionalOnMissingBean // let users define their own
public ProxyDataSourceBuilder.FormatQueryCallback sqlFormatterFormatQueryCallback() {
log.debug("{} will be used as formatter", SqlFormatter.class.getName());
return SqlFormatter::format;
}
}

0 comments on commit 5a3f370

Please sign in to comment.