committed by
GitHub
parent
b591e0f474
commit
190a15de57
@@ -5,10 +5,10 @@
|
||||
|spring.sleuth.async.enabled | `true` | Enable instrumenting async related components so that the tracing information is passed between threads.
|
||||
|spring.sleuth.async.ignored-beans | | List of {@link java.util.concurrent.Executor} bean names that should be ignored and not wrapped in a trace representation.
|
||||
|spring.sleuth.baggage.correlation-enabled | `true` | Enables correlating the baggage context with logging contexts.
|
||||
|spring.sleuth.baggage.correlation-fields | |
|
||||
|spring.sleuth.baggage.local-fields | |
|
||||
|spring.sleuth.baggage.correlation-fields | | List of fields that should be propagated over the wire.
|
||||
|spring.sleuth.baggage.local-fields | | List of fields that should be accessible within the JVM process but not propagated over the wire.
|
||||
|spring.sleuth.baggage.remote-fields | | List of fields that are referenced the same in-process as it is on the wire. For example, the field "x-vcap-request-id" would be set as-is including the prefix.
|
||||
|spring.sleuth.baggage.tag-fields | |
|
||||
|spring.sleuth.baggage.tag-fields | | List of fields that should automatically become tags.
|
||||
|spring.sleuth.batch.enabled | `true` | Enable Spring Batch instrumentation.
|
||||
|spring.sleuth.circuitbreaker.enabled | `true` | Enable Spring Cloud CircuitBreaker instrumentation.
|
||||
|spring.sleuth.config.server.enabled | `true` | Enable Spring Cloud Config Server instrumentation.
|
||||
@@ -23,6 +23,27 @@
|
||||
|spring.sleuth.integration.enabled | `true` | Enable Spring Integration instrumentation.
|
||||
|spring.sleuth.integration.patterns | `[!hystrixStreamOutput*, *, !channel*]` | An array of patterns against which channel names will be matched. @see org.springframework.integration.config.GlobalChannelInterceptor#patterns() Defaults to any channel name not matching the Hystrix Stream and functional Stream channel names.
|
||||
|spring.sleuth.integration.websockets.enabled | `true` | Enable tracing for WebSockets.
|
||||
|spring.sleuth.jdbc.decorator.datasource.datasource-proxy.count-query | `false` | Creates listener to count queries. @see ProxyDataSourceBuilder#countQuery() @see QueryCountHolder
|
||||
|spring.sleuth.jdbc.decorator.datasource.datasource-proxy.json-format | `false` | Use json output for logging query. @see ProxyDataSourceBuilder#asJson()
|
||||
|spring.sleuth.jdbc.decorator.datasource.datasource-proxy.logging | | Logging to use for logging queries.
|
||||
|spring.sleuth.jdbc.decorator.datasource.datasource-proxy.multiline | `true` | Use multiline output for logging query. @see ProxyDataSourceBuilder#multiline()
|
||||
|spring.sleuth.jdbc.decorator.datasource.datasource-proxy.query.enable-logging | `true` | Enable logging all queries to the log.
|
||||
|spring.sleuth.jdbc.decorator.datasource.datasource-proxy.query.log-level | `DEBUG` | Severity of query logger.
|
||||
|spring.sleuth.jdbc.decorator.datasource.datasource-proxy.query.logger-name | | Name of query logger.
|
||||
|spring.sleuth.jdbc.decorator.datasource.datasource-proxy.slow-query.log-level | `WARN` | Severity of slow query logger.
|
||||
|spring.sleuth.jdbc.decorator.datasource.datasource-proxy.slow-query.logger-name | | Name of slow query logger.
|
||||
|spring.sleuth.jdbc.decorator.datasource.datasource-proxy.slow-query.threshold | `300` | Number of seconds to consider query as slow.
|
||||
|spring.sleuth.jdbc.decorator.datasource.enabled | `true` | Enables data source decorating.
|
||||
|spring.sleuth.jdbc.decorator.datasource.excluded-beans | | Beans that won't be decorated.
|
||||
|spring.sleuth.jdbc.decorator.datasource.includes | | Which types of tracing we would like to include.
|
||||
|spring.sleuth.jdbc.decorator.datasource.p6spy.custom-appender-class | | Class file to use (only with logging=custom). The class must implement {@link com.p6spy.engine.spy.appender.FormattedLogger}.
|
||||
|spring.sleuth.jdbc.decorator.datasource.p6spy.enable-logging | `true` | Enables logging JDBC events.
|
||||
|spring.sleuth.jdbc.decorator.datasource.p6spy.log-file | `spy.log` | Name of log file to use (only with logging=file).
|
||||
|spring.sleuth.jdbc.decorator.datasource.p6spy.log-filter.pattern | | Use regex pattern to filter log messages. Only matched messages will be logged.
|
||||
|spring.sleuth.jdbc.decorator.datasource.p6spy.log-format | | Custom log format.
|
||||
|spring.sleuth.jdbc.decorator.datasource.p6spy.logging | | Logging to use for logging queries.
|
||||
|spring.sleuth.jdbc.decorator.datasource.p6spy.multiline | `true` | Enables multiline output.
|
||||
|spring.sleuth.jdbc.decorator.datasource.p6spy.tracing.include-parameter-values | `true` | Report the effective sql string (with '?' replaced with real values) to tracing systems. <p> NOTE this setting does not affect the logging message.
|
||||
|spring.sleuth.kafka.enabled | `true` | Enable instrumenting of Apache Kafka clients.
|
||||
|spring.sleuth.messaging.aspect.enabled | `false` | Should {@link MessageMapping} wrapping be enabled.
|
||||
|spring.sleuth.messaging.enabled | `false` | Should messaging be turned on.
|
||||
|
||||
@@ -292,6 +292,105 @@ Fully qualified name of the enclosing class `org.springframework.cloud.sleuth.in
|
||||
|deployer.start|When deployer started deploying the application.
|
||||
|===
|
||||
|
||||
=== Jdbc Connection Span
|
||||
|
||||
> Span created when a JDBC connection takes place.
|
||||
|
||||
**Span name** `connection`.
|
||||
|
||||
Fully qualified name of the enclosing class `org.springframework.cloud.sleuth.instrument.jdbc.SleuthJdbcSpan`
|
||||
|
||||
IMPORTANT: All tags and events must be prefixed with `jdbc.` prefix!
|
||||
|
||||
.Tag Keys
|
||||
|===
|
||||
|Name | Description
|
||||
|jdbc.datasource.driver|Name of the JDBC datasource driver.
|
||||
|jdbc.datasource.pool|Name of the JDBC datasource pool.
|
||||
|===
|
||||
|
||||
=== Jdbc Query Span
|
||||
|
||||
> Span created when a JDBC query gets executed.
|
||||
|
||||
**Span name** `%s` - since it contains `%s`, the name is dynamic and will be resolved at runtime.
|
||||
|
||||
Fully qualified name of the enclosing class `org.springframework.cloud.sleuth.instrument.jdbc.SleuthJdbcSpan`
|
||||
|
||||
IMPORTANT: All tags and events must be prefixed with `jdbc.` prefix!
|
||||
|
||||
.Tag Keys
|
||||
|===
|
||||
|Name | Description
|
||||
|jdbc.query|The SQL query value.
|
||||
|jdbc.row-count|Number of SQL rows.
|
||||
|===
|
||||
|
||||
.Event Values
|
||||
|===
|
||||
|Name | Description
|
||||
|jdbc.commit|When the transaction gets commited.
|
||||
|jdbc.rollback|When the transaction gets rolled back.
|
||||
|===
|
||||
|
||||
=== Jdbc Result Set Span
|
||||
|
||||
> Span created when working with JDBC result set.
|
||||
|
||||
**Span name** `result-set`.
|
||||
|
||||
Fully qualified name of the enclosing class `org.springframework.cloud.sleuth.instrument.jdbc.SleuthJdbcSpan`
|
||||
|
||||
IMPORTANT: All tags and events must be prefixed with `jdbc.` prefix!
|
||||
|
||||
.Tag Keys
|
||||
|===
|
||||
|Name | Description
|
||||
|jdbc.query|The SQL query value.
|
||||
|jdbc.row-count|Number of SQL rows.
|
||||
|===
|
||||
|
||||
.Event Values
|
||||
|===
|
||||
|Name | Description
|
||||
|jdbc.commit|When the transaction gets commited.
|
||||
|jdbc.rollback|When the transaction gets rolled back.
|
||||
|===
|
||||
|
||||
=== Kafka Consumer Span
|
||||
|
||||
> Span created on the Kafka consumer side.
|
||||
|
||||
**Span name** `kafka.consume`.
|
||||
|
||||
Fully qualified name of the enclosing class `org.springframework.cloud.sleuth.instrument.kafka.SleuthKafkaSpan`
|
||||
|
||||
IMPORTANT: All tags and events must be prefixed with `kafka.` prefix!
|
||||
|
||||
.Tag Keys
|
||||
|===
|
||||
|Name | Description
|
||||
|kafka.offset|Kafka offset number.
|
||||
|kafka.partition|Kafka partition number.
|
||||
|kafka.topic|Name of the Kafka topic.
|
||||
|===
|
||||
|
||||
=== Kafka Producer Span
|
||||
|
||||
> Span created on the Kafka consumer side.
|
||||
|
||||
**Span name** `kafka.produce`.
|
||||
|
||||
Fully qualified name of the enclosing class `org.springframework.cloud.sleuth.instrument.kafka.SleuthKafkaSpan`
|
||||
|
||||
IMPORTANT: All tags and events must be prefixed with `kafka.` prefix!
|
||||
|
||||
.Tag Keys
|
||||
|===
|
||||
|Name | Description
|
||||
|kafka.topic|Name of the Kafka topic.
|
||||
|===
|
||||
|
||||
=== Messaging Span
|
||||
|
||||
> Span created when message is sent or received.
|
||||
@@ -336,6 +435,41 @@ Fully qualified name of the enclosing class `org.springframework.cloud.sleuth.in
|
||||
|quartz.trigger|Name of the trigger.
|
||||
|===
|
||||
|
||||
=== R2dbc Query Span
|
||||
|
||||
> Span created on the Kafka consumer side.
|
||||
|
||||
**Span name** `query`.
|
||||
|
||||
Fully qualified name of the enclosing class `org.springframework.cloud.sleuth.instrument.r2dbc.SleuthR2dbcSpan`
|
||||
|
||||
IMPORTANT: All tags and events must be prefixed with `r2dbc.` prefix!
|
||||
|
||||
.Tag Keys
|
||||
|===
|
||||
|Name | Description
|
||||
|r2dbc.connection|Name of the R2DBC connection.
|
||||
|r2dbc.query[%s]|Name of the R2DBC query. (since the name contains `%s` the final value will be resolved at runtime)
|
||||
|r2dbc.thread|Name of the R2DBC thread.
|
||||
|===
|
||||
|
||||
=== Rsocket Requester Span
|
||||
|
||||
> Span created on the RSocket responder side.
|
||||
|
||||
**Span name** `%s` - since it contains `%s`, the name is dynamic and will be resolved at runtime.
|
||||
|
||||
Fully qualified name of the enclosing class `org.springframework.cloud.sleuth.instrument.rsocket.SleuthRSocketSpan`
|
||||
|
||||
IMPORTANT: All tags and events must be prefixed with `rsocket.` prefix!
|
||||
|
||||
.Tag Keys
|
||||
|===
|
||||
|Name | Description
|
||||
|rsocket.request-type|Name of the R2DBC thread.
|
||||
|rsocket.route|Name of the RSocket route.
|
||||
|===
|
||||
|
||||
=== Rsocket Responder Span
|
||||
|
||||
> Span created on the RSocket responder side.
|
||||
@@ -389,6 +523,27 @@ Fully qualified name of the enclosing class `org.springframework.cloud.sleuth.in
|
||||
|
||||
Fully qualified name of the enclosing class `org.springframework.cloud.sleuth.instrument.task.SleuthTaskSpan`
|
||||
|
||||
=== Tx Span
|
||||
|
||||
> Span created when there was no previous transaction. If there was one, we will continue it unless propagation is required.
|
||||
|
||||
**Span name** `tx`.
|
||||
|
||||
Fully qualified name of the enclosing class `org.springframework.cloud.sleuth.instrument.tx.SleuthTxSpan`
|
||||
|
||||
IMPORTANT: All tags and events must be prefixed with `tx.` prefix!
|
||||
|
||||
.Tag Keys
|
||||
|===
|
||||
|Name | Description
|
||||
|tx.isolation-level|Transaction isolation level.
|
||||
|tx.name|Transaction name.
|
||||
|tx.propagation-level|Transaction propagation level.
|
||||
|tx.read-only|Whether the transaction is read-only.
|
||||
|tx.timeout|Transaction timeout.
|
||||
|tx.transaction-manager|Name of the TransactionManager.
|
||||
|===
|
||||
|
||||
=== Web Filter Span
|
||||
|
||||
> Span around a WebFilter. Will continue the current span or create a new one and tag it
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
:numbered!:
|
||||
|
||||
[[appendix]]
|
||||
[[sleuth-spans]]
|
||||
== Spring Cloud Sleuth Spans
|
||||
|
||||
Below you can find a list of all the spans that are created by Spring Cloud Sleuth.
|
||||
|
||||
include::_spans.adoc[]
|
||||
|
||||
[[common-application-properties]]
|
||||
== Common application properties
|
||||
|
||||
include::_attributes.adoc[]
|
||||
|
||||
Various properties can be specified inside your `application.properties` file, inside your `application.yml` file, or as command line switches.
|
||||
This appendix provides a list of common {project-full-name} properties and references to the underlying classes that consume them.
|
||||
|
||||
NOTE: Property contributions can come from additional jar files on your classpath, so you should not consider this an exhaustive list.
|
||||
Also, you can define your own properties.
|
||||
|
||||
include::_configprops.adoc[]
|
||||
:numbered!:
|
||||
|
||||
[[appendix]]
|
||||
[[common-application-properties]]
|
||||
== Common application properties
|
||||
|
||||
include::_attributes.adoc[]
|
||||
|
||||
Various properties can be specified inside your `application.properties` file, inside your `application.yml` file, or as command line switches.
|
||||
This appendix provides a list of common {project-full-name} properties and references to the underlying classes that consume them.
|
||||
|
||||
NOTE: Property contributions can come from additional jar files on your classpath, so you should not consider this an exhaustive list.
|
||||
Also, you can define your own properties.
|
||||
|
||||
include::_configprops.adoc[]
|
||||
|
||||
[[sleuth-spans]]
|
||||
== Spring Cloud Sleuth Spans
|
||||
|
||||
Below you can find a list of all the spans that are created by Spring Cloud Sleuth.
|
||||
|
||||
include::_spans.adoc[]
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
include::_attributes.adoc[]
|
||||
|
||||
In this section, we describe how to customize various parts of Spring Cloud Sleuth.
|
||||
In this section, we describe how to customize various parts of Spring Cloud Sleuth. Please check the <<appendix.adoc#sleuth-spans,appendix>> for the list of spans, tags and events.
|
||||
|
||||
[[sleuth-kafka-integration]]
|
||||
== Apache Kafka
|
||||
@@ -650,3 +650,53 @@ This feature is available for all tracer implementations.
|
||||
|
||||
We're adding an instrumented Tomcat's `Valve` that originates the span.
|
||||
In order to disable this instrumentation set `spring.sleuth.web.tomcat.enabled` to `false`.
|
||||
|
||||
|
||||
[[sleuth-jdbc-integration]]
|
||||
== Spring JDBC
|
||||
|
||||
This feature is available for all tracer implementations. It has been ported from the https://github.com/gavlyukovskiy/spring-boot-data-source-decorator/[spring-boot-datasource-decorator] project.
|
||||
|
||||
We're decorating `DataSource`s in a trace representation. We delegate actual proxying to either https://github.com/p6spy/p6spy[p6spy] or https://github.com/ttddyy/datasource-proxy[datasource-proxy]. In order to use this feature you need to have them on the classpath.
|
||||
|
||||
====
|
||||
[source,xml,indent=0,subs="verbatim,attributes",role="primary"]
|
||||
.P6Spy Maven
|
||||
----
|
||||
<dependency>
|
||||
<groupId>p6spy</groupId>
|
||||
<artifactId>p6spy</artifactId>
|
||||
<version>${p6spy.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
----
|
||||
|
||||
[source,yml,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.P6Spy Gradle
|
||||
----
|
||||
runtimeOnly "p6spy:p6spy:${p6spyVersion}"
|
||||
----
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.Datasource Proxy Maven
|
||||
----
|
||||
<dependency>
|
||||
<groupId>net.ttddyy</groupId>
|
||||
<artifactId>datasource-proxy</artifactId>
|
||||
<version>${datasource-proxy.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
----
|
||||
|
||||
[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"]
|
||||
.Datasource Proxy Gradle
|
||||
----
|
||||
runtimeOnly "net.ttddyy:datasource-proxy:${datasourceProxyVersion}"
|
||||
----
|
||||
====
|
||||
|
||||
Please check the <<appendix.adoc#appendix,appendix>> page under `spring.sleuth.jdbc.decorator.datasource.p6spy` for all p6spy configuration options and `spring.sleuth.jdbc.decorator.datasource.datasource-proxy` for all datasource proxy configuration options.
|
||||
|
||||
You can configure P6Spy manually using one of available configuration methods. For more information please refer to the http://p6spy.readthedocs.io/en/latest/configandusage.html[P6Spy Configuration Guide].
|
||||
|
||||
In order to disable this instrumentation set `spring.sleuth.jdbc.decorator.datasource.enabled` to `false`.
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
[[spring-cloud-sleuth-reference-documentation]]
|
||||
= Spring Cloud Sleuth Reference Documentation
|
||||
Adrian Cole, Spencer Gibb, Marcin Grzejszczak, Dave Syer, Jay Bryant
|
||||
|
||||
:docinfo: shared
|
||||
include::_attributes.adoc[]
|
||||
|
||||
The reference documentation consists of the following sections:
|
||||
|
||||
[horizontal]
|
||||
<<legal.adoc#legal,Legal>> :: Legal information.
|
||||
<<documentation-overview.adoc#sleuth-documentation-about,Documentation Overview>> :: About the Documentation, Getting Help, First Steps, and more.
|
||||
<<getting-started.adoc#getting-started,Getting Started>> :: Introducing {project-full-name}, Developing Your First {project-full-name}-based Application
|
||||
<<using.adoc#using,Using {project-full-name}>> :: {project-full-name} usage examples and workflows.
|
||||
<<project-features.adoc#features,{project-full-name} Features>> :: Span creation, context propagation, and more.
|
||||
<<howto.adoc#howto,"`How-to`" Guides>> :: Add sampling, propagate remote tags, and more.
|
||||
<<integrations.adoc#sleuth-integration,{project-full-name} Integrations>> :: Instrumentation configuration, context propagation, and more.
|
||||
<<appendix.adoc#appendix,Appendices>> :: Configuration properties.
|
||||
[[spring-cloud-sleuth-reference-documentation]]
|
||||
= Spring Cloud Sleuth Reference Documentation
|
||||
Adrian Cole, Spencer Gibb, Marcin Grzejszczak, Dave Syer, Jay Bryant
|
||||
|
||||
:docinfo: shared
|
||||
include::_attributes.adoc[]
|
||||
|
||||
The reference documentation consists of the following sections:
|
||||
|
||||
[horizontal]
|
||||
<<legal.adoc#legal,Legal>> :: Legal information.
|
||||
<<documentation-overview.adoc#sleuth-documentation-about,Documentation Overview>> :: About the Documentation, Getting Help, First Steps, and more.
|
||||
<<getting-started.adoc#getting-started,Getting Started>> :: Introducing {project-full-name}, Developing Your First {project-full-name}-based Application
|
||||
<<using.adoc#using,Using {project-full-name}>> :: {project-full-name} usage examples and workflows.
|
||||
<<project-features.adoc#features,{project-full-name} Features>> :: Span creation, context propagation, and more.
|
||||
<<howto.adoc#howto,"`How-to`" Guides>> :: Add sampling, propagate remote tags, and more.
|
||||
<<integrations.adoc#sleuth-integration,{project-full-name} Integrations>> :: Instrumentation configuration, context propagation, and more.
|
||||
<<appendix.adoc#appendix,Appendices>> :: Span definitions and configuration properties.
|
||||
|
||||
@@ -35,11 +35,13 @@ class SpanEntry implements Comparable<SpanEntry> {
|
||||
|
||||
final String description;
|
||||
|
||||
final String prefix;
|
||||
|
||||
final Collection<KeyValueEntry> tagKeys;
|
||||
|
||||
final Collection<KeyValueEntry> events;
|
||||
|
||||
SpanEntry(String name, String enclosingClass, String enumName, String description,
|
||||
SpanEntry(String name, String enclosingClass, String enumName, String description, String prefix,
|
||||
Collection<KeyValueEntry> tagKeys, Collection<KeyValueEntry> events) {
|
||||
Assert.isTrue(StringUtils.hasText(name), "Span name must not be empty");
|
||||
Assert.isTrue(StringUtils.hasText(description), "Span description must not be empty");
|
||||
@@ -47,6 +49,7 @@ class SpanEntry implements Comparable<SpanEntry> {
|
||||
this.enclosingClass = enclosingClass;
|
||||
this.enumName = enumName;
|
||||
this.description = description;
|
||||
this.prefix = prefix;
|
||||
this.tagKeys = tagKeys;
|
||||
this.events = events;
|
||||
}
|
||||
@@ -89,6 +92,9 @@ class SpanEntry implements Comparable<SpanEntry> {
|
||||
text.append(".");
|
||||
}
|
||||
text.append("\n\n").append("Fully qualified name of the enclosing class `").append(this.enclosingClass).append("`");
|
||||
if (StringUtils.hasText(prefix)) {
|
||||
text.append("\n\nIMPORTANT: All tags and events must be prefixed with `").append(this.prefix).append("` prefix!");
|
||||
}
|
||||
if (!tagKeys.isEmpty()) {
|
||||
text.append("\n\n.Tag Keys\n|===\n|Name | Description\n").append(this.tagKeys.stream().map(KeyValueEntry::toString).collect(Collectors.joining("\n")))
|
||||
.append("\n|===");
|
||||
|
||||
@@ -99,6 +99,7 @@ class SpanSearchingFileVisitor extends SimpleFileVisitor<Path> {
|
||||
}
|
||||
String name = "";
|
||||
String description = enumConstant.getJavaDoc().getText();
|
||||
String prefix = "";
|
||||
Collection<KeyValueEntry> tags = new TreeSet<>();
|
||||
Collection<KeyValueEntry> events = new TreeSet<>();
|
||||
for (MemberSource<EnumConstantSource.Body, ?> member : members) {
|
||||
@@ -117,11 +118,12 @@ class SpanSearchingFileVisitor extends SimpleFileVisitor<Path> {
|
||||
else if ("getEvents".equals(methodName)) {
|
||||
events.addAll(keyValueEntries(myEnum, methodDeclaration, EventValue.class));
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
else if ("prefix".equals(methodName)) {
|
||||
prefix = readStringReturnValue(methodDeclaration);
|
||||
}
|
||||
}
|
||||
return new SpanEntry(name, myEnum.getCanonicalName(), enumConstant.getName(), description, tags, events);
|
||||
return new SpanEntry(name, myEnum.getCanonicalName(), enumConstant.getName(), description, prefix, tags,
|
||||
events);
|
||||
}
|
||||
|
||||
private Collection<KeyValueEntry> keyValueEntries(JavaEnumImpl myEnum, MethodDeclaration methodDeclaration,
|
||||
|
||||
24
pom.xml
24
pom.xml
@@ -84,6 +84,10 @@
|
||||
<cglib-nodep.version>3.3.0</cglib-nodep.version>
|
||||
<objenesis.version>3.0.1</objenesis.version>
|
||||
<spring-security-oauth2.version>2.2.0.RELEASE</spring-security-oauth2.version>
|
||||
<p6spy.version>3.9.1</p6spy.version>
|
||||
<datasource-proxy.version>1.7</datasource-proxy.version>
|
||||
<tomcat-jdbc.version>10.0.6</tomcat-jdbc.version>
|
||||
<commons-dbcp2.version>2.8.0</commons-dbcp2.version>
|
||||
|
||||
<!-- Until we switch it to true in sc-build -->
|
||||
<javadoc.failOnError>true</javadoc.failOnError>
|
||||
@@ -293,6 +297,26 @@
|
||||
<artifactId>brave-propagation-aws</artifactId>
|
||||
<version>${brave-propagation-aws.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>p6spy</groupId>
|
||||
<artifactId>p6spy</artifactId>
|
||||
<version>${p6spy.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.ttddyy</groupId>
|
||||
<artifactId>datasource-proxy</artifactId>
|
||||
<version>${datasource-proxy.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-dbcp2</artifactId>
|
||||
<version>${commons-dbcp2.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.tomcat</groupId>
|
||||
<artifactId>tomcat-jdbc</artifactId>
|
||||
<version>${tomcat-jdbc.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-core</artifactId>
|
||||
|
||||
@@ -99,6 +99,17 @@ public interface Span extends SpanCustomizer {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the remote url on the span.
|
||||
* @param ip remote ip
|
||||
* @param port remote port
|
||||
* @return this span
|
||||
* @since 3.1.0
|
||||
*/
|
||||
default Span remoteIpAndPort(String ip, int port) {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of span. Can be used to specify additional relationships between spans in
|
||||
* addition to a parent/child relationship.
|
||||
@@ -204,10 +215,11 @@ public interface Span extends SpanCustomizer {
|
||||
|
||||
/**
|
||||
* Sets the remote URL for the span.
|
||||
* @param remoteUrl remote service name
|
||||
* @param ip remote service ip
|
||||
* @param port remote service port
|
||||
* @return this
|
||||
*/
|
||||
default Builder remoteUrl(String remoteUrl) {
|
||||
default Builder remoteIpAndPort(String ip, int port) {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,13 +16,15 @@
|
||||
|
||||
package org.springframework.cloud.sleuth;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
/**
|
||||
* Container object for {@link Span} and its corresponding {@link Tracer.SpanInScope}.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public class SpanAndScope {
|
||||
public class SpanAndScope implements Closeable {
|
||||
|
||||
private final Span span;
|
||||
|
||||
@@ -46,4 +48,10 @@ public class SpanAndScope {
|
||||
return "SpanAndScope{" + "span=" + this.span + '}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
this.scope.close();
|
||||
this.span.end();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -188,7 +188,31 @@
|
||||
<artifactId>spring-vault-core</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>p6spy</groupId>
|
||||
<artifactId>p6spy</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.ttddyy</groupId>
|
||||
<artifactId>datasource-proxy</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-dbcp2</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.tomcat</groupId>
|
||||
<artifactId>tomcat-jdbc</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.zaxxer</groupId>
|
||||
<artifactId>HikariCP</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<!-- BRAVE -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
|
||||
@@ -36,19 +36,24 @@ public class SleuthBaggageProperties {
|
||||
private boolean correlationEnabled = true;
|
||||
|
||||
/**
|
||||
* List of fields that should be propagated over the wire.
|
||||
*/
|
||||
private List<String> correlationFields = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* List of fields that should be accessible within the JVM process but not propagated
|
||||
* over the wire.
|
||||
*/
|
||||
private List<String> localFields = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* List of fields that are referenced the same in-process as it is on the wire. For
|
||||
* example, the field "x-vcap-request-id" would be set as-is including the prefix.
|
||||
*
|
||||
*/
|
||||
private List<String> remoteFields = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* List of fields that should automatically become tags.
|
||||
*/
|
||||
private List<String> tagFields = new ArrayList<>();
|
||||
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.sleuth.autoconfig.instrument.jdbc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import net.ttddyy.dsproxy.listener.MethodExecutionListener;
|
||||
import net.ttddyy.dsproxy.listener.QueryCountStrategy;
|
||||
import net.ttddyy.dsproxy.listener.QueryExecutionListener;
|
||||
import net.ttddyy.dsproxy.listener.SingleQueryCountHolder;
|
||||
import net.ttddyy.dsproxy.proxy.GlobalConnectionIdManager;
|
||||
import net.ttddyy.dsproxy.proxy.ResultSetProxyLogicFactory;
|
||||
import net.ttddyy.dsproxy.proxy.SimpleResultSetProxyLogicFactory;
|
||||
import net.ttddyy.dsproxy.support.ProxyDataSource;
|
||||
import net.ttddyy.dsproxy.transform.ParameterTransformer;
|
||||
import net.ttddyy.dsproxy.transform.QueryTransformer;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cloud.sleuth.Tracer;
|
||||
import org.springframework.cloud.sleuth.instrument.jdbc.DataSourceNameResolver;
|
||||
import org.springframework.cloud.sleuth.instrument.jdbc.DataSourceProxyBuilderCustomizer;
|
||||
import org.springframework.cloud.sleuth.instrument.jdbc.DataSourceProxyConnectionIdManagerProvider;
|
||||
import org.springframework.cloud.sleuth.instrument.jdbc.DataSourceProxyDataSourceDecorator;
|
||||
import org.springframework.cloud.sleuth.instrument.jdbc.DataSourceProxyProperties;
|
||||
import org.springframework.cloud.sleuth.instrument.jdbc.TraceListenerStrategySpanCustomizer;
|
||||
import org.springframework.cloud.sleuth.instrument.jdbc.TraceQueryExecutionListener;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* Configuration for integration with datasource-proxy, allows to use define custom
|
||||
* {@link QueryExecutionListener}, {@link ParameterTransformer} and
|
||||
* {@link QueryTransformer}.
|
||||
*
|
||||
* @author Arthur Gavlyukovskiy
|
||||
*/
|
||||
@ConditionalOnClass(ProxyDataSource.class)
|
||||
class DataSourceProxyConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
DataSourceProxyConnectionIdManagerProvider traceConnectionIdManagerProvider() {
|
||||
return GlobalConnectionIdManager::new;
|
||||
}
|
||||
|
||||
@Bean
|
||||
DataSourceProxyBuilderCustomizer proxyDataSourceBuilderConfigurer(
|
||||
ObjectProvider<QueryCountStrategy> queryCountStrategy,
|
||||
ObjectProvider<List<QueryExecutionListener>> listeners,
|
||||
ObjectProvider<List<MethodExecutionListener>> methodExecutionListeners,
|
||||
ObjectProvider<ParameterTransformer> parameterTransformer,
|
||||
ObjectProvider<QueryTransformer> queryTransformer,
|
||||
ObjectProvider<ResultSetProxyLogicFactory> resultSetProxyLogicFactory,
|
||||
ObjectProvider<DataSourceProxyConnectionIdManagerProvider> dataSourceProxyConnectionIdManagerProvider,
|
||||
TraceDataSourceDecoratorProperties dataSourceDecoratorProperties) {
|
||||
return new DataSourceProxyBuilderCustomizer(queryCountStrategy.getIfAvailable(() -> null),
|
||||
listeners.getIfAvailable(() -> null), methodExecutionListeners.getIfAvailable(() -> null),
|
||||
parameterTransformer.getIfAvailable(() -> null), queryTransformer.getIfAvailable(() -> null),
|
||||
resultSetProxyLogicFactory.getIfAvailable(() -> null),
|
||||
dataSourceProxyConnectionIdManagerProvider.getIfAvailable(() -> null),
|
||||
props(dataSourceDecoratorProperties));
|
||||
}
|
||||
|
||||
private DataSourceProxyProperties props(TraceDataSourceDecoratorProperties dataSourceDecoratorProperties) {
|
||||
TraceDataSourceDecoratorProperties.DataSourceProxyProperties originalProxy = dataSourceDecoratorProperties
|
||||
.getDatasourceProxy();
|
||||
DataSourceProxyProperties props = new DataSourceProxyProperties();
|
||||
BeanUtils.copyProperties(originalProxy, props);
|
||||
props.setLogging(DataSourceProxyProperties.DataSourceProxyLogging.valueOf(originalProxy.getLogging().name()));
|
||||
return props;
|
||||
}
|
||||
|
||||
@Bean
|
||||
DataSourceProxyDataSourceDecorator proxyDataSourceDecorator(
|
||||
DataSourceProxyBuilderCustomizer dataSourceProxyBuilderCustomizer,
|
||||
DataSourceNameResolver dataSourceNameResolver) {
|
||||
return new DataSourceProxyDataSourceDecorator(dataSourceProxyBuilderCustomizer, dataSourceNameResolver);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(value = "spring.sleuth.jdbc.decorator.datasource.datasource-proxy.count-query",
|
||||
havingValue = "true")
|
||||
QueryCountStrategy queryCountStrategy() {
|
||||
return new SingleQueryCountHolder();
|
||||
}
|
||||
|
||||
@Bean
|
||||
TraceQueryExecutionListener traceQueryExecutionListener(Tracer tracer,
|
||||
TraceDataSourceDecoratorProperties dataSourceDecoratorProperties,
|
||||
ObjectProvider<List<TraceListenerStrategySpanCustomizer>> customizers) {
|
||||
return new TraceQueryExecutionListener(tracer, dataSourceDecoratorProperties.getIncludes(),
|
||||
customizers.getIfAvailable(ArrayList::new));
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
ResultSetProxyLogicFactory traceResultSetProxyLogicFactory() {
|
||||
return new SimpleResultSetProxyLogicFactory();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.sleuth.autoconfig.instrument.jdbc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.p6spy.engine.event.JdbcEventListener;
|
||||
import com.p6spy.engine.spy.DefaultJdbcEventListenerFactory;
|
||||
import com.p6spy.engine.spy.JdbcEventListenerFactory;
|
||||
import com.p6spy.engine.spy.P6DataSource;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.cloud.sleuth.Tracer;
|
||||
import org.springframework.cloud.sleuth.instrument.jdbc.DataSourceNameResolver;
|
||||
import org.springframework.cloud.sleuth.instrument.jdbc.P6SpyContextJdbcEventListenerFactory;
|
||||
import org.springframework.cloud.sleuth.instrument.jdbc.P6SpyDataSourceDecorator;
|
||||
import org.springframework.cloud.sleuth.instrument.jdbc.TraceJdbcEventListener;
|
||||
import org.springframework.cloud.sleuth.instrument.jdbc.TraceListenerStrategySpanCustomizer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* Configuration for integration with p6spy, allows to define custom
|
||||
* {@link JdbcEventListener}.
|
||||
*
|
||||
* @author Arthur Gavlyukovskiy
|
||||
*/
|
||||
@ConditionalOnClass(P6DataSource.class)
|
||||
class P6SpyConfiguration {
|
||||
|
||||
@Bean
|
||||
static P6SpyPropertiesSetter p6SpyPropertiesSetter(ConfigurableApplicationContext context) {
|
||||
return new P6SpyPropertiesSetter(context);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
JdbcEventListenerFactory traceJdbcEventListenerFactory(ObjectProvider<List<JdbcEventListener>> listeners) {
|
||||
JdbcEventListenerFactory jdbcEventListenerFactory = new DefaultJdbcEventListenerFactory();
|
||||
List<JdbcEventListener> listenerList = listeners.getIfAvailable(() -> null);
|
||||
return listenerList != null ? new P6SpyContextJdbcEventListenerFactory(jdbcEventListenerFactory, listenerList)
|
||||
: jdbcEventListenerFactory;
|
||||
}
|
||||
|
||||
@Bean
|
||||
P6SpyDataSourceDecorator p6SpyDataSourceDecorator(JdbcEventListenerFactory jdbcEventListenerFactory) {
|
||||
return new P6SpyDataSourceDecorator(jdbcEventListenerFactory);
|
||||
}
|
||||
|
||||
@Bean
|
||||
TraceJdbcEventListener tracingJdbcEventListener(Tracer tracer, DataSourceNameResolver dataSourceNameResolver,
|
||||
TraceDataSourceDecoratorProperties dataSourceDecoratorProperties,
|
||||
ObjectProvider<List<TraceListenerStrategySpanCustomizer>> customizers) {
|
||||
return new TraceJdbcEventListener(tracer, dataSourceNameResolver, dataSourceDecoratorProperties.getIncludes(),
|
||||
dataSourceDecoratorProperties.getP6spy().getTracing().isIncludeParameterValues(),
|
||||
customizers.getIfAvailable(ArrayList::new));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.sleuth.autoconfig.instrument.jdbc;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.p6spy.engine.logging.P6LogFactory;
|
||||
import com.p6spy.engine.spy.P6ModuleManager;
|
||||
import com.p6spy.engine.spy.P6SpyFactory;
|
||||
import com.p6spy.engine.spy.option.EnvironmentVariables;
|
||||
import com.p6spy.engine.spy.option.P6OptionsSource;
|
||||
import com.p6spy.engine.spy.option.SpyDotProperties;
|
||||
import com.p6spy.engine.spy.option.SystemProperties;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
/**
|
||||
* Sets p6spy properties to / from system properties.
|
||||
*
|
||||
* @author Arthur Gavlyukovskiy
|
||||
* @since 3.1.0
|
||||
*/
|
||||
class P6SpyPropertiesSetter implements BeanDefinitionRegistryPostProcessor, Closeable {
|
||||
|
||||
private static final Logger log = getLogger(P6SpyPropertiesSetter.class);
|
||||
|
||||
private final ConfigurableApplicationContext context;
|
||||
|
||||
private final Map<String, String> initialP6SpyOptions;
|
||||
|
||||
P6SpyPropertiesSetter(ConfigurableApplicationContext context) {
|
||||
this.context = context;
|
||||
this.initialP6SpyOptions = findDefinedOptions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
||||
ConfigurableEnvironment environment = this.context.getEnvironment();
|
||||
String customModuleList = initialP6SpyOptions.get("modulelist");
|
||||
boolean isEnableLogging = environment
|
||||
.getProperty("spring.sleuth.jdbc.decorator.datasource.p6spy.enable-logging", Boolean.class, true);
|
||||
if (customModuleList != null) {
|
||||
log.info("P6Spy modulelist is overridden, some p6spy configuration features will not be applied");
|
||||
}
|
||||
else {
|
||||
List<String> moduleList = new ArrayList<>();
|
||||
// default factory, holds P6Spy configuration
|
||||
moduleList.add(P6SpyFactory.class.getName());
|
||||
if (isEnableLogging) {
|
||||
moduleList.add(P6LogFactory.class.getName());
|
||||
}
|
||||
System.setProperty("p6spy.config.modulelist", String.join(",", moduleList));
|
||||
}
|
||||
if (!initialP6SpyOptions.containsKey("logMessageFormat")) {
|
||||
String logFormat = logFormat(environment);
|
||||
boolean isMultiline = multiLine(environment);
|
||||
if (logFormat != null) {
|
||||
System.setProperty("p6spy.config.logMessageFormat", "com.p6spy.engine.spy.appender.CustomLineFormat");
|
||||
System.setProperty("p6spy.config.customLogMessageFormat", logFormat);
|
||||
}
|
||||
else if (isMultiline) {
|
||||
System.setProperty("p6spy.config.logMessageFormat", "com.p6spy.engine.spy.appender.MultiLineFormat");
|
||||
}
|
||||
}
|
||||
if (isEnableLogging && !initialP6SpyOptions.containsKey("appender")) {
|
||||
TraceDataSourceDecoratorProperties.P6SpyProperties.P6SpyLogging logging = TraceDataSourceDecoratorProperties.P6SpyProperties.P6SpyLogging
|
||||
.valueOf(environment
|
||||
.getProperty("spring.sleuth.jdbc.decorator.datasource.p6spy.logging", String.class,
|
||||
TraceDataSourceDecoratorProperties.P6SpyProperties.P6SpyLogging.SLF4J.toString())
|
||||
.toUpperCase());
|
||||
switch (logging) {
|
||||
case SYSOUT:
|
||||
System.setProperty("p6spy.config.appender", "com.p6spy.engine.spy.appender.StdoutLogger");
|
||||
break;
|
||||
case SLF4J:
|
||||
System.setProperty("p6spy.config.appender", "com.p6spy.engine.spy.appender.Slf4JLogger");
|
||||
break;
|
||||
case FILE:
|
||||
System.setProperty("p6spy.config.appender", "com.p6spy.engine.spy.appender.FileLogger");
|
||||
break;
|
||||
case CUSTOM:
|
||||
String customAppender = environment.getProperty(
|
||||
"spring.sleuth.jdbc.decorator.datasource.p6spy.custom-appender-class", String.class, "");
|
||||
System.setProperty("p6spy.config.appender", customAppender);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!initialP6SpyOptions.containsKey("logfile")) {
|
||||
String logFile = environment.getProperty("spring.sleuth.jdbc.decorator.datasource.p6spy.log-file",
|
||||
String.class, "spy.log");
|
||||
System.setProperty("p6spy.config.logfile", logFile);
|
||||
}
|
||||
String pattern = environment.getProperty("spring.sleuth.jdbc.decorator.datasource.p6spy.log-filter.pattern",
|
||||
String.class);
|
||||
if (pattern != null) {
|
||||
System.setProperty("p6spy.config.filter", "true");
|
||||
System.setProperty("p6spy.config.sqlexpression", pattern);
|
||||
}
|
||||
// If factories were loaded before this method is initialized changing properties
|
||||
// will not be applied
|
||||
// Changes done in this method could not override anything user specified,
|
||||
// therefore it is safe to call reload
|
||||
P6ModuleManager.getInstance().reload();
|
||||
}
|
||||
|
||||
private Boolean multiLine(ConfigurableEnvironment environment) {
|
||||
return environment.getProperty("spring.sleuth.jdbc.decorator.datasource.p6spy.multiline", Boolean.class, true);
|
||||
}
|
||||
|
||||
private String logFormat(ConfigurableEnvironment environment) {
|
||||
return environment.getProperty("spring.sleuth.jdbc.decorator.datasource.p6spy.log-format", String.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (!initialP6SpyOptions.containsKey("modulelist")) {
|
||||
System.clearProperty("p6spy.config.modulelist");
|
||||
}
|
||||
if (!initialP6SpyOptions.containsKey("logMessageFormat")) {
|
||||
ConfigurableEnvironment environment = this.context.getEnvironment();
|
||||
String logFormat = logFormat(environment);
|
||||
boolean isMultiline = multiLine(environment);
|
||||
if (logFormat != null) {
|
||||
System.clearProperty("p6spy.config.logMessageFormat");
|
||||
System.clearProperty("p6spy.config.customLogMessageFormat");
|
||||
}
|
||||
else if (isMultiline) {
|
||||
System.clearProperty("p6spy.config.logMessageFormat");
|
||||
}
|
||||
}
|
||||
if (!initialP6SpyOptions.containsKey("appender")) {
|
||||
System.clearProperty("p6spy.config.appender");
|
||||
}
|
||||
if (!initialP6SpyOptions.containsKey("logfile")) {
|
||||
System.clearProperty("p6spy.config.logfile");
|
||||
}
|
||||
P6ModuleManager.getInstance().reload();
|
||||
}
|
||||
|
||||
private Map<String, String> findDefinedOptions() {
|
||||
SpyDotProperties spyDotProperties = null;
|
||||
try {
|
||||
spyDotProperties = new SpyDotProperties();
|
||||
}
|
||||
catch (IOException ignored) {
|
||||
}
|
||||
return Stream.of(spyDotProperties, new EnvironmentVariables(), new SystemProperties()).filter(Objects::nonNull)
|
||||
.map(P6OptionsSource::getOptions).filter(Objects::nonNull)
|
||||
.flatMap(options -> options.entrySet().stream())
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
|
||||
// always using value from the first P6OptionsSource
|
||||
(value1, value2) -> value1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.sleuth.autoconfig.instrument.jdbc;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.sleuth.Tracer;
|
||||
import org.springframework.cloud.sleuth.instrument.jdbc.DataSourceDecorator;
|
||||
import org.springframework.cloud.sleuth.instrument.jdbc.DataSourceNameResolver;
|
||||
import org.springframework.cloud.sleuth.instrument.jdbc.TraceHikariListenerStrategySpanCustomizer;
|
||||
import org.springframework.cloud.sleuth.instrument.jdbc.TraceListenerStrategySpanCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for proxying DataSource.
|
||||
*
|
||||
* @author Arthur Gavlyukovskiy
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(TraceDataSourceDecoratorProperties.class)
|
||||
@ConditionalOnProperty(name = "spring.sleuth.jdbc.decorator.datasource.enabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
@ConditionalOnBean({ DataSource.class, Tracer.class })
|
||||
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
|
||||
@Import({ P6SpyConfiguration.class, DataSourceProxyConfiguration.class })
|
||||
public class TraceDataSourceDecoratorAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(DataSourceDecorator.class)
|
||||
static TraceDataSourceDecoratorBeanPostProcessor traceDataSourceDecoratorBeanPostProcessor(
|
||||
TraceDataSourceDecoratorProperties dataSourceDecoratorProperties) {
|
||||
return new TraceDataSourceDecoratorBeanPostProcessor(dataSourceDecoratorProperties.getExcludedBeans());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
DataSourceNameResolver traceDataSourceNameResolver() {
|
||||
return new DataSourceNameResolver();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnClass(name = "com.zaxxer.hikari.HikariDataSource")
|
||||
TraceListenerStrategySpanCustomizer hikariTraceListenerStrategySpanCustomizer() {
|
||||
return new TraceHikariListenerStrategySpanCustomizer();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.sleuth.autoconfig.instrument.jdbc;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.aop.scope.ScopedProxyUtils;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.cloud.sleuth.instrument.jdbc.DataSourceDecorator;
|
||||
import org.springframework.cloud.sleuth.instrument.jdbc.DataSourceNameResolver;
|
||||
import org.springframework.cloud.sleuth.instrument.jdbc.DataSourceWrapper;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* {@link BeanPostProcessor} that wraps all data source beans in {@link DataSource}
|
||||
* proxies specified in property 'spring.datasource.type'.
|
||||
*
|
||||
* @author Arthur Gavlyukovskiy
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public class TraceDataSourceDecoratorBeanPostProcessor implements BeanPostProcessor, Ordered, ApplicationContextAware {
|
||||
|
||||
private static final Log log = LogFactory.getLog(TraceDataSourceDecoratorBeanPostProcessor.class);
|
||||
|
||||
private final static boolean HIKARI_AVAILABLE = ClassUtils.isPresent("com.zaxxer.hikari.HikariDataSource",
|
||||
DataSourceNameResolver.class.getClassLoader());
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private DataSourceNameResolver dataSourceNameResolver;
|
||||
|
||||
private final Collection<String> excludedBeans;
|
||||
|
||||
public TraceDataSourceDecoratorBeanPostProcessor(Collection<String> excludedBeans) {
|
||||
this.excludedBeans = excludedBeans;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (bean instanceof DataSource && !ScopedProxyUtils.isScopedTarget(beanName)
|
||||
&& !this.excludedBeans.contains(beanName)) {
|
||||
// TODO: This might be a problem
|
||||
Map<String, DataSourceDecorator> decorators = this.applicationContext
|
||||
.getBeansOfType(DataSourceDecorator.class).entrySet().stream()
|
||||
.sorted(Entry.comparingByValue(AnnotationAwareOrderComparator.INSTANCE))
|
||||
.collect(Collectors.toMap(Entry::getKey, Entry::getValue, (v1, v2) -> v2, LinkedHashMap::new));
|
||||
return decorate((DataSource) bean, getDataSourceName(bean, beanName), decorators);
|
||||
}
|
||||
else {
|
||||
return bean;
|
||||
}
|
||||
}
|
||||
|
||||
private String getDataSourceName(Object bean, String beanName) {
|
||||
if (HIKARI_AVAILABLE && bean instanceof HikariDataSource) {
|
||||
HikariDataSource hikariDataSource = (HikariDataSource) bean;
|
||||
if (hikariDataSource.getPoolName() != null && !hikariDataSource.getPoolName().startsWith("HikariPool-")) {
|
||||
return hikariDataSource.getPoolName();
|
||||
}
|
||||
}
|
||||
return beanName;
|
||||
}
|
||||
|
||||
private DataSource decorate(DataSource dataSource, String name, Map<String, DataSourceDecorator> decorators) {
|
||||
getDataSourceNameResolver().addDataSource(name, dataSource);
|
||||
DataSource decoratedDataSource = dataSource;
|
||||
for (Entry<String, DataSourceDecorator> decoratorEntry : decorators.entrySet()) {
|
||||
String decoratorBeanName = decoratorEntry.getKey();
|
||||
DataSourceDecorator decorator = decoratorEntry.getValue();
|
||||
DataSource dataSourceBeforeDecorating = decoratedDataSource;
|
||||
decoratedDataSource = Objects.requireNonNull(decorator.decorate(name, decoratedDataSource),
|
||||
"DataSourceDecorator (" + decoratorBeanName + ", " + decorator + ") should not return null");
|
||||
if (dataSourceBeforeDecorating != decoratedDataSource) {
|
||||
getDataSourceNameResolver().addDataSource(name, decoratedDataSource);
|
||||
}
|
||||
}
|
||||
if (dataSource != decoratedDataSource) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("The decorated data source [" + decoratedDataSource + "] will replace the original one ["
|
||||
+ dataSource + "]");
|
||||
}
|
||||
decoratedDataSource = new DataSourceWrapper(dataSource, decoratedDataSource);
|
||||
getDataSourceNameResolver().addDataSource(name, decoratedDataSource);
|
||||
}
|
||||
return decoratedDataSource;
|
||||
}
|
||||
|
||||
private DataSourceNameResolver getDataSourceNameResolver() {
|
||||
if (this.dataSourceNameResolver == null) {
|
||||
this.dataSourceNameResolver = this.applicationContext.getBean(DataSourceNameResolver.class);
|
||||
}
|
||||
return this.dataSourceNameResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.LOWEST_PRECEDENCE - 10;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,502 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.sleuth.autoconfig.instrument.jdbc;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import net.ttddyy.dsproxy.QueryCountHolder;
|
||||
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.cloud.sleuth.instrument.jdbc.TraceType;
|
||||
|
||||
/**
|
||||
* Properties for configuring proxy providers.
|
||||
*
|
||||
* @author Arthur Gavlyukovskiy
|
||||
* @since 3.1.0
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "spring.sleuth.jdbc.decorator.datasource")
|
||||
public class TraceDataSourceDecoratorProperties {
|
||||
|
||||
/**
|
||||
* Enables data source decorating.
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* Beans that won't be decorated.
|
||||
*/
|
||||
private Collection<String> excludedBeans = Collections.emptyList();
|
||||
|
||||
/**
|
||||
* Which types of tracing we would like to include.
|
||||
*/
|
||||
private List<TraceType> includes = Arrays.asList(TraceType.CONNECTION, TraceType.QUERY, TraceType.FETCH);
|
||||
|
||||
private DataSourceProxyProperties datasourceProxy = new DataSourceProxyProperties();
|
||||
|
||||
private P6SpyProperties p6spy = new P6SpyProperties();
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public Collection<String> getExcludedBeans() {
|
||||
return excludedBeans;
|
||||
}
|
||||
|
||||
public List<TraceType> getIncludes() {
|
||||
return includes;
|
||||
}
|
||||
|
||||
public void setIncludes(List<TraceType> includes) {
|
||||
this.includes = includes;
|
||||
}
|
||||
|
||||
public DataSourceProxyProperties getDatasourceProxy() {
|
||||
return datasourceProxy;
|
||||
}
|
||||
|
||||
public P6SpyProperties getP6spy() {
|
||||
return p6spy;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public void setExcludedBeans(Collection<String> excludedBeans) {
|
||||
this.excludedBeans = excludedBeans;
|
||||
}
|
||||
|
||||
public void setDatasourceProxy(DataSourceProxyProperties datasourceProxy) {
|
||||
this.datasourceProxy = datasourceProxy;
|
||||
}
|
||||
|
||||
public void setP6spy(P6SpyProperties p6spy) {
|
||||
this.p6spy = p6spy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Properties for datasource-proxy.
|
||||
*/
|
||||
public static class DataSourceProxyProperties {
|
||||
|
||||
/**
|
||||
* Logging to use for logging queries.
|
||||
*/
|
||||
private DataSourceProxyLogging logging = DataSourceProxyLogging.SLF4J;
|
||||
|
||||
/**
|
||||
* Query configuration.
|
||||
*/
|
||||
private Query query = new Query();
|
||||
|
||||
/**
|
||||
* Slow query configuration.
|
||||
*/
|
||||
private SlowQuery slowQuery = new SlowQuery();
|
||||
|
||||
/**
|
||||
* Use multiline output for logging query.
|
||||
*
|
||||
* @see ProxyDataSourceBuilder#multiline()
|
||||
*/
|
||||
private boolean multiline = true;
|
||||
|
||||
/**
|
||||
* Use json output for logging query.
|
||||
*
|
||||
* @see ProxyDataSourceBuilder#asJson()
|
||||
*/
|
||||
private boolean jsonFormat = false;
|
||||
|
||||
/**
|
||||
* Creates listener to count queries.
|
||||
*
|
||||
* @see ProxyDataSourceBuilder#countQuery()
|
||||
* @see QueryCountHolder
|
||||
*/
|
||||
private boolean countQuery = false;
|
||||
|
||||
public DataSourceProxyLogging getLogging() {
|
||||
return logging;
|
||||
}
|
||||
|
||||
public void setLogging(DataSourceProxyLogging logging) {
|
||||
this.logging = logging;
|
||||
}
|
||||
|
||||
public Query getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
public void setQuery(Query query) {
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
public SlowQuery getSlowQuery() {
|
||||
return slowQuery;
|
||||
}
|
||||
|
||||
public void setSlowQuery(SlowQuery slowQuery) {
|
||||
this.slowQuery = slowQuery;
|
||||
}
|
||||
|
||||
public boolean isMultiline() {
|
||||
return multiline;
|
||||
}
|
||||
|
||||
public void setMultiline(boolean multiline) {
|
||||
this.multiline = multiline;
|
||||
}
|
||||
|
||||
public boolean isJsonFormat() {
|
||||
return jsonFormat;
|
||||
}
|
||||
|
||||
public void setJsonFormat(boolean jsonFormat) {
|
||||
this.jsonFormat = jsonFormat;
|
||||
}
|
||||
|
||||
public boolean isCountQuery() {
|
||||
return countQuery;
|
||||
}
|
||||
|
||||
public void setCountQuery(boolean countQuery) {
|
||||
this.countQuery = countQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Properties to configure query logging listener.
|
||||
*/
|
||||
public static class Query {
|
||||
|
||||
/**
|
||||
* Enable logging all queries to the log.
|
||||
*/
|
||||
private boolean enableLogging = true;
|
||||
|
||||
/**
|
||||
* Name of query logger.
|
||||
*/
|
||||
private String loggerName;
|
||||
|
||||
/**
|
||||
* Severity of query logger.
|
||||
*/
|
||||
private String logLevel = "DEBUG";
|
||||
|
||||
public boolean isEnableLogging() {
|
||||
return enableLogging;
|
||||
}
|
||||
|
||||
public void setEnableLogging(boolean enableLogging) {
|
||||
this.enableLogging = enableLogging;
|
||||
}
|
||||
|
||||
public String getLoggerName() {
|
||||
return loggerName;
|
||||
}
|
||||
|
||||
public void setLoggerName(String loggerName) {
|
||||
this.loggerName = loggerName;
|
||||
}
|
||||
|
||||
public String getLogLevel() {
|
||||
return logLevel;
|
||||
}
|
||||
|
||||
public void setLogLevel(String logLevel) {
|
||||
this.logLevel = logLevel;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Properties to configure slow query logging listener.
|
||||
*/
|
||||
public static class SlowQuery {
|
||||
|
||||
/**
|
||||
* Enable logging slow queries to the log.
|
||||
*/
|
||||
private boolean enableLogging = true;
|
||||
|
||||
/**
|
||||
* Name of slow query logger.
|
||||
*/
|
||||
private String loggerName;
|
||||
|
||||
/**
|
||||
* Severity of slow query logger.
|
||||
*/
|
||||
private String logLevel = "WARN";
|
||||
|
||||
/**
|
||||
* Number of seconds to consider query as slow.
|
||||
*/
|
||||
private long threshold = 300;
|
||||
|
||||
boolean isEnableLogging() {
|
||||
return enableLogging;
|
||||
}
|
||||
|
||||
public void setEnableLogging(boolean enableLogging) {
|
||||
this.enableLogging = enableLogging;
|
||||
}
|
||||
|
||||
public String getLoggerName() {
|
||||
return loggerName;
|
||||
}
|
||||
|
||||
public void setLoggerName(String loggerName) {
|
||||
this.loggerName = loggerName;
|
||||
}
|
||||
|
||||
public String getLogLevel() {
|
||||
return logLevel;
|
||||
}
|
||||
|
||||
public void setLogLevel(String logLevel) {
|
||||
this.logLevel = logLevel;
|
||||
}
|
||||
|
||||
public long getThreshold() {
|
||||
return threshold;
|
||||
}
|
||||
|
||||
public void setThreshold(long threshold) {
|
||||
this.threshold = threshold;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Query logging listener is the most used listener that logs executing query with
|
||||
* actual parameters to. You can pick one of the following proxy logging
|
||||
* mechanisms.
|
||||
*/
|
||||
public enum DataSourceProxyLogging {
|
||||
|
||||
/**
|
||||
* Log using System.out.
|
||||
*/
|
||||
SYSOUT,
|
||||
|
||||
/**
|
||||
* Log using SLF4J.
|
||||
*/
|
||||
SLF4J,
|
||||
|
||||
/**
|
||||
* Log using Commons.
|
||||
*/
|
||||
COMMONS,
|
||||
|
||||
/**
|
||||
* Log using Java Util Logging.
|
||||
*/
|
||||
JUL
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Properties for configuring p6spy.
|
||||
*/
|
||||
public static class P6SpyProperties {
|
||||
|
||||
/**
|
||||
* Enables logging JDBC events.
|
||||
*/
|
||||
private boolean enableLogging = true;
|
||||
|
||||
/**
|
||||
* Enables multiline output.
|
||||
*/
|
||||
private boolean multiline = true;
|
||||
|
||||
/**
|
||||
* Logging to use for logging queries.
|
||||
*/
|
||||
private P6SpyLogging logging = P6SpyLogging.SLF4J;
|
||||
|
||||
/**
|
||||
* Name of log file to use (only with logging=file).
|
||||
*/
|
||||
private String logFile = "spy.log";
|
||||
|
||||
/**
|
||||
* Custom log format.
|
||||
*/
|
||||
private String logFormat;
|
||||
|
||||
/**
|
||||
* Tracing related properties.
|
||||
*/
|
||||
private P6SpyTracing tracing = new P6SpyTracing();
|
||||
|
||||
/**
|
||||
* Class file to use (only with logging=custom). The class must implement
|
||||
* {@link com.p6spy.engine.spy.appender.FormattedLogger}.
|
||||
*/
|
||||
private String customAppenderClass;
|
||||
|
||||
/**
|
||||
* Log filtering related properties.
|
||||
*/
|
||||
private P6SpyLogFilter logFilter = new P6SpyLogFilter();
|
||||
|
||||
public boolean isEnableLogging() {
|
||||
return enableLogging;
|
||||
}
|
||||
|
||||
public void setEnableLogging(boolean enableLogging) {
|
||||
this.enableLogging = enableLogging;
|
||||
}
|
||||
|
||||
public boolean isMultiline() {
|
||||
return multiline;
|
||||
}
|
||||
|
||||
public void setMultiline(boolean multiline) {
|
||||
this.multiline = multiline;
|
||||
}
|
||||
|
||||
public P6SpyLogging getLogging() {
|
||||
return logging;
|
||||
}
|
||||
|
||||
public void setLogging(P6SpyLogging logging) {
|
||||
this.logging = logging;
|
||||
}
|
||||
|
||||
public String getLogFile() {
|
||||
return logFile;
|
||||
}
|
||||
|
||||
public void setLogFile(String logFile) {
|
||||
this.logFile = logFile;
|
||||
}
|
||||
|
||||
public String getLogFormat() {
|
||||
return logFormat;
|
||||
}
|
||||
|
||||
public void setLogFormat(String logFormat) {
|
||||
this.logFormat = logFormat;
|
||||
}
|
||||
|
||||
public P6SpyTracing getTracing() {
|
||||
return tracing;
|
||||
}
|
||||
|
||||
public void setTracing(P6SpyTracing tracing) {
|
||||
this.tracing = tracing;
|
||||
}
|
||||
|
||||
public String getCustomAppenderClass() {
|
||||
return customAppenderClass;
|
||||
}
|
||||
|
||||
public void setCustomAppenderClass(String customAppenderClass) {
|
||||
this.customAppenderClass = customAppenderClass;
|
||||
}
|
||||
|
||||
public P6SpyLogFilter getLogFilter() {
|
||||
return logFilter;
|
||||
}
|
||||
|
||||
public void setLogFilter(P6SpyLogFilter logFilter) {
|
||||
this.logFilter = logFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* P6Spy logging options.
|
||||
*/
|
||||
public enum P6SpyLogging {
|
||||
|
||||
/**
|
||||
* Log using System.out.
|
||||
*/
|
||||
SYSOUT,
|
||||
|
||||
/**
|
||||
* Log using SLF4J.
|
||||
*/
|
||||
SLF4J,
|
||||
|
||||
/**
|
||||
* Log to file.
|
||||
*/
|
||||
FILE,
|
||||
|
||||
/**
|
||||
* Custom logging.
|
||||
*/
|
||||
CUSTOM
|
||||
|
||||
}
|
||||
|
||||
public static class P6SpyTracing {
|
||||
|
||||
/**
|
||||
* Report the effective sql string (with '?' replaced with real values) to
|
||||
* tracing systems.
|
||||
* <p>
|
||||
* NOTE this setting does not affect the logging message.
|
||||
*/
|
||||
private boolean includeParameterValues = true;
|
||||
|
||||
public boolean isIncludeParameterValues() {
|
||||
return includeParameterValues;
|
||||
}
|
||||
|
||||
public void setIncludeParameterValues(boolean includeParameterValues) {
|
||||
this.includeParameterValues = includeParameterValues;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class P6SpyLogFilter {
|
||||
|
||||
/**
|
||||
* Use regex pattern to filter log messages. Only matched messages will be
|
||||
* logged.
|
||||
*/
|
||||
private Pattern pattern;
|
||||
|
||||
public Pattern getPattern() {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
public void setPattern(Pattern pattern) {
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,6 +9,7 @@ org.springframework.cloud.sleuth.autoconfig.instrument.batch.TraceBatchAutoConfi
|
||||
org.springframework.cloud.sleuth.autoconfig.instrument.config.TraceSpringCloudConfigAutoConfiguration,\
|
||||
org.springframework.cloud.sleuth.autoconfig.instrument.circuitbreaker.TraceCircuitBreakerAutoConfiguration,\
|
||||
org.springframework.cloud.sleuth.autoconfig.instrument.deployer.TraceDeployerAutoConfiguration,\
|
||||
org.springframework.cloud.sleuth.autoconfig.instrument.jdbc.TraceDataSourceDecoratorAutoConfiguration,\
|
||||
org.springframework.cloud.sleuth.autoconfig.instrument.rxjava.TraceRxJavaAutoConfiguration,\
|
||||
org.springframework.cloud.sleuth.autoconfig.instrument.quartz.TraceQuartzAutoConfiguration,\
|
||||
org.springframework.cloud.sleuth.autoconfig.instrument.task.TraceTaskAutoConfiguration,\
|
||||
|
||||
@@ -0,0 +1,370 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.sleuth.autoconfig.instrument.jdbc;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.sql.Connection;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import com.p6spy.engine.spy.P6DataSource;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import net.ttddyy.dsproxy.support.ProxyDataSource;
|
||||
import org.apache.commons.dbcp2.BasicDataSource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.sleuth.autoconfig.brave.BraveAutoConfiguration;
|
||||
import org.springframework.cloud.sleuth.instrument.jdbc.DataSourceDecorator;
|
||||
import org.springframework.cloud.sleuth.instrument.jdbc.DataSourceWrapper;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.context.annotation.ScopedProxyMode;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class DataSourceDecoratorAutoConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class,
|
||||
TraceDataSourceDecoratorAutoConfiguration.class, BraveAutoConfiguration.class,
|
||||
TestSpanHandlerConfiguration.class, PropertyPlaceholderAutoConfiguration.class))
|
||||
.withPropertyValues("spring.datasource.initialization-mode=never",
|
||||
"spring.datasource.url:jdbc:h2:mem:testdb-" + ThreadLocalRandom.current().nextInt());
|
||||
|
||||
@Test
|
||||
void testDecoratingInDefaultOrder() {
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
assertThat(dataSource).isInstanceOf(DataSourceWrapper.class);
|
||||
|
||||
DataSourceWrapper DataSourceWrapper = (DataSourceWrapper) dataSource;
|
||||
assertThat(DataSourceWrapper.getDecoratedDataSource()).isInstanceOf(P6DataSource.class);
|
||||
P6DataSource p6DataSource = (P6DataSource) DataSourceWrapper.getDecoratedDataSource();
|
||||
|
||||
DataSource p6WrappedDataSource = (DataSource) ReflectionTestUtils.getField(p6DataSource, "realDataSource");
|
||||
assertThat(p6WrappedDataSource).isInstanceOf(ProxyDataSource.class);
|
||||
ProxyDataSource proxyDataSource = (ProxyDataSource) p6WrappedDataSource;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNoDecoratingForExcludeBeans() {
|
||||
ApplicationContextRunner contextRunner = this.contextRunner
|
||||
.withPropertyValues("spring.sleuth.jdbc.decorator.datasource.excluded-beans:dataSource");
|
||||
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
|
||||
assertThat(dataSource).isInstanceOf(HikariDataSource.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDecoratingWhenDefaultProxyProviderNotAvailable() {
|
||||
ApplicationContextRunner contextRunner = this.contextRunner;
|
||||
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
|
||||
assertThat(((DataSourceWrapper) dataSource).getOriginalDataSource()).isInstanceOf(HikariDataSource.class);
|
||||
|
||||
DataSourceWrapper DataSourceWrapper = (DataSourceWrapper) dataSource;
|
||||
assertThat(DataSourceWrapper.getDecoratedDataSource()).isInstanceOf(P6DataSource.class);
|
||||
P6DataSource p6DataSource = (P6DataSource) DataSourceWrapper.getDecoratedDataSource();
|
||||
|
||||
DataSource p6WrappedDataSource = (DataSource) ReflectionTestUtils.getField(p6DataSource, "realDataSource");
|
||||
assertThat(p6WrappedDataSource).isInstanceOf(ProxyDataSource.class);
|
||||
ProxyDataSource proxyDataSource = (ProxyDataSource) p6WrappedDataSource;
|
||||
|
||||
DataSource dsProxyWrappedDataSource = (DataSource) ReflectionTestUtils.getField(proxyDataSource,
|
||||
"dataSource");
|
||||
assertThat(dsProxyWrappedDataSource).isEqualTo(DataSourceWrapper.getOriginalDataSource());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDecoratedHikariSpecificPropertiesIsSet() {
|
||||
ApplicationContextRunner contextRunner = this.contextRunner.withPropertyValues(
|
||||
"spring.datasource.type:" + HikariDataSource.class.getName(),
|
||||
"spring.datasource.hikari.catalog:test_catalog");
|
||||
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
assertThat(dataSource).isNotNull();
|
||||
assertThat(dataSource).isInstanceOf(DataSourceWrapper.class);
|
||||
DataSource realDataSource = ((DataSourceWrapper) dataSource).getOriginalDataSource();
|
||||
assertThat(realDataSource).isInstanceOf(HikariDataSource.class);
|
||||
assertThat(((HikariDataSource) realDataSource).getCatalog()).isEqualTo("test_catalog");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCustomDataSourceIsDecorated() {
|
||||
ApplicationContextRunner contextRunner = this.contextRunner
|
||||
.withUserConfiguration(TestDataSourceConfiguration.class);
|
||||
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
assertThat(dataSource).isInstanceOf(DataSourceWrapper.class);
|
||||
DataSource realDataSource = ((DataSourceWrapper) dataSource).getOriginalDataSource();
|
||||
assertThat(realDataSource).isInstanceOf(BasicDataSource.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testScopedDataSourceIsNotDecorated() {
|
||||
ApplicationContextRunner contextRunner = this.contextRunner
|
||||
.withUserConfiguration(TestScopedDataSourceConfiguration.class);
|
||||
|
||||
contextRunner.run(context -> {
|
||||
assertThat(context).getBeanNames(DataSource.class).containsOnly("dataSource", "scopedTarget.dataSource");
|
||||
assertThat(context).getBean("dataSource").isInstanceOf(DataSourceWrapper.class);
|
||||
assertThat(context).getBean("scopedTarget.dataSource").isNotInstanceOf(DataSourceWrapper.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCustomDataSourceDecoratorApplied() {
|
||||
ApplicationContextRunner contextRunner = this.contextRunner
|
||||
.withUserConfiguration(TestDataSourceDecoratorConfiguration.class);
|
||||
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
assertThat(dataSource).isNotNull();
|
||||
|
||||
DataSource customDataSource = ((DataSourceWrapper) dataSource).getDecoratedDataSource();
|
||||
assertThat(customDataSource).isInstanceOf(CustomDataSourceProxy.class);
|
||||
|
||||
DataSource realDataSource = ((DataSourceWrapper) dataSource).getOriginalDataSource();
|
||||
assertThat(realDataSource).isInstanceOf(HikariDataSource.class);
|
||||
|
||||
assertThat(dataSource).isInstanceOf(DataSourceWrapper.class);
|
||||
|
||||
DataSourceWrapper DataSourceWrapper = (DataSourceWrapper) dataSource;
|
||||
|
||||
assertThat(DataSourceWrapper.getDecoratedDataSource()).isInstanceOf(CustomDataSourceProxy.class);
|
||||
CustomDataSourceProxy customDataSourceProxy = (CustomDataSourceProxy) DataSourceWrapper
|
||||
.getDecoratedDataSource();
|
||||
|
||||
assertThat(customDataSourceProxy.delegate).isInstanceOf(P6DataSource.class);
|
||||
P6DataSource p6DataSource = (P6DataSource) customDataSourceProxy.delegate;
|
||||
|
||||
DataSource p6WrappedDataSource = (DataSource) ReflectionTestUtils.getField(p6DataSource, "realDataSource");
|
||||
assertThat(p6WrappedDataSource).isInstanceOf(ProxyDataSource.class);
|
||||
ProxyDataSource proxyDataSource = (ProxyDataSource) p6WrappedDataSource;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDecoratingCanBeDisabled() {
|
||||
ApplicationContextRunner contextRunner = this.contextRunner
|
||||
.withPropertyValues("spring.sleuth.jdbc.decorator.datasource.enabled:false");
|
||||
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
assertThat(dataSource).isInstanceOf(HikariDataSource.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDecoratingCanBeDisabledForSpecificBeans() {
|
||||
ApplicationContextRunner contextRunner = this.contextRunner
|
||||
.withPropertyValues("spring.sleuth.jdbc.decorator.datasource.excluded-beans:secondDataSource")
|
||||
.withUserConfiguration(TestMultiDataSourceConfiguration.class);
|
||||
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean("dataSource", DataSource.class);
|
||||
assertThat(dataSource).isInstanceOf(DataSourceWrapper.class);
|
||||
|
||||
DataSource secondDataSource = context.getBean("secondDataSource", DataSource.class);
|
||||
assertThat(secondDataSource).isInstanceOf(BasicDataSource.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDecoratingChainBuiltCorrectly() {
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
|
||||
DataSourceWrapper dataSource1 = context.getBean(DataSourceWrapper.class);
|
||||
assertThat(dataSource1).isNotNull();
|
||||
|
||||
DataSource p6DataSource = dataSource1.getDecoratedDataSource();
|
||||
assertThat(p6DataSource).isNotNull();
|
||||
assertThat(p6DataSource).isInstanceOf(P6DataSource.class);
|
||||
|
||||
DataSource proxyDataSource = (DataSource) new DirectFieldAccessor(p6DataSource)
|
||||
.getPropertyValue("realDataSource");
|
||||
assertThat(proxyDataSource).isNotNull();
|
||||
assertThat(proxyDataSource).isInstanceOf(ProxyDataSource.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDecorateDynamicallyRegisteredBeans() {
|
||||
ApplicationContextRunner contextRunner = this.contextRunner.withInitializer(context -> {
|
||||
GenericApplicationContext gac = (GenericApplicationContext) context;
|
||||
gac.registerBean("ds1", DataSource.class, () -> new HikariDataSource());
|
||||
gac.registerBean("ds2", DataSource.class, () -> new HikariDataSource());
|
||||
});
|
||||
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource1 = context.getBean("ds1", DataSource.class);
|
||||
assertThat(dataSource1).isNotNull();
|
||||
assertThat(dataSource1).isInstanceOf(DataSourceWrapper.class);
|
||||
|
||||
DataSource dataSource2 = context.getBean("ds2", DataSource.class);
|
||||
assertThat(dataSource2).isNotNull();
|
||||
assertThat(dataSource2).isInstanceOf(DataSourceWrapper.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class TestDataSourceConfiguration {
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
BasicDataSource pool = new BasicDataSource();
|
||||
pool.setDriverClassName("org.hsqldb.jdbcDriver");
|
||||
pool.setUrl("jdbc:hsqldb:target/overridedb");
|
||||
pool.setUsername("sa");
|
||||
return pool;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class TestDataSourceDecoratorConfiguration {
|
||||
|
||||
@Bean
|
||||
public DataSourceDecorator customDataSourceDecorator() {
|
||||
return (beanName, dataSource) -> new CustomDataSourceProxy(dataSource);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class TestMultiDataSourceConfiguration {
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
public DataSource dataSource() {
|
||||
BasicDataSource pool = new BasicDataSource();
|
||||
pool.setDriverClassName("org.hsqldb.jdbcDriver");
|
||||
pool.setUrl("jdbc:hsqldb:target/db");
|
||||
pool.setUsername("sa");
|
||||
return pool;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DataSource secondDataSource() {
|
||||
BasicDataSource pool = new BasicDataSource();
|
||||
pool.setDriverClassName("org.hsqldb.jdbcDriver");
|
||||
pool.setUrl("jdbc:hsqldb:target/db2");
|
||||
pool.setUsername("sa");
|
||||
return pool;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class TestScopedDataSourceConfiguration {
|
||||
|
||||
@Bean
|
||||
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
|
||||
public DataSource dataSource() {
|
||||
BasicDataSource pool = new BasicDataSource();
|
||||
pool.setDriverClassName("org.hsqldb.jdbcDriver");
|
||||
pool.setUrl("jdbc:hsqldb:target/overridedb");
|
||||
pool.setUsername("sa");
|
||||
return pool;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom proxy data source for tests.
|
||||
*
|
||||
* @author Arthur Gavlyukovskiy
|
||||
*/
|
||||
static class CustomDataSourceProxy implements DataSource {
|
||||
|
||||
private final DataSource delegate;
|
||||
|
||||
CustomDataSourceProxy(DataSource delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getConnection() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getConnection(String username, String password) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T unwrap(Class<T> iface) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWrapperFor(Class<?> iface) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrintWriter getLogWriter() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLogWriter(PrintWriter out) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLoginTimeout(int seconds) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLoginTimeout() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Logger getParentLogger() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.sleuth.autoconfig.instrument.jdbc;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import com.p6spy.engine.common.ConnectionInformation;
|
||||
import com.p6spy.engine.common.P6LogQuery;
|
||||
import com.p6spy.engine.event.CompoundJdbcEventListener;
|
||||
import com.p6spy.engine.event.JdbcEventListener;
|
||||
import com.p6spy.engine.logging.Category;
|
||||
import com.p6spy.engine.logging.LoggingEventListener;
|
||||
import com.p6spy.engine.spy.JdbcEventListenerFactory;
|
||||
import com.p6spy.engine.spy.P6DataSource;
|
||||
import com.p6spy.engine.spy.appender.CustomLineFormat;
|
||||
import com.p6spy.engine.spy.appender.FormattedLogger;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.sleuth.autoconfig.brave.BraveAutoConfiguration;
|
||||
import org.springframework.cloud.sleuth.instrument.jdbc.DataSourceWrapper;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class P6SpyConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class,
|
||||
TraceDataSourceDecoratorAutoConfiguration.class, BraveAutoConfiguration.class,
|
||||
TestSpanHandlerConfiguration.class, PropertyPlaceholderAutoConfiguration.class))
|
||||
.withPropertyValues("spring.datasource.initialization-mode=never",
|
||||
"spring.datasource.url:jdbc:h2:mem:testdb-" + ThreadLocalRandom.current().nextInt())
|
||||
.withClassLoader(new FilteredClassLoader("net.ttddyy.dsproxy"));
|
||||
|
||||
@BeforeEach
|
||||
@AfterEach
|
||||
void resetLogAccumulator() {
|
||||
LogAccumulator.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCustomListeners() {
|
||||
ApplicationContextRunner contextRunner = this.contextRunner
|
||||
.withUserConfiguration(CustomListenerConfiguration.class);
|
||||
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
JdbcEventListenerFactory jdbcEventListenerFactory = context.getBean(JdbcEventListenerFactory.class);
|
||||
GetCountingListener getCountingListener = context.getBean(GetCountingListener.class);
|
||||
ClosingCountingListener closingCountingListener = context.getBean(ClosingCountingListener.class);
|
||||
P6DataSource p6DataSource = (P6DataSource) ((DataSourceWrapper) dataSource).getDecoratedDataSource();
|
||||
assertThat(p6DataSource).extracting("jdbcEventListenerFactory").isEqualTo(jdbcEventListenerFactory);
|
||||
|
||||
CompoundJdbcEventListener jdbcEventListener = (CompoundJdbcEventListener) jdbcEventListenerFactory
|
||||
.createJdbcEventListener();
|
||||
|
||||
assertThat(jdbcEventListener.getEventListeners()).contains(getCountingListener, closingCountingListener);
|
||||
assertThat(getCountingListener.connectionCount).isEqualTo(0);
|
||||
|
||||
Connection connection1 = p6DataSource.getConnection();
|
||||
|
||||
assertThat(getCountingListener.connectionCount).isEqualTo(1);
|
||||
assertThat(closingCountingListener.connectionCount).isEqualTo(0);
|
||||
|
||||
Connection connection2 = p6DataSource.getConnection();
|
||||
|
||||
assertThat(getCountingListener.connectionCount).isEqualTo(2);
|
||||
|
||||
connection1.close();
|
||||
|
||||
assertThat(closingCountingListener.connectionCount).isEqualTo(1);
|
||||
|
||||
connection2.close();
|
||||
|
||||
assertThat(closingCountingListener.connectionCount).isEqualTo(2);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDoesNotRegisterLoggingListenerIfDisabled() {
|
||||
ApplicationContextRunner contextRunner = this.contextRunner
|
||||
.withPropertyValues("spring.sleuth.jdbc.decorator.datasource.p6spy.enable-logging:false");
|
||||
|
||||
contextRunner.run(context -> {
|
||||
JdbcEventListenerFactory jdbcEventListenerFactory = context.getBean(JdbcEventListenerFactory.class);
|
||||
CompoundJdbcEventListener jdbcEventListener = (CompoundJdbcEventListener) jdbcEventListenerFactory
|
||||
.createJdbcEventListener();
|
||||
|
||||
assertThat(jdbcEventListener.getEventListeners()).extracting("class")
|
||||
.doesNotContain(LoggingEventListener.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCanSetCustomLoggingFormat() {
|
||||
ApplicationContextRunner contextRunner = this.contextRunner
|
||||
.withPropertyValues("spring.sleuth.jdbc.decorator.datasource.p6spy.log-format:test %{connectionId}");
|
||||
|
||||
contextRunner.run(context -> {
|
||||
JdbcEventListenerFactory jdbcEventListenerFactory = context.getBean(JdbcEventListenerFactory.class);
|
||||
CompoundJdbcEventListener jdbcEventListener = (CompoundJdbcEventListener) jdbcEventListenerFactory
|
||||
.createJdbcEventListener();
|
||||
|
||||
assertThat(jdbcEventListener.getEventListeners()).extracting("class").contains(LoggingEventListener.class);
|
||||
assertThat(P6LogQuery.getLogger()).extracting("strategy").extracting("class")
|
||||
.isEqualTo(CustomLineFormat.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMultilineShouldNotOverrideCustomProperties() {
|
||||
System.setProperty("p6spy.config.logMessageFormat", "com.p6spy.engine.spy.appender.CustomLineFormat");
|
||||
System.setProperty("p6spy.config.excludecategories", "debug");
|
||||
ApplicationContextRunner contextRunner = this.contextRunner
|
||||
.withPropertyValues("spring.sleuth.jdbc.decorator.datasource.p6spy.multiline:true");
|
||||
|
||||
contextRunner.run(context -> {
|
||||
JdbcEventListenerFactory jdbcEventListenerFactory = context.getBean(JdbcEventListenerFactory.class);
|
||||
CompoundJdbcEventListener jdbcEventListener = (CompoundJdbcEventListener) jdbcEventListenerFactory
|
||||
.createJdbcEventListener();
|
||||
|
||||
assertThat(jdbcEventListener.getEventListeners()).extracting("class").contains(LoggingEventListener.class);
|
||||
assertThat(P6LogQuery.getLogger()).extracting("strategy").extracting("class")
|
||||
.isEqualTo(CustomLineFormat.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUseCustomLogger() {
|
||||
ApplicationContextRunner contextRunner = this.contextRunner.withPropertyValues(
|
||||
"spring.sleuth.jdbc.decorator.datasource.p6spy.logging:custom",
|
||||
"spring.sleuth.jdbc.decorator.datasource.p6spy.custom-appender-class:"
|
||||
+ LogAccumulator.class.getName());
|
||||
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
dataSource.getConnection().close();
|
||||
|
||||
assertThat(P6LogQuery.getLogger()).isInstanceOf(LogAccumulator.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLogFilterPattern() {
|
||||
ApplicationContextRunner contextRunner = this.contextRunner.withPropertyValues(
|
||||
"spring.sleuth.jdbc.decorator.datasource.p6spy.logging:custom",
|
||||
"spring.sleuth.jdbc.decorator.datasource.p6spy.custom-appender-class:" + LogAccumulator.class.getName(),
|
||||
"spring.sleuth.jdbc.decorator.datasource.p6spy.log-filter.pattern:.*table1.*");
|
||||
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
try (Connection connection = dataSource.getConnection();
|
||||
PreparedStatement ps1 = connection.prepareStatement("select 1 /* from table1 */");
|
||||
PreparedStatement ps2 = connection.prepareStatement("select 1 /* from table2 */")) {
|
||||
ps1.execute();
|
||||
ps2.execute();
|
||||
}
|
||||
|
||||
assertThat(LogAccumulator.MESSAGES).hasSize(1);
|
||||
assertThat(LogAccumulator.MESSAGES).allMatch(message -> message.contains("table1"));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLogFilterPatternMatchAll() {
|
||||
ApplicationContextRunner contextRunner = this.contextRunner.withPropertyValues(
|
||||
"spring.sleuth.jdbc.decorator.datasource.p6spy.logging:custom",
|
||||
"spring.sleuth.jdbc.decorator.datasource.p6spy.custom-appender-class:" + LogAccumulator.class.getName(),
|
||||
"spring.sleuth.jdbc.decorator.datasource.p6spy.log-filter.pattern:.*");
|
||||
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
try (Connection connection = dataSource.getConnection();
|
||||
PreparedStatement ps1 = connection.prepareStatement("select 1 /* from table1 */");
|
||||
PreparedStatement ps2 = connection.prepareStatement("select 1 /* from table2 */")) {
|
||||
ps1.execute();
|
||||
ps2.execute();
|
||||
}
|
||||
|
||||
assertThat(LogAccumulator.MESSAGES).hasSize(2);
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class CustomListenerConfiguration {
|
||||
|
||||
@Bean
|
||||
public GetCountingListener wrappingCountingListener() {
|
||||
return new GetCountingListener();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ClosingCountingListener closingCountingListener() {
|
||||
return new ClosingCountingListener();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class GetCountingListener extends JdbcEventListener {
|
||||
|
||||
int connectionCount = 0;
|
||||
|
||||
@Override
|
||||
public void onAfterGetConnection(ConnectionInformation connectionInformation, SQLException e) {
|
||||
connectionCount++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class ClosingCountingListener extends JdbcEventListener {
|
||||
|
||||
int connectionCount = 0;
|
||||
|
||||
@Override
|
||||
public void onAfterConnectionClose(ConnectionInformation connectionInformation, SQLException e) {
|
||||
connectionCount++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class LogAccumulator extends FormattedLogger {
|
||||
|
||||
static final List<String> MESSAGES = new ArrayList<>();
|
||||
static final List<Exception> EXCEPTIONS = new ArrayList<>();
|
||||
|
||||
public static void reset() {
|
||||
MESSAGES.clear();
|
||||
EXCEPTIONS.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logException(Exception e) {
|
||||
EXCEPTIONS.add(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logText(String text) {
|
||||
MESSAGES.add(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCategoryEnabled(Category category) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.sleuth.autoconfig.instrument.jdbc;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import net.ttddyy.dsproxy.ExecutionInfo;
|
||||
import net.ttddyy.dsproxy.QueryInfo;
|
||||
import net.ttddyy.dsproxy.listener.ChainListener;
|
||||
import net.ttddyy.dsproxy.listener.QueryExecutionListener;
|
||||
import net.ttddyy.dsproxy.listener.logging.CommonsQueryLoggingListener;
|
||||
import net.ttddyy.dsproxy.listener.logging.CommonsSlowQueryListener;
|
||||
import net.ttddyy.dsproxy.listener.logging.JULQueryLoggingListener;
|
||||
import net.ttddyy.dsproxy.listener.logging.JULSlowQueryListener;
|
||||
import net.ttddyy.dsproxy.listener.logging.SLF4JQueryLoggingListener;
|
||||
import net.ttddyy.dsproxy.listener.logging.SLF4JSlowQueryListener;
|
||||
import net.ttddyy.dsproxy.listener.logging.SystemOutQueryLoggingListener;
|
||||
import net.ttddyy.dsproxy.listener.logging.SystemOutSlowQueryListener;
|
||||
import net.ttddyy.dsproxy.proxy.DefaultConnectionIdManager;
|
||||
import net.ttddyy.dsproxy.proxy.GlobalConnectionIdManager;
|
||||
import net.ttddyy.dsproxy.support.ProxyDataSource;
|
||||
import net.ttddyy.dsproxy.transform.ParameterTransformer;
|
||||
import net.ttddyy.dsproxy.transform.QueryTransformer;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.sleuth.autoconfig.brave.BraveAutoConfiguration;
|
||||
import org.springframework.cloud.sleuth.instrument.jdbc.DataSourceProxyConnectionIdManagerProvider;
|
||||
import org.springframework.cloud.sleuth.instrument.jdbc.DataSourceWrapper;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class ProxyDataSourceConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class,
|
||||
TraceDataSourceDecoratorAutoConfiguration.class, BraveAutoConfiguration.class,
|
||||
TestSpanHandlerConfiguration.class, PropertyPlaceholderAutoConfiguration.class))
|
||||
.withPropertyValues("spring.datasource.initialization-mode=never",
|
||||
"spring.datasource.url:jdbc:h2:mem:testdb-" + ThreadLocalRandom.current().nextInt())
|
||||
.withClassLoader(new FilteredClassLoader("com.p6spy"));
|
||||
|
||||
@Test
|
||||
void testRegisterLogAndSlowQueryLogByDefaultToSlf4j() {
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
ProxyDataSource proxyDataSource = (ProxyDataSource) ((DataSourceWrapper) dataSource)
|
||||
.getDecoratedDataSource();
|
||||
ChainListener chainListener = proxyDataSource.getProxyConfig().getQueryListener();
|
||||
assertThat(chainListener.getListeners()).extracting("class").contains(SLF4JSlowQueryListener.class);
|
||||
assertThat(chainListener.getListeners()).extracting("class").contains(SLF4JQueryLoggingListener.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRegisterLogAndSlowQueryLogByUsingSlf4j() {
|
||||
ApplicationContextRunner contextRunner = this.contextRunner
|
||||
.withPropertyValues("spring.sleuth.jdbc.decorator.datasource.datasource-proxy.logging:slf4j");
|
||||
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
ProxyDataSource proxyDataSource = (ProxyDataSource) ((DataSourceWrapper) dataSource)
|
||||
.getDecoratedDataSource();
|
||||
ChainListener chainListener = proxyDataSource.getProxyConfig().getQueryListener();
|
||||
assertThat(chainListener.getListeners()).extracting("class").contains(SLF4JSlowQueryListener.class);
|
||||
assertThat(chainListener.getListeners()).extracting("class").contains(SLF4JQueryLoggingListener.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRegisterLogAndSlowQueryLogUsingSystemOut() {
|
||||
ApplicationContextRunner contextRunner = this.contextRunner
|
||||
.withPropertyValues("spring.sleuth.jdbc.decorator.datasource.datasource-proxy.logging:sysout");
|
||||
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
ProxyDataSource proxyDataSource = (ProxyDataSource) ((DataSourceWrapper) dataSource)
|
||||
.getDecoratedDataSource();
|
||||
ChainListener chainListener = proxyDataSource.getProxyConfig().getQueryListener();
|
||||
assertThat(chainListener.getListeners()).extracting("class").contains(SystemOutSlowQueryListener.class);
|
||||
assertThat(chainListener.getListeners()).extracting("class").contains(SystemOutQueryLoggingListener.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRegisterLogAndSlowQueryLogUsingJUL() {
|
||||
ApplicationContextRunner contextRunner = this.contextRunner
|
||||
.withPropertyValues("spring.sleuth.jdbc.decorator.datasource.datasourceProxy.logging:jul");
|
||||
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
ProxyDataSource proxyDataSource = (ProxyDataSource) ((DataSourceWrapper) dataSource)
|
||||
.getDecoratedDataSource();
|
||||
ChainListener chainListener = proxyDataSource.getProxyConfig().getQueryListener();
|
||||
assertThat(chainListener.getListeners()).extracting("class").contains(JULSlowQueryListener.class);
|
||||
assertThat(chainListener.getListeners()).extracting("class").contains(JULQueryLoggingListener.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRegisterLogAndSlowQueryLogUsingApacheCommons() {
|
||||
ApplicationContextRunner contextRunner = this.contextRunner
|
||||
.withPropertyValues("spring.sleuth.jdbc.decorator.datasource.datasourceProxy.logging:commons");
|
||||
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
ProxyDataSource proxyDataSource = (ProxyDataSource) ((DataSourceWrapper) dataSource)
|
||||
.getDecoratedDataSource();
|
||||
ChainListener chainListener = proxyDataSource.getProxyConfig().getQueryListener();
|
||||
assertThat(chainListener.getListeners()).extracting("class").contains(CommonsSlowQueryListener.class);
|
||||
assertThat(chainListener.getListeners()).extracting("class").contains(CommonsQueryLoggingListener.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCustomParameterAndQueryTransformer() {
|
||||
ApplicationContextRunner contextRunner = this.contextRunner
|
||||
.withUserConfiguration(CustomDataSourceProxyConfiguration.class);
|
||||
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
ProxyDataSource proxyDataSource = (ProxyDataSource) ((DataSourceWrapper) dataSource)
|
||||
.getDecoratedDataSource();
|
||||
ParameterTransformer parameterTransformer = context.getBean(ParameterTransformer.class);
|
||||
QueryTransformer queryTransformer = context.getBean(QueryTransformer.class);
|
||||
assertThat(proxyDataSource.getProxyConfig().getParameterTransformer()).isSameAs(parameterTransformer);
|
||||
assertThat(proxyDataSource.getProxyConfig().getQueryTransformer()).isSameAs(queryTransformer);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCustomListeners() {
|
||||
ApplicationContextRunner contextRunner = this.contextRunner
|
||||
.withUserConfiguration(CustomListenerConfiguration.class);
|
||||
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
ProxyDataSource proxyDataSource = (ProxyDataSource) ((DataSourceWrapper) dataSource)
|
||||
.getDecoratedDataSource();
|
||||
QueryExecutionListener queryExecutionListener = context.getBean(QueryExecutionListener.class);
|
||||
|
||||
ChainListener chainListener = proxyDataSource.getProxyConfig().getQueryListener();
|
||||
assertThat(chainListener.getListeners()).contains(queryExecutionListener);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGlobalConnectionIdManagerByDefault() {
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
ProxyDataSource proxyDataSource = (ProxyDataSource) ((DataSourceWrapper) dataSource)
|
||||
.getDecoratedDataSource();
|
||||
|
||||
assertThat(proxyDataSource.getConnectionIdManager()).isInstanceOf(GlobalConnectionIdManager.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCustomConnectionIdManager() {
|
||||
ApplicationContextRunner contextRunner = this.contextRunner
|
||||
.withUserConfiguration(CustomDataSourceProxyConfiguration.class);
|
||||
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
ProxyDataSource proxyDataSource = (ProxyDataSource) ((DataSourceWrapper) dataSource)
|
||||
.getDecoratedDataSource();
|
||||
|
||||
assertThat(proxyDataSource.getConnectionIdManager()).isInstanceOf(DefaultConnectionIdManager.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class CustomDataSourceProxyConfiguration {
|
||||
|
||||
@Bean
|
||||
public ParameterTransformer parameterTransformer() {
|
||||
return (replacer, transformInfo) -> {
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public QueryTransformer queryTransformer() {
|
||||
return (transformInfo) -> "TestQuery";
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DataSourceProxyConnectionIdManagerProvider connectionIdManagerProvider() {
|
||||
return DefaultConnectionIdManager::new;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class CustomListenerConfiguration {
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
public QueryExecutionListener queryExecutionListener() {
|
||||
return new QueryExecutionListener() {
|
||||
@Override
|
||||
public void beforeQuery(ExecutionInfo execInfo, List<QueryInfo> queryInfoList) {
|
||||
System.out.println("beforeQuery");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterQuery(ExecutionInfo execInfo, List<QueryInfo> queryInfoList) {
|
||||
System.out.println("afterQuery");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.sleuth.autoconfig.instrument.jdbc;
|
||||
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import com.p6spy.engine.event.CompoundJdbcEventListener;
|
||||
import com.p6spy.engine.spy.JdbcEventListenerFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.sleuth.autoconfig.brave.BraveAutoConfiguration;
|
||||
import org.springframework.cloud.sleuth.instrument.jdbc.TraceJdbcEventListener;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class SleuthP6SpyListenerAutoConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class,
|
||||
TraceDataSourceDecoratorAutoConfiguration.class, BraveAutoConfiguration.class,
|
||||
TestSpanHandlerConfiguration.class, PropertyPlaceholderAutoConfiguration.class))
|
||||
.withPropertyValues("spring.datasource.initialization-mode=never",
|
||||
"spring.datasource.url:jdbc:h2:mem:testdb-" + ThreadLocalRandom.current().nextInt())
|
||||
.withClassLoader(new FilteredClassLoader("net.ttddyy.dsproxy"));
|
||||
|
||||
@Test
|
||||
void testAddsP6SpyListener() {
|
||||
contextRunner.run(context -> {
|
||||
JdbcEventListenerFactory jdbcEventListenerFactory = context.getBean(JdbcEventListenerFactory.class);
|
||||
CompoundJdbcEventListener jdbcEventListener = (CompoundJdbcEventListener) jdbcEventListenerFactory
|
||||
.createJdbcEventListener();
|
||||
assertThat(jdbcEventListener.getEventListeners()).extracting("class")
|
||||
.contains(TraceJdbcEventListener.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDoesNotAddP6SpyListenerIfNoTracer() {
|
||||
ApplicationContextRunner contextRunner = this.contextRunner.withPropertyValues("spring.sleuth.enabled:false");
|
||||
|
||||
contextRunner.run(context -> {
|
||||
assertThat(context).doesNotHaveBean(JdbcEventListenerFactory.class);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.sleuth.autoconfig.instrument.jdbc;
|
||||
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import net.ttddyy.dsproxy.listener.ChainListener;
|
||||
import net.ttddyy.dsproxy.support.ProxyDataSource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.sleuth.autoconfig.brave.BraveAutoConfiguration;
|
||||
import org.springframework.cloud.sleuth.instrument.jdbc.DataSourceWrapper;
|
||||
import org.springframework.cloud.sleuth.instrument.jdbc.TraceQueryExecutionListener;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class SleuthProxyDataSourceListenerAutoConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class,
|
||||
TraceDataSourceDecoratorAutoConfiguration.class, BraveAutoConfiguration.class,
|
||||
TestSpanHandlerConfiguration.class, PropertyPlaceholderAutoConfiguration.class))
|
||||
.withPropertyValues("spring.datasource.initialization-mode=never",
|
||||
"spring.datasource.url:jdbc:h2:mem:testdb-" + ThreadLocalRandom.current().nextInt())
|
||||
.withClassLoader(new FilteredClassLoader("com.p6spy"));
|
||||
|
||||
@Test
|
||||
void testAddsDatasourceProxyListener() {
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
ProxyDataSource proxyDataSource = (ProxyDataSource) ((DataSourceWrapper) dataSource)
|
||||
.getDecoratedDataSource();
|
||||
ChainListener chainListener = proxyDataSource.getProxyConfig().getQueryListener();
|
||||
assertThat(chainListener.getListeners()).extracting("class").contains(TraceQueryExecutionListener.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDoesNotAddDatasourceProxyListenerIfNoTracer() {
|
||||
ApplicationContextRunner contextRunner = this.contextRunner.withPropertyValues("spring.sleuth.enabled:false");
|
||||
|
||||
contextRunner.run(context -> {
|
||||
assertThat(context).doesNotHaveBean(DataSourceWrapper.class);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.sleuth.autoconfig.instrument.jdbc;
|
||||
|
||||
import brave.sampler.Sampler;
|
||||
import brave.test.TestSpanHandler;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
class TestSpanHandlerConfiguration {
|
||||
|
||||
@Bean
|
||||
public TestSpanHandler spanHandler() {
|
||||
return new TestSpanHandler();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Sampler sampler() {
|
||||
return Sampler.ALWAYS_SAMPLE;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.sleuth.autoconfig.instrument.jdbc;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import brave.handler.MutableSpan;
|
||||
import brave.test.TestSpanHandler;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.sleuth.autoconfig.brave.BraveAutoConfiguration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class TracingJdbcEventListenerTests extends TracingListenerStrategyTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class,
|
||||
TraceDataSourceDecoratorAutoConfiguration.class, BraveAutoConfiguration.class,
|
||||
TestSpanHandlerConfiguration.class, PropertyPlaceholderAutoConfiguration.class))
|
||||
.withPropertyValues("spring.datasource.initialization-mode=never",
|
||||
"spring.datasource.url:jdbc:h2:mem:testdb-baz", "spring.datasource.hikari.pool-name=test")
|
||||
.withClassLoader(new FilteredClassLoader("net.ttddyy.dsproxy"));
|
||||
|
||||
protected TracingJdbcEventListenerTests() {
|
||||
super(new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class,
|
||||
TraceDataSourceDecoratorAutoConfiguration.class, BraveAutoConfiguration.class,
|
||||
TestSpanHandlerConfiguration.class, PropertyPlaceholderAutoConfiguration.class))
|
||||
.withPropertyValues("spring.datasource.initialization-mode=never",
|
||||
"spring.datasource.url:jdbc:h2:mem:testdb-baz", "spring.datasource.hikari.pool-name=test")
|
||||
.withClassLoader(new FilteredClassLoader("net.ttddyy.dsproxy")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldUsePlaceholderInSqlTagOfSpansForPreparedStatementIfIncludeParameterValuesIsSetToFalse() {
|
||||
contextRunner
|
||||
.withPropertyValues(
|
||||
"spring.sleuth.jdbc.decorator.datasource.p6spy.tracing.include-parameter-values=false")
|
||||
.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
TestSpanHandler spanReporter = context.getBean(TestSpanHandler.class);
|
||||
|
||||
Connection connection = dataSource.getConnection();
|
||||
PreparedStatement preparedStatement = connection
|
||||
.prepareStatement("UPDATE INFORMATION_SCHEMA.TABLES SET table_Name = ? WHERE 0 = ?");
|
||||
preparedStatement.setString(1, "");
|
||||
preparedStatement.setInt(2, 1);
|
||||
preparedStatement.executeUpdate();
|
||||
connection.close();
|
||||
|
||||
assertThat(spanReporter.spans()).hasSize(2);
|
||||
MutableSpan connectionSpan = spanReporter.spans().get(1);
|
||||
MutableSpan statementSpan = spanReporter.spans().get(0);
|
||||
assertThat(connectionSpan.name()).isEqualTo("connection");
|
||||
assertThat(statementSpan.name()).isEqualTo("update");
|
||||
assertThat(statementSpan.tags()).containsEntry(SPAN_SQL_QUERY_TAG_NAME,
|
||||
"UPDATE INFORMATION_SCHEMA.TABLES SET table_Name = ? WHERE 0 = ?");
|
||||
assertThat(statementSpan.tags()).containsEntry(SPAN_ROW_COUNT_TAG_NAME, "0");
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,720 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.sleuth.autoconfig.instrument.jdbc;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import brave.handler.MutableSpan;
|
||||
import brave.test.TestSpanHandler;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.sleuth.instrument.jdbc.TraceJdbcEventListener;
|
||||
import org.springframework.cloud.sleuth.instrument.jdbc.TraceQueryExecutionListener;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
abstract class TracingListenerStrategyTests {
|
||||
|
||||
public static final String SPAN_SQL_QUERY_TAG_NAME = "jdbc.query";
|
||||
|
||||
public static final String SPAN_ROW_COUNT_TAG_NAME = "jdbc.row-count";
|
||||
|
||||
protected final ApplicationContextRunner contextRunner;
|
||||
|
||||
protected TracingListenerStrategyTests(ApplicationContextRunner contextRunner) {
|
||||
this.contextRunner = contextRunner;
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldAddSpanForConnection() {
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
TestSpanHandler spanReporter = context.getBean(TestSpanHandler.class);
|
||||
|
||||
Connection connection = dataSource.getConnection();
|
||||
connection.commit();
|
||||
connection.rollback();
|
||||
connection.close();
|
||||
|
||||
assertThat(spanReporter.spans()).hasSize(1);
|
||||
MutableSpan connectionSpan = spanReporter.spans().get(0);
|
||||
assertThat(connectionSpan.name()).isEqualTo("connection");
|
||||
assertThat(connectionSpan.remoteServiceName()).isEqualTo("TESTDB-BAZ");
|
||||
assertThat(connectionSpan.annotations()).extracting("value").contains("jdbc.commit");
|
||||
assertThat(connectionSpan.annotations()).extracting("value").contains("jdbc.rollback");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldAddSpanForConnectionWithFixedRemoteServiceName() {
|
||||
contextRunner.withPropertyValues("spring.datasource.url:jdbc:h2:mem:testdb-baz?sleuthServiceName=aaaabbbb")
|
||||
.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
TestSpanHandler spanReporter = context.getBean(TestSpanHandler.class);
|
||||
|
||||
Connection connection = dataSource.getConnection();
|
||||
connection.commit();
|
||||
connection.rollback();
|
||||
connection.close();
|
||||
|
||||
assertThat(spanReporter.spans()).hasSize(1);
|
||||
MutableSpan connectionSpan = spanReporter.spans().get(0);
|
||||
assertThat(connectionSpan.name()).isEqualTo("connection");
|
||||
assertThat(connectionSpan.remoteServiceName()).isEqualTo("aaaabbbb");
|
||||
assertThat(connectionSpan.annotations()).extracting("value").contains("jdbc.commit");
|
||||
assertThat(connectionSpan.annotations()).extracting("value").contains("jdbc.rollback");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldAddSpanForPreparedStatementExecute() {
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
TestSpanHandler spanReporter = context.getBean(TestSpanHandler.class);
|
||||
|
||||
Connection connection = dataSource.getConnection();
|
||||
connection.prepareStatement("SELECT NOW()").execute();
|
||||
connection.close();
|
||||
|
||||
assertThat(spanReporter.spans()).hasSize(2);
|
||||
MutableSpan connectionSpan = spanReporter.spans().get(1);
|
||||
MutableSpan statementSpan = spanReporter.spans().get(0);
|
||||
assertThat(connectionSpan.name()).isEqualTo("connection");
|
||||
assertThat(statementSpan.name()).isEqualTo("select");
|
||||
assertThat(statementSpan.remoteServiceName()).isEqualTo("TESTDB-BAZ");
|
||||
assertThat(statementSpan.tags()).containsEntry(SPAN_SQL_QUERY_TAG_NAME, "SELECT NOW()");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldAddSpanForPreparedStatementExecuteUpdate() {
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
TestSpanHandler spanReporter = context.getBean(TestSpanHandler.class);
|
||||
|
||||
Connection connection = dataSource.getConnection();
|
||||
connection.prepareStatement("UPDATE INFORMATION_SCHEMA.TABLES SET table_Name = '' WHERE 0 = 1")
|
||||
.executeUpdate();
|
||||
connection.close();
|
||||
|
||||
assertThat(spanReporter.spans()).hasSize(2);
|
||||
MutableSpan connectionSpan = spanReporter.spans().get(1);
|
||||
MutableSpan statementSpan = spanReporter.spans().get(0);
|
||||
assertThat(connectionSpan.name()).isEqualTo("connection");
|
||||
assertThat(statementSpan.name()).isEqualTo("update");
|
||||
assertThat(statementSpan.tags()).containsEntry(SPAN_SQL_QUERY_TAG_NAME,
|
||||
"UPDATE INFORMATION_SCHEMA.TABLES SET table_Name = '' WHERE 0 = 1");
|
||||
assertThat(statementSpan.tags()).containsEntry(SPAN_ROW_COUNT_TAG_NAME, "0");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldAddSpanForStatementExecuteUpdate() {
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
TestSpanHandler spanReporter = context.getBean(TestSpanHandler.class);
|
||||
|
||||
Connection connection = dataSource.getConnection();
|
||||
connection.createStatement()
|
||||
.executeUpdate("UPDATE INFORMATION_SCHEMA.TABLES SET table_Name = '' WHERE 0 = 1");
|
||||
connection.close();
|
||||
|
||||
assertThat(spanReporter.spans()).hasSize(2);
|
||||
MutableSpan connectionSpan = spanReporter.spans().get(1);
|
||||
MutableSpan statementSpan = spanReporter.spans().get(0);
|
||||
assertThat(connectionSpan.name()).isEqualTo("connection");
|
||||
assertThat(statementSpan.name()).isEqualTo("update");
|
||||
assertThat(statementSpan.tags()).containsEntry(SPAN_SQL_QUERY_TAG_NAME,
|
||||
"UPDATE INFORMATION_SCHEMA.TABLES SET table_Name = '' WHERE 0 = 1");
|
||||
assertThat(statementSpan.tags()).containsEntry(SPAN_ROW_COUNT_TAG_NAME, "0");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldAddSpanForPreparedStatementExecuteQueryIncludingTimeToCloseResultSet() {
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
TestSpanHandler spanReporter = context.getBean(TestSpanHandler.class);
|
||||
|
||||
Connection connection = dataSource.getConnection();
|
||||
ResultSet resultSet = connection.prepareStatement("SELECT NOW() UNION ALL select NOW()").executeQuery();
|
||||
resultSet.next();
|
||||
resultSet.next();
|
||||
resultSet.close();
|
||||
connection.close();
|
||||
|
||||
assertThat(spanReporter.spans()).hasSize(3);
|
||||
MutableSpan connectionSpan = spanReporter.spans().get(2);
|
||||
MutableSpan resultSetSpan = spanReporter.spans().get(1);
|
||||
MutableSpan statementSpan = spanReporter.spans().get(0);
|
||||
assertThat(connectionSpan.name()).isEqualTo("connection");
|
||||
assertThat(statementSpan.name()).isEqualTo("select");
|
||||
assertThat(statementSpan.tags()).containsEntry(SPAN_SQL_QUERY_TAG_NAME,
|
||||
"SELECT NOW() UNION ALL select NOW()");
|
||||
assertThat(resultSetSpan.name()).isEqualTo("result-set");
|
||||
assertThat(resultSetSpan.remoteServiceName()).isEqualTo("TESTDB-BAZ");
|
||||
if (isP6Spy(context)) {
|
||||
assertThat(resultSetSpan.tags()).containsEntry(SPAN_ROW_COUNT_TAG_NAME, "2");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldAddSpanForStatementAndResultSet() {
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
TestSpanHandler spanReporter = context.getBean(TestSpanHandler.class);
|
||||
|
||||
Connection connection = dataSource.getConnection();
|
||||
ResultSet resultSet = connection.createStatement().executeQuery("SELECT NOW()");
|
||||
resultSet.next();
|
||||
Thread.sleep(200L);
|
||||
resultSet.close();
|
||||
connection.close();
|
||||
|
||||
assertThat(spanReporter.spans()).hasSize(3);
|
||||
MutableSpan connectionSpan = spanReporter.spans().get(2);
|
||||
MutableSpan resultSetSpan = spanReporter.spans().get(1);
|
||||
MutableSpan statementSpan = spanReporter.spans().get(0);
|
||||
assertThat(connectionSpan.name()).isEqualTo("connection");
|
||||
assertThat(statementSpan.name()).isEqualTo("select");
|
||||
assertThat(statementSpan.tags()).containsEntry(SPAN_SQL_QUERY_TAG_NAME, "SELECT NOW()");
|
||||
assertThat(resultSetSpan.name()).isEqualTo("result-set");
|
||||
if (isP6Spy(context)) {
|
||||
assertThat(resultSetSpan.tags()).containsEntry(SPAN_ROW_COUNT_TAG_NAME, "1");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldNotFailWhenStatementIsClosedWihoutResultSet() {
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
TestSpanHandler spanReporter = context.getBean(TestSpanHandler.class);
|
||||
|
||||
Connection connection = dataSource.getConnection();
|
||||
Statement statement = connection.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery("SELECT NOW()");
|
||||
resultSet.next();
|
||||
statement.close();
|
||||
connection.close();
|
||||
|
||||
assertThat(spanReporter.spans()).hasSize(3);
|
||||
MutableSpan connectionSpan = spanReporter.spans().get(2);
|
||||
MutableSpan resultSetSpan = spanReporter.spans().get(1);
|
||||
MutableSpan statementSpan = spanReporter.spans().get(0);
|
||||
assertThat(connectionSpan.name()).isEqualTo("connection");
|
||||
assertThat(statementSpan.name()).isEqualTo("select");
|
||||
assertThat(resultSetSpan.name()).isEqualTo("result-set");
|
||||
assertThat(statementSpan.tags()).containsEntry(SPAN_SQL_QUERY_TAG_NAME, "SELECT NOW()");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldNotFailWhenConnectionIsClosedWihoutResultSet() {
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
TestSpanHandler spanReporter = context.getBean(TestSpanHandler.class);
|
||||
|
||||
Connection connection = dataSource.getConnection();
|
||||
Statement statement = connection.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery("SELECT NOW()");
|
||||
resultSet.next();
|
||||
connection.close();
|
||||
|
||||
assertThat(spanReporter.spans()).hasSize(3);
|
||||
MutableSpan connectionSpan = spanReporter.spans().get(2);
|
||||
MutableSpan resultSetSpan = spanReporter.spans().get(1);
|
||||
MutableSpan statementSpan = spanReporter.spans().get(0);
|
||||
assertThat(connectionSpan.name()).isEqualTo("connection");
|
||||
assertThat(statementSpan.name()).isEqualTo("select");
|
||||
assertThat(resultSetSpan.name()).isEqualTo("result-set");
|
||||
assertThat(statementSpan.tags()).containsEntry(SPAN_SQL_QUERY_TAG_NAME, "SELECT NOW()");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldNotFailWhenResultSetNextWasNotCalled() {
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
TestSpanHandler spanReporter = context.getBean(TestSpanHandler.class);
|
||||
|
||||
Connection connection = dataSource.getConnection();
|
||||
Statement statement = connection.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery("SELECT NOW()");
|
||||
resultSet.next();
|
||||
resultSet.close();
|
||||
statement.close();
|
||||
connection.close();
|
||||
|
||||
assertThat(spanReporter.spans()).hasSize(3);
|
||||
MutableSpan connectionSpan = spanReporter.spans().get(2);
|
||||
MutableSpan resultSetSpan = spanReporter.spans().get(1);
|
||||
MutableSpan statementSpan = spanReporter.spans().get(0);
|
||||
assertThat(connectionSpan.name()).isEqualTo("connection");
|
||||
assertThat(statementSpan.name()).isEqualTo("select");
|
||||
assertThat(resultSetSpan.name()).isEqualTo("result-set");
|
||||
assertThat(statementSpan.tags()).containsEntry(SPAN_SQL_QUERY_TAG_NAME, "SELECT NOW()");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldNotFailWhenResourceIsAlreadyClosed() {
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
TestSpanHandler spanReporter = context.getBean(TestSpanHandler.class);
|
||||
|
||||
Connection connection = dataSource.getConnection();
|
||||
Statement statement = connection.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery("SELECT NOW()");
|
||||
resultSet.next();
|
||||
resultSet.close();
|
||||
resultSet.close();
|
||||
statement.close();
|
||||
statement.close();
|
||||
connection.close();
|
||||
connection.close();
|
||||
|
||||
assertThat(spanReporter.spans()).hasSize(3);
|
||||
MutableSpan connectionSpan = spanReporter.spans().get(2);
|
||||
MutableSpan resultSetSpan = spanReporter.spans().get(1);
|
||||
MutableSpan statementSpan = spanReporter.spans().get(0);
|
||||
assertThat(connectionSpan.name()).isEqualTo("connection");
|
||||
assertThat(statementSpan.name()).isEqualTo("select");
|
||||
assertThat(resultSetSpan.name()).isEqualTo("result-set");
|
||||
assertThat(statementSpan.tags()).containsEntry(SPAN_SQL_QUERY_TAG_NAME, "SELECT NOW()");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldNotFailWhenResourceIsAlreadyClosed2() {
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
TestSpanHandler spanReporter = context.getBean(TestSpanHandler.class);
|
||||
|
||||
Connection connection = dataSource.getConnection();
|
||||
assertThatThrownBy(() -> {
|
||||
connection.close();
|
||||
connection.prepareStatement("SELECT NOW()");
|
||||
}).isInstanceOf(SQLException.class);
|
||||
|
||||
assertThat(spanReporter.spans()).hasSize(1);
|
||||
MutableSpan connectionSpan = spanReporter.spans().get(0);
|
||||
assertThat(connectionSpan.name()).isEqualTo("connection");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldNotFailWhenResourceIsAlreadyClosed3() {
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
|
||||
Connection connection = dataSource.getConnection();
|
||||
Statement statement = connection.createStatement();
|
||||
assertThatThrownBy(() -> {
|
||||
statement.close();
|
||||
statement.executeQuery("SELECT NOW()");
|
||||
}).isInstanceOf(SQLException.class);
|
||||
connection.close();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldNotFailWhenResourceIsAlreadyClosed4() {
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
|
||||
Connection connection = dataSource.getConnection();
|
||||
Statement statement = connection.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery("SELECT NOW()");
|
||||
assertThatThrownBy(() -> {
|
||||
resultSet.close();
|
||||
resultSet.next();
|
||||
}).isInstanceOf(SQLException.class);
|
||||
statement.close();
|
||||
connection.close();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldNotFailToCloseSpanForTwoConsecutiveConnections() {
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
TestSpanHandler spanReporter = context.getBean(TestSpanHandler.class);
|
||||
|
||||
Connection connection1 = dataSource.getConnection();
|
||||
Connection connection2 = dataSource.getConnection();
|
||||
connection1.close();
|
||||
connection2.close();
|
||||
|
||||
assertThat(spanReporter.spans()).hasSize(2);
|
||||
MutableSpan connection1Span = spanReporter.spans().get(0);
|
||||
MutableSpan connection2Span = spanReporter.spans().get(1);
|
||||
assertThat(connection1Span.name()).isEqualTo("connection");
|
||||
assertThat(connection2Span.name()).isEqualTo("connection");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldNotFailWhenClosedInReversedOrder() {
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
TestSpanHandler spanReporter = context.getBean(TestSpanHandler.class);
|
||||
|
||||
Connection connection = dataSource.getConnection();
|
||||
Statement statement = connection.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery("SELECT NOW()");
|
||||
resultSet.next();
|
||||
connection.close();
|
||||
statement.close();
|
||||
resultSet.close();
|
||||
|
||||
assertThat(spanReporter.spans()).hasSize(3);
|
||||
MutableSpan connectionSpan = spanReporter.spans().get(2);
|
||||
MutableSpan resultSetSpan = spanReporter.spans().get(1);
|
||||
MutableSpan statementSpan = spanReporter.spans().get(0);
|
||||
assertThat(connectionSpan.name()).isEqualTo("connection");
|
||||
assertThat(statementSpan.name()).isEqualTo("select");
|
||||
assertThat(resultSetSpan.name()).isEqualTo("result-set");
|
||||
assertThat(statementSpan.tags()).containsEntry(SPAN_SQL_QUERY_TAG_NAME, "SELECT NOW()");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void testShouldNotCauseMemoryLeakOnTomcatPool() {
|
||||
contextRunner.withPropertyValues("spring.datasource.type:org.apache.tomcat.jdbc.pool.DataSource")
|
||||
.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
Object listener = isP6Spy(context) ? context.getBean(TraceJdbcEventListener.class)
|
||||
: context.getBean(TraceQueryExecutionListener.class);
|
||||
|
||||
Connection connection = dataSource.getConnection();
|
||||
Statement statement = connection.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery("select 1 FROM dual");
|
||||
resultSet.next();
|
||||
resultSet.close();
|
||||
statement.close();
|
||||
connection.close();
|
||||
|
||||
assertThat(listener).extracting("strategy").extracting("openConnections")
|
||||
.isInstanceOfSatisfying(Map.class, map -> assertThat(map).isEmpty());
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isP6Spy(AssertableApplicationContext context) {
|
||||
if (context.getBeansOfType(TraceJdbcEventListener.class).size() == 1) {
|
||||
return true;
|
||||
}
|
||||
else if (context.getBeansOfType(TraceQueryExecutionListener.class).size() == 1) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("Expected exactly 1 tracing listener bean in the context.");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSingleConnectionAcrossMultipleThreads() {
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
TestSpanHandler spanReporter = context.getBean(TestSpanHandler.class);
|
||||
|
||||
Connection connection = dataSource.getConnection();
|
||||
IntStream.range(0, 5).mapToObj(i -> CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
Statement statement = connection.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery("SELECT NOW()");
|
||||
resultSet.next();
|
||||
statement.close();
|
||||
resultSet.close();
|
||||
}
|
||||
catch (SQLException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
})).collect(Collectors.toList()).forEach(CompletableFuture::join);
|
||||
connection.close();
|
||||
|
||||
assertThat(spanReporter.spans()).hasSize(1 + 2 * 5);
|
||||
assertThat(spanReporter.spans()).extracting("name").contains("select", "result-set", "connection");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldIncludeOnlyConnectionTraces() {
|
||||
contextRunner.withPropertyValues("spring.sleuth.jdbc.decorator.datasource.includes: connection")
|
||||
.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
TestSpanHandler spanReporter = context.getBean(TestSpanHandler.class);
|
||||
|
||||
Connection connection = dataSource.getConnection();
|
||||
Statement statement = connection.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery("select 1 FROM dual");
|
||||
resultSet.next();
|
||||
resultSet.close();
|
||||
statement.close();
|
||||
connection.close();
|
||||
|
||||
assertThat(spanReporter.spans()).hasSize(1);
|
||||
MutableSpan connectionSpan = spanReporter.spans().get(0);
|
||||
assertThat(connectionSpan.name()).isEqualTo("connection");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldIncludeOnlyQueryTraces() {
|
||||
contextRunner.withPropertyValues("spring.sleuth.jdbc.decorator.datasource.includes: query").run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
TestSpanHandler spanReporter = context.getBean(TestSpanHandler.class);
|
||||
|
||||
Connection connection = dataSource.getConnection();
|
||||
Statement statement = connection.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery("select 1 FROM dual");
|
||||
resultSet.next();
|
||||
resultSet.close();
|
||||
statement.close();
|
||||
connection.close();
|
||||
|
||||
assertThat(spanReporter.spans()).hasSize(1);
|
||||
MutableSpan statementSpan = spanReporter.spans().get(0);
|
||||
assertThat(statementSpan.name()).isEqualTo("select");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldIncludeOnlyFetchTraces() {
|
||||
contextRunner.withPropertyValues("spring.sleuth.jdbc.decorator.datasource.includes: fetch").run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
TestSpanHandler spanReporter = context.getBean(TestSpanHandler.class);
|
||||
|
||||
Connection connection = dataSource.getConnection();
|
||||
Statement statement = connection.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery("select 1 FROM dual");
|
||||
resultSet.next();
|
||||
resultSet.close();
|
||||
statement.close();
|
||||
connection.close();
|
||||
|
||||
assertThat(spanReporter.spans()).hasSize(1);
|
||||
MutableSpan resultSetSpan = spanReporter.spans().get(0);
|
||||
assertThat(resultSetSpan.name()).isEqualTo("result-set");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldIncludeOnlyConnectionAndQueryTraces() {
|
||||
contextRunner.withPropertyValues("spring.sleuth.jdbc.decorator.datasource.includes: connection, query")
|
||||
.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
TestSpanHandler spanReporter = context.getBean(TestSpanHandler.class);
|
||||
|
||||
Connection connection = dataSource.getConnection();
|
||||
Statement statement = connection.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery("select 1 FROM dual");
|
||||
resultSet.next();
|
||||
resultSet.close();
|
||||
statement.close();
|
||||
connection.close();
|
||||
|
||||
assertThat(spanReporter.spans()).hasSize(2);
|
||||
MutableSpan connectionSpan = spanReporter.spans().get(1);
|
||||
MutableSpan statementSpan = spanReporter.spans().get(0);
|
||||
assertThat(connectionSpan.name()).isEqualTo("connection");
|
||||
assertThat(statementSpan.name()).isEqualTo("select");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldIncludeOnlyConnectionAndFetchTraces() {
|
||||
contextRunner.withPropertyValues("spring.sleuth.jdbc.decorator.datasource.includes: connection, fetch")
|
||||
.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
TestSpanHandler spanReporter = context.getBean(TestSpanHandler.class);
|
||||
|
||||
Connection connection = dataSource.getConnection();
|
||||
Statement statement = connection.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery("select 1 FROM dual");
|
||||
resultSet.next();
|
||||
resultSet.close();
|
||||
statement.close();
|
||||
connection.close();
|
||||
|
||||
assertThat(spanReporter.spans()).hasSize(2);
|
||||
MutableSpan connectionSpan = spanReporter.spans().get(1);
|
||||
MutableSpan resultSetSpan = spanReporter.spans().get(0);
|
||||
assertThat(connectionSpan.name()).isEqualTo("connection");
|
||||
assertThat(resultSetSpan.name()).isEqualTo("result-set");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldIncludeOnlyQueryAndFetchTraces() {
|
||||
contextRunner.withPropertyValues("spring.sleuth.jdbc.decorator.datasource.includes: query, fetch")
|
||||
.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
TestSpanHandler spanReporter = context.getBean(TestSpanHandler.class);
|
||||
|
||||
Connection connection = dataSource.getConnection();
|
||||
Statement statement = connection.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery("select 1 FROM dual");
|
||||
resultSet.next();
|
||||
resultSet.close();
|
||||
statement.close();
|
||||
connection.close();
|
||||
|
||||
assertThat(spanReporter.spans()).hasSize(2);
|
||||
MutableSpan resultSetSpan = spanReporter.spans().get(1);
|
||||
MutableSpan statementSpan = spanReporter.spans().get(0);
|
||||
assertThat(statementSpan.name()).isEqualTo("select");
|
||||
assertThat(resultSetSpan.name()).isEqualTo("result-set");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldNotOverrideExceptionWhenConnectionWasClosedBeforeExecutingQuery() {
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
TestSpanHandler spanReporter = context.getBean(TestSpanHandler.class);
|
||||
|
||||
Connection connection = dataSource.getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("SELECT NOW()");
|
||||
connection.close();
|
||||
|
||||
assertThatThrownBy(statement::executeQuery).isInstanceOf(SQLException.class);
|
||||
|
||||
assertThat(spanReporter.spans()).hasSize(1);
|
||||
MutableSpan connectionSpan = spanReporter.spans().get(0);
|
||||
assertThat(connectionSpan.name()).isEqualTo("connection");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldNotOverrideExceptionWhenStatementWasClosedBeforeExecutingQuery() {
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
TestSpanHandler spanReporter = context.getBean(TestSpanHandler.class);
|
||||
|
||||
Connection connection = dataSource.getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("SELECT NOW()");
|
||||
statement.close();
|
||||
assertThatThrownBy(statement::executeQuery).isInstanceOf(SQLException.class);
|
||||
connection.close();
|
||||
|
||||
assertThat(spanReporter.spans()).hasSize(2);
|
||||
MutableSpan connectionSpan = spanReporter.spans().get(1);
|
||||
MutableSpan statementSpan = spanReporter.spans().get(0);
|
||||
assertThat(connectionSpan.name()).isEqualTo("connection");
|
||||
assertThat(statementSpan.name()).isEqualTo("select");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldNotOverrideExceptionWhenResultSetWasClosedBeforeNext() {
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
TestSpanHandler spanReporter = context.getBean(TestSpanHandler.class);
|
||||
|
||||
Connection connection = dataSource.getConnection();
|
||||
Statement statement = connection.createStatement();
|
||||
ResultSet resultSet = statement.executeQuery("SELECT NOW()");
|
||||
resultSet.close();
|
||||
assertThatThrownBy(resultSet::next).isInstanceOf(SQLException.class);
|
||||
statement.close();
|
||||
connection.close();
|
||||
|
||||
assertThat(spanReporter.spans()).hasSize(3);
|
||||
MutableSpan connectionSpan = spanReporter.spans().get(2);
|
||||
MutableSpan resultSetSpan = spanReporter.spans().get(1);
|
||||
MutableSpan statementSpan = spanReporter.spans().get(0);
|
||||
assertThat(connectionSpan.name()).isEqualTo("connection");
|
||||
assertThat(statementSpan.name()).isEqualTo("select");
|
||||
assertThat(resultSetSpan.name()).isEqualTo("result-set");
|
||||
assertThat(statementSpan.tags()).containsEntry(SPAN_SQL_QUERY_TAG_NAME, "SELECT NOW()");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testShouldNotFailWhenClosingConnectionFromDifferentDataSource() {
|
||||
ApplicationContextRunner contextRunner = this.contextRunner
|
||||
.withUserConfiguration(MultiDataSourceConfiguration.class);
|
||||
|
||||
contextRunner.run(context -> {
|
||||
DataSource dataSource1 = context.getBean("test1", DataSource.class);
|
||||
DataSource dataSource2 = context.getBean("test2", DataSource.class);
|
||||
|
||||
dataSource1.getConnection().close();
|
||||
dataSource2.getConnection().close();
|
||||
|
||||
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
Connection connection1 = dataSource1.getConnection();
|
||||
PreparedStatement statement = connection1.prepareStatement("SELECT NOW()");
|
||||
ResultSet resultSet = statement.executeQuery();
|
||||
Thread.sleep(200);
|
||||
resultSet.close();
|
||||
statement.close();
|
||||
connection1.close();
|
||||
}
|
||||
catch (SQLException | InterruptedException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
});
|
||||
Thread.sleep(100);
|
||||
Connection connection2 = dataSource2.getConnection();
|
||||
Thread.sleep(300);
|
||||
connection2.close();
|
||||
|
||||
future.join();
|
||||
});
|
||||
}
|
||||
|
||||
private static class MultiDataSourceConfiguration {
|
||||
|
||||
@Bean
|
||||
public HikariDataSource test1() {
|
||||
HikariDataSource dataSource = new HikariDataSource();
|
||||
dataSource.setJdbcUrl("jdbc:h2:mem:testdb-1-foo");
|
||||
dataSource.setPoolName("test1");
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public HikariDataSource test2() {
|
||||
HikariDataSource dataSource = new HikariDataSource();
|
||||
dataSource.setJdbcUrl("jdbc:h2:mem:testdb-2-bar");
|
||||
dataSource.setPoolName("test2");
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.sleuth.autoconfig.instrument.jdbc;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.sleuth.autoconfig.brave.BraveAutoConfiguration;
|
||||
|
||||
class TracingQueryExecutionListenerTests extends TracingListenerStrategyTests {
|
||||
|
||||
TracingQueryExecutionListenerTests() {
|
||||
super(new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class,
|
||||
TraceDataSourceDecoratorAutoConfiguration.class, BraveAutoConfiguration.class,
|
||||
TestSpanHandlerConfiguration.class, PropertyPlaceholderAutoConfiguration.class))
|
||||
.withPropertyValues("spring.datasource.initialization-mode=never",
|
||||
"spring.datasource.url:jdbc:h2:mem:testdb-baz", "spring.datasource.hikari.pool-name=test")
|
||||
.withClassLoader(new FilteredClassLoader("com.p6spy")));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,7 +26,6 @@ import org.springframework.boot.actuate.autoconfigure.security.servlet.Managemen
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
|
||||
@@ -35,7 +34,6 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.gateway.config.GatewayAutoConfiguration;
|
||||
import org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration;
|
||||
import org.springframework.cloud.gateway.config.GatewayMetricsAutoConfiguration;
|
||||
import org.springframework.cloud.sleuth.Tracer;
|
||||
import org.springframework.cloud.sleuth.instrument.quartz.TracingJobListener;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -47,8 +45,8 @@ import org.springframework.context.annotation.Primary;
|
||||
public class TraceQuartzAutoConfigurationTest {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.sleuth.noop.enabled=true").withConfiguration(
|
||||
AutoConfigurations.of(SchedulerConfig.class, TracingConfig.class, EnableAutoConfig.class));
|
||||
.withPropertyValues("spring.sleuth.noop.enabled=true")
|
||||
.withConfiguration(AutoConfigurations.of(SchedulerConfig.class, EnableAutoConfig.class));
|
||||
|
||||
@Test
|
||||
public void should_create_job_listener_bean_when_all_conditions_are_met() {
|
||||
@@ -94,9 +92,10 @@ public class TraceQuartzAutoConfigurationTest {
|
||||
|
||||
@Test
|
||||
public void should_not_create_listener_when_scheduler_bean_is_not_present() {
|
||||
new ApplicationContextRunner().withConfiguration(AutoConfigurations
|
||||
// given
|
||||
.of(TracingConfig.class, TraceQuartzAutoConfiguration.class))
|
||||
new ApplicationContextRunner().withPropertyValues("spring.sleuth.noop.enabled=true")
|
||||
.withConfiguration(AutoConfigurations
|
||||
// given
|
||||
.of(TraceQuartzAutoConfiguration.class))
|
||||
|
||||
// when
|
||||
.run(context -> {
|
||||
@@ -138,16 +137,4 @@ public class TraceQuartzAutoConfigurationTest {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@AutoConfigureBefore(TraceQuartzAutoConfiguration.class)
|
||||
@ConditionalOnProperty("spring.sleuth.noop.enabled")
|
||||
public static class TracingConfig {
|
||||
|
||||
@Bean
|
||||
public Tracer testTracer() {
|
||||
return Mockito.mock(Tracer.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -97,6 +97,12 @@ public class BraveSpan implements Span {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Span remoteIpAndPort(String ip, int port) {
|
||||
this.delegate.remoteIpAndPort(ip, port);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.delegate != null ? this.delegate.toString() : "null";
|
||||
|
||||
@@ -16,12 +16,8 @@
|
||||
|
||||
package org.springframework.cloud.sleuth.brave.bridge;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import brave.Tracer;
|
||||
import brave.propagation.TraceContextOrSamplingFlags;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.cloud.sleuth.Span;
|
||||
import org.springframework.cloud.sleuth.TraceContext;
|
||||
@@ -34,8 +30,6 @@ import org.springframework.cloud.sleuth.TraceContext;
|
||||
*/
|
||||
class BraveSpanBuilder implements Span.Builder {
|
||||
|
||||
private static final Log log = LogFactory.getLog(BraveSpanBuilder.class);
|
||||
|
||||
brave.Span delegate;
|
||||
|
||||
TraceContextOrSamplingFlags parentContext;
|
||||
@@ -114,16 +108,8 @@ class BraveSpanBuilder implements Span.Builder {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Span.Builder remoteUrl(String remoteUrl) {
|
||||
try {
|
||||
URI uri = URI.create(remoteUrl);
|
||||
span().remoteIpAndPort(uri.getHost(), uri.getPort());
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Failed to parse url [" + remoteUrl + "]. Will not set the value", e);
|
||||
}
|
||||
}
|
||||
public Span.Builder remoteIpAndPort(String ip, int port) {
|
||||
span().remoteIpAndPort(ip, port);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -198,6 +198,17 @@
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>p6spy</groupId>
|
||||
<artifactId>p6spy</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.ttddyy</groupId>
|
||||
<artifactId>datasource-proxy</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.sleuth.instrument.jdbc;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
/**
|
||||
* Decorator for context {@link DataSource} beans.
|
||||
*
|
||||
* @author Arthur Gavlyukovskiy
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public interface DataSourceDecorator {
|
||||
|
||||
/**
|
||||
* Decorates given {@link DataSource} instance. Should either return wrapped
|
||||
* {@link DataSource} or same instance.
|
||||
* @param beanName name of a bean
|
||||
* @param dataSource bean instance
|
||||
* @return decorated {@link DataSource} or given {@link DataSource} without changes.
|
||||
*/
|
||||
DataSource decorate(String beanName, DataSource dataSource);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.sleuth.instrument.jdbc;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import javax.sql.CommonDataSource;
|
||||
|
||||
/**
|
||||
* {@link CommonDataSource} name resolver based on bean name.
|
||||
*
|
||||
* @author Arthur Gavlyukovskiy
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public class DataSourceNameResolver {
|
||||
|
||||
private final ConcurrentHashMap<CommonDataSource, String> cachedNames = new ConcurrentHashMap<>();
|
||||
|
||||
public void addDataSource(String name, CommonDataSource dataSource) {
|
||||
this.cachedNames.put(dataSource, name);
|
||||
}
|
||||
|
||||
public String resolveDataSourceName(CommonDataSource dataSource) {
|
||||
return this.cachedNames.getOrDefault(dataSource, "dataSource");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.sleuth.instrument.jdbc;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import net.ttddyy.dsproxy.listener.MethodExecutionListener;
|
||||
import net.ttddyy.dsproxy.listener.QueryCountStrategy;
|
||||
import net.ttddyy.dsproxy.listener.QueryExecutionListener;
|
||||
import net.ttddyy.dsproxy.listener.logging.CommonsLogLevel;
|
||||
import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel;
|
||||
import net.ttddyy.dsproxy.proxy.ResultSetProxyLogicFactory;
|
||||
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
|
||||
import net.ttddyy.dsproxy.transform.ParameterTransformer;
|
||||
import net.ttddyy.dsproxy.transform.QueryTransformer;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Configurer for {@link ProxyDataSourceBuilder} based on the application context.
|
||||
*
|
||||
* @author Arthur Gavlyukovskiy
|
||||
* @since 3.1.0
|
||||
* @see ProxyDataSourceBuilder
|
||||
*/
|
||||
public class DataSourceProxyBuilderCustomizer {
|
||||
|
||||
private static final Log log = LogFactory.getLog(DataSourceProxyBuilderCustomizer.class);
|
||||
|
||||
private final QueryCountStrategy queryCountStrategy;
|
||||
|
||||
private final List<QueryExecutionListener> listeners;
|
||||
|
||||
private final List<MethodExecutionListener> methodExecutionListeners;
|
||||
|
||||
private final ParameterTransformer parameterTransformer;
|
||||
|
||||
private final QueryTransformer queryTransformer;
|
||||
|
||||
private final ResultSetProxyLogicFactory resultSetProxyLogicFactory;
|
||||
|
||||
private final DataSourceProxyConnectionIdManagerProvider dataSourceProxyConnectionIdManagerProvider;
|
||||
|
||||
private final DataSourceProxyProperties datasourceProxy;
|
||||
|
||||
public DataSourceProxyBuilderCustomizer(@Nullable QueryCountStrategy queryCountStrategy,
|
||||
@Nullable List<QueryExecutionListener> listeners,
|
||||
@Nullable List<MethodExecutionListener> methodExecutionListeners,
|
||||
@Nullable ParameterTransformer parameterTransformer, @Nullable QueryTransformer queryTransformer,
|
||||
@Nullable ResultSetProxyLogicFactory resultSetProxyLogicFactory,
|
||||
@Nullable DataSourceProxyConnectionIdManagerProvider dataSourceProxyConnectionIdManagerProvider,
|
||||
DataSourceProxyProperties datasourceProxy) {
|
||||
this.queryCountStrategy = queryCountStrategy;
|
||||
this.listeners = listeners;
|
||||
this.methodExecutionListeners = methodExecutionListeners;
|
||||
this.parameterTransformer = parameterTransformer;
|
||||
this.queryTransformer = queryTransformer;
|
||||
this.resultSetProxyLogicFactory = resultSetProxyLogicFactory;
|
||||
this.dataSourceProxyConnectionIdManagerProvider = dataSourceProxyConnectionIdManagerProvider;
|
||||
this.datasourceProxy = datasourceProxy;
|
||||
}
|
||||
|
||||
public ProxyDataSourceBuilder customize(ProxyDataSourceBuilder proxyDataSourceBuilder) {
|
||||
switch (this.datasourceProxy.getLogging()) {
|
||||
case SLF4J:
|
||||
if (this.datasourceProxy.getQuery().isEnableLogging()) {
|
||||
proxyDataSourceBuilder.logQueryBySlf4j(toSlf4JLogLevel(this.datasourceProxy.getQuery().getLogLevel()),
|
||||
this.datasourceProxy.getQuery().getLoggerName());
|
||||
}
|
||||
if (this.datasourceProxy.getSlowQuery().isEnableLogging()) {
|
||||
proxyDataSourceBuilder.logSlowQueryBySlf4j(this.datasourceProxy.getSlowQuery().getThreshold(),
|
||||
TimeUnit.SECONDS, toSlf4JLogLevel(this.datasourceProxy.getSlowQuery().getLogLevel()),
|
||||
datasourceProxy.getSlowQuery().getLoggerName());
|
||||
}
|
||||
break;
|
||||
case JUL:
|
||||
if (this.datasourceProxy.getQuery().isEnableLogging()) {
|
||||
proxyDataSourceBuilder.logQueryByJUL(toJULLogLevel(this.datasourceProxy.getQuery().getLogLevel()),
|
||||
this.datasourceProxy.getQuery().getLoggerName());
|
||||
}
|
||||
if (this.datasourceProxy.getSlowQuery().isEnableLogging()) {
|
||||
proxyDataSourceBuilder.logSlowQueryByJUL(this.datasourceProxy.getSlowQuery().getThreshold(),
|
||||
TimeUnit.SECONDS, toJULLogLevel(this.datasourceProxy.getSlowQuery().getLogLevel()),
|
||||
this.datasourceProxy.getSlowQuery().getLoggerName());
|
||||
}
|
||||
break;
|
||||
case COMMONS:
|
||||
if (this.datasourceProxy.getQuery().isEnableLogging()) {
|
||||
proxyDataSourceBuilder.logQueryByCommons(
|
||||
toCommonsLogLevel(this.datasourceProxy.getQuery().getLogLevel()),
|
||||
datasourceProxy.getQuery().getLoggerName());
|
||||
}
|
||||
if (this.datasourceProxy.getSlowQuery().isEnableLogging()) {
|
||||
proxyDataSourceBuilder.logSlowQueryByCommons(this.datasourceProxy.getSlowQuery().getThreshold(),
|
||||
TimeUnit.SECONDS, toCommonsLogLevel(this.datasourceProxy.getSlowQuery().getLogLevel()),
|
||||
this.datasourceProxy.getSlowQuery().getLoggerName());
|
||||
}
|
||||
break;
|
||||
case SYSOUT:
|
||||
if (this.datasourceProxy.getQuery().isEnableLogging()) {
|
||||
proxyDataSourceBuilder.logQueryToSysOut();
|
||||
}
|
||||
if (this.datasourceProxy.getSlowQuery().isEnableLogging()) {
|
||||
proxyDataSourceBuilder.logSlowQueryToSysOut(this.datasourceProxy.getSlowQuery().getThreshold(),
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (this.datasourceProxy.isMultiline() && this.datasourceProxy.isJsonFormat()) {
|
||||
log.warn(
|
||||
"Found opposite multiline and json format, multiline will be used (may depend on library version)");
|
||||
}
|
||||
if (this.datasourceProxy.isMultiline()) {
|
||||
proxyDataSourceBuilder.multiline();
|
||||
}
|
||||
if (this.datasourceProxy.isJsonFormat()) {
|
||||
proxyDataSourceBuilder.asJson();
|
||||
}
|
||||
if (this.datasourceProxy.isCountQuery()) {
|
||||
ifAvailable(this.queryCountStrategy, proxyDataSourceBuilder::countQuery);
|
||||
}
|
||||
ifAvailable(this.listeners, l -> l.forEach(proxyDataSourceBuilder::listener));
|
||||
ifAvailable(this.methodExecutionListeners, m -> m.forEach(proxyDataSourceBuilder::methodListener));
|
||||
ifAvailable(this.parameterTransformer, proxyDataSourceBuilder::parameterTransformer);
|
||||
ifAvailable(this.queryTransformer, proxyDataSourceBuilder::queryTransformer);
|
||||
ifAvailable(this.resultSetProxyLogicFactory, proxyDataSourceBuilder::proxyResultSet);
|
||||
ifAvailable(this.dataSourceProxyConnectionIdManagerProvider,
|
||||
d -> proxyDataSourceBuilder.connectionIdManager(d.get()));
|
||||
return proxyDataSourceBuilder;
|
||||
}
|
||||
|
||||
private <T> void ifAvailable(@Nullable T o, Consumer<T> consumer) {
|
||||
if (o != null) {
|
||||
consumer.accept(o);
|
||||
}
|
||||
}
|
||||
|
||||
private SLF4JLogLevel toSlf4JLogLevel(String logLevel) {
|
||||
if (logLevel == null) {
|
||||
return null;
|
||||
}
|
||||
for (SLF4JLogLevel slf4JLogLevel : SLF4JLogLevel.values()) {
|
||||
if (slf4JLogLevel.name().equalsIgnoreCase(logLevel)) {
|
||||
return slf4JLogLevel;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unresolved log level " + logLevel + " for slf4j logger, known levels: "
|
||||
+ Arrays.toString(SLF4JLogLevel.values()));
|
||||
}
|
||||
|
||||
private Level toJULLogLevel(String logLevel) {
|
||||
if (logLevel == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Level.parse(logLevel);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
if (logLevel.equalsIgnoreCase("DEBUG")) {
|
||||
return Level.FINE;
|
||||
}
|
||||
if (logLevel.equalsIgnoreCase("WARN")) {
|
||||
return Level.WARNING;
|
||||
}
|
||||
throw new IllegalArgumentException("Unresolved log level " + logLevel + " for java.util.logging", e);
|
||||
}
|
||||
}
|
||||
|
||||
private CommonsLogLevel toCommonsLogLevel(String logLevel) {
|
||||
if (logLevel == null) {
|
||||
return null;
|
||||
}
|
||||
for (CommonsLogLevel commonsLogLevel : CommonsLogLevel.values()) {
|
||||
if (commonsLogLevel.name().equalsIgnoreCase(logLevel)) {
|
||||
return commonsLogLevel;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unresolved log level " + logLevel
|
||||
+ " for apache commons logger, known levels " + Arrays.toString(CommonsLogLevel.values()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.sleuth.instrument.jdbc;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import net.ttddyy.dsproxy.ConnectionIdManager;
|
||||
|
||||
/**
|
||||
* Supplier of a {@link ConnectionIdManager}.
|
||||
*
|
||||
* @author Arthur Gavlyukovskiy
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public interface DataSourceProxyConnectionIdManagerProvider extends Supplier<ConnectionIdManager> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.sleuth.instrument.jdbc;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import net.ttddyy.dsproxy.support.ProxyDataSource;
|
||||
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
|
||||
/**
|
||||
* {@link Ordered} decorator for {@link ProxyDataSource}.
|
||||
*
|
||||
* @author Arthur Gavlyukovskiy
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public class DataSourceProxyDataSourceDecorator implements DataSourceDecorator, Ordered {
|
||||
|
||||
private final DataSourceProxyBuilderCustomizer dataSourceProxyBuilderCustomizer;
|
||||
|
||||
private final DataSourceNameResolver dataSourceNameResolver;
|
||||
|
||||
public DataSourceProxyDataSourceDecorator(DataSourceProxyBuilderCustomizer dataSourceProxyBuilderCustomizer,
|
||||
DataSourceNameResolver dataSourceNameResolver) {
|
||||
this.dataSourceProxyBuilderCustomizer = dataSourceProxyBuilderCustomizer;
|
||||
this.dataSourceNameResolver = dataSourceNameResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSource decorate(String beanName, DataSource dataSource) {
|
||||
ProxyDataSourceBuilder proxyDataSourceBuilder = ProxyDataSourceBuilder.create();
|
||||
proxyDataSourceBuilder = this.dataSourceProxyBuilderCustomizer.customize(proxyDataSourceBuilder);
|
||||
String dataSourceName = this.dataSourceNameResolver.resolveDataSourceName(dataSource);
|
||||
return proxyDataSourceBuilder.dataSource(dataSource).name(dataSourceName).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 20;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.sleuth.instrument.jdbc;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import net.ttddyy.dsproxy.QueryCountHolder;
|
||||
import net.ttddyy.dsproxy.listener.logging.CommonsLogLevel;
|
||||
import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel;
|
||||
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
|
||||
|
||||
/**
|
||||
* Properties for datasource-proxy.
|
||||
*
|
||||
* @author Arthur Gavlyukovskiy
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public class DataSourceProxyProperties {
|
||||
|
||||
/**
|
||||
* Logging to use for logging queries.
|
||||
*/
|
||||
private DataSourceProxyLogging logging = DataSourceProxyLogging.SLF4J;
|
||||
|
||||
/**
|
||||
* Query configuration.
|
||||
*/
|
||||
private Query query = new Query();
|
||||
|
||||
/**
|
||||
* Slow query configuration.
|
||||
*/
|
||||
private SlowQuery slowQuery = new SlowQuery();
|
||||
|
||||
/**
|
||||
* Use multiline output for logging query.
|
||||
*
|
||||
* @see ProxyDataSourceBuilder#multiline()
|
||||
*/
|
||||
private boolean multiline = true;
|
||||
|
||||
/**
|
||||
* Use json output for logging query.
|
||||
*
|
||||
* @see ProxyDataSourceBuilder#asJson()
|
||||
*/
|
||||
private boolean jsonFormat = false;
|
||||
|
||||
/**
|
||||
* Creates listener to count queries.
|
||||
*
|
||||
* @see ProxyDataSourceBuilder#countQuery()
|
||||
* @see QueryCountHolder
|
||||
*/
|
||||
private boolean countQuery = false;
|
||||
|
||||
public DataSourceProxyLogging getLogging() {
|
||||
return logging;
|
||||
}
|
||||
|
||||
public void setLogging(DataSourceProxyLogging logging) {
|
||||
this.logging = logging;
|
||||
}
|
||||
|
||||
public Query getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
public void setQuery(Query query) {
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
public SlowQuery getSlowQuery() {
|
||||
return slowQuery;
|
||||
}
|
||||
|
||||
public void setSlowQuery(SlowQuery slowQuery) {
|
||||
this.slowQuery = slowQuery;
|
||||
}
|
||||
|
||||
public boolean isMultiline() {
|
||||
return multiline;
|
||||
}
|
||||
|
||||
public void setMultiline(boolean multiline) {
|
||||
this.multiline = multiline;
|
||||
}
|
||||
|
||||
public boolean isJsonFormat() {
|
||||
return jsonFormat;
|
||||
}
|
||||
|
||||
public void setJsonFormat(boolean jsonFormat) {
|
||||
this.jsonFormat = jsonFormat;
|
||||
}
|
||||
|
||||
public boolean isCountQuery() {
|
||||
return countQuery;
|
||||
}
|
||||
|
||||
public void setCountQuery(boolean countQuery) {
|
||||
this.countQuery = countQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Properties to configure query logging listener.
|
||||
*
|
||||
* @see ProxyDataSourceBuilder#logQueryToSysOut()
|
||||
* @see ProxyDataSourceBuilder#logQueryBySlf4j(SLF4JLogLevel, String)
|
||||
* @see ProxyDataSourceBuilder#logQueryByCommons(CommonsLogLevel, String)
|
||||
* @see ProxyDataSourceBuilder#logQueryByJUL(Level, String)
|
||||
*/
|
||||
public static class Query {
|
||||
|
||||
/**
|
||||
* Enable logging all queries to the log.
|
||||
*/
|
||||
private boolean enableLogging = true;
|
||||
|
||||
/**
|
||||
* Name of query logger.
|
||||
*/
|
||||
private String loggerName;
|
||||
|
||||
/**
|
||||
* Severity of query logger.
|
||||
*/
|
||||
private String logLevel = "DEBUG";
|
||||
|
||||
public boolean isEnableLogging() {
|
||||
return enableLogging;
|
||||
}
|
||||
|
||||
public void setEnableLogging(boolean enableLogging) {
|
||||
this.enableLogging = enableLogging;
|
||||
}
|
||||
|
||||
public String getLoggerName() {
|
||||
return loggerName;
|
||||
}
|
||||
|
||||
public void setLoggerName(String loggerName) {
|
||||
this.loggerName = loggerName;
|
||||
}
|
||||
|
||||
public String getLogLevel() {
|
||||
return logLevel;
|
||||
}
|
||||
|
||||
public void setLogLevel(String logLevel) {
|
||||
this.logLevel = logLevel;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Properties to configure slow query logging listener.
|
||||
*
|
||||
* @see ProxyDataSourceBuilder#logSlowQueryToSysOut(long, TimeUnit)
|
||||
* @see ProxyDataSourceBuilder#logSlowQueryBySlf4j(long, TimeUnit)
|
||||
* @see ProxyDataSourceBuilder#logSlowQueryByCommons(long, TimeUnit)
|
||||
* @see ProxyDataSourceBuilder#logSlowQueryByJUL(long, TimeUnit)
|
||||
*/
|
||||
public static class SlowQuery {
|
||||
|
||||
/**
|
||||
* Enable logging slow queries to the log.
|
||||
*/
|
||||
private boolean enableLogging = true;
|
||||
|
||||
/**
|
||||
* Name of slow query logger.
|
||||
*/
|
||||
private String loggerName;
|
||||
|
||||
/**
|
||||
* Severity of slow query logger.
|
||||
*/
|
||||
private String logLevel = "WARN";
|
||||
|
||||
/**
|
||||
* Number of seconds to consider query as slow.
|
||||
*/
|
||||
private long threshold = 300;
|
||||
|
||||
boolean isEnableLogging() {
|
||||
return enableLogging;
|
||||
}
|
||||
|
||||
public void setEnableLogging(boolean enableLogging) {
|
||||
this.enableLogging = enableLogging;
|
||||
}
|
||||
|
||||
public String getLoggerName() {
|
||||
return loggerName;
|
||||
}
|
||||
|
||||
public void setLoggerName(String loggerName) {
|
||||
this.loggerName = loggerName;
|
||||
}
|
||||
|
||||
public String getLogLevel() {
|
||||
return logLevel;
|
||||
}
|
||||
|
||||
public void setLogLevel(String logLevel) {
|
||||
this.logLevel = logLevel;
|
||||
}
|
||||
|
||||
public long getThreshold() {
|
||||
return threshold;
|
||||
}
|
||||
|
||||
public void setThreshold(long threshold) {
|
||||
this.threshold = threshold;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Query logging listener is the most used listener that logs executing query with
|
||||
* actual parameters to. You can pick one of the following proxy logging mechanisms.
|
||||
*/
|
||||
public enum DataSourceProxyLogging {
|
||||
|
||||
/**
|
||||
* Log using System.out.
|
||||
*/
|
||||
SYSOUT,
|
||||
|
||||
/**
|
||||
* Log using SLF4J.
|
||||
*/
|
||||
SLF4J,
|
||||
|
||||
/**
|
||||
* Log using Commons.
|
||||
*/
|
||||
COMMONS,
|
||||
|
||||
/**
|
||||
* Log using Java Util Logging.
|
||||
*/
|
||||
JUL
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.sleuth.instrument.jdbc;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.jdbc.datasource.DelegatingDataSource;
|
||||
|
||||
/**
|
||||
* Interface that implicitly added to the CGLIB proxy of {@link DataSource}.
|
||||
*
|
||||
* Returns link of both real {@link DataSource}, decorated {@link DataSource} and all
|
||||
* decorating chain including decorator bean name, instance and result of decorating.
|
||||
*
|
||||
* @author Arthur Gavlyukovskiy
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public class DataSourceWrapper extends DelegatingDataSource {
|
||||
|
||||
private final DataSource originalDataSource;
|
||||
|
||||
public DataSourceWrapper(DataSource originalDataSource, DataSource decoratedDataSource) {
|
||||
super(decoratedDataSource);
|
||||
this.originalDataSource = originalDataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns original data source, before applying any decorator.
|
||||
* @return original data source
|
||||
*/
|
||||
public DataSource getOriginalDataSource() {
|
||||
return originalDataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns wrapped data source with the decorator applied.
|
||||
* @return decorated data source
|
||||
*/
|
||||
public DataSource getDecoratedDataSource() {
|
||||
return getTargetDataSource();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.sleuth.instrument.jdbc;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.p6spy.engine.event.CompoundJdbcEventListener;
|
||||
import com.p6spy.engine.event.JdbcEventListener;
|
||||
import com.p6spy.engine.spy.JdbcEventListenerFactory;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Factory to support all defined {@link JdbcEventListener} in the context.
|
||||
*
|
||||
* @author Arthur Gavlyukovskiy
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public class P6SpyContextJdbcEventListenerFactory implements JdbcEventListenerFactory {
|
||||
|
||||
private final CompoundJdbcEventListener compoundJdbcEventListener;
|
||||
|
||||
public P6SpyContextJdbcEventListenerFactory(JdbcEventListenerFactory delegate, List<JdbcEventListener> listeners) {
|
||||
Assert.notNull(delegate, "JdbcEventListenerFactory should not be null");
|
||||
Assert.notEmpty(listeners, "Listeners should not be empty");
|
||||
|
||||
JdbcEventListener jdbcEventListener = delegate.createJdbcEventListener();
|
||||
if (jdbcEventListener instanceof CompoundJdbcEventListener) {
|
||||
this.compoundJdbcEventListener = (CompoundJdbcEventListener) jdbcEventListener;
|
||||
}
|
||||
else {
|
||||
this.compoundJdbcEventListener = new CompoundJdbcEventListener();
|
||||
this.compoundJdbcEventListener.addListener(jdbcEventListener);
|
||||
}
|
||||
listeners.forEach(this.compoundJdbcEventListener::addListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JdbcEventListener createJdbcEventListener() {
|
||||
return this.compoundJdbcEventListener;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.sleuth.instrument.jdbc;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import com.p6spy.engine.spy.JdbcEventListenerFactory;
|
||||
import com.p6spy.engine.spy.P6DataSource;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
|
||||
/**
|
||||
* {@link Ordered} decorator for {@link P6DataSource}.
|
||||
*
|
||||
* @author Arthur Gavlyukovskiy
|
||||
*/
|
||||
public class P6SpyDataSourceDecorator implements DataSourceDecorator, Ordered {
|
||||
|
||||
/**
|
||||
* Bean order.
|
||||
*/
|
||||
public static final int ORDER = 30;
|
||||
|
||||
private final JdbcEventListenerFactory jdbcEventListenerFactory;
|
||||
|
||||
public P6SpyDataSourceDecorator(JdbcEventListenerFactory jdbcEventListenerFactory) {
|
||||
this.jdbcEventListenerFactory = jdbcEventListenerFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSource decorate(String beanName, DataSource dataSource) {
|
||||
P6DataSource p6DataSource = new P6DataSource(dataSource);
|
||||
p6DataSource.setJdbcEventListenerFactory(this.jdbcEventListenerFactory);
|
||||
return p6DataSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return P6SpyDataSourceDecorator.ORDER;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.sleuth.instrument.jdbc;
|
||||
|
||||
import org.springframework.cloud.sleuth.docs.DocumentedSpan;
|
||||
import org.springframework.cloud.sleuth.docs.EventValue;
|
||||
import org.springframework.cloud.sleuth.docs.TagKey;
|
||||
|
||||
enum SleuthJdbcSpan implements DocumentedSpan {
|
||||
|
||||
/**
|
||||
* Span created when a JDBC query gets executed.
|
||||
*/
|
||||
JDBC_QUERY_SPAN {
|
||||
@Override
|
||||
public String getName() {
|
||||
return "%s";
|
||||
}
|
||||
|
||||
@Override
|
||||
public TagKey[] getTagKeys() {
|
||||
return QueryTags.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventValue[] getEvents() {
|
||||
return QueryEvents.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String prefix() {
|
||||
return "jdbc.";
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Span created when working with JDBC result set.
|
||||
*/
|
||||
JDBC_RESULT_SET_SPAN {
|
||||
@Override
|
||||
public String getName() {
|
||||
return "result-set";
|
||||
}
|
||||
|
||||
@Override
|
||||
public TagKey[] getTagKeys() {
|
||||
return QueryTags.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventValue[] getEvents() {
|
||||
return QueryEvents.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String prefix() {
|
||||
return "jdbc.";
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Span created when a JDBC connection takes place.
|
||||
*/
|
||||
JDBC_CONNECTION_SPAN {
|
||||
@Override
|
||||
public String getName() {
|
||||
return "connection";
|
||||
}
|
||||
|
||||
@Override
|
||||
public TagKey[] getTagKeys() {
|
||||
return ConnectionTags.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String prefix() {
|
||||
return "jdbc.";
|
||||
}
|
||||
};
|
||||
|
||||
enum ConnectionTags implements TagKey {
|
||||
|
||||
/**
|
||||
* Name of the JDBC datasource driver.
|
||||
*/
|
||||
DATASOURCE_DRIVER {
|
||||
@Override
|
||||
public String getKey() {
|
||||
return "jdbc.datasource.driver";
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Name of the JDBC datasource pool.
|
||||
*/
|
||||
DATASOURCE_POOL {
|
||||
@Override
|
||||
public String getKey() {
|
||||
return "jdbc.datasource.pool";
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
enum QueryTags implements TagKey {
|
||||
|
||||
/**
|
||||
* The SQL query value.
|
||||
*/
|
||||
QUERY {
|
||||
@Override
|
||||
public String getKey() {
|
||||
return "jdbc.query";
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Number of SQL rows.
|
||||
*/
|
||||
ROW_COUNT {
|
||||
@Override
|
||||
public String getKey() {
|
||||
return "jdbc.row-count";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum QueryEvents implements EventValue {
|
||||
|
||||
/**
|
||||
* When the transaction gets commited.
|
||||
*/
|
||||
COMMIT {
|
||||
@Override
|
||||
public String getValue() {
|
||||
return "jdbc.commit";
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* When the transaction gets rolled back.
|
||||
*/
|
||||
ROLLBACK {
|
||||
@Override
|
||||
public String getValue() {
|
||||
return "jdbc.rollback";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.sleuth.instrument.jdbc;
|
||||
|
||||
import javax.sql.CommonDataSource;
|
||||
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
|
||||
import org.springframework.cloud.sleuth.Span;
|
||||
import org.springframework.cloud.sleuth.docs.AssertingSpanBuilder;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Customizer for {@link TraceListenerStrategy} for a {@link HikariDataSource}.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public class TraceHikariListenerStrategySpanCustomizer
|
||||
implements TraceListenerStrategySpanCustomizer<HikariDataSource> {
|
||||
|
||||
@Override
|
||||
public void customizeConnectionSpan(HikariDataSource hikariDataSource, Span.Builder spanBuilder) {
|
||||
AssertingSpanBuilder assertingSpanBuilder = AssertingSpanBuilder.of(SleuthJdbcSpan.JDBC_CONNECTION_SPAN,
|
||||
spanBuilder);
|
||||
if (StringUtils.hasText(hikariDataSource.getDriverClassName())) {
|
||||
assertingSpanBuilder.tag(SleuthJdbcSpan.ConnectionTags.DATASOURCE_DRIVER,
|
||||
hikariDataSource.getDriverClassName());
|
||||
}
|
||||
if (StringUtils.hasText(hikariDataSource.getPoolName())) {
|
||||
assertingSpanBuilder.tag(SleuthJdbcSpan.ConnectionTags.DATASOURCE_POOL, hikariDataSource.getPoolName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(CommonDataSource dataSource) {
|
||||
return dataSource instanceof HikariDataSource;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.sleuth.instrument.jdbc;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
import javax.sql.CommonDataSource;
|
||||
|
||||
import com.p6spy.engine.common.ConnectionInformation;
|
||||
import com.p6spy.engine.common.PreparedStatementInformation;
|
||||
import com.p6spy.engine.common.ResultSetInformation;
|
||||
import com.p6spy.engine.common.StatementInformation;
|
||||
import com.p6spy.engine.event.SimpleJdbcEventListener;
|
||||
|
||||
import org.springframework.cloud.sleuth.Tracer;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Listener to represent each connection and sql query as a span.
|
||||
*
|
||||
* @author Arthur Gavlyukovskiy
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public class TraceJdbcEventListener extends SimpleJdbcEventListener implements Ordered {
|
||||
|
||||
/**
|
||||
* Bean order.
|
||||
*/
|
||||
public static final int ORDER = Ordered.HIGHEST_PRECEDENCE + 10;
|
||||
|
||||
private final DataSourceNameResolver dataSourceNameResolver;
|
||||
|
||||
private final TraceListenerStrategy<ConnectionInformation, StatementInformation, ResultSetInformation> strategy;
|
||||
|
||||
private final boolean includeParameterValues;
|
||||
|
||||
public TraceJdbcEventListener(Tracer tracer, DataSourceNameResolver dataSourceNameResolver,
|
||||
List<TraceType> traceTypes, boolean includeParameterValues,
|
||||
List<TraceListenerStrategySpanCustomizer> customizers) {
|
||||
this.dataSourceNameResolver = dataSourceNameResolver;
|
||||
this.includeParameterValues = includeParameterValues;
|
||||
this.strategy = new TraceListenerStrategy<>(tracer, traceTypes, customizers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBeforeGetConnection(ConnectionInformation connectionInformation) {
|
||||
CommonDataSource dataSource = connectionInformation.getDataSource();
|
||||
String dataSourceName = this.dataSourceNameResolver.resolveDataSourceName(dataSource);
|
||||
this.strategy.beforeGetConnection(connectionInformation, dataSource, dataSourceName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAfterGetConnection(ConnectionInformation connectionInformation, SQLException e) {
|
||||
this.strategy.afterGetConnection(connectionInformation, connectionInformation.getConnection(), e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBeforeAnyExecute(StatementInformation statementInformation) {
|
||||
String dataSourceName = this.dataSourceNameResolver
|
||||
.resolveDataSourceName(statementInformation.getConnectionInformation().getDataSource());
|
||||
this.strategy.beforeQuery(statementInformation.getConnectionInformation(),
|
||||
statementInformation.getConnectionInformation().getConnection(), statementInformation, dataSourceName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAfterAnyExecute(StatementInformation statementInformation, long timeElapsedNanos, SQLException e) {
|
||||
this.strategy.afterQuery(statementInformation.getConnectionInformation(), statementInformation,
|
||||
getSql(statementInformation), e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBeforeResultSetNext(ResultSetInformation resultSetInformation) {
|
||||
String dataSourceName = this.dataSourceNameResolver
|
||||
.resolveDataSourceName(resultSetInformation.getConnectionInformation().getDataSource());
|
||||
this.strategy.beforeResultSetNext(resultSetInformation.getConnectionInformation(),
|
||||
resultSetInformation.getConnectionInformation().getConnection(),
|
||||
resultSetInformation.getStatementInformation(), resultSetInformation, dataSourceName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAfterExecuteUpdate(PreparedStatementInformation statementInformation, long timeElapsedNanos,
|
||||
int rowCount, SQLException e) {
|
||||
if (e == null) {
|
||||
this.strategy.addQueryRowCount(statementInformation.getConnectionInformation(), statementInformation,
|
||||
rowCount);
|
||||
}
|
||||
super.onAfterExecuteUpdate(statementInformation, timeElapsedNanos, rowCount, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAfterExecuteUpdate(StatementInformation statementInformation, long timeElapsedNanos, String sql,
|
||||
int rowCount, SQLException e) {
|
||||
if (e == null) {
|
||||
this.strategy.addQueryRowCount(statementInformation.getConnectionInformation(), statementInformation,
|
||||
rowCount);
|
||||
}
|
||||
super.onAfterExecuteUpdate(statementInformation, timeElapsedNanos, sql, rowCount, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAfterStatementClose(StatementInformation statementInformation, SQLException e) {
|
||||
this.strategy.afterStatementClose(statementInformation.getConnectionInformation(), statementInformation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAfterResultSetClose(ResultSetInformation resultSetInformation, SQLException e) {
|
||||
this.strategy.afterResultSetClose(resultSetInformation.getConnectionInformation(), resultSetInformation,
|
||||
resultSetInformation.getCurrRow() + 1, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAfterCommit(ConnectionInformation connectionInformation, long timeElapsedNanos, SQLException e) {
|
||||
this.strategy.afterCommit(connectionInformation, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAfterRollback(ConnectionInformation connectionInformation, long timeElapsedNanos, SQLException e) {
|
||||
this.strategy.afterRollback(connectionInformation, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAfterConnectionClose(ConnectionInformation connectionInformation, SQLException e) {
|
||||
this.strategy.afterConnectionClose(connectionInformation, e);
|
||||
}
|
||||
|
||||
private String getSql(StatementInformation statementInformation) {
|
||||
return this.includeParameterValues && StringUtils.hasText(statementInformation.getSqlWithValues())
|
||||
? statementInformation.getSqlWithValues() : statementInformation.getSql();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return ORDER;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,402 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.sleuth.instrument.jdbc;
|
||||
|
||||
import java.net.URI;
|
||||
import java.sql.Connection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.sql.CommonDataSource;
|
||||
|
||||
import org.springframework.cloud.sleuth.Span;
|
||||
import org.springframework.cloud.sleuth.SpanAndScope;
|
||||
import org.springframework.cloud.sleuth.Tracer;
|
||||
import org.springframework.cloud.sleuth.docs.AssertingSpan;
|
||||
import org.springframework.cloud.sleuth.docs.AssertingSpanBuilder;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Partially taken from
|
||||
* https://github.com/openzipkin/brave/blob/v5.6.4/instrumentation/p6spy/src/main/java/brave/p6spy/TracingJdbcEventListener.java.
|
||||
*
|
||||
* @param <CON> connection type
|
||||
* @param <STMT> statement
|
||||
* @param <RS> result set
|
||||
*/
|
||||
class TraceListenerStrategy<CON, STMT, RS> {
|
||||
|
||||
private final Map<CON, ConnectionInfo> openConnections = new ConcurrentHashMap<>();
|
||||
|
||||
// Captures all the characters between = and either the next & or the end of the
|
||||
// string.
|
||||
private static final Pattern URL_SERVICE_NAME_FINDER = Pattern.compile("sleuthServiceName=(.*?)(?:&|$)");
|
||||
|
||||
private final Tracer tracer;
|
||||
|
||||
private final List<TraceType> traceTypes;
|
||||
|
||||
private final List<TraceListenerStrategySpanCustomizer> customizers;
|
||||
|
||||
TraceListenerStrategy(Tracer tracer, List<TraceType> traceTypes,
|
||||
List<TraceListenerStrategySpanCustomizer> customizers) {
|
||||
this.tracer = tracer;
|
||||
this.traceTypes = traceTypes;
|
||||
this.customizers = customizers;
|
||||
}
|
||||
|
||||
void beforeGetConnection(CON connectionKey, @Nullable CommonDataSource dataSource, String dataSourceName) {
|
||||
SpanAndScope spanAndScope = null;
|
||||
if (this.traceTypes.contains(TraceType.CONNECTION)) {
|
||||
AssertingSpanBuilder connectionSpanBuilder = AssertingSpanBuilder
|
||||
.of(SleuthJdbcSpan.JDBC_CONNECTION_SPAN, tracer.spanBuilder())
|
||||
.name(SleuthJdbcSpan.JDBC_CONNECTION_SPAN.getName());
|
||||
connectionSpanBuilder.remoteServiceName(dataSourceName);
|
||||
connectionSpanBuilder.kind(Span.Kind.CLIENT);
|
||||
this.customizers.stream().filter(customizer -> customizer.isApplicable(dataSource))
|
||||
.forEach(customizer -> customizer.customizeConnectionSpan(dataSource, connectionSpanBuilder));
|
||||
Span connectionSpan = connectionSpanBuilder.start();
|
||||
spanAndScope = new SpanAndScope(connectionSpan, tracer.withSpan(connectionSpan));
|
||||
}
|
||||
ConnectionInfo connectionInfo = new ConnectionInfo(spanAndScope);
|
||||
this.openConnections.put(connectionKey, connectionInfo);
|
||||
}
|
||||
|
||||
void afterGetConnection(CON connectionKey, Connection connection, Throwable t) {
|
||||
if (t != null) {
|
||||
ConnectionInfo connectionInfo = this.openConnections.remove(connectionKey);
|
||||
connectionInfo.getSpan().ifPresent(connectionSpan -> {
|
||||
parseServerIpAndPort(connection, connectionSpan.getSpan());
|
||||
connectionSpan.getSpan().error(t);
|
||||
connectionSpan.close();
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.openConnections.get(connectionKey).getSpan().ifPresent(spanAndScope -> {
|
||||
parseServerIpAndPort(connection, spanAndScope.getSpan());
|
||||
});
|
||||
}
|
||||
|
||||
void beforeQuery(CON connectionKey, Connection connection, STMT statementKey, String dataSourceName) {
|
||||
SpanAndScope SpanAndScope = null;
|
||||
if (traceTypes.contains(TraceType.QUERY)) {
|
||||
Span.Builder statementSpanBuilder = AssertingSpanBuilder
|
||||
.of(SleuthJdbcSpan.JDBC_QUERY_SPAN, tracer.spanBuilder())
|
||||
.name(SleuthJdbcSpan.JDBC_QUERY_SPAN.getName());
|
||||
statementSpanBuilder.remoteServiceName(dataSourceName);
|
||||
parseServerIpAndPort(connection, statementSpanBuilder);
|
||||
statementSpanBuilder.kind(Span.Kind.CLIENT);
|
||||
Span statementSpan = statementSpanBuilder.start();
|
||||
SpanAndScope = new SpanAndScope(statementSpan, tracer.withSpan(statementSpan));
|
||||
}
|
||||
StatementInfo statementInfo = new StatementInfo(SpanAndScope);
|
||||
ConnectionInfo connectionInfo = openConnections.get(connectionKey);
|
||||
if (connectionInfo == null) {
|
||||
// Connection may be closed after statement preparation, but before statement
|
||||
// execution.
|
||||
return;
|
||||
}
|
||||
connectionInfo.getNestedStatements().put(statementKey, statementInfo);
|
||||
}
|
||||
|
||||
void addQueryRowCount(CON connectionKey, STMT statementKey, int rowCount) {
|
||||
ConnectionInfo connectionInfo = openConnections.get(connectionKey);
|
||||
if (connectionInfo == null) {
|
||||
// Connection is already closed
|
||||
return;
|
||||
}
|
||||
StatementInfo statementInfo = connectionInfo.getNestedStatements().get(statementKey);
|
||||
statementInfo.getSpan()
|
||||
.ifPresent(statementSpan -> AssertingSpan.of(SleuthJdbcSpan.JDBC_QUERY_SPAN, statementSpan.getSpan())
|
||||
.tag(SleuthJdbcSpan.QueryTags.ROW_COUNT, String.valueOf(rowCount)));
|
||||
}
|
||||
|
||||
void afterQuery(CON connectionKey, STMT statementKey, String sql, Throwable t) {
|
||||
ConnectionInfo connectionInfo = openConnections.get(connectionKey);
|
||||
if (connectionInfo == null) {
|
||||
// Connection may be closed after statement preparation, but before statement
|
||||
// execution.
|
||||
return;
|
||||
}
|
||||
StatementInfo statementInfo = connectionInfo.getNestedStatements().get(statementKey);
|
||||
statementInfo.getSpan().ifPresent(statementSpan -> {
|
||||
AssertingSpan.of(SleuthJdbcSpan.JDBC_QUERY_SPAN, statementSpan.getSpan())
|
||||
.tag(SleuthJdbcSpan.QueryTags.QUERY, sql).name(spanName(sql));
|
||||
if (t != null) {
|
||||
statementSpan.getSpan().error(t);
|
||||
}
|
||||
statementSpan.close();
|
||||
});
|
||||
}
|
||||
|
||||
void beforeResultSetNext(CON connectionKey, Connection connection, STMT statementKey, RS resultSetKey,
|
||||
String dataSourceName) {
|
||||
if (!traceTypes.contains(TraceType.FETCH)) {
|
||||
return;
|
||||
}
|
||||
ConnectionInfo connectionInfo = openConnections.get(connectionKey);
|
||||
// ConnectionInfo may be null if Connection was closed before ResultSet
|
||||
if (connectionInfo == null) {
|
||||
return;
|
||||
}
|
||||
if (connectionInfo.getNestedResultSetSpans().containsKey(resultSetKey)) {
|
||||
// ResultSet span is already created
|
||||
return;
|
||||
}
|
||||
AssertingSpanBuilder resultSetSpanBuilder = AssertingSpanBuilder
|
||||
.of(SleuthJdbcSpan.JDBC_RESULT_SET_SPAN, tracer.spanBuilder())
|
||||
.name(SleuthJdbcSpan.JDBC_RESULT_SET_SPAN.getName());
|
||||
resultSetSpanBuilder.remoteServiceName(dataSourceName);
|
||||
resultSetSpanBuilder.kind(Span.Kind.CLIENT);
|
||||
parseServerIpAndPort(connection, resultSetSpanBuilder);
|
||||
Span resultSetSpan = resultSetSpanBuilder.start();
|
||||
SpanAndScope SpanAndScope = new SpanAndScope(resultSetSpan, tracer.withSpan(resultSetSpan));
|
||||
connectionInfo.getNestedResultSetSpans().put(resultSetKey, SpanAndScope);
|
||||
StatementInfo statementInfo = connectionInfo.getNestedStatements().get(statementKey);
|
||||
// StatementInfo may be null when Statement is proxied and instance returned from
|
||||
// ResultSet is different from instance returned in query method
|
||||
// in this case if Statement is closed before ResultSet span won't be finished
|
||||
// immediately, but when Connection is closed
|
||||
if (statementInfo != null) {
|
||||
statementInfo.getNestedResultSetSpans().put(resultSetKey, SpanAndScope);
|
||||
}
|
||||
}
|
||||
|
||||
void afterResultSetClose(CON connectionKey, RS resultSetKey, int rowCount, Throwable t) {
|
||||
ConnectionInfo connectionInfo = openConnections.get(connectionKey);
|
||||
// ConnectionInfo may be null if Connection was closed before ResultSet
|
||||
if (connectionInfo == null) {
|
||||
return;
|
||||
}
|
||||
SpanAndScope resultSetSpan = connectionInfo.getNestedResultSetSpans().remove(resultSetKey);
|
||||
// ResultSet span may be null if Statement or ResultSet were already closed
|
||||
if (resultSetSpan == null) {
|
||||
return;
|
||||
}
|
||||
if (rowCount != -1) {
|
||||
AssertingSpan.of(SleuthJdbcSpan.JDBC_RESULT_SET_SPAN, resultSetSpan.getSpan())
|
||||
.tag(SleuthJdbcSpan.QueryTags.ROW_COUNT, String.valueOf(rowCount));
|
||||
}
|
||||
if (t != null) {
|
||||
resultSetSpan.getSpan().error(t);
|
||||
}
|
||||
resultSetSpan.close();
|
||||
}
|
||||
|
||||
void afterStatementClose(CON connectionKey, STMT statementKey) {
|
||||
ConnectionInfo connectionInfo = openConnections.get(connectionKey);
|
||||
// ConnectionInfo may be null if Connection was closed before Statement
|
||||
if (connectionInfo == null) {
|
||||
return;
|
||||
}
|
||||
StatementInfo statementInfo = connectionInfo.getNestedStatements().remove(statementKey);
|
||||
if (statementInfo != null) {
|
||||
statementInfo.getNestedResultSetSpans().forEach((resultSetKey, span) -> {
|
||||
connectionInfo.getNestedResultSetSpans().remove(resultSetKey);
|
||||
span.close();
|
||||
});
|
||||
statementInfo.getNestedResultSetSpans().clear();
|
||||
}
|
||||
}
|
||||
|
||||
void afterCommit(CON connectionKey, Throwable t) {
|
||||
ConnectionInfo connectionInfo = openConnections.get(connectionKey);
|
||||
if (connectionInfo == null) {
|
||||
// Connection is already closed
|
||||
return;
|
||||
}
|
||||
connectionInfo.getSpan().ifPresent(connectionSpan -> {
|
||||
if (t != null) {
|
||||
connectionSpan.getSpan().error(t);
|
||||
}
|
||||
AssertingSpan.of(SleuthJdbcSpan.JDBC_QUERY_SPAN, connectionSpan.getSpan())
|
||||
.event(SleuthJdbcSpan.QueryEvents.COMMIT);
|
||||
});
|
||||
}
|
||||
|
||||
void afterRollback(CON connectionKey, Throwable t) {
|
||||
ConnectionInfo connectionInfo = openConnections.get(connectionKey);
|
||||
if (connectionInfo == null) {
|
||||
// Connection is already closed
|
||||
return;
|
||||
}
|
||||
connectionInfo.getSpan().ifPresent(connectionSpan -> {
|
||||
if (t != null) {
|
||||
connectionSpan.getSpan().error(t);
|
||||
}
|
||||
else {
|
||||
connectionSpan.getSpan().error(new JdbcException("Transaction rolled back"));
|
||||
}
|
||||
AssertingSpan.of(SleuthJdbcSpan.JDBC_QUERY_SPAN, connectionSpan.getSpan())
|
||||
.event(SleuthJdbcSpan.QueryEvents.ROLLBACK);
|
||||
});
|
||||
}
|
||||
|
||||
void afterConnectionClose(CON connectionKey, Throwable t) {
|
||||
ConnectionInfo connectionInfo = openConnections.remove(connectionKey);
|
||||
if (connectionInfo == null) {
|
||||
// connection is already closed
|
||||
return;
|
||||
}
|
||||
connectionInfo.getNestedResultSetSpans().values().forEach(SpanAndScope::close);
|
||||
connectionInfo.getNestedStatements().values()
|
||||
.forEach(statementInfo -> statementInfo.getSpan().ifPresent(SpanAndScope::close));
|
||||
connectionInfo.getSpan().ifPresent(connectionSpan -> {
|
||||
if (t != null) {
|
||||
connectionSpan.getSpan().error(t);
|
||||
}
|
||||
connectionSpan.close();
|
||||
});
|
||||
}
|
||||
|
||||
private String spanName(String sql) {
|
||||
return sql.substring(0, sql.indexOf(' ')).toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
private void parseServerIpAndPort(Connection connection, Span.Builder span) {
|
||||
if (connection == null) {
|
||||
return;
|
||||
}
|
||||
UrlAndRemoteServiceName urlAndRemoteServiceName = parseServerIpAndPort(connection);
|
||||
span.remoteServiceName(urlAndRemoteServiceName.remoteServiceName);
|
||||
URI url = urlAndRemoteServiceName.url;
|
||||
if (url != null) {
|
||||
span.remoteIpAndPort(url.getHost(), url.getPort());
|
||||
}
|
||||
}
|
||||
|
||||
private void parseServerIpAndPort(Connection connection, Span span) {
|
||||
if (connection == null) {
|
||||
return;
|
||||
}
|
||||
UrlAndRemoteServiceName urlAndRemoteServiceName = parseServerIpAndPort(connection);
|
||||
span.remoteServiceName(urlAndRemoteServiceName.remoteServiceName);
|
||||
URI url = urlAndRemoteServiceName.url;
|
||||
if (url != null) {
|
||||
span.remoteIpAndPort(url.getHost(), url.getPort());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This attempts to get the ip and port from the JDBC URL. Ex. localhost and 5555 from
|
||||
* {@code
|
||||
* jdbc:mysql://localhost:5555/mydatabase}.
|
||||
*
|
||||
* Taken from Brave.
|
||||
*/
|
||||
private UrlAndRemoteServiceName parseServerIpAndPort(Connection connection) {
|
||||
String remoteServiceName = "";
|
||||
try {
|
||||
String urlAsString = connection.getMetaData().getURL().substring(5); // strip
|
||||
// "jdbc:"
|
||||
URI url = URI.create(urlAsString.replace(" ", "")); // Remove all white space
|
||||
// according to RFC 2396
|
||||
Matcher matcher = URL_SERVICE_NAME_FINDER.matcher(url.toString());
|
||||
if (matcher.find() && matcher.groupCount() == 1) {
|
||||
String parsedServiceName = matcher.group(1);
|
||||
if (parsedServiceName != null && !parsedServiceName.isEmpty()) {
|
||||
remoteServiceName = parsedServiceName;
|
||||
}
|
||||
}
|
||||
if (!StringUtils.hasText(remoteServiceName)) {
|
||||
String databaseName = connection.getCatalog();
|
||||
if (databaseName != null && !databaseName.isEmpty()) {
|
||||
remoteServiceName = databaseName;
|
||||
}
|
||||
}
|
||||
return new UrlAndRemoteServiceName(url, remoteServiceName);
|
||||
}
|
||||
catch (Exception e) {
|
||||
// remote address is optional
|
||||
return new UrlAndRemoteServiceName(null, remoteServiceName);
|
||||
}
|
||||
}
|
||||
|
||||
private final class ConnectionInfo {
|
||||
|
||||
private final SpanAndScope span;
|
||||
|
||||
private final Map<STMT, StatementInfo> nestedStatements = new ConcurrentHashMap<>();
|
||||
|
||||
private final Map<RS, SpanAndScope> nestedResultSetSpans = new ConcurrentHashMap<>();
|
||||
|
||||
private ConnectionInfo(@Nullable SpanAndScope span) {
|
||||
this.span = span;
|
||||
}
|
||||
|
||||
Optional<SpanAndScope> getSpan() {
|
||||
return Optional.ofNullable(span);
|
||||
}
|
||||
|
||||
Map<STMT, StatementInfo> getNestedStatements() {
|
||||
return nestedStatements;
|
||||
}
|
||||
|
||||
Map<RS, SpanAndScope> getNestedResultSetSpans() {
|
||||
return nestedResultSetSpans;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final class StatementInfo {
|
||||
|
||||
private final SpanAndScope span;
|
||||
|
||||
private final Map<RS, SpanAndScope> nestedResultSetSpans = new ConcurrentHashMap<>();
|
||||
|
||||
private StatementInfo(SpanAndScope span) {
|
||||
this.span = span;
|
||||
}
|
||||
|
||||
Optional<SpanAndScope> getSpan() {
|
||||
return Optional.ofNullable(span);
|
||||
}
|
||||
|
||||
Map<RS, SpanAndScope> getNestedResultSetSpans() {
|
||||
return nestedResultSetSpans;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final class UrlAndRemoteServiceName {
|
||||
|
||||
final URI url;
|
||||
|
||||
final String remoteServiceName;
|
||||
|
||||
private UrlAndRemoteServiceName(@Nullable URI url, String remoteServiceName) {
|
||||
this.url = url;
|
||||
this.remoteServiceName = remoteServiceName;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class JdbcException extends RuntimeException {
|
||||
|
||||
JdbcException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.sleuth.instrument.jdbc;
|
||||
|
||||
import javax.sql.CommonDataSource;
|
||||
|
||||
import org.springframework.cloud.sleuth.Span;
|
||||
|
||||
/**
|
||||
* Customizer for {@link TraceListenerStrategy} client span.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public interface TraceListenerStrategySpanCustomizer<T extends CommonDataSource> {
|
||||
|
||||
/**
|
||||
* Customizes the client database span.
|
||||
* @param spanBuilder span builder
|
||||
*/
|
||||
void customizeConnectionSpan(T dataSource, Span.Builder spanBuilder);
|
||||
|
||||
/**
|
||||
* @param dataSource data source for which we're building the span
|
||||
* @return {@code true} when this customizer can be applied
|
||||
*/
|
||||
boolean isApplicable(CommonDataSource dataSource);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.sleuth.instrument.jdbc;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.sql.CommonDataSource;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import net.ttddyy.dsproxy.ExecutionInfo;
|
||||
import net.ttddyy.dsproxy.QueryInfo;
|
||||
import net.ttddyy.dsproxy.listener.MethodExecutionContext;
|
||||
import net.ttddyy.dsproxy.listener.MethodExecutionListener;
|
||||
import net.ttddyy.dsproxy.listener.QueryExecutionListener;
|
||||
import net.ttddyy.dsproxy.support.ProxyDataSource;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.cloud.sleuth.Tracer;
|
||||
import org.springframework.core.Ordered;
|
||||
|
||||
/**
|
||||
* Listener to represent each connection and sql query as a span.
|
||||
*
|
||||
* @author Arthur Gavlyukovskiy
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public class TraceQueryExecutionListener implements QueryExecutionListener, MethodExecutionListener, Ordered {
|
||||
|
||||
private static final Log log = LogFactory.getLog(TraceQueryExecutionListener.class);
|
||||
|
||||
private final TraceListenerStrategy<String, Statement, ResultSet> strategy;
|
||||
|
||||
public TraceQueryExecutionListener(Tracer tracer, List<TraceType> traceTypes,
|
||||
List<TraceListenerStrategySpanCustomizer> customizers) {
|
||||
this.strategy = new TraceListenerStrategy<>(tracer, traceTypes, customizers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeQuery(ExecutionInfo execInfo, List<QueryInfo> queryInfoList) {
|
||||
this.strategy.beforeQuery(execInfo.getConnectionId(), connection(execInfo), execInfo.getStatement(),
|
||||
execInfo.getDataSourceName());
|
||||
}
|
||||
|
||||
private Connection connection(ExecutionInfo execInfo) {
|
||||
try {
|
||||
return execInfo.getStatement().getConnection();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Can't retrieve the connection - will return null", ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterQuery(ExecutionInfo execInfo, List<QueryInfo> queryInfoList) {
|
||||
if (execInfo.getMethod().getName().equals("executeUpdate") && execInfo.getThrowable() == null) {
|
||||
this.strategy.addQueryRowCount(execInfo.getConnectionId(), execInfo.getStatement(),
|
||||
(int) execInfo.getResult());
|
||||
}
|
||||
String sql = queryInfoList.stream().map(QueryInfo::getQuery).collect(Collectors.joining("\n"));
|
||||
this.strategy.afterQuery(execInfo.getConnectionId(), execInfo.getStatement(), sql, execInfo.getThrowable());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeMethod(MethodExecutionContext executionContext) {
|
||||
Object target = executionContext.getTarget();
|
||||
String methodName = executionContext.getMethod().getName();
|
||||
String dataSourceName = executionContext.getProxyConfig().getDataSourceName();
|
||||
String connectionId = executionContext.getConnectionInfo().getConnectionId();
|
||||
if (target instanceof DataSource && methodName.equals("getConnection")) {
|
||||
this.strategy.beforeGetConnection(connectionId, toCommonDataSource(target), dataSourceName);
|
||||
}
|
||||
else if (target instanceof ResultSet) {
|
||||
ResultSet resultSet = (ResultSet) target;
|
||||
if (methodName.equals("next")) {
|
||||
try {
|
||||
this.strategy.beforeResultSetNext(connectionId, resultSet.getStatement().getConnection(),
|
||||
resultSet.getStatement(), resultSet, dataSourceName);
|
||||
}
|
||||
catch (SQLException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CommonDataSource toCommonDataSource(Object target) {
|
||||
try {
|
||||
return ((DataSource) target).unwrap(CommonDataSource.class);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Failed to cast to common data source. Will return null", ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Connection getConnection(DataSource targetDataSource) {
|
||||
try {
|
||||
DataSource source = targetDataSource instanceof ProxyDataSource
|
||||
? (targetDataSource).unwrap(DataSource.class) : targetDataSource;
|
||||
return source.getConnection();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Failed to retrieve connection. Will return null", ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterMethod(MethodExecutionContext executionContext) {
|
||||
Object target = executionContext.getTarget();
|
||||
String methodName = executionContext.getMethod().getName();
|
||||
String connectionId = executionContext.getConnectionInfo().getConnectionId();
|
||||
Throwable t = executionContext.getThrown();
|
||||
if (target instanceof DataSource && methodName.equals("getConnection")) {
|
||||
this.strategy.afterGetConnection(connectionId, getConnection((DataSource) target), t);
|
||||
}
|
||||
else if (target instanceof Connection) {
|
||||
switch (methodName) {
|
||||
case "commit":
|
||||
this.strategy.afterCommit(connectionId, t);
|
||||
break;
|
||||
case "rollback":
|
||||
this.strategy.afterRollback(connectionId, t);
|
||||
break;
|
||||
case "close":
|
||||
this.strategy.afterConnectionClose(connectionId, t);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (target instanceof Statement && methodName.equals("close")) {
|
||||
this.strategy.afterStatementClose(connectionId, (Statement) target);
|
||||
}
|
||||
else if (target instanceof ResultSet && methodName.equals("close")) {
|
||||
ResultSet resultSet = (ResultSet) target;
|
||||
this.strategy.afterResultSetClose(connectionId, resultSet, -1, t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.HIGHEST_PRECEDENCE + 10;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.sleuth.instrument.jdbc;
|
||||
|
||||
/**
|
||||
* Trace types.
|
||||
*/
|
||||
public enum TraceType {
|
||||
|
||||
/**
|
||||
* Connection.
|
||||
*/
|
||||
CONNECTION,
|
||||
|
||||
/**
|
||||
* Query.
|
||||
*/
|
||||
QUERY,
|
||||
|
||||
/**
|
||||
* Fetch.
|
||||
*/
|
||||
FETCH
|
||||
|
||||
}
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.cloud.sleuth.instrument.r2dbc;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import io.r2dbc.proxy.core.QueryExecutionInfo;
|
||||
import io.r2dbc.proxy.core.QueryInfo;
|
||||
import io.r2dbc.proxy.listener.ProxyExecutionListener;
|
||||
@@ -84,7 +86,15 @@ public class TraceProxyExecutionListener implements ProxyExecutionListener {
|
||||
.tag(SleuthR2dbcSpan.Tags.THREAD, executionInfo.getThreadName());
|
||||
// @formatter:on
|
||||
if (StringUtils.hasText(url)) {
|
||||
builder.remoteUrl(url);
|
||||
try {
|
||||
URI uri = URI.create(url);
|
||||
builder.remoteIpAndPort(uri.getHost(), uri.getPort());
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Failed to parse the url [" + url + "]. Won't set this value as atag");
|
||||
}
|
||||
}
|
||||
}
|
||||
return builder.start();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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 org.springframework.cloud.sleuth.instrument.jdbc;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import com.p6spy.engine.event.CompoundJdbcEventListener;
|
||||
import com.p6spy.engine.event.SimpleJdbcEventListener;
|
||||
import com.p6spy.engine.spy.JdbcEventListenerFactory;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class ContextJdbcEventListenerFactoryTest {
|
||||
|
||||
private JdbcEventListenerFactory delegate;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
delegate = Mockito.mock(JdbcEventListenerFactory.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseDelegateToCreateListener() {
|
||||
SimpleJdbcEventListener listener1 = new SimpleJdbcEventListener() {
|
||||
};
|
||||
SimpleJdbcEventListener listener2 = new SimpleJdbcEventListener() {
|
||||
};
|
||||
Mockito.when(delegate.createJdbcEventListener()).thenReturn(listener1);
|
||||
P6SpyContextJdbcEventListenerFactory contextJdbcEventListenerFactory = new P6SpyContextJdbcEventListenerFactory(
|
||||
delegate, Collections.singletonList(listener2));
|
||||
|
||||
CompoundJdbcEventListener jdbcEventListener = (CompoundJdbcEventListener) contextJdbcEventListenerFactory
|
||||
.createJdbcEventListener();
|
||||
assertThat(jdbcEventListener.getEventListeners()).hasSize(2);
|
||||
assertThat(jdbcEventListener.getEventListeners()).contains(listener1, listener2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReuseCompoundListenerFromFactory() {
|
||||
SimpleJdbcEventListener listener1 = new SimpleJdbcEventListener() {
|
||||
};
|
||||
Mockito.when(delegate.createJdbcEventListener()).thenReturn(new CompoundJdbcEventListener());
|
||||
P6SpyContextJdbcEventListenerFactory contextJdbcEventListenerFactory = new P6SpyContextJdbcEventListenerFactory(
|
||||
delegate, Collections.singletonList(listener1));
|
||||
|
||||
CompoundJdbcEventListener jdbcEventListener = (CompoundJdbcEventListener) contextJdbcEventListenerFactory
|
||||
.createJdbcEventListener();
|
||||
assertThat(jdbcEventListener.getEventListeners()).hasSize(1);
|
||||
assertThat(jdbcEventListener.getEventListeners()).contains(listener1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -140,6 +140,7 @@ public class TraceAsyncIntegrationTests {
|
||||
|
||||
private void thenTraceIdIsPassedFromTheCurrentThreadToTheAsyncOneAndSpanHasCustomName(final Span span) {
|
||||
Awaitility.await().atMost(5, SECONDS).untilAsserted(() -> {
|
||||
then(TraceAsyncIntegrationTests.this.classPerformingAsyncLogic.getSpan()).isNotNull();
|
||||
then(TraceAsyncIntegrationTests.this.classPerformingAsyncLogic.getSpan().context().traceId())
|
||||
.isEqualTo(span.context().traceId());
|
||||
then(this.spans).hasSize(2);
|
||||
|
||||
Reference in New Issue
Block a user