pull/32880/head
Phillip Webb 2 years ago
parent 0f405c06bf
commit a59b6cb1f3

@ -47,21 +47,8 @@ public abstract class AbstractCompositeHealthContributorConfiguration<C, I exten
*/
@Deprecated(since = "3.0.0", forRemoval = true)
protected AbstractCompositeHealthContributorConfiguration() {
ResolvableType type = ResolvableType.forClass(AbstractCompositeHealthContributorConfiguration.class,
getClass());
Class<?> indicatorType = type.resolveGeneric(1);
Class<?> beanType = type.resolveGeneric(2);
this.indicatorFactory = (bean) -> {
try {
@SuppressWarnings("unchecked")
Constructor<I> constructor = (Constructor<I>) indicatorType.getDeclaredConstructor(beanType);
return BeanUtils.instantiateClass(constructor, bean);
}
catch (Exception ex) {
throw new IllegalStateException(
"Unable to create health indicator " + indicatorType + " for bean type " + beanType, ex);
}
};
this.indicatorFactory = new ReflectionIndicatorFactory(
ResolvableType.forClass(AbstractCompositeHealthContributorConfiguration.class, getClass()));
}
/**
@ -88,4 +75,34 @@ public abstract class AbstractCompositeHealthContributorConfiguration<C, I exten
return this.indicatorFactory.apply(bean);
}
private class ReflectionIndicatorFactory implements Function<B, I> {
private final Class<?> indicatorType;
private final Class<?> beanType;
ReflectionIndicatorFactory(ResolvableType type) {
this.indicatorType = type.resolveGeneric(1);
this.beanType = type.resolveGeneric(2);
}
@Override
public I apply(B bean) {
try {
return BeanUtils.instantiateClass(getConstructor(), bean);
}
catch (Exception ex) {
throw new IllegalStateException("Unable to create health indicator %s for bean type %s"
.formatted(this.indicatorType, this.beanType), ex);
}
}
@SuppressWarnings("unchecked")
private Constructor<I> getConstructor() throws NoSuchMethodException {
return (Constructor<I>) this.indicatorType.getDeclaredConstructor(this.beanType);
}
}
}

@ -132,8 +132,8 @@ public class ObservationAutoConfiguration {
@Bean
TracingAwareMeterObservationHandler<Observation.Context> tracingAwareMeterObservationHandler(
MeterRegistry meterRegistry, Tracer tracer) {
return new TracingAwareMeterObservationHandler<>(new DefaultMeterObservationHandler(meterRegistry),
tracer);
DefaultMeterObservationHandler delegate = new DefaultMeterObservationHandler(meterRegistry);
return new TracingAwareMeterObservationHandler<>(delegate, tracer);
}
}

@ -16,16 +16,15 @@
package org.springframework.boot.actuate.autoconfigure.observation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.micrometer.observation.ObservationHandler;
import io.micrometer.observation.ObservationHandler.FirstMatchingCompositeObservationHandler;
import io.micrometer.observation.ObservationRegistry.ObservationConfig;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* Groups {@link ObservationHandler ObservationHandlers} by type.
@ -46,11 +45,11 @@ class ObservationHandlerGrouping {
}
void apply(List<ObservationHandler<?>> handlers, ObservationConfig config) {
Map<Class<? extends ObservationHandler>, List<ObservationHandler<?>>> groupings = new HashMap<>();
MultiValueMap<Class<? extends ObservationHandler>, ObservationHandler<?>> groupings = new LinkedMultiValueMap<>();
for (ObservationHandler<?> handler : handlers) {
Class<? extends ObservationHandler> category = findCategory(handler);
if (category != null) {
groupings.computeIfAbsent(category, (c) -> new ArrayList<>()).add(handler);
groupings.add(category, handler);
}
else {
config.observationHandler(handler);

@ -73,13 +73,11 @@ class ObservationRegistryConfigurer {
}
private void registerObservationPredicates(ObservationRegistry registry) {
this.observationPredicates.orderedStream().forEach(
(observationPredicate) -> registry.observationConfig().observationPredicate(observationPredicate));
this.observationPredicates.orderedStream().forEach(registry.observationConfig()::observationPredicate);
}
private void registerGlobalObservationConventions(ObservationRegistry registry) {
this.observationConventions.orderedStream()
.forEach((convention) -> registry.observationConfig().observationConvention(convention));
this.observationConventions.orderedStream().forEach(registry.observationConfig()::observationConvention);
}
@SuppressWarnings("unchecked")

@ -44,9 +44,9 @@ class ClientHttpObservationConventionAdapter implements ClientRequestObservation
@Override
@SuppressWarnings("deprecation")
public KeyValues getLowCardinalityKeyValues(ClientRequestObservationContext context) {
KeyValues keyValues = KeyValues.empty();
Iterable<Tag> tags = this.tagsProvider.getTags(context.getUriTemplate(), context.getCarrier(),
context.getResponse());
KeyValues keyValues = KeyValues.empty();
for (Tag tag : tags) {
keyValues = keyValues.and(tag.getKey(), tag.getValue());
}

@ -21,6 +21,7 @@ import io.micrometer.core.instrument.Tag;
import io.micrometer.observation.Observation;
import org.springframework.boot.actuate.metrics.web.reactive.client.WebClientExchangeTagsProvider;
import org.springframework.core.Conventions;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientRequestObservationContext;
import org.springframework.web.reactive.function.client.ClientRequestObservationConvention;
@ -32,10 +33,11 @@ import org.springframework.web.reactive.function.client.WebClient;
*
* @author Brian Clozel
*/
@SuppressWarnings({ "deprecation", "removal" })
@SuppressWarnings("removal")
class ClientObservationConventionAdapter implements ClientRequestObservationConvention {
private static final String URI_TEMPLATE_ATTRIBUTE = WebClient.class.getName() + ".uriTemplate";
private static final String URI_TEMPLATE_ATTRIBUTE = Conventions.getQualifiedAttributeName(WebClient.class,
"uriTemplate");
private final String metricName;
@ -53,20 +55,18 @@ class ClientObservationConventionAdapter implements ClientRequestObservationConv
@Override
public KeyValues getLowCardinalityKeyValues(ClientRequestObservationContext context) {
KeyValues keyValues = KeyValues.empty();
mutateClientRequest(context);
Iterable<Tag> tags = this.tagsProvider.tags(context.getCarrier(), context.getResponse(), context.getError());
KeyValues keyValues = KeyValues.empty();
for (Tag tag : tags) {
keyValues = keyValues.and(tag.getKey(), tag.getValue());
}
return keyValues;
}
/*
* {@link WebClientExchangeTagsProvider} relies on a request attribute to get the URI
* template, we need to adapt to that.
*/
private static void mutateClientRequest(ClientRequestObservationContext context) {
private void mutateClientRequest(ClientRequestObservationContext context) {
// WebClientExchangeTagsProvider relies on a request attribute to get the URI
// template, we need to adapt to that.
ClientRequest clientRequest = ClientRequest.from(context.getCarrier())
.attribute(URI_TEMPLATE_ATTRIBUTE, context.getUriTemplate()).build();
context.setCarrier(clientRequest);

@ -23,6 +23,7 @@ import io.micrometer.observation.ObservationRegistry;
import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties.Web.Client;
import org.springframework.boot.actuate.autoconfigure.metrics.OnlyOnceLoggingDenyMeterFilter;
import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.observation.ObservationProperties;
@ -67,13 +68,14 @@ public class HttpClientObservationsAutoConfiguration {
@SuppressWarnings("removal")
MeterFilter metricsHttpClientUriTagFilter(ObservationProperties observationProperties,
MetricsProperties metricsProperties) {
String metricName = metricsProperties.getWeb().getClient().getRequest().getMetricName();
Client clientProperties = metricsProperties.getWeb().getClient();
String metricName = clientProperties.getRequest().getMetricName();
String observationName = observationProperties.getHttp().getClient().getRequests().getName();
String name = (observationName != null) ? observationName : metricName;
MeterFilter denyFilter = new OnlyOnceLoggingDenyMeterFilter(() -> String
.format("Reached the maximum number of URI tags for '%s'. Are you using 'uriVariables'?", name));
return MeterFilter.maximumAllowableTags(name, "uri", metricsProperties.getWeb().getClient().getMaxUriTags(),
denyFilter);
MeterFilter denyFilter = new OnlyOnceLoggingDenyMeterFilter(
() -> "Reached the maximum number of URI tags for '%s'. Are you using 'uriVariables'?"
.formatted(name));
return MeterFilter.maximumAllowableTags(name, "uri", clientProperties.getMaxUriTags(), denyFilter);
}
}

@ -58,8 +58,8 @@ class ServerRequestObservationConventionAdapter implements ServerRequestObservat
@Override
public KeyValues getLowCardinalityKeyValues(ServerRequestObservationContext context) {
KeyValues keyValues = KeyValues.empty();
Iterable<Tag> tags = this.tagsProvider.httpRequestTags(context.getServerWebExchange(), context.getError());
KeyValues keyValues = KeyValues.empty();
for (Tag tag : tags) {
keyValues = keyValues.and(tag.getKey(), tag.getValue());
}

@ -27,6 +27,7 @@ import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties.Web.Server;
import org.springframework.boot.actuate.autoconfigure.metrics.OnlyOnceLoggingDenyMeterFilter;
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
@ -80,20 +81,24 @@ public class WebFluxObservationAutoConfiguration {
public ServerHttpObservationFilter webfluxObservationFilter(ObservationRegistry registry,
ObjectProvider<WebFluxTagsProvider> tagConfigurer,
ObjectProvider<WebFluxTagsContributor> contributorsProvider) {
String observationName = this.observationProperties.getHttp().getServer().getRequests().getName();
String metricName = this.metricsProperties.getWeb().getServer().getRequest().getMetricName();
String name = (observationName != null) ? observationName : metricName;
WebFluxTagsProvider tagsProvider = tagConfigurer.getIfAvailable();
List<WebFluxTagsContributor> tagsContributors = contributorsProvider.orderedStream().toList();
ServerRequestObservationConvention convention = new DefaultServerRequestObservationConvention(name);
ServerRequestObservationConvention convention = extracted(name, tagsProvider, tagsContributors);
return new ServerHttpObservationFilter(registry, convention);
}
private ServerRequestObservationConvention extracted(String name, WebFluxTagsProvider tagsProvider,
List<WebFluxTagsContributor> tagsContributors) {
if (tagsProvider != null) {
convention = new ServerRequestObservationConventionAdapter(name, tagsProvider);
return new ServerRequestObservationConventionAdapter(name, tagsProvider);
}
else if (!tagsContributors.isEmpty()) {
convention = new ServerRequestObservationConventionAdapter(name, tagsContributors);
if (!tagsContributors.isEmpty()) {
return new ServerRequestObservationConventionAdapter(name, tagsContributors);
}
return new ServerHttpObservationFilter(registry, convention);
return new DefaultServerRequestObservationConvention(name);
}
@Configuration(proxyBeanMethods = false)
@ -104,11 +109,11 @@ public class WebFluxObservationAutoConfiguration {
@Bean
@Order(0)
MeterFilter metricsHttpServerUriTagFilter(MetricsProperties properties) {
String metricName = properties.getWeb().getServer().getRequest().getMetricName();
Server serverProperties = properties.getWeb().getServer();
String metricName = serverProperties.getRequest().getMetricName();
MeterFilter filter = new OnlyOnceLoggingDenyMeterFilter(
() -> String.format("Reached the maximum number of URI tags for '%s'.", metricName));
return MeterFilter.maximumAllowableTags(metricName, "uri", properties.getWeb().getServer().getMaxUriTags(),
filter);
() -> "Reached the maximum number of URI tags for '%s'.".formatted(metricName));
return MeterFilter.maximumAllowableTags(metricName, "uri", serverProperties.getMaxUriTags(), filter);
}
}

@ -64,9 +64,9 @@ class ServerRequestObservationConventionAdapter implements ServerRequestObservat
@Override
public KeyValues getLowCardinalityKeyValues(ServerRequestObservationContext context) {
KeyValues keyValues = KeyValues.empty();
Iterable<Tag> tags = this.tagsProvider.getTags(context.getCarrier(), context.getResponse(), getHandler(context),
context.getError());
KeyValues keyValues = KeyValues.empty();
for (Tag tag : tags) {
keyValues = keyValues.and(tag.getKey(), tag.getValue());
}

@ -110,9 +110,7 @@ public class BraveAutoConfiguration {
Builder builder = Tracing.newBuilder().currentTraceContext(currentTraceContext).traceId128Bit(true)
.supportsJoin(false).propagationFactory(propagationFactory).sampler(sampler)
.localServiceName(applicationName);
for (SpanHandler spanHandler : spanHandlers) {
builder.addSpanHandler(spanHandler);
}
spanHandlers.forEach(builder::addSpanHandler);
for (TracingCustomizer tracingCustomizer : tracingCustomizers) {
tracingCustomizer.customize(builder);
}
@ -130,9 +128,7 @@ public class BraveAutoConfiguration {
public CurrentTraceContext braveCurrentTraceContext(List<CurrentTraceContext.ScopeDecorator> scopeDecorators,
List<CurrentTraceContextCustomizer> currentTraceContextCustomizers) {
ThreadLocalCurrentTraceContext.Builder builder = ThreadLocalCurrentTraceContext.newBuilder();
for (ScopeDecorator scopeDecorator : scopeDecorators) {
builder.addScopeDecorator(scopeDecorator);
}
scopeDecorators.forEach(builder::addScopeDecorator);
for (CurrentTraceContextCustomizer currentTraceContextCustomizer : currentTraceContextCustomizers) {
currentTraceContextCustomizer.customize(builder);
}

@ -66,8 +66,11 @@ class MeterRegistrySpanMetrics implements SpanMetrics {
@Override
public void registerQueueRemainingCapacity(BlockingQueue<?> queue) {
this.meterRegistry.gauge("wavefront.reporter.queue.remaining_capacity", queue,
(q) -> (double) q.remainingCapacity());
this.meterRegistry.gauge("wavefront.reporter.queue.remaining_capacity", queue, this::remainingCapacity);
}
private double remainingCapacity(BlockingQueue<?> queue) {
return queue.remainingCapacity();
}
}

@ -60,9 +60,11 @@ class ZipkinConfigurations {
@Bean
@ConditionalOnMissingBean(Sender.class)
URLConnectionSender urlConnectionSender(ZipkinProperties properties) {
return URLConnectionSender.newBuilder().connectTimeout((int) properties.getConnectTimeout().toMillis())
.readTimeout((int) properties.getReadTimeout().toMillis()).endpoint(properties.getEndpoint())
.build();
URLConnectionSender.Builder builder = URLConnectionSender.newBuilder();
builder.connectTimeout((int) properties.getConnectTimeout().toMillis());
builder.readTimeout((int) properties.getReadTimeout().toMillis());
builder.endpoint(properties.getEndpoint());
return builder.build();
}
}
@ -78,7 +80,7 @@ class ZipkinConfigurations {
ObjectProvider<ZipkinRestTemplateBuilderCustomizer> customizers) {
RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder()
.setConnectTimeout(properties.getConnectTimeout()).setReadTimeout(properties.getReadTimeout());
customizers.orderedStream().forEach((c) -> c.customize(restTemplateBuilder));
customizers.orderedStream().forEach((customizer) -> customizer.customize(restTemplateBuilder));
return new ZipkinRestTemplateSender(properties.getEndpoint(), restTemplateBuilder.build());
}
@ -94,7 +96,7 @@ class ZipkinConfigurations {
ZipkinWebClientSender webClientSender(ZipkinProperties properties,
ObjectProvider<ZipkinWebClientBuilderCustomizer> customizers) {
WebClient.Builder builder = WebClient.builder();
customizers.orderedStream().forEach((c) -> c.customize(builder));
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return new ZipkinWebClientSender(properties.getEndpoint(), builder.build());
}

@ -20,6 +20,7 @@ import reactor.core.publisher.Mono;
import zipkin2.Call;
import zipkin2.Callback;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.reactive.function.client.WebClient;
@ -73,8 +74,12 @@ class ZipkinWebClientSender extends HttpSender {
}
private Mono<ResponseEntity<Void>> sendRequest() {
return this.webClient.post().uri(this.endpoint).headers((headers) -> headers.addAll(getDefaultHeaders()))
.bodyValue(getBody()).retrieve().toBodilessEntity();
return this.webClient.post().uri(this.endpoint).headers(this::addDefaultHeaders).bodyValue(getBody())
.retrieve().toBodilessEntity();
}
private void addDefaultHeaders(HttpHeaders headers) {
headers.addAll(getDefaultHeaders());
}
}

@ -48,12 +48,12 @@ public class WavefrontSenderConfiguration {
@ConditionalOnMissingBean
public WavefrontSender wavefrontSender(WavefrontProperties properties) {
Builder builder = new Builder(properties.getEffectiveUri().toString(), properties.getApiTokenOrThrow());
PropertyMapper mapper = PropertyMapper.get().alwaysApplyingWhenNonNull();
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
WavefrontProperties.Sender sender = properties.getSender();
mapper.from(sender.getMaxQueueSize()).to(builder::maxQueueSize);
mapper.from(sender.getFlushInterval()).asInt(Duration::getSeconds).to(builder::flushIntervalSeconds);
mapper.from(sender.getMessageSize()).asInt(DataSize::toBytes).to(builder::messageSizeBytes);
mapper.from(sender.getBatchSize()).to(builder::batchSize);
map.from(sender.getMaxQueueSize()).to(builder::maxQueueSize);
map.from(sender.getFlushInterval()).asInt(Duration::getSeconds).to(builder::flushIntervalSeconds);
map.from(sender.getMessageSize()).asInt(DataSize::toBytes).to(builder::messageSizeBytes);
map.from(sender.getBatchSize()).to(builder::batchSize);
return builder.build();
}

@ -43,6 +43,7 @@ import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpoint;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
@ -208,10 +209,7 @@ public class HeapDumpWebEndpoint {
@Override
public File dumpHeap(Boolean live) throws IOException, InterruptedException {
if (live != null) {
throw new IllegalArgumentException(
"OpenJ9DiagnosticsMXBean does not support live parameter when dumping the heap");
}
Assert.isNull(live, "OpenJ9DiagnosticsMXBean does not support live parameter when dumping the heap");
return new File(
(String) ReflectionUtils.invokeMethod(this.dumpHeapMethod, this.diagnosticMXBean, "heap", null));
}

@ -99,11 +99,11 @@ class RabbitStreamConfiguration {
static EnvironmentBuilder configure(EnvironmentBuilder builder, RabbitProperties properties) {
builder.lazyInitialization(true);
RabbitProperties.Stream stream = properties.getStream();
PropertyMapper mapper = PropertyMapper.get();
mapper.from(stream.getHost()).to(builder::host);
mapper.from(stream.getPort()).to(builder::port);
mapper.from(stream.getUsername()).as(withFallback(properties::getUsername)).whenNonNull().to(builder::username);
mapper.from(stream.getPassword()).as(withFallback(properties::getPassword)).whenNonNull().to(builder::password);
PropertyMapper map = PropertyMapper.get();
map.from(stream.getHost()).to(builder::host);
map.from(stream.getPort()).to(builder::port);
map.from(stream.getUsername()).as(withFallback(properties::getUsername)).whenNonNull().to(builder::username);
map.from(stream.getPassword()).as(withFallback(properties::getPassword)).whenNonNull().to(builder::password);
return builder;
}

@ -52,9 +52,9 @@ class MongoDataConfiguration {
@ConditionalOnMissingBean
MongoMappingContext mongoMappingContext(MongoProperties properties, MongoCustomConversions conversions,
MongoManagedTypes managedTypes) {
PropertyMapper mapper = PropertyMapper.get().alwaysApplyingWhenNonNull();
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
MongoMappingContext context = new MongoMappingContext();
mapper.from(properties.isAutoIndexCreation()).to(context::setAutoIndexCreation);
map.from(properties.isAutoIndexCreation()).to(context::setAutoIndexCreation);
context.setManagedTypes(managedTypes);
Class<?> strategyClass = properties.getFieldNamingStrategy();
if (strategyClass != null) {

@ -24,6 +24,7 @@ import java.util.Collection;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Predicate;
import org.flywaydb.core.api.FlywayException;
import org.flywaydb.core.api.Location;
@ -57,7 +58,7 @@ class NativeImageResourceProvider implements ResourceProvider {
private final boolean failOnMissingLocations;
private final List<ResourceWithLocation> resources = new ArrayList<>();
private final List<LocatedResource> locatedResources = new ArrayList<>();
private final Lock lock = new ReentrantLock();
@ -93,14 +94,21 @@ class NativeImageResourceProvider implements ResourceProvider {
return this.scanner.getResources(prefix, suffixes);
}
ensureInitialized();
List<LoadableResource> result = new ArrayList<>(this.scanner.getResources(prefix, suffixes));
this.resources.stream().filter((r) -> StringUtils.startsAndEndsWith(r.resource.getFilename(), prefix, suffixes))
.map((r) -> (LoadableResource) new ClassPathResource(r.location(),
r.location().getPath() + "/" + r.resource().getFilename(), this.classLoader, this.encoding))
Predicate<LocatedResource> matchesPrefixAndSuffixes = (locatedResource) -> StringUtils
.startsAndEndsWith(locatedResource.resource.getFilename(), prefix, suffixes);
List<LoadableResource> result = new ArrayList<>();
result.addAll(this.scanner.getResources(prefix, suffixes));
this.locatedResources.stream().filter(matchesPrefixAndSuffixes).map(this::asClassPathResource)
.forEach(result::add);
return result;
}
private ClassPathResource asClassPathResource(LocatedResource locatedResource) {
Location location = locatedResource.location();
String fileNameWithAbsolutePath = location.getPath() + "/" + locatedResource.resource().getFilename();
return new ClassPathResource(location, fileNameWithAbsolutePath, this.classLoader, this.encoding);
}
private void ensureInitialized() {
this.lock.lock();
try {
@ -127,20 +135,24 @@ class NativeImageResourceProvider implements ResourceProvider {
}
continue;
}
Resource[] resources;
try {
resources = resolver.getResources(root.getURI() + "/*");
}
catch (IOException ex) {
throw new UncheckedIOException("Failed to list resources for " + location.getDescriptor(), ex);
}
Resource[] resources = getResources(resolver, location, root);
for (Resource resource : resources) {
this.resources.add(new ResourceWithLocation(resource, location));
this.locatedResources.add(new LocatedResource(resource, location));
}
}
}
private record ResourceWithLocation(Resource resource, Location location) {
private Resource[] getResources(PathMatchingResourcePatternResolver resolver, Location location, Resource root) {
try {
return resolver.getResources(root.getURI() + "/*");
}
catch (IOException ex) {
throw new UncheckedIOException("Failed to list resources for " + location.getDescriptor(), ex);
}
}
private record LocatedResource(Resource resource, Location location) {
}
}

@ -52,8 +52,8 @@ class HazelcastSessionConfiguration {
SessionRepositoryCustomizer<HazelcastIndexedSessionRepository> springBootSessionRepositoryCustomizer(
SessionProperties sessionProperties, HazelcastSessionProperties hazelcastSessionProperties,
ServerProperties serverProperties) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
return (sessionRepository) -> {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(sessionProperties.determineTimeout(() -> serverProperties.getServlet().getSession().getTimeout()))
.to(sessionRepository::setDefaultMaxInactiveInterval);
map.from(hazelcastSessionProperties::getMapName).to(sessionRepository::setSessionMapName);

@ -67,8 +67,8 @@ class JdbcSessionConfiguration {
SessionRepositoryCustomizer<JdbcIndexedSessionRepository> springBootSessionRepositoryCustomizer(
SessionProperties sessionProperties, JdbcSessionProperties jdbcSessionProperties,
ServerProperties serverProperties) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
return (sessionRepository) -> {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(sessionProperties.determineTimeout(() -> serverProperties.getServlet().getSession().getTimeout()))
.to(sessionRepository::setDefaultMaxInactiveInterval);
map.from(jdbcSessionProperties::getTableName).to(sessionRepository::setTableName);

@ -50,8 +50,8 @@ class MongoReactiveSessionConfiguration {
ReactiveSessionRepositoryCustomizer<ReactiveMongoSessionRepository> springBootSessionRepositoryCustomizer(
SessionProperties sessionProperties, MongoSessionProperties mongoSessionProperties,
ServerProperties serverProperties) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
return (sessionRepository) -> {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(sessionProperties.determineTimeout(() -> serverProperties.getReactive().getSession().getTimeout()))
.to(sessionRepository::setDefaultMaxInactiveInterval);
map.from(mongoSessionProperties::getCollectionName).to(sessionRepository::setCollectionName);

@ -50,8 +50,8 @@ class RedisReactiveSessionConfiguration {
ReactiveSessionRepositoryCustomizer<ReactiveRedisSessionRepository> springBootSessionRepositoryCustomizer(
SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties,
ServerProperties serverProperties) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
return (sessionRepository) -> {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(sessionProperties.determineTimeout(() -> serverProperties.getReactive().getSession().getTimeout()))
.to(sessionRepository::setDefaultMaxInactiveInterval);
map.from(redisSessionProperties::getNamespace).to(sessionRepository::setRedisKeyNamespace);

@ -67,11 +67,11 @@ class RedisSessionConfiguration {
String cleanupCron = redisSessionProperties.getCleanupCron();
if (cleanupCron != null) {
throw new InvalidConfigurationPropertyValueException("spring.session.redis.cleanup-cron", cleanupCron,
"Cron-based cleanup is only supported when spring.session.redis.repository-type is set to "
+ "indexed.");
"Cron-based cleanup is only supported when "
+ "spring.session.redis.repository-type is set to indexed.");
}
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
return (sessionRepository) -> {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(sessionProperties
.determineTimeout(() -> serverProperties.getServlet().getSession().getTimeout()))
.to(sessionRepository::setDefaultMaxInactiveInterval);
@ -101,8 +101,8 @@ class RedisSessionConfiguration {
SessionRepositoryCustomizer<RedisIndexedSessionRepository> springBootSessionRepositoryCustomizer(
SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties,
ServerProperties serverProperties) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
return (sessionRepository) -> {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(sessionProperties
.determineTimeout(() -> serverProperties.getServlet().getSession().getTimeout()))
.to(sessionRepository::setDefaultMaxInactiveInterval);

@ -1,6 +1,5 @@
[[actuator.tracing]]
== HTTP Tracing
You can enable HTTP Tracing by providing a bean of type `HttpTraceRepository` in your application's configuration.
For convenience, Spring Boot offers `InMemoryHttpTraceRepository`, which stores traces for the last 100 (the default) request-response exchanges.
`InMemoryHttpTraceRepository` is limited compared to other tracing solutions, and we recommend using it only for development environments.
@ -9,8 +8,9 @@ Alternatively, you can create your own `HttpTraceRepository`.
You can use the `httptrace` endpoint to obtain information about the request-response exchanges that are stored in the `HttpTraceRepository`.
[[actuator.tracing.custom]]
=== Custom HTTP tracing
[[actuator.tracing.custom]]
=== Custom HTTP Tracing
To customize the items that are included in each trace, use the configprop:management.trace.http.include[] configuration property.
For advanced customization, consider registering your own `HttpExchangeTracer` implementation.

@ -1,22 +1,23 @@
[[actuator.micrometer-tracing]]
== Tracing
Spring Boot Actuator provides dependency management and auto-configuration for https://micrometer.io/docs/tracing[Micrometer Tracing], a facade for popular tracer libraries.
Micrometer Tracing hooks into Micrometer's `ObservationHandler`, which means a https://micrometer.io/docs/tracing#_glossary[span] is reported for every completed observation.
TIP: To learn more about Micrometer Tracing capabilities, see its https://micrometer.io/docs/tracing[reference documentation].
[[actuator.micrometer-tracing.tracers]]
=== Supported tracers
[[actuator.micrometer-tracing.tracers]]
=== Supported Tracers
Spring Boot ships auto-configuration for the following tracers:
* https://opentelemetry.io/[OpenTelemetry] with https://zipkin.io/[Zipkin] or https://docs.wavefront.com/[Wavefront]
* https://github.com/openzipkin/brave[OpenZipkin Brave] with https://zipkin.io/[Zipkin] or https://docs.wavefront.com/[Wavefront]
[[actuator.micrometer-tracing.getting-started]]
=== Getting Started
We need an example application that we can use to getting started with tracing.
For our purposes, the simple "`Hello World!`" web application that's covered in the "`<<getting-started#getting-started.first-application>>`" section will suffice.
We're going to use the OpenTelemetry tracer with Zipkin as trace backend.
@ -64,42 +65,52 @@ Press the "Show" button to see the details of that trace.
TIP: You can include the current trace and span id in the logs by setting the `logging.pattern.level` property to `%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]`
[[actuator.micrometer-tracing.tracer-implementations]]
=== Tracer implementations
[[actuator.micrometer-tracing.tracer-implementations]]
=== Tracer Implementations
As Micrometer Tracer supports multiple tracer implementations, there are multiple dependency combinations possible with Spring Boot.
All tracer implementations need the `org.springframework.boot:spring-boot-starter-actuator` dependency.
[[actuator.micrometer-tracing.tracer-implementations.otel-zipkin]]
==== OpenTelemetry with Zipkin
==== OpenTelemetry With Zipkin
* `io.micrometer:micrometer-tracing-bridge-otel` - which is needed to bride the Micrometer Observation API to OpenTelemetry.
* `io.opentelemetry:opentelemetry-exporter-zipkin` - which is needed to report traces to Zipkin.
[[actuator.micrometer-tracing.tracer-implementations.otel-wavefront]]
==== OpenTelemetry with Wavefront
==== OpenTelemetry With Wavefront
* `io.micrometer:micrometer-tracing-bridge-otel` - which is needed to bride the Micrometer Observation API to OpenTelemetry.
* `io.micrometer:micrometer-tracing-reporter-wavefront` - which is needed to report traces to Wavefront.
[[actuator.micrometer-tracing.tracer-implementations.brave-zipkin]]
==== OpenZipkin Brave with Zipkin
==== OpenZipkin Brave With Zipkin
* `io.micrometer:micrometer-tracing-bridge-brave` - which is needed to bridge the Micrometer Observation API to Brave.
* `io.zipkin.reporter2:zipkin-reporter-brave` - which is needed to report traces to Zipkin.
NOTE: If your project doesn't use Spring MVC or Spring WebFlux, the `io.zipkin.reporter2:zipkin-sender-urlconnection` dependency is needed, too.
[[actuator.micrometer-tracing.tracer-implementations.brave-wavefront]]
==== OpenZipkin Brave with Wavefront
==== OpenZipkin Brave With Wavefront
* `io.micrometer:micrometer-tracing-bridge-brave` - which is needed to bridge the Micrometer Observation API to Brave.
* `io.micrometer:micrometer-tracing-reporter-wavefront` - which is needed to report traces to Wavefront.
[[actuator.micrometer-tracing.creating-spans]]
=== Creating custom spans
[[actuator.micrometer-tracing.creating-spans]]
=== Creating Custom Spans
You can create your own spans by starting an observation.
For this, inject `ObservationRegistry` into your component:

@ -67,11 +67,21 @@ public class MockServerRestTemplateCustomizer implements RestTemplateCustomizer
this(SimpleRequestExpectationManager::new);
}
/**
* Crate a new {@link MockServerRestTemplateCustomizer} instance.
* @param expectationManager the expectation manager class to use
*/
public MockServerRestTemplateCustomizer(Class<? extends RequestExpectationManager> expectationManager) {
this(() -> BeanUtils.instantiateClass(expectationManager));
Assert.notNull(expectationManager, "ExpectationManager must not be null");
}
/**
* Crate a new {@link MockServerRestTemplateCustomizer} instance.
* @param expectationManagerSupplier a supplier that provides the
* {@link RequestExpectationManager} to use
* @since 3.0.0
*/
public MockServerRestTemplateCustomizer(Supplier<? extends RequestExpectationManager> expectationManagerSupplier) {
Assert.notNull(expectationManagerSupplier, "ExpectationManagerSupplier must not be null");
this.expectationManagerSupplier = expectationManagerSupplier;

@ -76,11 +76,14 @@ class NativeImagePluginAction implements PluginApplicationAction {
}
private Iterable<Configuration> removeDevelopmentOnly(Set<Configuration> configurations) {
return configurations.stream().filter((
configuration) -> !SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME.equals(configuration.getName()))
return configurations.stream().filter(this::isNotDevelopmentOnly)
.collect(Collectors.toCollection(LinkedHashSet::new));
}
private boolean isNotDevelopmentOnly(Configuration configuration) {
return !SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME.equals(configuration.getName());
}
private void configureTestNativeBinaryClasspath(SourceSetContainer sourceSets, GraalVMExtension graalVmExtension,
String sourceSetName) {
SourceSetOutput output = sourceSets.getByName(SpringBootAotPlugin.AOT_TEST_SOURCE_SET_NAME).getOutput();

@ -38,6 +38,7 @@ import org.springframework.util.StringUtils;
*
* @author Phillip Webb
* @author Dave Syer
* @author Moritz Halbritter
*/
class StartupInfoLogger {

@ -222,44 +222,39 @@ class SpringBootJoranConfigurator extends JoranConfigurator {
}
private Class<?> determineType(Model model, Supplier<Object> parentSupplier) {
String className = null;
if (model instanceof ComponentModel) {
className = ((ComponentModel) model).getClassName();
String className = (model instanceof ComponentModel componentModel) ? componentModel.getClassName() : null;
if (className != null) {
return loadImportType(className);
}
if (className == null) {
String tag = model.getTag();
if (tag != null) {
className = this.modelInterpretationContext.getDefaultNestedComponentRegistry()
.findDefaultComponentTypeByTag(tag);
if (className == null) {
Class<?> type = inferTypeFromParent(parentSupplier, tag);
if (type != null) {
return type;
}
}
String tag = model.getTag();
if (tag != null) {
className = this.modelInterpretationContext.getDefaultNestedComponentRegistry()
.findDefaultComponentTypeByTag(tag);
if (className != null) {
return loadImportType(className);
}
}
if (className != null) {
className = this.modelInterpretationContext.getImport(className);
return loadComponentType(className);
return inferTypeFromParent(parentSupplier, tag);
}
return null;
}
private Class<?> loadImportType(String className) {
return loadComponentType(this.modelInterpretationContext.getImport(className));
}
private Class<?> inferTypeFromParent(Supplier<Object> parentSupplier, String tag) {
Object parent = parentSupplier.get();
if (parent != null) {
try {
Class<?> typeFromSetter = new PropertySetter(
this.modelInterpretationContext.getBeanDescriptionCache(), parent)
.getClassNameViaImplicitRules(tag, AggregationType.AS_COMPLEX_PROPERTY,
this.modelInterpretationContext.getDefaultNestedComponentRegistry());
if (typeFromSetter != null) {
return typeFromSetter;
}
PropertySetter propertySetter = new PropertySetter(
this.modelInterpretationContext.getBeanDescriptionCache(), parent);
Class<?> typeFromPropertySetter = propertySetter.getClassNameViaImplicitRules(tag,
AggregationType.AS_COMPLEX_PROPERTY,
this.modelInterpretationContext.getDefaultNestedComponentRegistry());
return typeFromPropertySetter;
}
catch (Exception ex) {
// Continue
return null;
}
}
return null;

@ -234,7 +234,6 @@ class LogbackConfigurationAotContributionTests {
public static class Outer {
public void setImplementation(Implementation implementation) {
}
}
@ -243,7 +242,6 @@ class LogbackConfigurationAotContributionTests {
@DefaultClass(Implementation.class)
public void setContract(Contract contract) {
}
}

Loading…
Cancel
Save