From 39671a76a3850371cba2e15d7b2b6a81a5f307f1 Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Wed, 8 Jul 2020 14:56:31 +0800 Subject: [PATCH 1/3] Permits Actuator endpoints to depend on HttpTracing (#1680) * Permits Actuator endpoints to depend on HttpTracing It is unusual to inject HttpTracing into an actuator endpoint vs an end-user api such as `Tracer` or `SpanCustomizer`. However, we decided to allow this and so need a test to prove this continues to work. Fixes #1679 --- .../web/TraceWebAutoConfiguration.java | 48 +++++++++--- .../EndpointWithCyclicDependenciesTests.java | 74 +++++++++++++++++++ .../web/SkipPatternProviderConfigTest.java | 19 ++++- .../cloud/sleuth/internal/LazyBeanTests.java | 43 ++++++++--- 4 files changed, 159 insertions(+), 25 deletions(-) create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/EndpointWithCyclicDependenciesTests.java diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java index 98e7ca8eb..19301464a 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java @@ -16,7 +16,6 @@ package org.springframework.cloud.sleuth.instrument.web; -import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Optional; @@ -26,7 +25,7 @@ import java.util.stream.Collectors; import brave.Tracing; -import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.BeanCurrentlyInCreationException; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; import org.springframework.boot.actuate.autoconfigure.web.server.ConditionalOnManagementPort; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType; @@ -43,6 +42,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.lang.Nullable; import org.springframework.util.StringUtils; /** @@ -64,32 +64,56 @@ import org.springframework.util.StringUtils; @EnableConfigurationProperties(SleuthWebProperties.class) public class TraceWebAutoConfiguration { - @Autowired(required = false) - List patterns = new ArrayList<>(); - @Bean @ConditionalOnMissingBean - SkipPatternProvider sleuthSkipPatternProvider() { - if (this.patterns == null) { + SkipPatternProvider sleuthSkipPatternProvider( + @Nullable List patterns) { + if (patterns == null || patterns.isEmpty()) { return null; } - List presentPatterns = this.patterns.stream() + + // Actuator endpoints are queried to make the default skip pattern. There's an + // edge case where actuator endpoints indirectly reference the still constructing + // HttpTracing bean. Ex: an instrumented client could cause a cyclic dep. + // + // Below optimizes for the opposite: that custom actuator endpoints are not in + // use. This allows configuration to be eagerly parsed, allowing any errors to + // surface earlier. In the case there is a cyclic dep, this parsing becomes lazy, + // deferring any errors creating the skip pattern. + // + // See #1679 + try { + Pattern result = consolidateSkipPatterns(patterns); + if (result == null) { + return null; + } + return () -> result; + } + catch (BeanCurrentlyInCreationException e) { + // Most likely, there is an actuator endpoint that indirectly references an + // instrumented HTTP client. + return () -> consolidateSkipPatterns(patterns); + } + } + + @Nullable + static Pattern consolidateSkipPatterns(List patterns) { + List presentPatterns = patterns.stream() .map(SingleSkipPattern::skipPattern).filter(Optional::isPresent) .map(Optional::get).collect(Collectors.toList()); if (presentPatterns.isEmpty()) { return null; } if (presentPatterns.size() == 1) { - Pattern pattern = presentPatterns.get(0); - return () -> pattern; + return presentPatterns.get(0); } + StringJoiner joiner = new StringJoiner("|"); for (Pattern pattern : presentPatterns) { String s = pattern.pattern(); joiner.add(s); } - Pattern pattern = Pattern.compile(joiner.toString()); - return () -> pattern; + return Pattern.compile(joiner.toString()); } @Configuration(proxyBeanMethods = false) diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/EndpointWithCyclicDependenciesTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/EndpointWithCyclicDependenciesTests.java new file mode 100644 index 000000000..a998461e6 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/EndpointWithCyclicDependenciesTests.java @@ -0,0 +1,74 @@ +/* + * Copyright 2013-2019 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; + +import brave.http.HttpTracing; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Service; + +/** + * This tests that actuator components can have instrumented HTTP clients inside of them. + * + * @author Marcin Grzejszczak + */ +@SpringBootTest(classes = { EndpointWithCyclicDependenciesTests.ClientConfig.class }) +public class EndpointWithCyclicDependenciesTests { + + @Test + void should_load_context() { + } + + static class Client { + + } + + @EnableAutoConfiguration + @Configuration + static class ClientConfig { + + @Bean + public Client client(HttpTracing httpTracing) { + // imagine this instruments the client. + return new Client(); + } + + } + + @Service + static class MyService { + + @Autowired + Client client; + + } + + @RestControllerEndpoint(id = "admin-endpoint") + static class MyRestEndpoint { + + @Autowired + MyService myService; + + } + +} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipPatternProviderConfigTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipPatternProviderConfigTest.java index 9ff60d736..302b49672 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipPatternProviderConfigTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipPatternProviderConfigTest.java @@ -19,6 +19,7 @@ package org.springframework.cloud.sleuth.instrument.web; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.Optional; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -42,6 +43,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration; import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import static org.assertj.core.api.BDDAssertions.then; @@ -113,10 +115,9 @@ public class SkipPatternProviderConfigTest { @Test public void should_return_empty_when_no_endpoints() { - EndpointsSupplier endpointsSupplier = Collections::emptyList; Optional pattern = new TraceWebAutoConfiguration.ActuatorSkipPatternProviderConfig() .skipPatternForActuatorEndpointsSamePort(new ServerProperties(), - new WebEndpointProperties(), endpointsSupplier) + new WebEndpointProperties(), Collections::emptyList) .skipPattern(); then(pattern).isEmpty(); @@ -237,9 +238,9 @@ public class SkipPatternProviderConfigTest { @Test public void should_combine_skip_patterns_from_list() throws Exception { TraceWebAutoConfiguration configuration = new TraceWebAutoConfiguration(); - configuration.patterns.addAll(Arrays.asList(foo(), bar())); + List patterns = Arrays.asList(foo(), bar()); - Pattern pattern = configuration.sleuthSkipPatternProvider().skipPattern(); + Pattern pattern = configuration.sleuthSkipPatternProvider(patterns).skipPattern(); then(pattern.pattern()).isEqualTo("foo|bar"); } @@ -276,4 +277,14 @@ public class SkipPatternProviderConfigTest { } + @Configuration + static class EmptyEndpoints { + + @Bean + EndpointsSupplier endpointsSupplier() { + return Collections::emptyList; + } + + } + } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/internal/LazyBeanTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/internal/LazyBeanTests.java index db03fb410..b985c848c 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/internal/LazyBeanTests.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/internal/LazyBeanTests.java @@ -17,28 +17,53 @@ package org.springframework.cloud.sleuth.internal; import brave.propagation.CurrentTraceContext; +import org.junit.After; import org.junit.Test; -import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import static org.assertj.core.api.BDDAssertions.then; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; public class LazyBeanTests { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + + @After + public void close() { + context.close(); + } + @Test - public void should_return_null_when_exception_thrown_upon_bean_retrieval() { - ConfigurableApplicationContext springContext = mock( - ConfigurableApplicationContext.class); + public void should_work_with_basic_type() { + context.register(BasicConfig.class); + context.refresh(); - when(springContext.getBean(CurrentTraceContext.class)) - .thenThrow(new IllegalStateException()); + LazyBean provider = LazyBean.create(context, + CurrentTraceContext.class); - LazyBean provider = new LazyBean<>(springContext, + then(provider.get()).isNotNull(); + } + + @Test + public void should_return_null_when_no_basic_type() { + context.refresh(); + + LazyBean provider = LazyBean.create(context, CurrentTraceContext.class); then(provider.get()).isNull(); } + @Configuration + static class BasicConfig { + + @Bean + CurrentTraceContext currentTraceContext() { + return CurrentTraceContext.Default.create(); + } + + } + } From 4c90b872a79c49415c3145e07e67fe7bc16a4ac6 Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Tue, 14 Jul 2020 19:27:51 +0800 Subject: [PATCH 2/3] Makes a nice toString on RestTemplateSender (#1686) The AsyncZipkinSpanHandler calls 'check' once on startup to let someone know an error that may affect tracing up front. Before, this didn't include the endpoint so it is less obvious what could be the problem. Ex people goof the URL (don't add /api/v2/spans or it is added twice) --- .../zipkin2/sender/RestTemplateSender.java | 5 +++++ .../sender/RestTemplateSenderTest.java | 21 ++++++++++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/sender/RestTemplateSender.java b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/sender/RestTemplateSender.java index 5b6c123ec..982dcb0d9 100644 --- a/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/sender/RestTemplateSender.java +++ b/spring-cloud-sleuth-zipkin/src/main/java/org/springframework/cloud/sleuth/zipkin2/sender/RestTemplateSender.java @@ -129,6 +129,11 @@ final class RestTemplateSender extends Sender { this.restTemplate.exchange(requestEntity, String.class); } + @Override + public String toString() { + return "RestTemplateSender{" + url + "}"; + } + class HttpPostCall extends Call.Base { private final byte[] message; diff --git a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin2/sender/RestTemplateSenderTest.java b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin2/sender/RestTemplateSenderTest.java index dbbc13eb7..63fb32211 100644 --- a/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin2/sender/RestTemplateSenderTest.java +++ b/spring-cloud-sleuth-zipkin/src/test/java/org/springframework/cloud/sleuth/zipkin2/sender/RestTemplateSenderTest.java @@ -28,6 +28,8 @@ import zipkin2.Endpoint; import zipkin2.Span; import zipkin2.codec.Encoding; import zipkin2.codec.SpanBytesEncoder; +import zipkin2.reporter.Sender; +import zipkin2.reporter.brave.AsyncZipkinSpanHandler; import org.springframework.web.client.RestTemplate; @@ -49,9 +51,9 @@ public class RestTemplateSenderTest { @Rule public MockWebServer server = new MockWebServer(); - String endpoint = this.server.url("/api/v2/spans").toString(); + String baseUrl = "http://localhost:" + this.server.getPort(); - RestTemplateSender sender = new RestTemplateSender(new RestTemplate(), this.endpoint, + RestTemplateSender sender = new RestTemplateSender(new RestTemplate(), this.baseUrl, JSON_V2); /** @@ -71,7 +73,7 @@ public class RestTemplateSenderTest { @Test public void proto3() throws Exception { this.server.enqueue(new MockResponse()); - this.sender = new RestTemplateSender(new RestTemplate(), this.endpoint, PROTO3); + this.sender = new RestTemplateSender(new RestTemplate(), this.baseUrl, PROTO3); send(SPAN).execute(); @@ -83,6 +85,19 @@ public class RestTemplateSenderTest { .containsExactly(SpanBytesEncoder.PROTO3.encode(SPAN)); } + /** + * The output of toString() on {@link Sender} implementations appears in thread names + * created by {@link AsyncZipkinSpanHandler}. Since thread names are likely to be + * exposed in logs and other monitoring tools, care should be taken to ensure the + * toString() output is a reasonable length and does not contain sensitive + * information. + */ + @Test + public void toStringContainsOnlySenderTypeAndEndpoint() { + assertThat(sender.toString()) + .isEqualTo("RestTemplateSender{" + baseUrl + "/api/v2/spans}"); + } + Call send(Span... spans) { SpanBytesEncoder bytesEncoder = this.sender.encoding() == Encoding.JSON ? SpanBytesEncoder.JSON_V2 : SpanBytesEncoder.PROTO3; From 97574373a97b690b8b3530fded5874029c27d288 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 16 Jul 2020 11:03:57 +0200 Subject: [PATCH 3/3] Resolves placeholders for skip pattern; fixes gh-1689 --- .../web/TraceWebAutoConfiguration.java | 35 ++++--- .../web/SkipPatternProviderConfigTest.java | 95 ++++++++++++++++++- 2 files changed, 115 insertions(+), 15 deletions(-) diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java index 19301464a..5858ba405 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/TraceWebAutoConfiguration.java @@ -42,6 +42,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; import org.springframework.lang.Nullable; import org.springframework.util.StringUtils; @@ -129,10 +130,12 @@ public class TraceWebAutoConfiguration { * @return optional skip pattern */ static Optional getPatternForManagementServerProperties( + Environment environment, ManagementServerProperties managementServerProperties) { String contextPath = managementServerProperties.getServlet().getContextPath(); if (StringUtils.hasText(contextPath)) { - return Optional.of(Pattern.compile(contextPath + ".*")); + return Optional.of(Pattern + .compile(environment.resolvePlaceholders(contextPath) + ".*")); } return Optional.empty(); } @@ -140,8 +143,9 @@ public class TraceWebAutoConfiguration { @Bean @ConditionalOnBean(ManagementServerProperties.class) public SingleSkipPattern skipPatternForManagementServerProperties( + Environment environment, final ManagementServerProperties managementServerProperties) { - return () -> getPatternForManagementServerProperties( + return () -> getPatternForManagementServerProperties(environment, managementServerProperties); } @@ -155,8 +159,8 @@ public class TraceWebAutoConfiguration { havingValue = "false", matchIfMissing = true) protected static class ActuatorSkipPatternProviderConfig { - static Optional getEndpointsPatterns(String contextPath, - WebEndpointProperties webEndpointProperties, + static Optional getEndpointsPatterns(Environment environment, + String contextPath, WebEndpointProperties webEndpointProperties, EndpointsSupplier endpointsSupplier) { Collection endpoints = endpointsSupplier.getEndpoints(); if (endpoints.isEmpty()) { @@ -165,7 +169,8 @@ public class TraceWebAutoConfiguration { String basePath = webEndpointProperties.getBasePath(); String pattern = patternFromEndpoints(contextPath, endpoints, basePath); if (StringUtils.hasText(pattern)) { - return Optional.of(Pattern.compile(pattern)); + return Optional + .of(Pattern.compile(environment.resolvePlaceholders(pattern))); } return Optional.empty(); } @@ -211,10 +216,10 @@ public class TraceWebAutoConfiguration { @Bean @ConditionalOnManagementPort(ManagementPortType.SAME) public SingleSkipPattern skipPatternForActuatorEndpointsSamePort( - final ServerProperties serverProperties, + Environment environment, final ServerProperties serverProperties, final WebEndpointProperties webEndpointProperties, final EndpointsSupplier endpointsSupplier) { - return () -> getEndpointsPatterns( + return () -> getEndpointsPatterns(environment, serverProperties.getServlet().getContextPath(), webEndpointProperties, endpointsSupplier); } @@ -224,10 +229,10 @@ public class TraceWebAutoConfiguration { @ConditionalOnProperty(name = "management.server.servlet.context-path", havingValue = "/", matchIfMissing = true) public SingleSkipPattern skipPatternForActuatorEndpointsDifferentPort( - final ServerProperties serverProperties, + Environment environment, final ServerProperties serverProperties, final WebEndpointProperties webEndpointProperties, final EndpointsSupplier endpointsSupplier) { - return () -> getEndpointsPatterns(null, webEndpointProperties, + return () -> getEndpointsPatterns(environment, null, webEndpointProperties, endpointsSupplier); } @@ -237,10 +242,16 @@ public class TraceWebAutoConfiguration { static class DefaultSkipPatternConfig { @Bean - SingleSkipPattern defaultSkipPatternBean( + SingleSkipPattern defaultSkipPatternBean(Environment environment, SleuthWebProperties sleuthWebProperties) { - Pattern pattern = combinePatterns(sleuthWebProperties.getSkipPattern(), - sleuthWebProperties.getAdditionalSkipPattern()); + String skipPattern = sleuthWebProperties.getSkipPattern(); + String left = StringUtils.hasText(skipPattern) + ? environment.resolvePlaceholders(skipPattern) : skipPattern; + String additionalSkipPattern = sleuthWebProperties.getAdditionalSkipPattern(); + String right = StringUtils.hasText(additionalSkipPattern) + ? environment.resolvePlaceholders(additionalSkipPattern) + : additionalSkipPattern; + Pattern pattern = combinePatterns(left, right); return () -> Optional.ofNullable(pattern); } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipPatternProviderConfigTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipPatternProviderConfigTest.java index 302b49672..0e5ef3242 100644 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipPatternProviderConfigTest.java +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/SkipPatternProviderConfigTest.java @@ -45,6 +45,8 @@ import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.mock.env.MockEnvironment; import static org.assertj.core.api.BDDAssertions.then; @@ -88,11 +90,17 @@ public class SkipPatternProviderConfigTest { }); } + private Environment environment() { + MockEnvironment environment = new MockEnvironment(); + environment.setProperty("test", "value"); + return environment; + } + @Test public void should_return_empty_when_management_context_has_no_context_path() throws Exception { Optional pattern = new TraceWebAutoConfiguration.ManagementSkipPatternProviderConfig() - .skipPatternForManagementServerProperties( + .skipPatternForManagementServerProperties(environment(), new ManagementServerProperties()) .skipPattern(); @@ -113,11 +121,28 @@ public class SkipPatternProviderConfigTest { }); } + @Test + public void should_return_management_context_with_context_path_with_placeholders() + throws Exception { + contextRunner + .withConfiguration( + UserConfigurations.of(ManagementContextAutoConfiguration.class, + ServerPropertiesConfig.class)) + .withPropertyValues( + "management.server.servlet.context-path=${test:value}") + .run(context -> { + then(extractAllPatterns(context)).containsExactlyInAnyOrder( + "/actuator(/|/(health|health/.*|info|info/.*))?", "value.*", + SleuthWebProperties.DEFAULT_SKIP_PATTERN); + }); + } + @Test public void should_return_empty_when_no_endpoints() { Optional pattern = new TraceWebAutoConfiguration.ActuatorSkipPatternProviderConfig() - .skipPatternForActuatorEndpointsSamePort(new ServerProperties(), - new WebEndpointProperties(), Collections::emptyList) + .skipPatternForActuatorEndpointsSamePort(environment(), + new ServerProperties(), new WebEndpointProperties(), + Collections::emptyList) .skipPattern(); then(pattern).isEmpty(); @@ -145,6 +170,18 @@ public class SkipPatternProviderConfigTest { }); } + @Test + public void should_return_endpoints_with_context_path_with_placeholders() { + contextRunner + .withConfiguration(UserConfigurations.of(ServerPropertiesConfig.class)) + .withPropertyValues("server.servlet.context-path=${test:foo}") + .run(context -> { + then(extractAllPatterns(context)).containsExactlyInAnyOrder( + "foo/actuator(/|/(health|health/.*|info|info/.*))?", + SleuthWebProperties.DEFAULT_SKIP_PATTERN); + }); + } + @Test public void should_return_endpoints_without_context_path_and_base_path_set_to_root() { contextRunner @@ -157,6 +194,18 @@ public class SkipPatternProviderConfigTest { }); } + @Test + public void should_return_endpoints_without_context_path_and_base_path_set_to_root_with_placeholders() { + contextRunner + .withConfiguration(UserConfigurations.of(ServerPropertiesConfig.class)) + .withPropertyValues("management.endpoints.web.base-path=${test:/}") + .run(context -> { + then(extractAllPatterns(context)).containsExactlyInAnyOrder( + "/(health|health/.*|info|info/.*)", + SleuthWebProperties.DEFAULT_SKIP_PATTERN); + }); + } + @Test public void should_return_endpoints_with_context_path_and_base_path_set_to_root() { contextRunner @@ -170,6 +219,19 @@ public class SkipPatternProviderConfigTest { }); } + @Test + public void should_return_endpoints_with_context_path_and_base_path_set_to_root_with_placeholder() { + contextRunner + .withConfiguration(UserConfigurations.of(ServerPropertiesConfig.class)) + .withPropertyValues("management.endpoints.web.base-path=${test:/}", + "server.servlet.context-path=foo") + .run(context -> { + then(extractAllPatterns(context)).containsExactlyInAnyOrder( + "foo(/|/(health|health/.*|info|info/.*))?", + SleuthWebProperties.DEFAULT_SKIP_PATTERN); + }); + } + @Test public void should_return_endpoints_with_context_path_and_base_path_set_to_root_different_port() { contextRunner @@ -183,6 +245,20 @@ public class SkipPatternProviderConfigTest { }); } + @Test + public void should_return_endpoints_with_context_path_and_base_path_set_to_root_different_port_with_placeholder() { + contextRunner + .withConfiguration(UserConfigurations.of(ServerPropertiesConfig.class)) + .withPropertyValues("management.endpoints.web.base-path=/", + "management.server.port=${some-port:0}", + "server.servlet.context-path=${some-path:foo}") + .run(context -> { + then(extractAllPatterns(context)).containsExactlyInAnyOrder( + "/(health|health/.*|info|info/.*)", + SleuthWebProperties.DEFAULT_SKIP_PATTERN); + }); + } + @Test public void should_return_endpoints_with_actuator_context_path_only() { contextRunner @@ -196,6 +272,19 @@ public class SkipPatternProviderConfigTest { }); } + @Test + public void should_return_endpoints_with_actuator_context_path_only_with_placeholder() { + contextRunner + .withConfiguration(UserConfigurations.of(ServerPropertiesConfig.class)) + .withPropertyValues("management.endpoints.web.base-path=/${test:mgt}", + "server.servlet.context-path=${test2:foo}") + .run(context -> { + then(extractAllPatterns(context)).containsExactlyInAnyOrder( + "foo/mgt(/|/(health|health/.*|info|info/.*))?", + SleuthWebProperties.DEFAULT_SKIP_PATTERN); + }); + } + @Test public void should_return_endpoints_with_actuator_default_context_path_different_port() { contextRunner