From 682398daecb02ca24593b8c830b4d7cc2600c98b Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 26 May 2021 18:36:50 +0200 Subject: [PATCH] Polish --- docs/src/main/asciidoc/_configprops.adoc | 224 ++-- docs/src/main/asciidoc/integrations.adoc | 4 +- .../TraceAsyncDefaultAutoConfiguration.java | 304 ++--- .../jdbc/DataSourceProxyConfiguration.java | 3 +- .../jdbc/P6SpyPropertiesSetter.java | 20 +- ...eDataSourceDecoratorAutoConfiguration.java | 3 +- .../TraceDataSourceDecoratorProperties.java | 19 +- .../instrument/web/client/WebClientTests.java | 411 +++--- ...SourceDecoratorAutoConfigurationTests.java | 6 +- .../jdbc/P6SpyConfigurationTests.java | 561 ++++---- .../ProxyDataSourceConfigurationTests.java | 492 +++---- .../jdbc/TracingJdbcEventListenerTests.java | 4 +- .../jdbc/TracingListenerStrategyTests.java | 140 +- .../DataSourceProxyBuilderCustomizer.java | 3 - .../jdbc/DataSourceProxyProperties.java | 17 - .../instrument/jdbc/DataSourceWrapper.java | 5 +- .../jdbc/TraceListenerStrategy.java | 1012 +++++++------- .../instrument/kafka/KafkaProducerTest.java | 132 +- .../web/TraceAsyncIntegrationTests.java | 470 +++---- .../r2dbc/R2dbcIntegrationTests.java | 204 +-- .../integration/sampled/WebClientTests.java | 1194 ++++++++--------- 21 files changed, 2592 insertions(+), 2636 deletions(-) diff --git a/docs/src/main/asciidoc/_configprops.adoc b/docs/src/main/asciidoc/_configprops.adoc index 1399bc59a..26042bb09 100644 --- a/docs/src/main/asciidoc/_configprops.adoc +++ b/docs/src/main/asciidoc/_configprops.adoc @@ -1,112 +1,112 @@ -|=== -|Name | Default | Description - -|spring.sleuth.async.configurer.enabled | `true` | Enable default AsyncConfigurer. -|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 | | 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 | | 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. -|spring.sleuth.deployer.enabled | `true` | Enable Spring Cloud Deployer instrumentation. -|spring.sleuth.deployer.status-poll-delay | `500` | Default poll delay to retrieve the deployed application status. -|spring.sleuth.enabled | `true` | -|spring.sleuth.feign.enabled | `true` | Enable span information propagation when using Feign. -|spring.sleuth.feign.processor.enabled | `true` | Enable post processor that wraps Feign Context in its tracing representations. -|spring.sleuth.function.enabled | `true` | Enable instrumenting of Spring Cloud Function and Spring Cloud Function based projects (e.g. Spring Cloud Stream). -|spring.sleuth.grpc.enabled | `true` | Enable span information propagation when using GRPC. -|spring.sleuth.http.enabled | `true` | Enables HTTP support. -|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.

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. -|spring.sleuth.messaging.jms.enabled | `true` | Enable tracing of JMS. -|spring.sleuth.messaging.jms.remote-service-name | `jms` | JMS remote service name. -|spring.sleuth.messaging.kafka.enabled | `true` | Enable tracing of Kafka. -|spring.sleuth.messaging.kafka.mapper.enabled | `true` | Enable DefaultKafkaHeaderMapper tracing for Kafka. -|spring.sleuth.messaging.kafka.remote-service-name | `kafka` | Kafka remote service name. -|spring.sleuth.messaging.kafka.streams.enabled | `false` | Should Kafka Streams be turned on. -|spring.sleuth.messaging.rabbit.enabled | `true` | Enable tracing of RabbitMQ. -|spring.sleuth.messaging.rabbit.remote-service-name | `rabbitmq` | Rabbit remote service name. -|spring.sleuth.mongodb.enabled | `true` | Enable tracing for MongoDb. -|spring.sleuth.opentracing.enabled | `true` | Enables OpenTracing support. -|spring.sleuth.propagation.type | | Tracing context propagation types. -|spring.sleuth.quartz.enabled | `true` | Enable tracing for Quartz. -|spring.sleuth.r2dbc.enabled | `true` | Enable R2dbc instrumentation. -|spring.sleuth.reactor.decorate-on-each | `true` | When true decorates on each operator, will be less performing, but logging will always contain the tracing entries in each operator. When false decorates on last operator, will be more performing, but logging might not always contain the tracing entries. @deprecated use explicit value via {@link SleuthReactorProperties#instrumentationType} -|spring.sleuth.reactor.enabled | `true` | When true enables instrumentation for reactor. -|spring.sleuth.reactor.instrumentation-type | | -|spring.sleuth.redis.enabled | `true` | Enable span information propagation when using Redis. -|spring.sleuth.redis.remote-service-name | `redis` | Service name for the remote Redis endpoint. -|spring.sleuth.rpc.enabled | `true` | Enable tracing of RPC. -|spring.sleuth.rsocket.enabled | `true` | When true enables instrumentation for rsocket. -|spring.sleuth.rxjava.schedulers.hook.enabled | `true` | Enable support for RxJava via RxJavaSchedulersHook. -|spring.sleuth.rxjava.schedulers.ignoredthreads | `[HystrixMetricPoller, ^RxComputation.*$]` | Thread names for which spans will not be sampled. -|spring.sleuth.sampler.probability | | Probability of requests that should be sampled. E.g. 1.0 - 100% requests should be sampled. The precision is whole-numbers only (i.e. there's no support for 0.1% of the traces). -|spring.sleuth.sampler.rate | `10` | A rate per second can be a nice choice for low-traffic endpoints as it allows you surge protection. For example, you may never expect the endpoint to get more than 50 requests per second. If there was a sudden surge of traffic, to 5000 requests per second, you would still end up with 50 traces per second. Conversely, if you had a percentage, like 10%, the same surge would end up with 500 traces per second, possibly overloading your storage. Amazon X-Ray includes a rate-limited sampler (named Reservoir) for this purpose. Brave has taken the same approach via the {@link brave.sampler.RateLimitingSampler}. -|spring.sleuth.sampler.refresh.enabled | `true` | Enable refresh scope for sampler. -|spring.sleuth.scheduled.enabled | `true` | Enable tracing for {@link org.springframework.scheduling.annotation.Scheduled}. -|spring.sleuth.scheduled.skip-pattern | | Pattern for the fully qualified name of a class that should be skipped. -|spring.sleuth.span-filter.additional-span-name-patterns-to-ignore | | Additional list of span names to ignore. Will be appended to {@link #spanNamePatternsToSkip}. -|spring.sleuth.span-filter.enabled | `false` | Will turn on the default Sleuth handler mechanism. Might ignore exporting of certain spans; -|spring.sleuth.span-filter.span-name-patterns-to-skip | `^catalogWatchTaskScheduler$` | List of span names to ignore. They will not be sent to external systems. -|spring.sleuth.supports-join | `true` | True means the tracing system supports sharing a span ID between a client and server. -|spring.sleuth.task.enabled | `true` | Enable Spring Cloud Task instrumentation. -|spring.sleuth.trace-id128 | `false` | When true, generate 128-bit trace IDs instead of 64-bit ones. -|spring.sleuth.tracer.mode | | Set which tracer implementation should be picked. -|spring.sleuth.tx.enabled | `true` | Enable Spring TX instrumentation. -|spring.sleuth.vault.enabled | `true` | Enable Spring Vault instrumentation. -|spring.sleuth.web.additional-skip-pattern | | Additional pattern for URLs that should be skipped in tracing. This will be appended to the {@link SleuthWebProperties#skipPattern}. -|spring.sleuth.web.client.enabled | `true` | Enable interceptor injecting into {@link org.springframework.web.client.RestTemplate}. -|spring.sleuth.web.client.skip-pattern | | Pattern for URLs that should be skipped in client side tracing. -|spring.sleuth.web.enabled | `true` | When true enables instrumentation for web applications. -|spring.sleuth.web.filter-order | `0` | Order in which the tracing filters should be registered. -|spring.sleuth.web.ignore-auto-configured-skip-patterns | `false` | If set to true, auto-configured skip patterns will be ignored. -|spring.sleuth.web.servlet.enabled | `true` | Enable servlet instrumentation. -|spring.sleuth.web.skip-pattern | `/api-docs.*\|/swagger.*\|.*\.png\|.*\.css\|.*\.js\|.*\.html\|/favicon.ico\|/hystrix.stream` | Pattern for URLs that should be skipped in tracing. -|spring.sleuth.web.tomcat.enabled | `true` | Enable tracing instrumentation for Tomcat. -|spring.sleuth.web.webclient.enabled | `true` | Enable tracing instrumentation for WebClient. -|spring.zipkin.activemq.message-max-bytes | `100000` | Maximum number of bytes for a given message with spans sent to Zipkin over ActiveMQ. -|spring.zipkin.activemq.queue | `zipkin` | Name of the ActiveMQ queue where spans should be sent to Zipkin. -|spring.zipkin.api-path | | The API path to append to baseUrl (above) as suffix. This applies if you use other monitoring tools, such as New Relic. The trace API doesn't need the API path, so you can set it to blank ("") in the configuration. -|spring.zipkin.base-url | `http://localhost:9411/` | URL of the zipkin query server instance. You can also provide the service id of the Zipkin server if Zipkin's registered in service discovery (e.g. https://zipkinserver/). -|spring.zipkin.compression.enabled | `false` | -|spring.zipkin.discovery-client-enabled | | If set to {@code false}, will treat the {@link ZipkinProperties#baseUrl} as a URL always. -|spring.zipkin.enabled | `true` | Enables sending spans to Zipkin. -|spring.zipkin.encoder | | Encoding type of spans sent to Zipkin. Set to {@link SpanBytesEncoder#JSON_V1} if your server is not recent. -|spring.zipkin.kafka.topic | `zipkin` | Name of the Kafka topic where spans should be sent to Zipkin. -|spring.zipkin.locator.discovery.enabled | `false` | Enabling of locating the host name via service discovery. -|spring.zipkin.message-timeout | `1` | Timeout in seconds before pending spans will be sent in batches to Zipkin. -|spring.zipkin.rabbitmq.addresses | | Addresses of the RabbitMQ brokers used to send spans to Zipkin -|spring.zipkin.rabbitmq.queue | `zipkin` | Name of the RabbitMQ queue where spans should be sent to Zipkin. -|spring.zipkin.sender.type | | Means of sending spans to Zipkin. -|spring.zipkin.service.name | | The name of the service, from which the Span was sent via HTTP, that should appear in Zipkin. - -|=== \ No newline at end of file +|=== +|Name | Default | Description + +|spring.sleuth.async.configurer.enabled | `true` | Enable default AsyncConfigurer. +|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 | | 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 | | 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. +|spring.sleuth.deployer.enabled | `true` | Enable Spring Cloud Deployer instrumentation. +|spring.sleuth.deployer.status-poll-delay | `500` | Default poll delay to retrieve the deployed application status. +|spring.sleuth.enabled | `true` | +|spring.sleuth.feign.enabled | `true` | Enable span information propagation when using Feign. +|spring.sleuth.feign.processor.enabled | `true` | Enable post processor that wraps Feign Context in its tracing representations. +|spring.sleuth.function.enabled | `true` | Enable instrumenting of Spring Cloud Function and Spring Cloud Function based projects (e.g. Spring Cloud Stream). +|spring.sleuth.grpc.enabled | `true` | Enable span information propagation when using GRPC. +|spring.sleuth.http.enabled | `true` | Enables HTTP support. +|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.datasource-proxy.count-query | `false` | Creates listener to count queries. @see ProxyDataSourceBuilder#countQuery() @see QueryCountHolder +|spring.sleuth.jdbc.datasource-proxy.json-format | `false` | Use json output for logging query. @see ProxyDataSourceBuilder#asJson() +|spring.sleuth.jdbc.datasource-proxy.logging | | Logging to use for logging queries. +|spring.sleuth.jdbc.datasource-proxy.multiline | `true` | Use multiline output for logging query. @see ProxyDataSourceBuilder#multiline() +|spring.sleuth.jdbc.datasource-proxy.query.enable-logging | `true` | Enable logging all queries to the log. +|spring.sleuth.jdbc.datasource-proxy.query.log-level | `DEBUG` | Severity of query logger. +|spring.sleuth.jdbc.datasource-proxy.query.logger-name | | Name of query logger. +|spring.sleuth.jdbc.datasource-proxy.slow-query.log-level | `WARN` | Severity of slow query logger. +|spring.sleuth.jdbc.datasource-proxy.slow-query.logger-name | | Name of slow query logger. +|spring.sleuth.jdbc.datasource-proxy.slow-query.threshold | `300` | Number of seconds to consider query as slow. +|spring.sleuth.jdbc.enabled | `true` | Enables data source decorating. +|spring.sleuth.jdbc.excluded-beans | | Beans that won't be decorated. +|spring.sleuth.jdbc.includes | | Which types of tracing we would like to include. +|spring.sleuth.jdbc.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.p6spy.enable-logging | `true` | Enables logging JDBC events. +|spring.sleuth.jdbc.p6spy.log-file | `spy.log` | Name of log file to use (only with logging=file). +|spring.sleuth.jdbc.p6spy.log-filter.pattern | | Use regex pattern to filter log messages. Only matched messages will be logged. +|spring.sleuth.jdbc.p6spy.log-format | | Custom log format. +|spring.sleuth.jdbc.p6spy.logging | | Logging to use for logging queries. +|spring.sleuth.jdbc.p6spy.multiline | `true` | Enables multiline output. +|spring.sleuth.jdbc.p6spy.tracing.include-parameter-values | `true` | Report the effective sql string (with '?' replaced with real values) to tracing systems.

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. +|spring.sleuth.messaging.jms.enabled | `true` | Enable tracing of JMS. +|spring.sleuth.messaging.jms.remote-service-name | `jms` | JMS remote service name. +|spring.sleuth.messaging.kafka.enabled | `true` | Enable tracing of Kafka. +|spring.sleuth.messaging.kafka.mapper.enabled | `true` | Enable DefaultKafkaHeaderMapper tracing for Kafka. +|spring.sleuth.messaging.kafka.remote-service-name | `kafka` | Kafka remote service name. +|spring.sleuth.messaging.kafka.streams.enabled | `false` | Should Kafka Streams be turned on. +|spring.sleuth.messaging.rabbit.enabled | `true` | Enable tracing of RabbitMQ. +|spring.sleuth.messaging.rabbit.remote-service-name | `rabbitmq` | Rabbit remote service name. +|spring.sleuth.mongodb.enabled | `true` | Enable tracing for MongoDb. +|spring.sleuth.opentracing.enabled | `true` | Enables OpenTracing support. +|spring.sleuth.propagation.type | | Tracing context propagation types. +|spring.sleuth.quartz.enabled | `true` | Enable tracing for Quartz. +|spring.sleuth.r2dbc.enabled | `true` | Enable R2dbc instrumentation. +|spring.sleuth.reactor.decorate-on-each | `true` | When true decorates on each operator, will be less performing, but logging will always contain the tracing entries in each operator. When false decorates on last operator, will be more performing, but logging might not always contain the tracing entries. @deprecated use explicit value via {@link SleuthReactorProperties#instrumentationType} +|spring.sleuth.reactor.enabled | `true` | When true enables instrumentation for reactor. +|spring.sleuth.reactor.instrumentation-type | | +|spring.sleuth.redis.enabled | `true` | Enable span information propagation when using Redis. +|spring.sleuth.redis.remote-service-name | `redis` | Service name for the remote Redis endpoint. +|spring.sleuth.rpc.enabled | `true` | Enable tracing of RPC. +|spring.sleuth.rsocket.enabled | `true` | When true enables instrumentation for rsocket. +|spring.sleuth.rxjava.schedulers.hook.enabled | `true` | Enable support for RxJava via RxJavaSchedulersHook. +|spring.sleuth.rxjava.schedulers.ignoredthreads | `[HystrixMetricPoller, ^RxComputation.*$]` | Thread names for which spans will not be sampled. +|spring.sleuth.sampler.probability | | Probability of requests that should be sampled. E.g. 1.0 - 100% requests should be sampled. The precision is whole-numbers only (i.e. there's no support for 0.1% of the traces). +|spring.sleuth.sampler.rate | `10` | A rate per second can be a nice choice for low-traffic endpoints as it allows you surge protection. For example, you may never expect the endpoint to get more than 50 requests per second. If there was a sudden surge of traffic, to 5000 requests per second, you would still end up with 50 traces per second. Conversely, if you had a percentage, like 10%, the same surge would end up with 500 traces per second, possibly overloading your storage. Amazon X-Ray includes a rate-limited sampler (named Reservoir) for this purpose. Brave has taken the same approach via the {@link brave.sampler.RateLimitingSampler}. +|spring.sleuth.sampler.refresh.enabled | `true` | Enable refresh scope for sampler. +|spring.sleuth.scheduled.enabled | `true` | Enable tracing for {@link org.springframework.scheduling.annotation.Scheduled}. +|spring.sleuth.scheduled.skip-pattern | | Pattern for the fully qualified name of a class that should be skipped. +|spring.sleuth.span-filter.additional-span-name-patterns-to-ignore | | Additional list of span names to ignore. Will be appended to {@link #spanNamePatternsToSkip}. +|spring.sleuth.span-filter.enabled | `false` | Will turn on the default Sleuth handler mechanism. Might ignore exporting of certain spans; +|spring.sleuth.span-filter.span-name-patterns-to-skip | `^catalogWatchTaskScheduler$` | List of span names to ignore. They will not be sent to external systems. +|spring.sleuth.supports-join | `true` | True means the tracing system supports sharing a span ID between a client and server. +|spring.sleuth.task.enabled | `true` | Enable Spring Cloud Task instrumentation. +|spring.sleuth.trace-id128 | `false` | When true, generate 128-bit trace IDs instead of 64-bit ones. +|spring.sleuth.tracer.mode | | Set which tracer implementation should be picked. +|spring.sleuth.tx.enabled | `true` | Enable Spring TX instrumentation. +|spring.sleuth.vault.enabled | `true` | Enable Spring Vault instrumentation. +|spring.sleuth.web.additional-skip-pattern | | Additional pattern for URLs that should be skipped in tracing. This will be appended to the {@link SleuthWebProperties#skipPattern}. +|spring.sleuth.web.client.enabled | `true` | Enable interceptor injecting into {@link org.springframework.web.client.RestTemplate}. +|spring.sleuth.web.client.skip-pattern | | Pattern for URLs that should be skipped in client side tracing. +|spring.sleuth.web.enabled | `true` | When true enables instrumentation for web applications. +|spring.sleuth.web.filter-order | `0` | Order in which the tracing filters should be registered. +|spring.sleuth.web.ignore-auto-configured-skip-patterns | `false` | If set to true, auto-configured skip patterns will be ignored. +|spring.sleuth.web.servlet.enabled | `true` | Enable servlet instrumentation. +|spring.sleuth.web.skip-pattern | `/api-docs.*\|/swagger.*\|.*\.png\|.*\.css\|.*\.js\|.*\.html\|/favicon.ico\|/hystrix.stream` | Pattern for URLs that should be skipped in tracing. +|spring.sleuth.web.tomcat.enabled | `true` | Enable tracing instrumentation for Tomcat. +|spring.sleuth.web.webclient.enabled | `true` | Enable tracing instrumentation for WebClient. +|spring.zipkin.activemq.message-max-bytes | `100000` | Maximum number of bytes for a given message with spans sent to Zipkin over ActiveMQ. +|spring.zipkin.activemq.queue | `zipkin` | Name of the ActiveMQ queue where spans should be sent to Zipkin. +|spring.zipkin.api-path | | The API path to append to baseUrl (above) as suffix. This applies if you use other monitoring tools, such as New Relic. The trace API doesn't need the API path, so you can set it to blank ("") in the configuration. +|spring.zipkin.base-url | `http://localhost:9411/` | URL of the zipkin query server instance. You can also provide the service id of the Zipkin server if Zipkin's registered in service discovery (e.g. https://zipkinserver/). +|spring.zipkin.compression.enabled | `false` | +|spring.zipkin.discovery-client-enabled | | If set to {@code false}, will treat the {@link ZipkinProperties#baseUrl} as a URL always. +|spring.zipkin.enabled | `true` | Enables sending spans to Zipkin. +|spring.zipkin.encoder | | Encoding type of spans sent to Zipkin. Set to {@link SpanBytesEncoder#JSON_V1} if your server is not recent. +|spring.zipkin.kafka.topic | `zipkin` | Name of the Kafka topic where spans should be sent to Zipkin. +|spring.zipkin.locator.discovery.enabled | `false` | Enabling of locating the host name via service discovery. +|spring.zipkin.message-timeout | `1` | Timeout in seconds before pending spans will be sent in batches to Zipkin. +|spring.zipkin.rabbitmq.addresses | | Addresses of the RabbitMQ brokers used to send spans to Zipkin +|spring.zipkin.rabbitmq.queue | `zipkin` | Name of the RabbitMQ queue where spans should be sent to Zipkin. +|spring.zipkin.sender.type | | Means of sending spans to Zipkin. +|spring.zipkin.service.name | | The name of the service, from which the Span was sent via HTTP, that should appear in Zipkin. + +|=== diff --git a/docs/src/main/asciidoc/integrations.adoc b/docs/src/main/asciidoc/integrations.adoc index 719002b3a..e57563d3a 100644 --- a/docs/src/main/asciidoc/integrations.adoc +++ b/docs/src/main/asciidoc/integrations.adoc @@ -695,8 +695,8 @@ runtimeOnly "net.ttddyy:datasource-proxy:${datasourceProxyVersion}" ---- ==== -Please check the <> 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. +Please check the <> page under `spring.sleuth.jdbc.p6spy` for all p6spy configuration options and `spring.sleuth.jdbc.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`. +In order to disable this instrumentation set `spring.sleuth.jdbc.enabled` to `false`. diff --git a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/async/TraceAsyncDefaultAutoConfiguration.java b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/async/TraceAsyncDefaultAutoConfiguration.java index 0fa6d75f0..72feab40a 100644 --- a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/async/TraceAsyncDefaultAutoConfiguration.java +++ b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/async/TraceAsyncDefaultAutoConfiguration.java @@ -1,151 +1,153 @@ -/* - * 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.async; - -import java.util.concurrent.Executor; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.aop.interceptor.AsyncExecutionAspectSupport; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.beans.factory.NoUniqueBeanDefinitionException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; -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.context.properties.EnableConfigurationProperties; -import org.springframework.cloud.sleuth.SpanNamer; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.autoconfig.brave.BraveAutoConfiguration; -import org.springframework.cloud.sleuth.instrument.async.LazyTraceExecutor; -import org.springframework.cloud.sleuth.instrument.async.TraceAsyncAspect; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Role; -import org.springframework.core.task.SimpleAsyncTaskExecutor; -import org.springframework.core.task.TaskExecutor; -import org.springframework.scheduling.annotation.AsyncConfigurer; -import org.springframework.scheduling.annotation.AsyncConfigurerSupport; - -/** - * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration - * Auto-configuration} enabling async related processing. - * - * @author Dave Syer - * @author Marcin Grzejszczak - * @since 1.0.0 - * @see LazyTraceExecutor - * @see TraceAsyncAspect - */ -@Configuration(proxyBeanMethods = false) -@EnableConfigurationProperties(SleuthAsyncProperties.class) -@ConditionalOnProperty(value = "spring.sleuth.async.enabled", matchIfMissing = true) -@ConditionalOnBean(Tracer.class) -@AutoConfigureAfter(BraveAutoConfiguration.class) -public class TraceAsyncDefaultAutoConfiguration { - - @Bean - @ConditionalOnProperty(value = "spring.sleuth.scheduled.enabled", matchIfMissing = true) - static ExecutorBeanPostProcessor executorBeanPostProcessor(BeanFactory beanFactory) { - return new ExecutorBeanPostProcessor(beanFactory); - } - - @Bean - TraceAsyncAspect traceAsyncAspect(Tracer tracer, SpanNamer spanNamer) { - return new TraceAsyncAspect(tracer, spanNamer); - } - - /** - * Wrapper for the async executor. - */ - @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(AsyncConfigurer.class) - @ConditionalOnMissingBean(AsyncConfigurer.class) - @ConditionalOnProperty(value = "spring.sleuth.async.configurer.enabled", matchIfMissing = true) - @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - static class DefaultAsyncConfigurerSupport extends AsyncConfigurerSupport { - - private static final Log log = LogFactory.getLog(DefaultAsyncConfigurerSupport.class); - - @Autowired - private BeanFactory beanFactory; - - @Override - public Executor getAsyncExecutor() { - Executor delegate = getDefaultExecutor(); - return new LazyTraceExecutor(this.beanFactory, delegate); - } - - /** - * Retrieve or build a default executor for this advice instance. An executor - * returned from here will be cached for further use. - *

- * The default implementation searches for a unique {@link TaskExecutor} bean in - * the context, or for an {@link Executor} bean named "taskExecutor" otherwise. If - * neither of the two is resolvable, this implementation will return {@code null}. - * @return the default executor, or {@code null} if none available - * @see AsyncExecutionAspectSupport#getDefaultExecutor(org.springframework.beans.factory.BeanFactory) - */ - private Executor getDefaultExecutor() { - try { - // Search for TaskExecutor bean... not plain Executor since that would - // match with ScheduledExecutorService as well, which is unusable for - // our purposes here. TaskExecutor is more clearly designed for it. - return this.beanFactory.getBean(TaskExecutor.class); - } - catch (NoUniqueBeanDefinitionException ex) { - log.debug("Could not find unique TaskExecutor bean", ex); - try { - return this.beanFactory.getBean(AsyncExecutionAspectSupport.DEFAULT_TASK_EXECUTOR_BEAN_NAME, - Executor.class); - } - catch (NoSuchBeanDefinitionException ex2) { - if (log.isInfoEnabled()) { - log.info("More than one TaskExecutor bean found within the context, and none is named " - + "'taskExecutor'. Mark one of them as primary or name it 'taskExecutor' (possibly " - + "as an alias) in order to use it for async processing: " + ex.getBeanNamesFound()); - } - } - } - catch (NoSuchBeanDefinitionException ex) { - log.debug("Could not find default TaskExecutor bean", ex); - try { - return this.beanFactory.getBean(AsyncExecutionAspectSupport.DEFAULT_TASK_EXECUTOR_BEAN_NAME, - Executor.class); - } - catch (NoSuchBeanDefinitionException ex2) { - log.info("No task executor bean found for async processing: " - + "no bean of type TaskExecutor and no bean named 'taskExecutor' either"); - } - // Giving up -> either using local default executor or none at all... - } - // backward compatibility - if (log.isInfoEnabled()) { - log.info( - "For backward compatibility, will fallback to the default, SimpleAsyncTaskExecutor implementation"); - } - return new SimpleAsyncTaskExecutor(); - } - - } - -} +/* + * 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.async; + +import java.util.concurrent.Executor; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.aop.interceptor.AsyncExecutionAspectSupport; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.NoUniqueBeanDefinitionException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +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.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.sleuth.SpanNamer; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.autoconfig.brave.BraveAutoConfiguration; +import org.springframework.cloud.sleuth.instrument.async.LazyTraceExecutor; +import org.springframework.cloud.sleuth.instrument.async.TraceAsyncAspect; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Role; +import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.annotation.AsyncConfigurerSupport; + +/** + * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration + * Auto-configuration} enabling async related processing. + * + * @author Dave Syer + * @author Marcin Grzejszczak + * @since 1.0.0 + * @see LazyTraceExecutor + * @see TraceAsyncAspect + */ +@Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties(SleuthAsyncProperties.class) +@ConditionalOnProperty(value = "spring.sleuth.async.enabled", matchIfMissing = true) +@ConditionalOnBean(Tracer.class) +@AutoConfigureAfter(BraveAutoConfiguration.class) +public class TraceAsyncDefaultAutoConfiguration { + + @Bean + @ConditionalOnProperty(value = "spring.sleuth.scheduled.enabled", matchIfMissing = true) + static ExecutorBeanPostProcessor executorBeanPostProcessor(BeanFactory beanFactory) { + return new ExecutorBeanPostProcessor(beanFactory); + } + + @Bean + TraceAsyncAspect traceAsyncAspect(Tracer tracer, SpanNamer spanNamer) { + return new TraceAsyncAspect(tracer, spanNamer); + } + + /** + * Wrapper for the async executor. + */ + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(AsyncConfigurer.class) + @ConditionalOnMissingBean(AsyncConfigurer.class) + @ConditionalOnProperty(value = "spring.sleuth.async.configurer.enabled", matchIfMissing = true) + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + static class DefaultAsyncConfigurerSupport extends AsyncConfigurerSupport { + + private static final Log log = LogFactory.getLog(DefaultAsyncConfigurerSupport.class); + + @Autowired + private BeanFactory beanFactory; + + @Override + public Executor getAsyncExecutor() { + Executor delegate = getDefaultExecutor(); + return new LazyTraceExecutor(this.beanFactory, delegate); + } + + /** + * Retrieve or build a default executor for this advice instance. An executor + * returned from here will be cached for further use. + *

+ * The default implementation searches for a unique {@link TaskExecutor} bean in + * the context, or for an {@link Executor} bean named "taskExecutor" otherwise. If + * neither of the two is resolvable, this implementation will return {@code null}. + * @return the default executor, or {@code null} if none available + * @see AsyncExecutionAspectSupport#getDefaultExecutor(org.springframework.beans.factory.BeanFactory) + */ + private Executor getDefaultExecutor() { + try { + // Search for TaskExecutor bean... not plain Executor since that would + // match with ScheduledExecutorService as well, which is unusable for + // our purposes here. TaskExecutor is more clearly designed for it. + return this.beanFactory.getBean(TaskExecutor.class); + } + catch (NoUniqueBeanDefinitionException ex) { + if (log.isDebugEnabled()) { + log.debug("Could not find unique TaskExecutor bean", ex); + } + try { + return this.beanFactory.getBean(AsyncExecutionAspectSupport.DEFAULT_TASK_EXECUTOR_BEAN_NAME, + Executor.class); + } + catch (NoSuchBeanDefinitionException ex2) { + if (log.isInfoEnabled()) { + log.info("More than one TaskExecutor bean found within the context, and none is named " + + "'taskExecutor'. Mark one of them as primary or name it 'taskExecutor' (possibly " + + "as an alias) in order to use it for async processing: " + ex.getBeanNamesFound()); + } + } + } + catch (NoSuchBeanDefinitionException ex) { + log.debug("Could not find default TaskExecutor bean", ex); + try { + return this.beanFactory.getBean(AsyncExecutionAspectSupport.DEFAULT_TASK_EXECUTOR_BEAN_NAME, + Executor.class); + } + catch (NoSuchBeanDefinitionException ex2) { + log.info("No task executor bean found for async processing: " + + "no bean of type TaskExecutor and no bean named 'taskExecutor' either"); + } + // Giving up -> either using local default executor or none at all... + } + // backward compatibility + if (log.isInfoEnabled()) { + log.info( + "For backward compatibility, will fallback to the default, SimpleAsyncTaskExecutor implementation"); + } + return new SimpleAsyncTaskExecutor(); + } + + } + +} diff --git a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/DataSourceProxyConfiguration.java b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/DataSourceProxyConfiguration.java index fb0daac9b..9775cb354 100644 --- a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/DataSourceProxyConfiguration.java +++ b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/DataSourceProxyConfiguration.java @@ -97,8 +97,7 @@ class DataSourceProxyConfiguration { @Bean @ConditionalOnMissingBean - @ConditionalOnProperty(value = "spring.sleuth.jdbc.decorator.datasource.datasource-proxy.count-query", - havingValue = "true") + @ConditionalOnProperty(value = "spring.sleuth.jdbc.datasource-proxy.count-query", havingValue = "true") QueryCountStrategy queryCountStrategy() { return new SingleQueryCountHolder(); } diff --git a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/P6SpyPropertiesSetter.java b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/P6SpyPropertiesSetter.java index dc97a2740..c0227ac10 100644 --- a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/P6SpyPropertiesSetter.java +++ b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/P6SpyPropertiesSetter.java @@ -66,8 +66,8 @@ class P6SpyPropertiesSetter implements BeanDefinitionRegistryPostProcessor, Clos 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); + boolean isEnableLogging = environment.getProperty("spring.sleuth.jdbc.p6spy.enable-logging", Boolean.class, + true); if (customModuleList != null) { log.info("P6Spy modulelist is overridden, some p6spy configuration features will not be applied"); } @@ -94,7 +94,7 @@ class P6SpyPropertiesSetter implements BeanDefinitionRegistryPostProcessor, Clos if (isEnableLogging && !initialP6SpyOptions.containsKey("appender")) { TraceDataSourceDecoratorProperties.P6SpyProperties.P6SpyLogging logging = TraceDataSourceDecoratorProperties.P6SpyProperties.P6SpyLogging .valueOf(environment - .getProperty("spring.sleuth.jdbc.decorator.datasource.p6spy.logging", String.class, + .getProperty("spring.sleuth.jdbc.p6spy.logging", String.class, TraceDataSourceDecoratorProperties.P6SpyProperties.P6SpyLogging.SLF4J.toString()) .toUpperCase()); switch (logging) { @@ -108,19 +108,17 @@ class P6SpyPropertiesSetter implements BeanDefinitionRegistryPostProcessor, Clos 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, ""); + String customAppender = environment.getProperty("spring.sleuth.jdbc.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"); + String logFile = environment.getProperty("spring.sleuth.jdbc.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); + String pattern = environment.getProperty("spring.sleuth.jdbc.p6spy.log-filter.pattern", String.class); if (pattern != null) { System.setProperty("p6spy.config.filter", "true"); System.setProperty("p6spy.config.sqlexpression", pattern); @@ -133,11 +131,11 @@ class P6SpyPropertiesSetter implements BeanDefinitionRegistryPostProcessor, Clos } private Boolean multiLine(ConfigurableEnvironment environment) { - return environment.getProperty("spring.sleuth.jdbc.decorator.datasource.p6spy.multiline", Boolean.class, true); + return environment.getProperty("spring.sleuth.jdbc.p6spy.multiline", Boolean.class, true); } private String logFormat(ConfigurableEnvironment environment) { - return environment.getProperty("spring.sleuth.jdbc.decorator.datasource.p6spy.log-format", String.class); + return environment.getProperty("spring.sleuth.jdbc.p6spy.log-format", String.class); } @Override diff --git a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/TraceDataSourceDecoratorAutoConfiguration.java b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/TraceDataSourceDecoratorAutoConfiguration.java index 2066149c7..0c0c46f9e 100644 --- a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/TraceDataSourceDecoratorAutoConfiguration.java +++ b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/TraceDataSourceDecoratorAutoConfiguration.java @@ -42,8 +42,7 @@ import org.springframework.context.annotation.Import; */ @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(TraceDataSourceDecoratorProperties.class) -@ConditionalOnProperty(name = "spring.sleuth.jdbc.decorator.datasource.enabled", havingValue = "true", - matchIfMissing = true) +@ConditionalOnProperty(name = "spring.sleuth.jdbc.enabled", havingValue = "true", matchIfMissing = true) @ConditionalOnBean({ DataSource.class, Tracer.class }) @AutoConfigureAfter(DataSourceAutoConfiguration.class) @Import({ P6SpyConfiguration.class, DataSourceProxyConfiguration.class }) diff --git a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/TraceDataSourceDecoratorProperties.java b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/TraceDataSourceDecoratorProperties.java index 2e655e903..9e71c9013 100644 --- a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/TraceDataSourceDecoratorProperties.java +++ b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/TraceDataSourceDecoratorProperties.java @@ -22,7 +22,6 @@ 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; @@ -34,7 +33,7 @@ import org.springframework.cloud.sleuth.instrument.jdbc.TraceType; * @author Arthur Gavlyukovskiy * @since 3.1.0 */ -@ConfigurationProperties(prefix = "spring.sleuth.jdbc.decorator.datasource") +@ConfigurationProperties(prefix = "spring.sleuth.jdbc") public class TraceDataSourceDecoratorProperties { /** @@ -130,14 +129,6 @@ public class TraceDataSourceDecoratorProperties { */ private boolean jsonFormat = false; - /** - * Creates listener to count queries. - * - * @see ProxyDataSourceBuilder#countQuery() - * @see QueryCountHolder - */ - private boolean countQuery = false; - public DataSourceProxyLogging getLogging() { return logging; } @@ -178,14 +169,6 @@ public class TraceDataSourceDecoratorProperties { this.jsonFormat = jsonFormat; } - public boolean isCountQuery() { - return countQuery; - } - - public void setCountQuery(boolean countQuery) { - this.countQuery = countQuery; - } - /** * Properties to configure query logging listener. */ diff --git a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/brave/instrument/web/client/WebClientTests.java b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/brave/instrument/web/client/WebClientTests.java index eef976520..5da06cf57 100644 --- a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/brave/instrument/web/client/WebClientTests.java +++ b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/brave/instrument/web/client/WebClientTests.java @@ -1,205 +1,206 @@ -/* - * 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.brave.instrument.web.client; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.Future; -import java.util.stream.Collectors; - -import brave.Span; -import brave.Tracer; -import brave.baggage.BaggagePropagation; -import brave.handler.SpanHandler; -import brave.propagation.B3Propagation; -import brave.sampler.Sampler; -import brave.test.TestSpanHandler; -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.concurrent.FutureCallback; -import org.apache.http.impl.client.BasicResponseHandler; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; -import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration; -import org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration; -import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; -import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.web.server.LocalServerPort; -import org.springframework.cloud.gateway.config.GatewayAutoConfiguration; -import org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration; -import org.springframework.cloud.sleuth.DisableSecurity; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpHeaders; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import static brave.Span.Kind.CLIENT; -import static brave.propagation.B3Propagation.Format.SINGLE_NO_PARENT; -import static org.assertj.core.api.BDDAssertions.then; - -@SpringBootTest(classes = WebClientTests.TestConfiguration.class, - webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, - properties = {"spring.sleuth.web.servlet.enabled=false", "spring.application.name=fooservice", - "spring.sleuth.web.client.skip-pattern=/skip.*"}) -@DirtiesContext -public class WebClientTests { - - @Autowired - HttpClientBuilder httpClientBuilder; // #845 - - @Autowired - HttpAsyncClientBuilder httpAsyncClientBuilder; // #845 - - @Autowired - TestSpanHandler spans; - - @Autowired - Tracer tracer; - - @LocalServerPort - int port; - - @Autowired - FooController fooController; - - @AfterEach - @BeforeEach - public void close() { - this.spans.clear(); - this.fooController.clear(); - } - - @Test - @SuppressWarnings("unchecked") - public void shouldAttachTraceIdWhenCallingAnotherServiceForHttpClient() throws Exception { - then(this.spans).isEmpty(); - Span span = this.tracer.nextSpan().name("foo").start(); - - try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { - String response = this.httpClientBuilder.build().execute(new HttpGet("http://localhost:" + this.port), - new BasicResponseHandler()); - - then(response).isNotEmpty(); - } - - then(this.tracer.currentSpan()).isNull(); - then(this.spans).isNotEmpty().extracting("traceId", String.class).containsOnly(span.context().traceIdString()); - then(this.spans.spans().stream().map(s -> s.kind().name()).collect(Collectors.toList())).contains("CLIENT"); - } - - @Test - @SuppressWarnings("unchecked") - public void shouldAttachTraceIdWhenCallingAnotherServiceForAsyncHttpClient() throws Exception { - Span span = this.tracer.nextSpan().name("foo").start(); - - CloseableHttpAsyncClient client = this.httpAsyncClientBuilder.build(); - try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { - client.start(); - Future future = client.execute(new HttpGet("http://localhost:" + this.port), - new FutureCallback() { - @Override - public void completed(HttpResponse result) { - - } - - @Override - public void failed(Exception ex) { - - } - - @Override - public void cancelled() { - - } - }); - then(future.get()).isNotNull(); - } - finally { - client.close(); - } - - then(this.tracer.currentSpan()).isNull(); - then(this.spans).isNotEmpty().extracting("traceId", String.class).containsOnly(span.context().traceIdString()); - then(this.spans.spans().stream().map(s -> s.kind().name()).collect(Collectors.toList())).contains("CLIENT"); - } - - @Configuration(proxyBeanMethods = false) - @EnableAutoConfiguration(exclude = {GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class, - R2dbcAutoConfiguration.class, R2dbcDataAutoConfiguration.class, MongoAutoConfiguration.class, MongoDataAutoConfiguration.class}) - @DisableSecurity - public static class TestConfiguration { - - @Bean - BaggagePropagation.FactoryBuilder baggagePropagationFactoryBuilder() { - // Use b3 single format as it is less verbose - return BaggagePropagation.newFactoryBuilder( - B3Propagation.newFactoryBuilder().injectFormat(CLIENT, SINGLE_NO_PARENT).build()); - } - - @Bean - FooController fooController() { - return new FooController(); - } - - @Bean - Sampler testSampler() { - return Sampler.ALWAYS_SAMPLE; - } - - @Bean - SpanHandler testSpanHandler() { - return new TestSpanHandler(); - } - - } - - @RestController - public static class FooController { - - Span span; - - @RequestMapping("/") - public Map home(@RequestHeader HttpHeaders headers) { - Map map = new HashMap<>(); - for (String key : headers.keySet()) { - map.put(key, headers.getFirst(key)); - } - return map; - } - - public Span getSpan() { - return this.span; - } - - public void clear() { - this.span = null; - } - - } - -} +/* + * 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.brave.instrument.web.client; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Future; +import java.util.stream.Collectors; + +import brave.Span; +import brave.Tracer; +import brave.baggage.BaggagePropagation; +import brave.handler.SpanHandler; +import brave.propagation.B3Propagation; +import brave.sampler.Sampler; +import brave.test.TestSpanHandler; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.concurrent.FutureCallback; +import org.apache.http.impl.client.BasicResponseHandler; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; +import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration; +import org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration; +import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; +import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.cloud.gateway.config.GatewayAutoConfiguration; +import org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration; +import org.springframework.cloud.sleuth.DisableSecurity; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import static brave.Span.Kind.CLIENT; +import static brave.propagation.B3Propagation.Format.SINGLE_NO_PARENT; +import static org.assertj.core.api.BDDAssertions.then; + +@SpringBootTest(classes = WebClientTests.TestConfiguration.class, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = { "spring.sleuth.web.servlet.enabled=false", "spring.application.name=fooservice", + "spring.sleuth.web.client.skip-pattern=/skip.*" }) +@DirtiesContext +public class WebClientTests { + + @Autowired + HttpClientBuilder httpClientBuilder; // #845 + + @Autowired + HttpAsyncClientBuilder httpAsyncClientBuilder; // #845 + + @Autowired + TestSpanHandler spans; + + @Autowired + Tracer tracer; + + @LocalServerPort + int port; + + @Autowired + FooController fooController; + + @AfterEach + @BeforeEach + public void close() { + this.spans.clear(); + this.fooController.clear(); + } + + @Test + @SuppressWarnings("unchecked") + public void shouldAttachTraceIdWhenCallingAnotherServiceForHttpClient() throws Exception { + then(this.spans).isEmpty(); + Span span = this.tracer.nextSpan().name("foo").start(); + + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { + String response = this.httpClientBuilder.build().execute(new HttpGet("http://localhost:" + this.port), + new BasicResponseHandler()); + + then(response).isNotEmpty(); + } + + then(this.tracer.currentSpan()).isNull(); + then(this.spans).isNotEmpty().extracting("traceId", String.class).containsOnly(span.context().traceIdString()); + then(this.spans.spans().stream().map(s -> s.kind().name()).collect(Collectors.toList())).contains("CLIENT"); + } + + @Test + @SuppressWarnings("unchecked") + public void shouldAttachTraceIdWhenCallingAnotherServiceForAsyncHttpClient() throws Exception { + Span span = this.tracer.nextSpan().name("foo").start(); + + CloseableHttpAsyncClient client = this.httpAsyncClientBuilder.build(); + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { + client.start(); + Future future = client.execute(new HttpGet("http://localhost:" + this.port), + new FutureCallback() { + @Override + public void completed(HttpResponse result) { + + } + + @Override + public void failed(Exception ex) { + + } + + @Override + public void cancelled() { + + } + }); + then(future.get()).isNotNull(); + } + finally { + client.close(); + } + + then(this.tracer.currentSpan()).isNull(); + then(this.spans).isNotEmpty().extracting("traceId", String.class).containsOnly(span.context().traceIdString()); + then(this.spans.spans().stream().map(s -> s.kind().name()).collect(Collectors.toList())).contains("CLIENT"); + } + + @Configuration(proxyBeanMethods = false) + @EnableAutoConfiguration(exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class, + R2dbcAutoConfiguration.class, R2dbcDataAutoConfiguration.class, MongoAutoConfiguration.class, + MongoDataAutoConfiguration.class }) + @DisableSecurity + public static class TestConfiguration { + + @Bean + BaggagePropagation.FactoryBuilder baggagePropagationFactoryBuilder() { + // Use b3 single format as it is less verbose + return BaggagePropagation.newFactoryBuilder( + B3Propagation.newFactoryBuilder().injectFormat(CLIENT, SINGLE_NO_PARENT).build()); + } + + @Bean + FooController fooController() { + return new FooController(); + } + + @Bean + Sampler testSampler() { + return Sampler.ALWAYS_SAMPLE; + } + + @Bean + SpanHandler testSpanHandler() { + return new TestSpanHandler(); + } + + } + + @RestController + public static class FooController { + + Span span; + + @RequestMapping("/") + public Map home(@RequestHeader HttpHeaders headers) { + Map map = new HashMap<>(); + for (String key : headers.keySet()) { + map.put(key, headers.getFirst(key)); + } + return map; + } + + public Span getSpan() { + return this.span; + } + + public void clear() { + this.span = null; + } + + } + +} diff --git a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/DataSourceDecoratorAutoConfigurationTests.java b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/DataSourceDecoratorAutoConfigurationTests.java index 734b4f514..b8c38ee16 100644 --- a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/DataSourceDecoratorAutoConfigurationTests.java +++ b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/DataSourceDecoratorAutoConfigurationTests.java @@ -75,7 +75,7 @@ class DataSourceDecoratorAutoConfigurationTests { @Test void testNoDecoratingForExcludeBeans() { ApplicationContextRunner contextRunner = this.contextRunner - .withPropertyValues("spring.sleuth.jdbc.decorator.datasource.excluded-beans:dataSource"); + .withPropertyValues("spring.sleuth.jdbc.excluded-beans:dataSource"); contextRunner.run(context -> { DataSource dataSource = context.getBean(DataSource.class); @@ -183,7 +183,7 @@ class DataSourceDecoratorAutoConfigurationTests { @Test void testDecoratingCanBeDisabled() { ApplicationContextRunner contextRunner = this.contextRunner - .withPropertyValues("spring.sleuth.jdbc.decorator.datasource.enabled:false"); + .withPropertyValues("spring.sleuth.jdbc.enabled:false"); contextRunner.run(context -> { DataSource dataSource = context.getBean(DataSource.class); @@ -194,7 +194,7 @@ class DataSourceDecoratorAutoConfigurationTests { @Test void testDecoratingCanBeDisabledForSpecificBeans() { ApplicationContextRunner contextRunner = this.contextRunner - .withPropertyValues("spring.sleuth.jdbc.decorator.datasource.excluded-beans:secondDataSource") + .withPropertyValues("spring.sleuth.jdbc.excluded-beans:secondDataSource") .withUserConfiguration(TestMultiDataSourceConfiguration.class); contextRunner.run(context -> { diff --git a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/P6SpyConfigurationTests.java b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/P6SpyConfigurationTests.java index c626017e4..cb45faada 100644 --- a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/P6SpyConfigurationTests.java +++ b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/P6SpyConfigurationTests.java @@ -1,281 +1,280 @@ -/* - * 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.Tracer; -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); - - // order matters! - connection2.close(); - - assertThat(closingCountingListener.connectionCount).isEqualTo(1); - - // order matters! - connection1.close(); - - assertThat(closingCountingListener.connectionCount).isEqualTo(2); - assertThat(context.getBean(Tracer.class).currentSpan()).isNull(); - }); - } - - @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 MESSAGES = new ArrayList<>(); - static final List 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; - } - - } - -} +/* + * 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.Tracer; +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); + + // order matters! + connection2.close(); + + assertThat(closingCountingListener.connectionCount).isEqualTo(1); + + // order matters! + connection1.close(); + + assertThat(closingCountingListener.connectionCount).isEqualTo(2); + assertThat(context.getBean(Tracer.class).currentSpan()).isNull(); + }); + } + + @Test + void testDoesNotRegisterLoggingListenerIfDisabled() { + ApplicationContextRunner contextRunner = this.contextRunner + .withPropertyValues("spring.sleuth.jdbc.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.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.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.p6spy.logging:custom", + "spring.sleuth.jdbc.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.p6spy.logging:custom", + "spring.sleuth.jdbc.p6spy.custom-appender-class:" + LogAccumulator.class.getName(), + "spring.sleuth.jdbc.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.p6spy.logging:custom", + "spring.sleuth.jdbc.p6spy.custom-appender-class:" + LogAccumulator.class.getName(), + "spring.sleuth.jdbc.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 MESSAGES = new ArrayList<>(); + static final List 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; + } + + } + +} diff --git a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/ProxyDataSourceConfigurationTests.java b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/ProxyDataSourceConfigurationTests.java index 898a0de7a..ca3528afb 100644 --- a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/ProxyDataSourceConfigurationTests.java +++ b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/ProxyDataSourceConfigurationTests.java @@ -1,246 +1,246 @@ -/* - * 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.Tracer; -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); - assertThat(context.getBean(Tracer.class).currentSpan()).isNull(); - }); - } - - @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); - assertThat(context.getBean(Tracer.class).currentSpan()).isNull(); - }); - } - - @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); - assertThat(context.getBean(Tracer.class).currentSpan()).isNull(); - }); - } - - @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); - assertThat(context.getBean(Tracer.class).currentSpan()).isNull(); - }); - } - - @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); - assertThat(context.getBean(Tracer.class).currentSpan()).isNull(); - }); - } - - @Test - void testGlobalConnectionIdManagerByDefault() { - contextRunner.run(context -> { - DataSource dataSource = context.getBean(DataSource.class); - ProxyDataSource proxyDataSource = (ProxyDataSource) ((DataSourceWrapper) dataSource) - .getDecoratedDataSource(); - - assertThat(proxyDataSource.getConnectionIdManager()).isInstanceOf(GlobalConnectionIdManager.class); - assertThat(context.getBean(Tracer.class).currentSpan()).isNull(); - }); - } - - @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); - assertThat(context.getBean(Tracer.class).currentSpan()).isNull(); - }); - } - - @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 queryInfoList) { - System.out.println("beforeQuery"); - } - - @Override - public void afterQuery(ExecutionInfo execInfo, List queryInfoList) { - System.out.println("afterQuery"); - } - }; - } - - } - -} +/* + * 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.Tracer; +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.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.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); + assertThat(context.getBean(Tracer.class).currentSpan()).isNull(); + }); + } + + @Test + void testRegisterLogAndSlowQueryLogUsingJUL() { + ApplicationContextRunner contextRunner = this.contextRunner + .withPropertyValues("spring.sleuth.jdbc.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); + assertThat(context.getBean(Tracer.class).currentSpan()).isNull(); + }); + } + + @Test + void testRegisterLogAndSlowQueryLogUsingApacheCommons() { + ApplicationContextRunner contextRunner = this.contextRunner + .withPropertyValues("spring.sleuth.jdbc.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); + assertThat(context.getBean(Tracer.class).currentSpan()).isNull(); + }); + } + + @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); + assertThat(context.getBean(Tracer.class).currentSpan()).isNull(); + }); + } + + @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); + assertThat(context.getBean(Tracer.class).currentSpan()).isNull(); + }); + } + + @Test + void testGlobalConnectionIdManagerByDefault() { + contextRunner.run(context -> { + DataSource dataSource = context.getBean(DataSource.class); + ProxyDataSource proxyDataSource = (ProxyDataSource) ((DataSourceWrapper) dataSource) + .getDecoratedDataSource(); + + assertThat(proxyDataSource.getConnectionIdManager()).isInstanceOf(GlobalConnectionIdManager.class); + assertThat(context.getBean(Tracer.class).currentSpan()).isNull(); + }); + } + + @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); + assertThat(context.getBean(Tracer.class).currentSpan()).isNull(); + }); + } + + @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 queryInfoList) { + System.out.println("beforeQuery"); + } + + @Override + public void afterQuery(ExecutionInfo execInfo, List queryInfoList) { + System.out.println("afterQuery"); + } + }; + } + + } + +} diff --git a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/TracingJdbcEventListenerTests.java b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/TracingJdbcEventListenerTests.java index 446a620e7..ba2ad8b26 100644 --- a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/TracingJdbcEventListenerTests.java +++ b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/TracingJdbcEventListenerTests.java @@ -50,9 +50,7 @@ class TracingJdbcEventListenerTests extends TracingListenerStrategyTests { @Test void testShouldUsePlaceholderInSqlTagOfSpansForPreparedStatementIfIncludeParameterValuesIsSetToFalse() { - contextRunner - .withPropertyValues( - "spring.sleuth.jdbc.decorator.datasource.p6spy.tracing.include-parameter-values=false") + contextRunner.withPropertyValues("spring.sleuth.jdbc.p6spy.tracing.include-parameter-values=false") .run(context -> { DataSource dataSource = context.getBean(DataSource.class); TestSpanHandler spanReporter = context.getBean(TestSpanHandler.class); diff --git a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/TracingListenerStrategyTests.java b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/TracingListenerStrategyTests.java index 604eee779..9ff9efa2b 100644 --- a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/TracingListenerStrategyTests.java +++ b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/jdbc/TracingListenerStrategyTests.java @@ -490,29 +490,28 @@ abstract class TracingListenerStrategyTests { @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); + contextRunner.withPropertyValues("spring.sleuth.jdbc.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(); + 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"); - assertThat(context.getBean(Tracer.class).currentSpan()).isNull(); - }); + assertThat(spanReporter.spans()).hasSize(1); + MutableSpan connectionSpan = spanReporter.spans().get(0); + assertThat(connectionSpan.name()).isEqualTo("connection"); + assertThat(context.getBean(Tracer.class).currentSpan()).isNull(); + }); } @Test void testShouldIncludeOnlyQueryTraces() { - contextRunner.withPropertyValues("spring.sleuth.jdbc.decorator.datasource.includes: query").run(context -> { + contextRunner.withPropertyValues("spring.sleuth.jdbc.includes: query").run(context -> { DataSource dataSource = context.getBean(DataSource.class); TestSpanHandler spanReporter = context.getBean(TestSpanHandler.class); @@ -533,7 +532,7 @@ abstract class TracingListenerStrategyTests { @Test void testShouldIncludeOnlyFetchTraces() { - contextRunner.withPropertyValues("spring.sleuth.jdbc.decorator.datasource.includes: fetch").run(context -> { + contextRunner.withPropertyValues("spring.sleuth.jdbc.includes: fetch").run(context -> { DataSource dataSource = context.getBean(DataSource.class); TestSpanHandler spanReporter = context.getBean(TestSpanHandler.class); @@ -554,74 +553,71 @@ abstract class TracingListenerStrategyTests { @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); + contextRunner.withPropertyValues("spring.sleuth.jdbc.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(); + 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"); - assertThat(context.getBean(Tracer.class).currentSpan()).isNull(); - }); + 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(context.getBean(Tracer.class).currentSpan()).isNull(); + }); } @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); + contextRunner.withPropertyValues("spring.sleuth.jdbc.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(); + 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"); - assertThat(context.getBean(Tracer.class).currentSpan()).isNull(); - }); + 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"); + assertThat(context.getBean(Tracer.class).currentSpan()).isNull(); + }); } @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); + contextRunner.withPropertyValues("spring.sleuth.jdbc.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(); + 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"); - assertThat(context.getBean(Tracer.class).currentSpan()).isNull(); - }); + 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"); + assertThat(context.getBean(Tracer.class).currentSpan()).isNull(); + }); } @Test diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/jdbc/DataSourceProxyBuilderCustomizer.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/jdbc/DataSourceProxyBuilderCustomizer.java index 87fff522b..befaa4f62 100644 --- a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/jdbc/DataSourceProxyBuilderCustomizer.java +++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/jdbc/DataSourceProxyBuilderCustomizer.java @@ -136,9 +136,6 @@ public class DataSourceProxyBuilderCustomizer { 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); diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/jdbc/DataSourceProxyProperties.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/jdbc/DataSourceProxyProperties.java index 802fff5bf..be8a40839 100644 --- a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/jdbc/DataSourceProxyProperties.java +++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/jdbc/DataSourceProxyProperties.java @@ -19,7 +19,6 @@ 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; @@ -61,14 +60,6 @@ public class DataSourceProxyProperties { */ private boolean jsonFormat = false; - /** - * Creates listener to count queries. - * - * @see ProxyDataSourceBuilder#countQuery() - * @see QueryCountHolder - */ - private boolean countQuery = false; - public DataSourceProxyLogging getLogging() { return logging; } @@ -109,14 +100,6 @@ public class DataSourceProxyProperties { this.jsonFormat = jsonFormat; } - public boolean isCountQuery() { - return countQuery; - } - - public void setCountQuery(boolean countQuery) { - this.countQuery = countQuery; - } - /** * Properties to configure query logging listener. * diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/jdbc/DataSourceWrapper.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/jdbc/DataSourceWrapper.java index 0679b9aaa..3a3a8511e 100644 --- a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/jdbc/DataSourceWrapper.java +++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/jdbc/DataSourceWrapper.java @@ -21,10 +21,7 @@ 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. + * Returns link of both real {@link DataSource}, decorated {@link DataSource}. * * @author Arthur Gavlyukovskiy * @since 3.1.0 diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/jdbc/TraceListenerStrategy.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/jdbc/TraceListenerStrategy.java index 9ebe8e830..c89cbb2d2 100644 --- a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/jdbc/TraceListenerStrategy.java +++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/jdbc/TraceListenerStrategy.java @@ -1,506 +1,506 @@ -/* - * 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.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -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 connection type - * @param statement - * @param result set - */ -class TraceListenerStrategy { - - private static final Log log = LogFactory.getLog(TraceListenerStrategy.class); - - private final Map 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 traceTypes; - - private final List customizers; - - TraceListenerStrategy(Tracer tracer, List traceTypes, - List customizers) { - this.tracer = tracer; - this.traceTypes = traceTypes; - this.customizers = customizers; - } - - void beforeGetConnection(CON connectionKey, @Nullable CommonDataSource dataSource, String dataSourceName) { - if (log.isTraceEnabled()) { - log.trace("Before get connection key [" + connectionKey + "] - current span is [" + tracer.currentSpan() - + "]"); - } - 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)); - if (log.isTraceEnabled()) { - log.trace("Started client span before connection [" + connectionSpan + "] - current span is [" - + tracer.currentSpan() + "]"); - } - } - ConnectionInfo connectionInfo = new ConnectionInfo(spanAndScope); - this.openConnections.put(connectionKey, connectionInfo); - } - - void afterGetConnection(CON connectionKey, Connection connection, Throwable t) { - if (log.isTraceEnabled()) { - log.trace("After get connection [" + connectionKey + "]. Current span is [" + tracer.currentSpan() + "]"); - } - this.openConnections.get(connectionKey).getSpan().ifPresent(spanAndScope -> { - parseServerIpAndPort(connection, spanAndScope.getSpan()); - }); - if (t != null) { - ConnectionInfo connectionInfo = this.openConnections.remove(connectionKey); - connectionInfo.getSpan().ifPresent(connectionSpan -> { - parseServerIpAndPort(connection, connectionSpan.getSpan()); - if (log.isTraceEnabled()) { - log.trace("Closing client span due to exception [" + connectionSpan.getSpan() - + "] - current span is [" + tracer.currentSpan() + "]"); - } - connectionSpan.getSpan().error(t); - connectionSpan.close(); - if (log.isTraceEnabled()) { - log.trace("Current span [" + tracer.currentSpan() + "]"); - } - }); - } - } - - void beforeQuery(CON connectionKey, Connection connection, STMT statementKey, String dataSourceName) { - if (log.isTraceEnabled()) { - log.trace("Before query - connection [" + connectionKey + "] and current span [" + tracer.currentSpan() - + "]"); - } - ConnectionInfo connectionInfo = openConnections.get(connectionKey); - if (connectionInfo == null) { - if (log.isTraceEnabled()) { - log.trace("Connection may be closed after statement preparation, but before statement execution"); - } - return; - } - SpanAndScope spanAndScope = null; - if (traceTypes.contains(TraceType.QUERY)) { - Span.Builder statementSpanBuilder = AssertingSpanBuilder - .of(SleuthJdbcSpan.JDBC_QUERY_SPAN, tracer.spanBuilder()) - .name(String.format(SleuthJdbcSpan.JDBC_QUERY_SPAN.getName(), "query")); - statementSpanBuilder.remoteServiceName(dataSourceName); - parseServerIpAndPort(connection, statementSpanBuilder); - statementSpanBuilder.kind(Span.Kind.CLIENT); - Span statementSpan = statementSpanBuilder.start(); - spanAndScope = new SpanAndScope(statementSpan, tracer.withSpan(statementSpan)); - if (log.isTraceEnabled()) { - log.trace("Started client span before query [" + statementSpan + "] - current span is [" - + tracer.currentSpan() + "]"); - } - } - StatementInfo statementInfo = new StatementInfo(spanAndScope); - connectionInfo.getNestedStatements().put(statementKey, statementInfo); - } - - void addQueryRowCount(CON connectionKey, STMT statementKey, int rowCount) { - if (log.isTraceEnabled()) { - log.trace("Add query row count for connection key [" + connectionKey + "]"); - } - ConnectionInfo connectionInfo = openConnections.get(connectionKey); - if (connectionInfo == null) { - if (log.isTraceEnabled()) { - log.trace("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) { - if (log.isTraceEnabled()) { - log.trace("After query for connection key [" + connectionKey + "]"); - } - ConnectionInfo connectionInfo = openConnections.get(connectionKey); - if (connectionInfo == null) { - if (log.isTraceEnabled()) { - log.trace( - "Connection may be closed after statement preparation, but before statement execution. Current span is [" - + tracer.currentSpan() + "]"); - } - return; - } - StatementInfo statementInfo = connectionInfo.getNestedStatements().get(statementKey); - statementInfo.getSpan().ifPresent(statementSpan -> { - updateQuerySpan(sql, t, statementSpan); - statementSpan.close(); - if (log.isTraceEnabled()) { - log.trace("Current span [" + tracer.currentSpan() + "]"); - } - }); - } - - private void updateQuerySpan(String sql, Throwable t, SpanAndScope statementSpan) { - AssertingSpan.of(SleuthJdbcSpan.JDBC_QUERY_SPAN, statementSpan.getSpan()) - .tag(SleuthJdbcSpan.QueryTags.QUERY, sql).name(spanName(sql)); - if (t != null) { - statementSpan.getSpan().error(t); - } - if (log.isTraceEnabled()) { - log.trace( - "Closing statement span [" + statementSpan + "] - current span is [" + tracer.currentSpan() + "]"); - } - } - - void beforeResultSetNext(CON connectionKey, Connection connection, STMT statementKey, RS resultSetKey, - String dataSourceName) { - if (log.isTraceEnabled()) { - log.trace("Before result set next"); - } - if (!traceTypes.contains(TraceType.FETCH)) { - return; - } - ConnectionInfo connectionInfo = openConnections.get(connectionKey); - // ConnectionInfo may be null if Connection was closed before ResultSet - if (connectionInfo == null) { - if (log.isTraceEnabled()) { - log.trace("No connection info, skipping"); - } - return; - } - if (connectionInfo.getNestedResultSetSpans().containsKey(resultSetKey)) { - if (log.isTraceEnabled()) { - log.trace("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)); - if (log.isTraceEnabled()) { - log.trace("Started client result set span [" + resultSetSpan + "] - current span is [" - + tracer.currentSpan() + "]"); - } - 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) { - if (log.isTraceEnabled()) { - log.trace("After result set close"); - } - 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); - } - if (log.isTraceEnabled()) { - log.trace("Closing client result set span [" + resultSetSpan + "] - current span is [" - + tracer.currentSpan() + "]"); - } - resultSetSpan.close(); - if (log.isTraceEnabled()) { - log.trace("Current span [" + tracer.currentSpan() + "]"); - } - } - - void afterStatementClose(CON connectionKey, STMT statementKey) { - if (log.isTraceEnabled()) { - log.trace("After statement close"); - } - 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); - if (log.isTraceEnabled()) { - log.trace("Closing span after statement close [" + span.getSpan() + "] - current span is [" - + tracer.currentSpan() + "]"); - } - span.close(); - if (log.isTraceEnabled()) { - log.trace("Current span [" + tracer.currentSpan() + "]"); - } - }); - statementInfo.getNestedResultSetSpans().clear(); - } - } - - void afterCommit(CON connectionKey, Throwable t) { - if (log.isTraceEnabled()) { - log.trace("After commit"); - } - 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) { - if (log.isTraceEnabled()) { - log.trace("After rollback"); - } - 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) { - if (log.isTraceEnabled()) { - log.trace("After connection close with key [" + connectionKey + "]"); - } - 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)); - if (log.isTraceEnabled()) { - log.trace("Current span after closing statements [" + tracer.currentSpan() + "]"); - } - connectionInfo.getSpan().ifPresent(connectionSpan -> { - if (t != null) { - connectionSpan.getSpan().error(t); - } - if (log.isTraceEnabled()) { - log.trace("Closing span after connection close [" + connectionSpan.getSpan() + "] - current span is [" - + tracer.currentSpan() + "]"); - } - connectionSpan.close(); - if (log.isTraceEnabled()) { - log.trace("Current span [" + tracer.currentSpan() + "]"); - } - }); - } - - 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 nestedStatements = new ConcurrentHashMap<>(); - - private final Map nestedResultSetSpans = new ConcurrentHashMap<>(); - - private ConnectionInfo(@Nullable SpanAndScope span) { - this.span = span; - } - - Optional getSpan() { - return Optional.ofNullable(span); - } - - Map getNestedStatements() { - return nestedStatements; - } - - Map getNestedResultSetSpans() { - return nestedResultSetSpans; - } - - } - - private final class StatementInfo { - - private final SpanAndScope span; - - private final Map nestedResultSetSpans = new ConcurrentHashMap<>(); - - private StatementInfo(SpanAndScope span) { - this.span = span; - } - - Optional getSpan() { - return Optional.ofNullable(span); - } - - Map 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); - } - - } - -} +/* + * 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.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +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 connection type + * @param statement + * @param result set + */ +class TraceListenerStrategy { + + private static final Log log = LogFactory.getLog(TraceListenerStrategy.class); + + private final Map 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 traceTypes; + + private final List customizers; + + TraceListenerStrategy(Tracer tracer, List traceTypes, + List customizers) { + this.tracer = tracer; + this.traceTypes = traceTypes; + this.customizers = customizers; + } + + void beforeGetConnection(CON connectionKey, @Nullable CommonDataSource dataSource, String dataSourceName) { + if (log.isTraceEnabled()) { + log.trace("Before get connection key [" + connectionKey + "] - current span is [" + tracer.currentSpan() + + "]"); + } + 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)); + if (log.isTraceEnabled()) { + log.trace("Started client span before connection [" + connectionSpan + "] - current span is [" + + tracer.currentSpan() + "]"); + } + } + ConnectionInfo connectionInfo = new ConnectionInfo(spanAndScope); + this.openConnections.put(connectionKey, connectionInfo); + } + + void afterGetConnection(CON connectionKey, Connection connection, Throwable t) { + if (log.isTraceEnabled()) { + log.trace("After get connection [" + connectionKey + "]. Current span is [" + tracer.currentSpan() + "]"); + } + this.openConnections.get(connectionKey).getSpan().ifPresent(spanAndScope -> { + parseServerIpAndPort(connection, spanAndScope.getSpan()); + }); + if (t != null) { + ConnectionInfo connectionInfo = this.openConnections.remove(connectionKey); + connectionInfo.getSpan().ifPresent(connectionSpan -> { + parseServerIpAndPort(connection, connectionSpan.getSpan()); + if (log.isTraceEnabled()) { + log.trace("Closing client span due to exception [" + connectionSpan.getSpan() + + "] - current span is [" + tracer.currentSpan() + "]"); + } + connectionSpan.getSpan().error(t); + connectionSpan.close(); + if (log.isTraceEnabled()) { + log.trace("Current span [" + tracer.currentSpan() + "]"); + } + }); + } + } + + void beforeQuery(CON connectionKey, Connection connection, STMT statementKey, String dataSourceName) { + if (log.isTraceEnabled()) { + log.trace("Before query - connection [" + connectionKey + "] and current span [" + tracer.currentSpan() + + "]"); + } + ConnectionInfo connectionInfo = openConnections.get(connectionKey); + if (connectionInfo == null) { + if (log.isTraceEnabled()) { + log.trace("Connection may be closed after statement preparation, but before statement execution"); + } + return; + } + SpanAndScope spanAndScope = null; + if (traceTypes.contains(TraceType.QUERY)) { + Span.Builder statementSpanBuilder = AssertingSpanBuilder + .of(SleuthJdbcSpan.JDBC_QUERY_SPAN, tracer.spanBuilder()) + .name(String.format(SleuthJdbcSpan.JDBC_QUERY_SPAN.getName(), "query")); + statementSpanBuilder.remoteServiceName(dataSourceName); + parseServerIpAndPort(connection, statementSpanBuilder); + statementSpanBuilder.kind(Span.Kind.CLIENT); + Span statementSpan = statementSpanBuilder.start(); + spanAndScope = new SpanAndScope(statementSpan, tracer.withSpan(statementSpan)); + if (log.isTraceEnabled()) { + log.trace("Started client span before query [" + statementSpan + "] - current span is [" + + tracer.currentSpan() + "]"); + } + } + StatementInfo statementInfo = new StatementInfo(spanAndScope); + connectionInfo.getNestedStatements().put(statementKey, statementInfo); + } + + void addQueryRowCount(CON connectionKey, STMT statementKey, int rowCount) { + if (log.isTraceEnabled()) { + log.trace("Add query row count for connection key [" + connectionKey + "]"); + } + ConnectionInfo connectionInfo = openConnections.get(connectionKey); + if (connectionInfo == null) { + if (log.isTraceEnabled()) { + log.trace("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) { + if (log.isTraceEnabled()) { + log.trace("After query for connection key [" + connectionKey + "]"); + } + ConnectionInfo connectionInfo = openConnections.get(connectionKey); + if (connectionInfo == null) { + if (log.isTraceEnabled()) { + log.trace( + "Connection may be closed after statement preparation, but before statement execution. Current span is [" + + tracer.currentSpan() + "]"); + } + return; + } + StatementInfo statementInfo = connectionInfo.getNestedStatements().get(statementKey); + statementInfo.getSpan().ifPresent(statementSpan -> { + updateQuerySpan(sql, t, statementSpan); + statementSpan.close(); + if (log.isTraceEnabled()) { + log.trace("Current span [" + tracer.currentSpan() + "]"); + } + }); + } + + private void updateQuerySpan(String sql, Throwable t, SpanAndScope statementSpan) { + AssertingSpan.of(SleuthJdbcSpan.JDBC_QUERY_SPAN, statementSpan.getSpan()) + .tag(SleuthJdbcSpan.QueryTags.QUERY, sql).name(spanName(sql)); + if (t != null) { + statementSpan.getSpan().error(t); + } + if (log.isTraceEnabled()) { + log.trace( + "Closing statement span [" + statementSpan + "] - current span is [" + tracer.currentSpan() + "]"); + } + } + + void beforeResultSetNext(CON connectionKey, Connection connection, STMT statementKey, RS resultSetKey, + String dataSourceName) { + if (log.isTraceEnabled()) { + log.trace("Before result set next"); + } + if (!traceTypes.contains(TraceType.FETCH)) { + return; + } + ConnectionInfo connectionInfo = openConnections.get(connectionKey); + // ConnectionInfo may be null if Connection was closed before ResultSet + if (connectionInfo == null) { + if (log.isTraceEnabled()) { + log.trace("No connection info, skipping"); + } + return; + } + if (connectionInfo.getNestedResultSetSpans().containsKey(resultSetKey)) { + if (log.isTraceEnabled()) { + log.trace("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)); + if (log.isTraceEnabled()) { + log.trace("Started client result set span [" + resultSetSpan + "] - current span is [" + + tracer.currentSpan() + "]"); + } + 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) { + if (log.isTraceEnabled()) { + log.trace("After result set close"); + } + 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); + } + if (log.isTraceEnabled()) { + log.trace("Closing client result set span [" + resultSetSpan + "] - current span is [" + + tracer.currentSpan() + "]"); + } + resultSetSpan.close(); + if (log.isTraceEnabled()) { + log.trace("Current span [" + tracer.currentSpan() + "]"); + } + } + + void afterStatementClose(CON connectionKey, STMT statementKey) { + if (log.isTraceEnabled()) { + log.trace("After statement close"); + } + 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); + if (log.isTraceEnabled()) { + log.trace("Closing span after statement close [" + span.getSpan() + "] - current span is [" + + tracer.currentSpan() + "]"); + } + span.close(); + if (log.isTraceEnabled()) { + log.trace("Current span [" + tracer.currentSpan() + "]"); + } + }); + statementInfo.getNestedResultSetSpans().clear(); + } + } + + void afterCommit(CON connectionKey, Throwable t) { + if (log.isTraceEnabled()) { + log.trace("After commit"); + } + 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) { + if (log.isTraceEnabled()) { + log.trace("After rollback"); + } + 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) { + if (log.isTraceEnabled()) { + log.trace("After connection close with key [" + connectionKey + "]"); + } + 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)); + if (log.isTraceEnabled()) { + log.trace("Current span after closing statements [" + tracer.currentSpan() + "]"); + } + connectionInfo.getSpan().ifPresent(connectionSpan -> { + if (t != null) { + connectionSpan.getSpan().error(t); + } + if (log.isTraceEnabled()) { + log.trace("Closing span after connection close [" + connectionSpan.getSpan() + "] - current span is [" + + tracer.currentSpan() + "]"); + } + connectionSpan.close(); + if (log.isTraceEnabled()) { + log.trace("Current span [" + tracer.currentSpan() + "]"); + } + }); + } + + 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 nestedStatements = new ConcurrentHashMap<>(); + + private final Map nestedResultSetSpans = new ConcurrentHashMap<>(); + + private ConnectionInfo(@Nullable SpanAndScope span) { + this.span = span; + } + + Optional getSpan() { + return Optional.ofNullable(span); + } + + Map getNestedStatements() { + return nestedStatements; + } + + Map getNestedResultSetSpans() { + return nestedResultSetSpans; + } + + } + + private final class StatementInfo { + + private final SpanAndScope span; + + private final Map nestedResultSetSpans = new ConcurrentHashMap<>(); + + private StatementInfo(SpanAndScope span) { + this.span = span; + } + + Optional getSpan() { + return Optional.ofNullable(span); + } + + Map 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); + } + + } + +} diff --git a/tests/brave/spring-cloud-sleuth-instrumentation-kafka-tests/src/test/java/org/springframework/cloud/sleuth/brave/instrument/kafka/KafkaProducerTest.java b/tests/brave/spring-cloud-sleuth-instrumentation-kafka-tests/src/test/java/org/springframework/cloud/sleuth/brave/instrument/kafka/KafkaProducerTest.java index 1692beac8..a7e76064f 100644 --- a/tests/brave/spring-cloud-sleuth-instrumentation-kafka-tests/src/test/java/org/springframework/cloud/sleuth/brave/instrument/kafka/KafkaProducerTest.java +++ b/tests/brave/spring-cloud-sleuth-instrumentation-kafka-tests/src/test/java/org/springframework/cloud/sleuth/brave/instrument/kafka/KafkaProducerTest.java @@ -1,66 +1,66 @@ -/* - * 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.brave.instrument.kafka; - -import java.util.Optional; -import java.util.concurrent.TimeUnit; - -import org.apache.kafka.clients.consumer.ConsumerRecord; -import org.apache.kafka.clients.producer.ProducerRecord; -import org.apache.kafka.common.header.Header; -import org.assertj.core.api.BDDAssertions; -import org.awaitility.Awaitility; -import org.junit.jupiter.api.Test; - -import org.springframework.cloud.sleuth.brave.BraveTestTracing; -import org.springframework.cloud.sleuth.test.TestTracingAware; - -public class KafkaProducerTest extends org.springframework.cloud.sleuth.instrument.kafka.KafkaProducerTest { - - BraveTestTracing testTracing; - - @Override - public TestTracingAware tracerTest() { - if (this.testTracing == null) { - this.testTracing = new BraveTestTracing(); - } - return this.testTracing; - } - - @Test - public void should_inject_native_headers() throws InterruptedException { - ProducerRecord producerRecord = new ProducerRecord<>(testTopic, "test", "test"); - startKafkaConsumer(); - - this.kafkaProducer.send(producerRecord); - - Awaitility.await().atMost(1, TimeUnit.MINUTES).pollInterval(1, TimeUnit.SECONDS).untilAsserted(() -> { - ConsumerRecord consumerRecord = consumerRecords.poll(15, TimeUnit.SECONDS); - - BDDAssertions.then(consumerRecord).isNotNull(); - BDDAssertions.then(getHeaderValueOrNull(consumerRecord, "X-B3-TraceId")).isNotNull(); - BDDAssertions.then(getHeaderValueOrNull(consumerRecord, "X-B3-SpanId")).isNotNull(); - BDDAssertions.then(getHeaderValueOrNull(consumerRecord, "X-B3-Sampled")).isNotNull(); - }); - } - - private static String getHeaderValueOrNull(ConsumerRecord consumerRecord, String header) { - return Optional.ofNullable(consumerRecord).map(ConsumerRecord::headers) - .map(headers -> headers.lastHeader(header)).map(Header::value).map(String::new).orElse(null); - } - -} +/* + * 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.brave.instrument.kafka; + +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.apache.kafka.common.header.Header; +import org.assertj.core.api.BDDAssertions; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Test; + +import org.springframework.cloud.sleuth.brave.BraveTestTracing; +import org.springframework.cloud.sleuth.test.TestTracingAware; + +public class KafkaProducerTest extends org.springframework.cloud.sleuth.instrument.kafka.KafkaProducerTest { + + BraveTestTracing testTracing; + + @Override + public TestTracingAware tracerTest() { + if (this.testTracing == null) { + this.testTracing = new BraveTestTracing(); + } + return this.testTracing; + } + + @Test + public void should_inject_native_headers() throws InterruptedException { + ProducerRecord producerRecord = new ProducerRecord<>(testTopic, "test", "test"); + startKafkaConsumer(); + + this.kafkaProducer.send(producerRecord); + + Awaitility.await().atMost(1, TimeUnit.MINUTES).pollInterval(1, TimeUnit.SECONDS).untilAsserted(() -> { + ConsumerRecord consumerRecord = consumerRecords.poll(15, TimeUnit.SECONDS); + + BDDAssertions.then(consumerRecord).isNotNull(); + BDDAssertions.then(getHeaderValueOrNull(consumerRecord, "X-B3-TraceId")).isNotNull(); + BDDAssertions.then(getHeaderValueOrNull(consumerRecord, "X-B3-SpanId")).isNotNull(); + BDDAssertions.then(getHeaderValueOrNull(consumerRecord, "X-B3-Sampled")).isNotNull(); + }); + } + + private static String getHeaderValueOrNull(ConsumerRecord consumerRecord, String header) { + return Optional.ofNullable(consumerRecord).map(ConsumerRecord::headers) + .map(headers -> headers.lastHeader(header)).map(Header::value).map(String::new).orElse(null); + } + +} diff --git a/tests/brave/spring-cloud-sleuth-instrumentation-mvc-tests/src/test/java/org/springframework/cloud/sleuth/brave/instrument/web/TraceAsyncIntegrationTests.java b/tests/brave/spring-cloud-sleuth-instrumentation-mvc-tests/src/test/java/org/springframework/cloud/sleuth/brave/instrument/web/TraceAsyncIntegrationTests.java index b8a862262..5ebdeb20d 100644 --- a/tests/brave/spring-cloud-sleuth-instrumentation-mvc-tests/src/test/java/org/springframework/cloud/sleuth/brave/instrument/web/TraceAsyncIntegrationTests.java +++ b/tests/brave/spring-cloud-sleuth-instrumentation-mvc-tests/src/test/java/org/springframework/cloud/sleuth/brave/instrument/web/TraceAsyncIntegrationTests.java @@ -1,233 +1,237 @@ -/* - * 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.brave.instrument.web; - -import java.util.concurrent.Executor; -import java.util.concurrent.atomic.AtomicReference; - -import brave.Span; -import brave.Tracer; -import brave.handler.MutableSpan; -import brave.handler.SpanHandler; -import brave.sampler.Sampler; -import brave.test.TestSpanHandler; -import org.awaitility.Awaitility; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.sleuth.SpanName; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.task.SimpleAsyncTaskExecutor; -import org.springframework.scheduling.annotation.Async; -import org.springframework.scheduling.annotation.EnableAsync; - -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.assertj.core.api.BDDAssertions.then; - -@SpringBootTest(classes = { TraceAsyncIntegrationTests.TraceAsyncITestConfiguration.class }) -public class TraceAsyncIntegrationTests { - - @Autowired - ClassPerformingAsyncLogic classPerformingAsyncLogic; - - @Autowired - Tracer tracer; - - @Autowired - TestSpanHandler spans; - - @BeforeEach - public void cleanup() { - this.spans.clear(); - this.classPerformingAsyncLogic.clear(); - } - - @Test - public void should_set_span_on_an_async_annotated_method() { - whenAsyncProcessingTakesPlace(); - - thenANewAsyncSpanGetsCreated(); - } - - @Test - public void should_set_span_with_custom_method_on_an_async_annotated_method() { - whenAsyncProcessingTakesPlaceWithCustomSpanName(); - - thenAsyncSpanHasCustomName(); - } - - @Test - public void should_continue_a_span_on_an_async_annotated_method() { - Span span = givenASpanInCurrentThread(); - - try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { - whenAsyncProcessingTakesPlace(); - } - finally { - span.finish(); - } - - thenTraceIdIsPassedFromTheCurrentThreadToTheAsyncOne(span); - } - - @Test - public void should_continue_a_span_with_custom_method_on_an_async_annotated_method() { - Span span = givenASpanInCurrentThread(); - - try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { - whenAsyncProcessingTakesPlaceWithCustomSpanName(); - } - finally { - span.finish(); - } - - thenTraceIdIsPassedFromTheCurrentThreadToTheAsyncOneAndSpanHasCustomName(span); - } - - private Span givenASpanInCurrentThread() { - return this.tracer.nextSpan().name("http:existing"); - } - - private void whenAsyncProcessingTakesPlace() { - this.classPerformingAsyncLogic.invokeAsynchronousLogic(); - } - - private void whenAsyncProcessingTakesPlaceWithCustomSpanName() { - this.classPerformingAsyncLogic.customNameInvokeAsynchronousLogic(); - } - - private void thenTraceIdIsPassedFromTheCurrentThreadToTheAsyncOne(final Span span) { - Awaitility.await().atMost(5, SECONDS).untilAsserted(() -> { - then(TraceAsyncIntegrationTests.this.classPerformingAsyncLogic.getSpan().context().traceId()) - .isEqualTo(span.context().traceId()); - then(this.spans).hasSize(2); - // HTTP - then(this.spans.get(0).name()).isEqualTo("http:existing"); - // ASYNC - then(this.spans.get(1).tags()).containsEntry("class", "ClassPerformingAsyncLogic").containsEntry("method", - "invokeAsynchronousLogic"); - }); - } - - private void thenANewAsyncSpanGetsCreated() { - Awaitility.await().atMost(5, SECONDS).untilAsserted(() -> { - then(this.spans).hasSize(1); - MutableSpan storedSpan = this.spans.get(0); - then(storedSpan.name()).isEqualTo("invoke-asynchronous-logic"); - then(storedSpan.tags()).containsEntry("class", "ClassPerformingAsyncLogic").containsEntry("method", - "invokeAsynchronousLogic"); - }); - } - - 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); - // HTTP - then(this.spans.get(0).name()).isEqualTo("http:existing"); - // ASYNC - then(this.spans.get(1).tags()).containsEntry("class", "ClassPerformingAsyncLogic").containsEntry("method", - "customNameInvokeAsynchronousLogic"); - }); - } - - private void thenAsyncSpanHasCustomName() { - Awaitility.await().atMost(5, SECONDS).untilAsserted(() -> { - then(this.spans).hasSize(1); - MutableSpan storedSpan = this.spans.get(0); - then(storedSpan.name()).isEqualTo("foo"); - then(storedSpan.tags()).containsEntry("class", "ClassPerformingAsyncLogic").containsEntry("method", - "customNameInvokeAsynchronousLogic"); - }); - } - - @AfterEach - public void cleanTrace() { - this.spans.clear(); - } - - @EnableAutoConfiguration - @EnableAsync - @Configuration(proxyBeanMethods = false) - static class TraceAsyncITestConfiguration { - - @Bean - ClassPerformingAsyncLogic asyncClass(Tracer tracer) { - return new ClassPerformingAsyncLogic(tracer); - } - - @Bean - Sampler defaultSampler() { - return Sampler.ALWAYS_SAMPLE; - } - - @Bean - SpanHandler testSpanHandler() { - return new TestSpanHandler(); - } - - @Bean - Executor fooExecutor() { - return new SimpleAsyncTaskExecutor(); - } - - @Bean - Executor barExecutor() { - return new SimpleAsyncTaskExecutor(); - } - - } - - static class ClassPerformingAsyncLogic { - - private final Tracer tracer; - - AtomicReference span = new AtomicReference<>(); - - ClassPerformingAsyncLogic(Tracer tracer) { - this.tracer = tracer; - } - - @Async("fooExecutor") - public void invokeAsynchronousLogic() { - this.span.set(this.tracer.currentSpan()); - } - - @Async - @SpanName("foo") - public void customNameInvokeAsynchronousLogic() { - this.span.set(this.tracer.currentSpan()); - } - - public Span getSpan() { - return this.span.get(); - } - - public void clear() { - this.span.set(null); - } - - } - -} +/* + * 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.brave.instrument.web; + +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +import brave.Span; +import brave.Tracer; +import brave.handler.MutableSpan; +import brave.handler.SpanHandler; +import brave.sampler.Sampler; +import brave.test.TestSpanHandler; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.sleuth.SpanName; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.EnableAsync; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.BDDAssertions.then; + +@SpringBootTest(classes = { TraceAsyncIntegrationTests.TraceAsyncITestConfiguration.class }) +public class TraceAsyncIntegrationTests { + + @Autowired + ClassPerformingAsyncLogic classPerformingAsyncLogic; + + @Autowired + Tracer tracer; + + @Autowired + TestSpanHandler spans; + + @BeforeEach + public void cleanup() { + this.spans.clear(); + this.classPerformingAsyncLogic.clear(); + } + + @Test + public void should_set_span_on_an_async_annotated_method() { + whenAsyncProcessingTakesPlace(); + + thenANewAsyncSpanGetsCreated(); + } + + @Test + public void should_set_span_with_custom_method_on_an_async_annotated_method() { + whenAsyncProcessingTakesPlaceWithCustomSpanName(); + + thenAsyncSpanHasCustomName(); + } + + @Test + public void should_continue_a_span_on_an_async_annotated_method() { + Span span = givenASpanInCurrentThread(); + + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { + whenAsyncProcessingTakesPlace(); + } + finally { + span.finish(); + } + + thenTraceIdIsPassedFromTheCurrentThreadToTheAsyncOne(span); + } + + @Test + public void should_continue_a_span_with_custom_method_on_an_async_annotated_method() { + Span span = givenASpanInCurrentThread(); + + try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { + whenAsyncProcessingTakesPlaceWithCustomSpanName(); + } + finally { + span.finish(); + } + + thenTraceIdIsPassedFromTheCurrentThreadToTheAsyncOneAndSpanHasCustomName(span); + } + + private Span givenASpanInCurrentThread() { + return this.tracer.nextSpan().name("http:existing"); + } + + private void whenAsyncProcessingTakesPlace() { + this.classPerformingAsyncLogic.invokeAsynchronousLogic(); + } + + private void whenAsyncProcessingTakesPlaceWithCustomSpanName() { + this.classPerformingAsyncLogic.customNameInvokeAsynchronousLogic(); + } + + private void thenTraceIdIsPassedFromTheCurrentThreadToTheAsyncOne(final Span span) { + Awaitility.await().atMost(5, SECONDS).untilAsserted(() -> { + then(TraceAsyncIntegrationTests.this.classPerformingAsyncLogic.getSpan().context().traceId()) + .isEqualTo(span.context().traceId()); + List webSpans = this.spans.spans().stream().filter(mutableSpan -> mutableSpan.traceId().equalsIgnoreCase(span.context().traceIdString())) + .collect(Collectors.toList()); + then(webSpans).hasSize(2); + // HTTP + then(webSpans.get(0).name()).isEqualTo("http:existing"); + // ASYNC + then(webSpans.get(1).tags()).containsEntry("class", "ClassPerformingAsyncLogic").containsEntry("method", + "invokeAsynchronousLogic"); + }); + } + + private void thenANewAsyncSpanGetsCreated() { + Awaitility.await().atMost(5, SECONDS).untilAsserted(() -> { + then(this.spans).hasSize(1); + MutableSpan storedSpan = this.spans.get(0); + then(storedSpan.name()).isEqualTo("invoke-asynchronous-logic"); + then(storedSpan.tags()).containsEntry("class", "ClassPerformingAsyncLogic").containsEntry("method", + "invokeAsynchronousLogic"); + }); + } + + 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); + // HTTP + then(this.spans.get(0).name()).isEqualTo("http:existing"); + // ASYNC + then(this.spans.get(1).tags()).containsEntry("class", "ClassPerformingAsyncLogic").containsEntry("method", + "customNameInvokeAsynchronousLogic"); + }); + } + + private void thenAsyncSpanHasCustomName() { + Awaitility.await().atMost(5, SECONDS).untilAsserted(() -> { + then(this.spans).hasSize(1); + MutableSpan storedSpan = this.spans.get(0); + then(storedSpan.name()).isEqualTo("foo"); + then(storedSpan.tags()).containsEntry("class", "ClassPerformingAsyncLogic").containsEntry("method", + "customNameInvokeAsynchronousLogic"); + }); + } + + @AfterEach + public void cleanTrace() { + this.spans.clear(); + } + + @EnableAutoConfiguration + @EnableAsync + @Configuration(proxyBeanMethods = false) + static class TraceAsyncITestConfiguration { + + @Bean + ClassPerformingAsyncLogic asyncClass(Tracer tracer) { + return new ClassPerformingAsyncLogic(tracer); + } + + @Bean + Sampler defaultSampler() { + return Sampler.ALWAYS_SAMPLE; + } + + @Bean + SpanHandler testSpanHandler() { + return new TestSpanHandler(); + } + + @Bean + Executor fooExecutor() { + return new SimpleAsyncTaskExecutor(); + } + + @Bean + Executor barExecutor() { + return new SimpleAsyncTaskExecutor(); + } + + } + + static class ClassPerformingAsyncLogic { + + private final Tracer tracer; + + AtomicReference span = new AtomicReference<>(); + + ClassPerformingAsyncLogic(Tracer tracer) { + this.tracer = tracer; + } + + @Async("fooExecutor") + public void invokeAsynchronousLogic() { + this.span.set(this.tracer.currentSpan()); + } + + @Async + @SpanName("foo") + public void customNameInvokeAsynchronousLogic() { + this.span.set(this.tracer.currentSpan()); + } + + public Span getSpan() { + return this.span.get(); + } + + public void clear() { + this.span.set(null); + } + + } + +} diff --git a/tests/common/src/main/java/org/springframework/cloud/sleuth/instrument/r2dbc/R2dbcIntegrationTests.java b/tests/common/src/main/java/org/springframework/cloud/sleuth/instrument/r2dbc/R2dbcIntegrationTests.java index 7844ae8ba..7ad6008b8 100644 --- a/tests/common/src/main/java/org/springframework/cloud/sleuth/instrument/r2dbc/R2dbcIntegrationTests.java +++ b/tests/common/src/main/java/org/springframework/cloud/sleuth/instrument/r2dbc/R2dbcIntegrationTests.java @@ -1,102 +1,102 @@ -/* - * 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.r2dbc; - -import java.time.Duration; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import io.r2dbc.spi.ConnectionFactory; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.CommandLineRunner; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; -import org.springframework.cloud.sleuth.exporter.FinishedSpan; -import org.springframework.cloud.sleuth.test.TestSpanHandler; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.ClassPathResource; -import org.springframework.dao.DataAccessException; -import org.springframework.r2dbc.connection.init.ConnectionFactoryInitializer; -import org.springframework.r2dbc.connection.init.ResourceDatabasePopulator; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestPropertySource; - -import static org.assertj.core.api.BDDAssertions.then; - -@ContextConfiguration(classes = R2dbcIntegrationTests.TestConfig.class) -@TestPropertySource(properties = { "spring.application.name=MyApplication", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1" }) -@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.ANY) -public abstract class R2dbcIntegrationTests { - - @Autowired - TestSpanHandler spans; - - @Test - public void should_pass_tracing_information_when_using_r2dbc() { - Set traceIds = this.spans.reportedSpans().stream().map(FinishedSpan::getTraceId) - .collect(Collectors.toSet()); - then(traceIds).as("There's one traceid").hasSize(1); - Set spanIds = this.spans.reportedSpans().stream().map(FinishedSpan::getSpanId) - .collect(Collectors.toSet()); - - // 2 transactions - 9 database interactions - then(spanIds).as("There are 11 spans").hasSize(11); - List spanNames = this.spans.reportedSpans().stream().map(FinishedSpan::getName) - .collect(Collectors.toList()); - List remoteServiceNames = this.spans.reportedSpans().stream().map(FinishedSpan::getRemoteServiceName) - .collect(Collectors.toList()); - then(spanNames.stream().filter("tx"::equalsIgnoreCase).collect(Collectors.toList())).hasSize(2); - then(remoteServiceNames.stream().filter("h2"::equalsIgnoreCase).collect(Collectors.toList())).hasSize(9); - } - - @Configuration(proxyBeanMethods = false) - @EnableAutoConfiguration - @ComponentScan - public static class TestConfig { - - private static final Logger log = LoggerFactory.getLogger(TestConfig.class); - - @Bean - public CommandLineRunner demo(ReactiveNewTransactionService reactiveNewTransactionService) { - return (args) -> { - try { - reactiveNewTransactionService.newTransaction().block(Duration.ofSeconds(50)); - } - catch (DataAccessException e) { - log.info("Expected to throw an exception so that we see if rollback works", e); - } - }; - } - - @Bean - ConnectionFactoryInitializer initializer(ConnectionFactory connectionFactory) { - ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer(); - initializer.setConnectionFactory(connectionFactory); - initializer.setDatabasePopulator(new ResourceDatabasePopulator(new ClassPathResource("schema.sql"))); - return initializer; - } - - } - -} +/* + * 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.r2dbc; + +import java.time.Duration; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import io.r2dbc.spi.ConnectionFactory; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.cloud.sleuth.exporter.FinishedSpan; +import org.springframework.cloud.sleuth.test.TestSpanHandler; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; +import org.springframework.dao.DataAccessException; +import org.springframework.r2dbc.connection.init.ConnectionFactoryInitializer; +import org.springframework.r2dbc.connection.init.ResourceDatabasePopulator; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; + +import static org.assertj.core.api.BDDAssertions.then; + +@ContextConfiguration(classes = R2dbcIntegrationTests.TestConfig.class) +@TestPropertySource(properties = { "spring.application.name=MyApplication", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1" }) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.ANY) +public abstract class R2dbcIntegrationTests { + + @Autowired + TestSpanHandler spans; + + @Test + public void should_pass_tracing_information_when_using_r2dbc() { + Set traceIds = this.spans.reportedSpans().stream().map(FinishedSpan::getTraceId) + .collect(Collectors.toSet()); + then(traceIds).as("There's one traceid").hasSize(1); + Set spanIds = this.spans.reportedSpans().stream().map(FinishedSpan::getSpanId) + .collect(Collectors.toSet()); + + // 2 transactions - 9 database interactions + then(spanIds).as("There are 11 spans").hasSize(11); + List spanNames = this.spans.reportedSpans().stream().map(FinishedSpan::getName) + .collect(Collectors.toList()); + List remoteServiceNames = this.spans.reportedSpans().stream().map(FinishedSpan::getRemoteServiceName) + .collect(Collectors.toList()); + then(spanNames.stream().filter("tx"::equalsIgnoreCase).collect(Collectors.toList())).hasSize(2); + then(remoteServiceNames.stream().filter("h2"::equalsIgnoreCase).collect(Collectors.toList())).hasSize(9); + } + + @Configuration(proxyBeanMethods = false) + @EnableAutoConfiguration + @ComponentScan + public static class TestConfig { + + private static final Logger log = LoggerFactory.getLogger(TestConfig.class); + + @Bean + public CommandLineRunner demo(ReactiveNewTransactionService reactiveNewTransactionService) { + return (args) -> { + try { + reactiveNewTransactionService.newTransaction().block(Duration.ofSeconds(50)); + } + catch (DataAccessException e) { + log.info("Expected to throw an exception so that we see if rollback works", e); + } + }; + } + + @Bean + ConnectionFactoryInitializer initializer(ConnectionFactory connectionFactory) { + ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer(); + initializer.setConnectionFactory(connectionFactory); + initializer.setDatabasePopulator(new ResourceDatabasePopulator(new ClassPathResource("schema.sql"))); + return initializer; + } + + } + +} diff --git a/tests/common/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/integration/sampled/WebClientTests.java b/tests/common/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/integration/sampled/WebClientTests.java index 618c83d0a..7af97c32b 100644 --- a/tests/common/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/integration/sampled/WebClientTests.java +++ b/tests/common/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/integration/sampled/WebClientTests.java @@ -1,597 +1,597 @@ -/* - * 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.web.client.integration.sampled; - -import java.time.Duration; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import javax.servlet.http.HttpServletRequest; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.awaitility.Awaitility; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import org.reactivestreams.Subscription; -import reactor.core.publisher.BaseSubscriber; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration; -import org.springframework.boot.autoconfigure.web.ServerProperties; -import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController; -import org.springframework.boot.web.client.RestTemplateBuilder; -import org.springframework.boot.web.client.RestTemplateCustomizer; -import org.springframework.boot.web.server.LocalServerPort; -import org.springframework.boot.web.servlet.error.ErrorAttributes; -import org.springframework.cloud.client.DefaultServiceInstance; -import org.springframework.cloud.client.loadbalancer.LoadBalanced; -import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient; -import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; -import org.springframework.cloud.loadbalancer.support.ServiceInstanceListSuppliers; -import org.springframework.cloud.openfeign.EnableFeignClients; -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.cloud.sleuth.Span; -import org.springframework.cloud.sleuth.Tracer; -import org.springframework.cloud.sleuth.exporter.FinishedSpan; -import org.springframework.cloud.sleuth.test.TestSpanHandler; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpHeaders; -import org.springframework.http.ResponseEntity; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestPropertySource; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.client.HttpClientErrorException; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.reactive.function.client.UnknownHttpStatusCodeException; -import org.springframework.web.reactive.function.client.WebClient; - -import static org.assertj.core.api.Assertions.fail; -import static org.assertj.core.api.BDDAssertions.then; - -@ContextConfiguration(classes = WebClientTests.TestConfiguration.class) -@TestPropertySource(properties = { "spring.sleuth.web.servlet.enabled=false", "spring.application.name=fooservice", - "spring.sleuth.web.client.skip-pattern=/skip.*" }) -@DirtiesContext -public abstract class WebClientTests { - - private static final Log log = LogFactory.getLog(WebClientTests.class); - - @Autowired - TestFeignInterface testFeignInterface; - - @Autowired - @LoadBalanced - RestTemplate template; - - @Autowired - WebClient webClient; - - @Autowired - WebClient.Builder webClientBuilder; - - @Autowired - TestSpanHandler spans; - - @Autowired - Tracer tracer; - - @Autowired - TestErrorController testErrorController; - - @Autowired - RestTemplateBuilder restTemplateBuilder; - - @LocalServerPort - int port; - - @Autowired - FooController fooController; - - @Autowired - MyRestTemplateCustomizer customizer; - - @AfterEach - @BeforeEach - public void close() { - this.spans.clear(); - this.testErrorController.clear(); - this.fooController.clear(); - } - - @BeforeEach - public void setup() { - log.info("Starting test"); - } - - @ParameterizedTest - @MethodSource("parametersForShouldCreateANewSpanWithClientSideTagsWhenNoPreviousTracingWasPresent") - @SuppressWarnings("unchecked") - public void shouldCreateANewSpanWithClientSideTagsWhenNoPreviousTracingWasPresent(ResponseEntityProvider provider) { - ResponseEntity response = provider.get(this); - - Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAsserted(() -> { - then(getHeader(response, "b3")).isNull(); - then(this.spans).isNotEmpty(); - Optional noTraceSpan = this.spans.reportedSpans().stream() - .filter(span -> span.getName().contains("GET") && !span.getTags().isEmpty() - && span.getTags().containsKey(pathKey())) - .findFirst(); - then(noTraceSpan.isPresent()).isTrue(); - then(noTraceSpan.get().getTags()).containsEntry(pathKey(), "/notrace").containsEntry("http.method", "GET"); - // TODO: matches cause there is an issue with Feign not providing the full URL - // at the interceptor level - then(noTraceSpan.get().getTags().get(pathKey())).matches(".*/notrace"); - }); - thenThereIsNoCurrentSpan(); - } - - protected String pathKey() { - return "http.path"; - } - - private void thenThereIsNoCurrentSpan() { - log.info("Current span [" + this.tracer.currentSpan() + "]"); - then(this.tracer.currentSpan()).isNull(); - } - - static Stream parametersForShouldCreateANewSpanWithClientSideTagsWhenNoPreviousTracingWasPresent() { - return Stream.of((ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), - (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), - (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), - (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), - (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), - (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), - (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), - (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), - (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", - String.class), - (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", - String.class), - (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", - String.class), - (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", - String.class), - (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", - String.class), - (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", - String.class), - (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", - String.class), - (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", - String.class)); - } - - @ParameterizedTest - @MethodSource("parametersForShouldAttachTraceIdWhenCallingAnotherService") - @SuppressWarnings("unchecked") - public void shouldAttachTraceIdWhenCallingAnotherService(ResponseEntityProvider provider) { - Span span = this.tracer.nextSpan().name("foo").start(); - - try (Tracer.SpanInScope ws = this.tracer.withSpan(span)) { - ResponseEntity response = provider.get(this); - - // https://github.com/spring-cloud/spring-cloud-sleuth/issues/327 - // we don't want to respond with any tracing data - then(getHeader(response, "b3")).isNull(); - } - finally { - span.end(); - } - - thenThereIsNoCurrentSpan(); - then(this.spans).isNotEmpty(); - } - - @Test - @SuppressWarnings("unchecked") - public void shouldAttachTraceIdWhenCallingAnotherServiceViaWebClient() { - Span span = this.tracer.nextSpan().name("foo").start(); - - try (Tracer.SpanInScope ws = this.tracer.withSpan(span)) { - this.webClient.get().uri("http://localhost:" + this.port + "/traceid").retrieve().bodyToMono(String.class) - .block(Duration.ofSeconds(5)); - } - finally { - span.end(); - } - thenThereIsNoCurrentSpan(); - then(this.spans.reportedSpans().stream().filter(r -> r.getKind() != null).map(r -> r.getKind().name()) - .collect(Collectors.toList())).isNotEmpty().contains("CLIENT"); - } - - @Test - @SuppressWarnings("unchecked") - public void shouldWorkWhenCustomStatusCodeIsReturned() { - Span span = this.tracer.nextSpan().name("foo").start(); - - try (Tracer.SpanInScope ws = this.tracer.withSpan(span)) { - this.webClient.get().uri("http://localhost:" + this.port + "/issue1462").retrieve().bodyToMono(String.class) - .block(Duration.ofSeconds(5)); - } - catch (UnknownHttpStatusCodeException ex) { - - } - finally { - span.end(); - } - - thenThereIsNoCurrentSpan(); - then(this.spans.reportedSpans().stream().filter(r -> r.getKind() != null).map(r -> r.getKind().name()) - .collect(Collectors.toList())).isNotEmpty().contains("CLIENT"); - } - - @Test - @SuppressWarnings("unchecked") - public void shouldUseUriTemplateInSpanName() { - Span span = this.tracer.nextSpan().name("foo").start(); - - try (Tracer.SpanInScope ws = this.tracer.withSpan(span)) { - this.webClientBuilder.baseUrl("http://localhost:" + this.port).build().get() - .uri("/prefix/{variable}/suffix", "value").retrieve().bodyToMono(String.class) - .block(Duration.ofSeconds(5)); - } - finally { - span.end(); - } - - thenThereIsNoCurrentSpan(); - then(this.spans.reportedSpans().stream().filter(r -> r.getKind() == Span.Kind.CLIENT).map(r -> r.getName()) - .collect(Collectors.toList())).isNotEmpty().contains(templatedName()); - } - - protected String templatedName() { - return "GET /prefix/{variable}/suffix"; - } - - /** - * Cancel before {@link Subscription#request(long)} means a network request was never - * sent - */ - @Test - @Disabled("flakey") - public void shouldNotTagOnCancel() { - this.webClient.get().uri("http://localhost:" + this.port + "/doNotSkip").retrieve().bodyToMono(String.class) - .subscribe(new BaseSubscriber() { - @Override - protected void hookOnSubscribe(Subscription subscription) { - cancel(); - } - }); - - then(this.spans).isEmpty(); - } - - @Test - public void shouldRespectSkipPattern() { - this.webClient.get().uri("http://localhost:" + this.port + "/skip").retrieve().bodyToMono(String.class) - .block(Duration.ofSeconds(5)); - then(this.spans).isEmpty(); - - this.webClient.get().uri("http://localhost:" + this.port + "/doNotSkip").retrieve().bodyToMono(String.class) - .block(Duration.ofSeconds(5)); - then(this.spans).isNotEmpty(); - } - - static Stream parametersForShouldAttachTraceIdWhenCallingAnotherService() { - return Stream.of((ResponseEntityProvider) (tests) -> tests.testFeignInterface.headers(), - (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/traceid", - String.class)); - } - - @ParameterizedTest - @MethodSource("parametersForShouldAttachTraceIdWhenUsingFeignClientWithoutResponseBody") - public void shouldAttachTraceIdWhenUsingFeignClientWithoutResponseBody(ResponseEntityProvider provider) { - Span span = this.tracer.nextSpan().name("foo").start(); - - try (Tracer.SpanInScope ws = this.tracer.withSpan(span)) { - provider.get(this); - } - finally { - span.end(); - } - - thenThereIsNoCurrentSpan(); - then(this.spans).isNotEmpty(); - } - - static Stream parametersForShouldAttachTraceIdWhenUsingFeignClientWithoutResponseBody() { - return Stream.of((ResponseEntityProvider) (tests) -> tests.testFeignInterface.noResponseBody(), - (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/noresponse", - String.class)); - } - - @Test - public void shouldCloseSpanWhenErrorControllerGetsCalled() { - try { - this.template.getForEntity("http://fooservice/nonExistent", String.class); - fail("An exception should be thrown"); - } - catch (HttpClientErrorException e) { - } - - thenThereIsNoCurrentSpan(); - Optional storedSpan = this.spans.reportedSpans().stream() - .filter(span -> "404".equals(span.getTags().get("http.status_code"))).findFirst(); - then(storedSpan.isPresent()).isTrue(); - this.spans.reportedSpans().stream().forEach(span -> { - int initialSize = span.getEvents().size(); - int distinctSize = span.getEvents().stream().map(Map.Entry::getValue).distinct() - .collect(Collectors.toList()).size(); - log.info("logs " + span.getEvents()); - then(initialSize).as("there are no duplicate log entries").isEqualTo(distinctSize); - }); - - then(this.spans.reportedSpans().stream().filter(r -> r.getKind() != null).map(r -> r.getKind().name()) - .collect(Collectors.toList())).isNotEmpty().contains("CLIENT"); - } - - @Test - public void shouldNotExecuteErrorControllerWhenUrlIsFound() { - this.template.getForEntity("http://fooservice/notrace", String.class); - - thenThereIsNoCurrentSpan(); - then(this.testErrorController.getSpan()).isNull(); - } - - @Test - public void should_wrap_rest_template_builders() { - Span span = this.tracer.nextSpan().name("foo").start(); - - try (Tracer.SpanInScope ws = this.tracer.withSpan(span)) { - RestTemplate template = this.restTemplateBuilder.build(); - - template.getForObject("http://localhost:" + this.port + "/traceid", String.class); - } - finally { - span.end(); - } - thenThereIsNoCurrentSpan(); - then(this.customizer.isExecuted()).isTrue(); - then(this.spans.reportedSpans().stream().filter(s -> s.getKind() != null).map(s -> s.getKind().name()) - .collect(Collectors.toList())).contains("CLIENT"); - } - - @Test - public void should_add_headers_eagerly() { - Span span = this.tracer.nextSpan().name("foo").start(); - - AtomicReference traceId = new AtomicReference<>(); - try (Tracer.SpanInScope ws = this.tracer.withSpan(span)) { - this.webClientBuilder.filter((request, exchange) -> { - traceId.set(request.headers().getFirst("b3")); - - return exchange.exchange(request); - }).build().get().uri("http://localhost:" + this.port + "/traceid").retrieve().bodyToMono(String.class) - .block(Duration.ofSeconds(5)); - } - finally { - span.end(); - } - then(traceId).doesNotHaveValue(null); - } - - private String getHeader(ResponseEntity response, String name) { - List headers = response.getHeaders().get(name); - return headers == null || headers.isEmpty() ? null : headers.get(0); - } - - @FeignClient("fooservice") - public interface TestFeignInterface { - - @RequestMapping(method = RequestMethod.GET, value = "/traceid") - ResponseEntity getTraceId(); - - @RequestMapping(method = RequestMethod.GET, value = "/notrace") - ResponseEntity getNoTrace(); - - @RequestMapping(method = RequestMethod.GET, value = "/") - ResponseEntity> headers(); - - @RequestMapping(method = RequestMethod.GET, value = "/noresponse") - ResponseEntity noResponseBody(); - - } - - @FunctionalInterface - interface ResponseEntityProvider { - - @SuppressWarnings("rawtypes") - ResponseEntity get(WebClientTests webClientTests); - - } - - @Configuration(proxyBeanMethods = false) - @EnableAutoConfiguration(exclude = JmxAutoConfiguration.class) - @EnableFeignClients - @LoadBalancerClient(value = "fooservice", configuration = SimpleLoadBalancerClientConfiguration.class) - public static class TestConfiguration { - - @Bean - FooController fooController() { - return new FooController(); - } - - @Bean - WebClientController webClientController() { - return new WebClientController(); - } - - @LoadBalanced - @Bean - public RestTemplate restTemplate() { - return new RestTemplate(); - } - - @Bean - TestErrorController testErrorController(ErrorAttributes errorAttributes, Tracer tracer) { - return new TestErrorController(errorAttributes, tracer); - } - - @Bean - WebClient webClient() { - return WebClient.builder().build(); - } - - @Bean - WebClient.Builder webClientBuilder() { - return WebClient.builder(); - } - - @Bean - RestTemplateCustomizer myRestTemplateCustomizer() { - return new MyRestTemplateCustomizer(); - } - - } - - static class MyRestTemplateCustomizer implements RestTemplateCustomizer { - - boolean executed; - - @Override - public void customize(RestTemplate restTemplate) { - this.executed = true; - } - - public boolean isExecuted() { - return this.executed; - } - - } - - public static class TestErrorController extends BasicErrorController { - - private final Tracer tracer; - - Span span; - - public TestErrorController(ErrorAttributes errorAttributes, Tracer tracer) { - super(errorAttributes, new ServerProperties().getError()); - this.tracer = tracer; - } - - @Override - public ResponseEntity> error(HttpServletRequest request) { - this.span = this.tracer.currentSpan(); - return super.error(request); - } - - public Span getSpan() { - return this.span; - } - - public void clear() { - this.span = null; - } - - } - - @RestController - public static class FooController { - - Span span; - - @RequestMapping(value = "/notrace", method = RequestMethod.GET) - public String notrace(@RequestHeader(name = "b3", required = false) String b3Single) { - then(b3Single).isNotNull(); - return "OK"; - } - - @RequestMapping(value = "/traceid", method = RequestMethod.GET) - public String traceId(@RequestHeader("b3") String b3Single) { - then(b3Single).isNotEmpty(); - return b3Single; - } - - @RequestMapping("/") - public Map home(@RequestHeader HttpHeaders headers) { - Map map = new HashMap<>(); - for (String key : headers.keySet()) { - map.put(key, headers.getFirst(key)); - } - return map; - } - - @RequestMapping("/noresponse") - public void noResponse(@RequestHeader("b3") String b3Single) { - then(b3Single).isNotEmpty(); - } - - public Span getSpan() { - return this.span; - } - - public void clear() { - this.span = null; - } - - } - - @RestController - public static class WebClientController { - - @RequestMapping(value = "/issue1462", method = RequestMethod.GET) - public ResponseEntity issue1462() { - return ResponseEntity.status(499).body("issue1462"); - } - - @RequestMapping(value = { "/skip", "/doNotSkip" }, method = RequestMethod.GET) - String skip() { - return "ok"; - } - - @RequestMapping(value = "/prefix/{variable}/suffix", method = RequestMethod.GET) - String pathVariable(@PathVariable("variable") String variable) { - return "variable = " + variable; - } - - } - - @Configuration(proxyBeanMethods = false) - public static class SimpleLoadBalancerClientConfiguration { - - @Value("${local.server.port}") - private int port = 0; - - @Bean - public ServiceInstanceListSupplier serviceInstanceListSupplier() { - return ServiceInstanceListSuppliers.from("fooservice", - new DefaultServiceInstance("fooservice" + "-1", "fooservice", "localhost", port, false)); - } - - } - -} +/* + * 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.web.client.integration.sampled; + +import java.time.Duration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.reactivestreams.Subscription; +import reactor.core.publisher.BaseSubscriber; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.boot.web.client.RestTemplateCustomizer; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.boot.web.servlet.error.ErrorAttributes; +import org.springframework.cloud.client.DefaultServiceInstance; +import org.springframework.cloud.client.loadbalancer.LoadBalanced; +import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient; +import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; +import org.springframework.cloud.loadbalancer.support.ServiceInstanceListSuppliers; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.cloud.sleuth.exporter.FinishedSpan; +import org.springframework.cloud.sleuth.test.TestSpanHandler; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.reactive.function.client.UnknownHttpStatusCodeException; +import org.springframework.web.reactive.function.client.WebClient; + +import static org.assertj.core.api.Assertions.fail; +import static org.assertj.core.api.BDDAssertions.then; + +@ContextConfiguration(classes = WebClientTests.TestConfiguration.class) +@TestPropertySource(properties = { "spring.sleuth.web.servlet.enabled=false", "spring.application.name=fooservice", + "spring.sleuth.web.client.skip-pattern=/skip.*" }) +@DirtiesContext +public abstract class WebClientTests { + + private static final Log log = LogFactory.getLog(WebClientTests.class); + + @Autowired + TestFeignInterface testFeignInterface; + + @Autowired + @LoadBalanced + RestTemplate template; + + @Autowired + WebClient webClient; + + @Autowired + WebClient.Builder webClientBuilder; + + @Autowired + TestSpanHandler spans; + + @Autowired + Tracer tracer; + + @Autowired + TestErrorController testErrorController; + + @Autowired + RestTemplateBuilder restTemplateBuilder; + + @LocalServerPort + int port; + + @Autowired + FooController fooController; + + @Autowired + MyRestTemplateCustomizer customizer; + + @AfterEach + @BeforeEach + public void close() { + this.spans.clear(); + this.testErrorController.clear(); + this.fooController.clear(); + } + + @BeforeEach + public void setup() { + log.info("Starting test"); + } + + @ParameterizedTest + @MethodSource("parametersForShouldCreateANewSpanWithClientSideTagsWhenNoPreviousTracingWasPresent") + @SuppressWarnings("unchecked") + public void shouldCreateANewSpanWithClientSideTagsWhenNoPreviousTracingWasPresent(ResponseEntityProvider provider) { + ResponseEntity response = provider.get(this); + + Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAsserted(() -> { + then(getHeader(response, "b3")).isNull(); + then(this.spans).isNotEmpty(); + Optional noTraceSpan = this.spans.reportedSpans().stream() + .filter(span -> span.getName().contains("GET") && !span.getTags().isEmpty() + && span.getTags().containsKey(pathKey())) + .findFirst(); + then(noTraceSpan.isPresent()).isTrue(); + then(noTraceSpan.get().getTags()).containsEntry(pathKey(), "/notrace").containsEntry("http.method", "GET"); + // TODO: matches cause there is an issue with Feign not providing the full URL + // at the interceptor level + then(noTraceSpan.get().getTags().get(pathKey())).matches(".*/notrace"); + }); + thenThereIsNoCurrentSpan(); + } + + protected String pathKey() { + return "http.path"; + } + + private void thenThereIsNoCurrentSpan() { + log.info("Current span [" + this.tracer.currentSpan() + "]"); + then(this.tracer.currentSpan()).isNull(); + } + + static Stream parametersForShouldCreateANewSpanWithClientSideTagsWhenNoPreviousTracingWasPresent() { + return Stream.of((ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), + (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), + (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), + (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), + (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), + (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), + (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), + (ResponseEntityProvider) (tests) -> tests.testFeignInterface.getNoTrace(), + (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", + String.class), + (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", + String.class), + (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", + String.class), + (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", + String.class), + (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", + String.class), + (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", + String.class), + (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", + String.class), + (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/notrace", + String.class)); + } + + @ParameterizedTest + @MethodSource("parametersForShouldAttachTraceIdWhenCallingAnotherService") + @SuppressWarnings("unchecked") + public void shouldAttachTraceIdWhenCallingAnotherService(ResponseEntityProvider provider) { + Span span = this.tracer.nextSpan().name("foo").start(); + + try (Tracer.SpanInScope ws = this.tracer.withSpan(span)) { + ResponseEntity response = provider.get(this); + + // https://github.com/spring-cloud/spring-cloud-sleuth/issues/327 + // we don't want to respond with any tracing data + then(getHeader(response, "b3")).isNull(); + } + finally { + span.end(); + } + + thenThereIsNoCurrentSpan(); + then(this.spans).isNotEmpty(); + } + + @Test + @SuppressWarnings("unchecked") + public void shouldAttachTraceIdWhenCallingAnotherServiceViaWebClient() { + Span span = this.tracer.nextSpan().name("foo").start(); + + try (Tracer.SpanInScope ws = this.tracer.withSpan(span)) { + this.webClient.get().uri("http://localhost:" + this.port + "/traceid").retrieve().bodyToMono(String.class) + .block(Duration.ofSeconds(5)); + } + finally { + span.end(); + } + thenThereIsNoCurrentSpan(); + then(this.spans.reportedSpans().stream().filter(r -> r.getKind() != null).map(r -> r.getKind().name()) + .collect(Collectors.toList())).isNotEmpty().contains("CLIENT"); + } + + @Test + @SuppressWarnings("unchecked") + public void shouldWorkWhenCustomStatusCodeIsReturned() { + Span span = this.tracer.nextSpan().name("foo").start(); + + try (Tracer.SpanInScope ws = this.tracer.withSpan(span)) { + this.webClient.get().uri("http://localhost:" + this.port + "/issue1462").retrieve().bodyToMono(String.class) + .block(Duration.ofSeconds(5)); + } + catch (UnknownHttpStatusCodeException ex) { + + } + finally { + span.end(); + } + + thenThereIsNoCurrentSpan(); + then(this.spans.reportedSpans().stream().filter(r -> r.getKind() != null).map(r -> r.getKind().name()) + .collect(Collectors.toList())).isNotEmpty().contains("CLIENT"); + } + + @Test + @SuppressWarnings("unchecked") + public void shouldUseUriTemplateInSpanName() { + Span span = this.tracer.nextSpan().name("foo").start(); + + try (Tracer.SpanInScope ws = this.tracer.withSpan(span)) { + this.webClientBuilder.baseUrl("http://localhost:" + this.port).build().get() + .uri("/prefix/{variable}/suffix", "value").retrieve().bodyToMono(String.class) + .block(Duration.ofSeconds(5)); + } + finally { + span.end(); + } + + thenThereIsNoCurrentSpan(); + then(this.spans.reportedSpans().stream().filter(r -> r.getKind() == Span.Kind.CLIENT).map(r -> r.getName()) + .collect(Collectors.toList())).isNotEmpty().contains(templatedName()); + } + + protected String templatedName() { + return "GET /prefix/{variable}/suffix"; + } + + /** + * Cancel before {@link Subscription#request(long)} means a network request was never + * sent + */ + @Test + @Disabled("flakey") + public void shouldNotTagOnCancel() { + this.webClient.get().uri("http://localhost:" + this.port + "/doNotSkip").retrieve().bodyToMono(String.class) + .subscribe(new BaseSubscriber() { + @Override + protected void hookOnSubscribe(Subscription subscription) { + cancel(); + } + }); + + then(this.spans).isEmpty(); + } + + @Test + public void shouldRespectSkipPattern() { + this.webClient.get().uri("http://localhost:" + this.port + "/skip").retrieve().bodyToMono(String.class) + .block(Duration.ofSeconds(5)); + then(this.spans).isEmpty(); + + this.webClient.get().uri("http://localhost:" + this.port + "/doNotSkip").retrieve().bodyToMono(String.class) + .block(Duration.ofSeconds(5)); + then(this.spans).isNotEmpty(); + } + + static Stream parametersForShouldAttachTraceIdWhenCallingAnotherService() { + return Stream.of((ResponseEntityProvider) (tests) -> tests.testFeignInterface.headers(), + (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/traceid", + String.class)); + } + + @ParameterizedTest + @MethodSource("parametersForShouldAttachTraceIdWhenUsingFeignClientWithoutResponseBody") + public void shouldAttachTraceIdWhenUsingFeignClientWithoutResponseBody(ResponseEntityProvider provider) { + Span span = this.tracer.nextSpan().name("foo").start(); + + try (Tracer.SpanInScope ws = this.tracer.withSpan(span)) { + provider.get(this); + } + finally { + span.end(); + } + + thenThereIsNoCurrentSpan(); + then(this.spans).isNotEmpty(); + } + + static Stream parametersForShouldAttachTraceIdWhenUsingFeignClientWithoutResponseBody() { + return Stream.of((ResponseEntityProvider) (tests) -> tests.testFeignInterface.noResponseBody(), + (ResponseEntityProvider) (tests) -> tests.template.getForEntity("http://fooservice/noresponse", + String.class)); + } + + @Test + public void shouldCloseSpanWhenErrorControllerGetsCalled() { + try { + this.template.getForEntity("http://fooservice/nonExistent", String.class); + fail("An exception should be thrown"); + } + catch (HttpClientErrorException e) { + } + + thenThereIsNoCurrentSpan(); + Optional storedSpan = this.spans.reportedSpans().stream() + .filter(span -> "404".equals(span.getTags().get("http.status_code"))).findFirst(); + then(storedSpan.isPresent()).isTrue(); + this.spans.reportedSpans().stream().forEach(span -> { + int initialSize = span.getEvents().size(); + int distinctSize = span.getEvents().stream().map(Map.Entry::getValue).distinct() + .collect(Collectors.toList()).size(); + log.info("logs " + span.getEvents()); + then(initialSize).as("there are no duplicate log entries").isEqualTo(distinctSize); + }); + + then(this.spans.reportedSpans().stream().filter(r -> r.getKind() != null).map(r -> r.getKind().name()) + .collect(Collectors.toList())).isNotEmpty().contains("CLIENT"); + } + + @Test + public void shouldNotExecuteErrorControllerWhenUrlIsFound() { + this.template.getForEntity("http://fooservice/notrace", String.class); + + thenThereIsNoCurrentSpan(); + then(this.testErrorController.getSpan()).isNull(); + } + + @Test + public void should_wrap_rest_template_builders() { + Span span = this.tracer.nextSpan().name("foo").start(); + + try (Tracer.SpanInScope ws = this.tracer.withSpan(span)) { + RestTemplate template = this.restTemplateBuilder.build(); + + template.getForObject("http://localhost:" + this.port + "/traceid", String.class); + } + finally { + span.end(); + } + thenThereIsNoCurrentSpan(); + then(this.customizer.isExecuted()).isTrue(); + then(this.spans.reportedSpans().stream().filter(s -> s.getKind() != null).map(s -> s.getKind().name()) + .collect(Collectors.toList())).contains("CLIENT"); + } + + @Test + public void should_add_headers_eagerly() { + Span span = this.tracer.nextSpan().name("foo").start(); + + AtomicReference traceId = new AtomicReference<>(); + try (Tracer.SpanInScope ws = this.tracer.withSpan(span)) { + this.webClientBuilder.filter((request, exchange) -> { + traceId.set(request.headers().getFirst("b3")); + + return exchange.exchange(request); + }).build().get().uri("http://localhost:" + this.port + "/traceid").retrieve().bodyToMono(String.class) + .block(Duration.ofSeconds(5)); + } + finally { + span.end(); + } + then(traceId).doesNotHaveValue(null); + } + + private String getHeader(ResponseEntity response, String name) { + List headers = response.getHeaders().get(name); + return headers == null || headers.isEmpty() ? null : headers.get(0); + } + + @FeignClient("fooservice") + public interface TestFeignInterface { + + @RequestMapping(method = RequestMethod.GET, value = "/traceid") + ResponseEntity getTraceId(); + + @RequestMapping(method = RequestMethod.GET, value = "/notrace") + ResponseEntity getNoTrace(); + + @RequestMapping(method = RequestMethod.GET, value = "/") + ResponseEntity> headers(); + + @RequestMapping(method = RequestMethod.GET, value = "/noresponse") + ResponseEntity noResponseBody(); + + } + + @FunctionalInterface + interface ResponseEntityProvider { + + @SuppressWarnings("rawtypes") + ResponseEntity get(WebClientTests webClientTests); + + } + + @Configuration(proxyBeanMethods = false) + @EnableAutoConfiguration(exclude = JmxAutoConfiguration.class) + @EnableFeignClients + @LoadBalancerClient(value = "fooservice", configuration = SimpleLoadBalancerClientConfiguration.class) + public static class TestConfiguration { + + @Bean + FooController fooController() { + return new FooController(); + } + + @Bean + WebClientController webClientController() { + return new WebClientController(); + } + + @LoadBalanced + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } + + @Bean + TestErrorController testErrorController(ErrorAttributes errorAttributes, Tracer tracer) { + return new TestErrorController(errorAttributes, tracer); + } + + @Bean + WebClient webClient() { + return WebClient.builder().build(); + } + + @Bean + WebClient.Builder webClientBuilder() { + return WebClient.builder(); + } + + @Bean + RestTemplateCustomizer myRestTemplateCustomizer() { + return new MyRestTemplateCustomizer(); + } + + } + + static class MyRestTemplateCustomizer implements RestTemplateCustomizer { + + boolean executed; + + @Override + public void customize(RestTemplate restTemplate) { + this.executed = true; + } + + public boolean isExecuted() { + return this.executed; + } + + } + + public static class TestErrorController extends BasicErrorController { + + private final Tracer tracer; + + Span span; + + public TestErrorController(ErrorAttributes errorAttributes, Tracer tracer) { + super(errorAttributes, new ServerProperties().getError()); + this.tracer = tracer; + } + + @Override + public ResponseEntity> error(HttpServletRequest request) { + this.span = this.tracer.currentSpan(); + return super.error(request); + } + + public Span getSpan() { + return this.span; + } + + public void clear() { + this.span = null; + } + + } + + @RestController + public static class FooController { + + Span span; + + @RequestMapping(value = "/notrace", method = RequestMethod.GET) + public String notrace(@RequestHeader(name = "b3", required = false) String b3Single) { + then(b3Single).isNotNull(); + return "OK"; + } + + @RequestMapping(value = "/traceid", method = RequestMethod.GET) + public String traceId(@RequestHeader("b3") String b3Single) { + then(b3Single).isNotEmpty(); + return b3Single; + } + + @RequestMapping("/") + public Map home(@RequestHeader HttpHeaders headers) { + Map map = new HashMap<>(); + for (String key : headers.keySet()) { + map.put(key, headers.getFirst(key)); + } + return map; + } + + @RequestMapping("/noresponse") + public void noResponse(@RequestHeader("b3") String b3Single) { + then(b3Single).isNotEmpty(); + } + + public Span getSpan() { + return this.span; + } + + public void clear() { + this.span = null; + } + + } + + @RestController + public static class WebClientController { + + @RequestMapping(value = "/issue1462", method = RequestMethod.GET) + public ResponseEntity issue1462() { + return ResponseEntity.status(499).body("issue1462"); + } + + @RequestMapping(value = { "/skip", "/doNotSkip" }, method = RequestMethod.GET) + String skip() { + return "ok"; + } + + @RequestMapping(value = "/prefix/{variable}/suffix", method = RequestMethod.GET) + String pathVariable(@PathVariable("variable") String variable) { + return "variable = " + variable; + } + + } + + @Configuration(proxyBeanMethods = false) + public static class SimpleLoadBalancerClientConfiguration { + + @Value("${local.server.port}") + private int port = 0; + + @Bean + public ServiceInstanceListSupplier serviceInstanceListSupplier() { + return ServiceInstanceListSuppliers.from("fooservice", + new DefaultServiceInstance("fooservice" + "-1", "fooservice", "localhost", port, false)); + } + + } + +}