Commit d829d522 authored by Vedran Pavic's avatar Vedran Pavic Committed by Stephane Nicoll

Introduce HealthIndicatorRegistry

This commit introduces HealthIndicatorRegistry which handles
registration of HealthIndicator instances. Registering new
HealthIndicator instances is now possible in runtime.

See gh-4965
parent ffdcdc0d
/* /*
* Copyright 2012-2017 the original author or authors. * Copyright 2012-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,10 +16,13 @@ ...@@ -16,10 +16,13 @@
package org.springframework.boot.actuate.autoconfigure.health; package org.springframework.boot.actuate.autoconfigure.health;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint;
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HealthIndicatorRegistry;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
...@@ -27,15 +30,27 @@ import org.springframework.context.annotation.Configuration; ...@@ -27,15 +30,27 @@ import org.springframework.context.annotation.Configuration;
* Configuration for {@link HealthEndpoint}. * Configuration for {@link HealthEndpoint}.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Vedran Pavic
*/ */
@Configuration @Configuration
class HealthEndpointConfiguration { class HealthEndpointConfiguration {
private final HealthAggregator healthAggregator;
private final HealthIndicatorRegistry healthIndicatorRegistry;
HealthEndpointConfiguration(ObjectProvider<HealthAggregator> healthAggregator,
ObjectProvider<HealthIndicatorRegistry> healthIndicatorRegistry) {
this.healthAggregator = healthAggregator
.getIfAvailable(OrderedHealthAggregator::new);
this.healthIndicatorRegistry = healthIndicatorRegistry.getObject();
}
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint @ConditionalOnEnabledEndpoint
public HealthEndpoint healthEndpoint(ApplicationContext applicationContext) { public HealthEndpoint healthEndpoint() {
return new HealthEndpoint(HealthIndicatorBeansComposite.get(applicationContext)); return new HealthEndpoint(this.healthAggregator, this.healthIndicatorRegistry);
} }
} }
...@@ -36,7 +36,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean ...@@ -36,7 +36,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
...@@ -107,11 +106,9 @@ class HealthEndpointWebExtensionConfiguration { ...@@ -107,11 +106,9 @@ class HealthEndpointWebExtensionConfiguration {
@ConditionalOnEnabledEndpoint @ConditionalOnEnabledEndpoint
@ConditionalOnBean(HealthEndpoint.class) @ConditionalOnBean(HealthEndpoint.class)
public HealthEndpointWebExtension healthEndpointWebExtension( public HealthEndpointWebExtension healthEndpointWebExtension(
ApplicationContext applicationContext, HealthEndpoint healthEndpoint,
HealthWebEndpointResponseMapper responseMapper) { HealthWebEndpointResponseMapper responseMapper) {
return new HealthEndpointWebExtension( return new HealthEndpointWebExtension(healthEndpoint, responseMapper);
HealthIndicatorBeansComposite.get(applicationContext),
responseMapper);
} }
} }
......
/* /*
* Copyright 2012-2017 the original author or authors. * Copyright 2012-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,16 +16,23 @@ ...@@ -16,16 +16,23 @@
package org.springframework.boot.actuate.autoconfigure.health; package org.springframework.boot.actuate.autoconfigure.health;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.health.ApplicationHealthIndicator;
import org.springframework.boot.actuate.health.DefaultHealthIndicatorRegistry;
import org.springframework.boot.actuate.health.HealthAggregator; import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.HealthIndicatorRegistry;
import org.springframework.boot.actuate.health.OrderedHealthAggregator; import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.boot.actuate.health.ReactiveHealthIndicator; import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.util.ClassUtils;
/** /**
* {@link EnableAutoConfiguration Auto-configuration} for {@link HealthIndicator}s. * {@link EnableAutoConfiguration Auto-configuration} for {@link HealthIndicator}s.
...@@ -33,6 +40,7 @@ import org.springframework.context.annotation.Configuration; ...@@ -33,6 +40,7 @@ import org.springframework.context.annotation.Configuration;
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Phillip Webb * @author Phillip Webb
* @author Vedran Pavic
* @since 2.0.0 * @since 2.0.0
*/ */
@Configuration @Configuration
...@@ -61,4 +69,34 @@ public class HealthIndicatorAutoConfiguration { ...@@ -61,4 +69,34 @@ public class HealthIndicatorAutoConfiguration {
return healthAggregator; return healthAggregator;
} }
@Bean
@ConditionalOnMissingBean(HealthIndicatorRegistry.class)
public HealthIndicatorRegistry healthIndicatorRegistry(
ApplicationContext applicationContext) {
HealthIndicatorRegistry registry = new DefaultHealthIndicatorRegistry();
Map<String, HealthIndicator> indicators = new LinkedHashMap<>();
indicators.putAll(applicationContext.getBeansOfType(HealthIndicator.class));
if (ClassUtils.isPresent("reactor.core.publisher.Flux", null)) {
new ReactiveHealthIndicators().get(applicationContext)
.forEach(indicators::putIfAbsent);
}
indicators.forEach(registry::register);
return registry;
}
private static class ReactiveHealthIndicators {
public Map<String, HealthIndicator> get(ApplicationContext applicationContext) {
Map<String, HealthIndicator> indicators = new LinkedHashMap<>();
applicationContext.getBeansOfType(ReactiveHealthIndicator.class)
.forEach((name, indicator) -> indicators.put(name, adapt(indicator)));
return indicators;
}
private HealthIndicator adapt(ReactiveHealthIndicator indicator) {
return () -> indicator.health().block();
}
}
} }
/*
* Copyright 2012-2017 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
*
* http://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.boot.actuate.autoconfigure.health;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.actuate.health.CompositeHealthIndicator;
import org.springframework.boot.actuate.health.CompositeHealthIndicatorFactory;
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
import org.springframework.context.ApplicationContext;
import org.springframework.util.ClassUtils;
/**
* Creates a {@link CompositeHealthIndicator} from beans in the
* {@link ApplicationContext}.
*
* @author Phillip Webb
*/
final class HealthIndicatorBeansComposite {
private HealthIndicatorBeansComposite() {
}
public static HealthIndicator get(ApplicationContext applicationContext) {
HealthAggregator healthAggregator = getHealthAggregator(applicationContext);
Map<String, HealthIndicator> indicators = new LinkedHashMap<>();
indicators.putAll(applicationContext.getBeansOfType(HealthIndicator.class));
if (ClassUtils.isPresent("reactor.core.publisher.Flux", null)) {
new ReactiveHealthIndicators().get(applicationContext)
.forEach(indicators::putIfAbsent);
}
CompositeHealthIndicatorFactory factory = new CompositeHealthIndicatorFactory();
return factory.createHealthIndicator(healthAggregator, indicators);
}
private static HealthAggregator getHealthAggregator(
ApplicationContext applicationContext) {
try {
return applicationContext.getBean(HealthAggregator.class);
}
catch (NoSuchBeanDefinitionException ex) {
return new OrderedHealthAggregator();
}
}
private static class ReactiveHealthIndicators {
public Map<String, HealthIndicator> get(ApplicationContext applicationContext) {
Map<String, HealthIndicator> indicators = new LinkedHashMap<>();
applicationContext.getBeansOfType(ReactiveHealthIndicator.class)
.forEach((name, indicator) -> indicators.put(name, adapt(indicator)));
return indicators;
}
private HealthIndicator adapt(ReactiveHealthIndicator indicator) {
return () -> indicator.health().block();
}
}
}
...@@ -34,8 +34,9 @@ import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; ...@@ -34,8 +34,9 @@ import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
import org.springframework.boot.actuate.endpoint.web.PathMapper; import org.springframework.boot.actuate.endpoint.web.PathMapper;
import org.springframework.boot.actuate.endpoint.web.WebOperation; import org.springframework.boot.actuate.endpoint.web.WebOperation;
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension; import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.HealthIndicatorRegistry;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
...@@ -109,7 +110,8 @@ public class CloudFoundryWebEndpointDiscovererTests { ...@@ -109,7 +110,8 @@ public class CloudFoundryWebEndpointDiscovererTests {
@Bean @Bean
public HealthEndpoint healthEndpoint() { public HealthEndpoint healthEndpoint() {
return new HealthEndpoint(mock(HealthIndicator.class)); return new HealthEndpoint(mock(HealthAggregator.class),
mock(HealthIndicatorRegistry.class));
} }
@Bean @Bean
......
...@@ -32,6 +32,7 @@ import reactor.ipc.netty.http.HttpResources; ...@@ -32,6 +32,7 @@ import reactor.ipc.netty.http.HttpResources;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
...@@ -87,6 +88,7 @@ public class ReactiveCloudFoundryActuatorAutoConfigurationTests { ...@@ -87,6 +88,7 @@ public class ReactiveCloudFoundryActuatorAutoConfigurationTests {
WebClientCustomizerConfig.class, WebClientAutoConfiguration.class, WebClientCustomizerConfig.class, WebClientAutoConfiguration.class,
ManagementContextAutoConfiguration.class, ManagementContextAutoConfiguration.class,
EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,
HealthIndicatorAutoConfiguration.class,
HealthEndpointAutoConfiguration.class, HealthEndpointAutoConfiguration.class,
ReactiveCloudFoundryActuatorAutoConfiguration.class)); ReactiveCloudFoundryActuatorAutoConfiguration.class));
......
...@@ -24,6 +24,7 @@ import org.junit.Test; ...@@ -24,6 +24,7 @@ import org.junit.Test;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
...@@ -270,7 +271,8 @@ public class CloudFoundryActuatorAutoConfigurationTests { ...@@ -270,7 +271,8 @@ public class CloudFoundryActuatorAutoConfigurationTests {
"vcap.application.application_id:my-app-id", "vcap.application.application_id:my-app-id",
"vcap.application.cf_api:http://my-cloud-controller.com") "vcap.application.cf_api:http://my-cloud-controller.com")
.withConfiguration( .withConfiguration(
AutoConfigurations.of(HealthEndpointAutoConfiguration.class)) AutoConfigurations.of(HealthIndicatorAutoConfiguration.class,
HealthEndpointAutoConfiguration.class))
.run((context) -> { .run((context) -> {
Collection<ExposableWebEndpoint> endpoints = context Collection<ExposableWebEndpoint> endpoints = context
.getBean("cloudFoundryWebEndpointServletHandlerMapping", .getBean("cloudFoundryWebEndpointServletHandlerMapping",
......
...@@ -23,9 +23,10 @@ import javax.sql.DataSource; ...@@ -23,9 +23,10 @@ import javax.sql.DataSource;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.actuate.health.CompositeHealthIndicator; import org.springframework.boot.actuate.health.DefaultHealthIndicatorRegistry;
import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.HealthIndicatorRegistry;
import org.springframework.boot.actuate.health.OrderedHealthAggregator; import org.springframework.boot.actuate.health.OrderedHealthAggregator;
import org.springframework.boot.actuate.jdbc.DataSourceHealthIndicator; import org.springframework.boot.actuate.jdbc.DataSourceHealthIndicator;
import org.springframework.boot.actuate.system.DiskSpaceHealthIndicator; import org.springframework.boot.actuate.system.DiskSpaceHealthIndicator;
...@@ -72,8 +73,9 @@ public class HealthEndpointDocumentationTests extends MockMvcEndpointDocumentati ...@@ -72,8 +73,9 @@ public class HealthEndpointDocumentationTests extends MockMvcEndpointDocumentati
@Bean @Bean
public HealthEndpoint endpoint(Map<String, HealthIndicator> healthIndicators) { public HealthEndpoint endpoint(Map<String, HealthIndicator> healthIndicators) {
return new HealthEndpoint(new CompositeHealthIndicator( HealthIndicatorRegistry registry = new DefaultHealthIndicatorRegistry();
new OrderedHealthAggregator(), healthIndicators)); healthIndicators.forEach(registry::register);
return new HealthEndpoint(new OrderedHealthAggregator(), registry);
} }
@Bean @Bean
......
...@@ -46,7 +46,8 @@ public class HealthEndpointAutoConfigurationTests { ...@@ -46,7 +46,8 @@ public class HealthEndpointAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration( .withConfiguration(
AutoConfigurations.of(HealthEndpointAutoConfiguration.class)); AutoConfigurations.of(HealthIndicatorAutoConfiguration.class,
HealthEndpointAutoConfiguration.class));
@Test @Test
public void healthEndpointShowDetailsDefault() { public void healthEndpointShowDetailsDefault() {
......
...@@ -28,6 +28,7 @@ import org.junit.Test; ...@@ -28,6 +28,7 @@ import org.junit.Test;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.jmx.JmxEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.jmx.JmxEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.trace.http.HttpTraceAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.trace.http.HttpTraceAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration; import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
...@@ -47,7 +48,7 @@ public class JmxEndpointIntegrationTests { ...@@ -47,7 +48,7 @@ public class JmxEndpointIntegrationTests {
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(JmxAutoConfiguration.class, .withConfiguration(AutoConfigurations.of(JmxAutoConfiguration.class,
EndpointAutoConfiguration.class, JmxEndpointAutoConfiguration.class, EndpointAutoConfiguration.class, JmxEndpointAutoConfiguration.class,
HttpTraceAutoConfiguration.class)) HttpTraceAutoConfiguration.class, HealthIndicatorAutoConfiguration.class))
.withConfiguration( .withConfiguration(
AutoConfigurations.of(EndpointAutoConfigurationClasses.ALL)); AutoConfigurations.of(EndpointAutoConfigurationClasses.ALL));
......
...@@ -28,6 +28,7 @@ import org.junit.Test; ...@@ -28,6 +28,7 @@ import org.junit.Test;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.trace.http.HttpTraceAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.trace.http.HttpTraceAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration;
...@@ -73,7 +74,8 @@ public class WebMvcEndpointExposureIntegrationTests { ...@@ -73,7 +74,8 @@ public class WebMvcEndpointExposureIntegrationTests {
ServletManagementContextAutoConfiguration.class, ServletManagementContextAutoConfiguration.class,
ManagementContextAutoConfiguration.class, ManagementContextAutoConfiguration.class,
ServletManagementContextAutoConfiguration.class, ServletManagementContextAutoConfiguration.class,
HttpTraceAutoConfiguration.class)) HttpTraceAutoConfiguration.class,
HealthIndicatorAutoConfiguration.class))
.withConfiguration( .withConfiguration(
AutoConfigurations.of(EndpointAutoConfigurationClasses.ALL)) AutoConfigurations.of(EndpointAutoConfigurationClasses.ALL))
.withUserConfiguration(CustomMvcEndpoint.class, .withUserConfiguration(CustomMvcEndpoint.class,
......
/*
* Copyright 2012-2018 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
*
* http://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.boot.actuate.health;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.util.Assert;
/**
* Default implementation of {@link HealthIndicatorRegistry}.
*
* @author Vedran Pavic
* @since 2.1.0
*/
public class DefaultHealthIndicatorRegistry implements HealthIndicatorRegistry {
private final Map<String, HealthIndicator> healthIndicators = new HashMap<>();
@Override
public void register(String name, HealthIndicator healthIndicator) {
Assert.notNull(healthIndicator, "HealthIndicator must not be null");
synchronized (this.healthIndicators) {
if (this.healthIndicators.get(name) != null) {
throw new IllegalStateException(
"HealthIndicator with name '" + name + "' already registered");
}
this.healthIndicators.put(name, healthIndicator);
}
}
@Override
public HealthIndicator unregister(String name) {
synchronized (this.healthIndicators) {
return this.healthIndicators.remove(name);
}
}
@Override
public HealthIndicator get(String name) {
synchronized (this.healthIndicators) {
return this.healthIndicators.get(name);
}
}
@Override
public Map<String, HealthIndicator> getAll() {
synchronized (this.healthIndicators) {
return Collections.unmodifiableMap(new HashMap<>(this.healthIndicators));
}
}
}
...@@ -26,25 +26,35 @@ import org.springframework.util.Assert; ...@@ -26,25 +26,35 @@ import org.springframework.util.Assert;
* @author Dave Syer * @author Dave Syer
* @author Christian Dupuis * @author Christian Dupuis
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Vedran Pavic
* @since 2.0.0 * @since 2.0.0
*/ */
@Endpoint(id = "health") @Endpoint(id = "health")
public class HealthEndpoint { public class HealthEndpoint {
private final HealthIndicator healthIndicator; private final HealthAggregator healthAggregator;
private final HealthIndicatorRegistry healthIndicatorRegistry;
/** /**
* Create a new {@link HealthEndpoint} instance. * Create a new {@link HealthEndpoint} instance.
* @param healthIndicator the health indicator * @param healthAggregator the health aggregator
* @param healthIndicatorRegistry the health indicator registry
*/ */
public HealthEndpoint(HealthIndicator healthIndicator) { public HealthEndpoint(HealthAggregator healthAggregator,
Assert.notNull(healthIndicator, "HealthIndicator must not be null"); HealthIndicatorRegistry healthIndicatorRegistry) {
this.healthIndicator = healthIndicator; Assert.notNull(healthAggregator, "healthAggregator must not be null");
Assert.notNull(healthIndicatorRegistry, "healthIndicatorRegistry must not be null");
this.healthAggregator = healthAggregator;
this.healthIndicatorRegistry = healthIndicatorRegistry;
} }
@ReadOperation @ReadOperation
public Health health() { public Health health() {
return this.healthIndicator.health(); CompositeHealthIndicatorFactory factory = new CompositeHealthIndicatorFactory();
CompositeHealthIndicator healthIndicator = factory.createHealthIndicator(
this.healthAggregator, this.healthIndicatorRegistry.getAll());
return healthIndicator.health();
} }
} }
...@@ -35,11 +35,11 @@ import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExten ...@@ -35,11 +35,11 @@ import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExten
@EndpointWebExtension(endpoint = HealthEndpoint.class) @EndpointWebExtension(endpoint = HealthEndpoint.class)
public class HealthEndpointWebExtension { public class HealthEndpointWebExtension {
private final HealthIndicator delegate; private final HealthEndpoint delegate;
private final HealthWebEndpointResponseMapper responseMapper; private final HealthWebEndpointResponseMapper responseMapper;
public HealthEndpointWebExtension(HealthIndicator delegate, public HealthEndpointWebExtension(HealthEndpoint delegate,
HealthWebEndpointResponseMapper responseMapper) { HealthWebEndpointResponseMapper responseMapper) {
this.delegate = delegate; this.delegate = delegate;
this.responseMapper = responseMapper; this.responseMapper = responseMapper;
......
/*
* Copyright 2012-2018 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
*
* http://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.boot.actuate.health;
import java.util.Map;
/**
* A registry of {@link HealthIndicator}s.
* <p>
* Implementations <strong>must</strong> be thread-safe.
*
* @author Andy Wilkinson
* @author Vedran Pavic
* @since 2.1.0
*/
public interface HealthIndicatorRegistry {
/**
* Registers the given {@code healthIndicator}, associating it with the given
* {@code name}.
* @param name the name of the indicator
* @param healthIndicator the indicator
* @throws IllegalStateException if an indicator with the given {@code name} is
* already registered.
*/
void register(String name, HealthIndicator healthIndicator);
/**
* Unregisters the {@code HealthIndicator} previously registered with the given
* {@code name}.
* @param name the name of the indicator
* @return the unregistered indicator, or {@code null} if no indicator was found in
* the registry for the given {@code name}.
*/
HealthIndicator unregister(String name);
/**
* Returns the health indicator registered with the given {@code name}.
* @param name the name of the indicator
* @return the health indicator, or {@code null} if no indicator was registered with
* the given {@code name}.
*/
HealthIndicator get(String name);
/**
* Returns a snapshot of the registered health indicators and their names. The
* contents of the map do not reflect subsequent changes to the registry.
* @return the snapshot of registered health indicators
*/
Map<String, HealthIndicator> getAll();
}
/*
* Copyright 2012-2018 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
*
* http://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.boot.actuate.health;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link DefaultHealthIndicatorRegistry}.
*
* @author Vedran Pavic
*/
public class DefaultHealthIndicatorRegistryTest {
@Rule
public ExpectedException thrown = ExpectedException.none();
private HealthIndicator one = mock(HealthIndicator.class);
private HealthIndicator two = mock(HealthIndicator.class);
private DefaultHealthIndicatorRegistry registry;
@Before
public void setUp() {
given(this.one.health()).willReturn(new Health.Builder().up().build());
given(this.two.health()).willReturn(new Health.Builder().unknown().build());
this.registry = new DefaultHealthIndicatorRegistry();
}
@Test
public void register() {
this.registry.register("one", this.one);
this.registry.register("two", this.two);
assertThat(this.registry.getAll()).hasSize(2);
assertThat(this.registry.get("one")).isSameAs(this.one);
assertThat(this.registry.get("two")).isSameAs(this.two);
}
@Test
public void registerAlreadyUsedName() {
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("HealthIndicator with name 'one' already registered");
this.registry.register("one", this.one);
this.registry.register("one", this.two);
}
@Test
public void unregister() {
this.registry.register("one", this.one);
this.registry.register("two", this.two);
assertThat(this.registry.getAll()).hasSize(2);
HealthIndicator two = this.registry.unregister("two");
assertThat(two).isSameAs(this.two);
assertThat(this.registry.getAll()).hasSize(1);
}
@Test
public void unregisterNotKnown() {
this.registry.register("one", this.one);
assertThat(this.registry.getAll()).hasSize(1);
HealthIndicator two = this.registry.unregister("two");
assertThat(two).isNull();
assertThat(this.registry.getAll()).hasSize(1);
}
}
/* /*
* Copyright 2012-2017 the original author or authors. * Copyright 2012-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -30,6 +30,7 @@ import static org.assertj.core.api.Assertions.entry; ...@@ -30,6 +30,7 @@ import static org.assertj.core.api.Assertions.entry;
* @author Phillip Webb * @author Phillip Webb
* @author Christian Dupuis * @author Christian Dupuis
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Vedran Pavic
*/ */
public class HealthEndpointTests { public class HealthEndpointTests {
...@@ -40,8 +41,8 @@ public class HealthEndpointTests { ...@@ -40,8 +41,8 @@ public class HealthEndpointTests {
.withDetail("first", "1").build()); .withDetail("first", "1").build());
healthIndicators.put("upAgain", () -> new Health.Builder().status(Status.UP) healthIndicators.put("upAgain", () -> new Health.Builder().status(Status.UP)
.withDetail("second", "2").build()); .withDetail("second", "2").build());
HealthEndpoint endpoint = new HealthEndpoint( HealthEndpoint endpoint = new HealthEndpoint(new OrderedHealthAggregator(),
createHealthIndicator(healthIndicators)); createHealthIndicatorRegistry(healthIndicators));
Health health = endpoint.health(); Health health = endpoint.health();
assertThat(health.getStatus()).isEqualTo(Status.UP); assertThat(health.getStatus()).isEqualTo(Status.UP);
assertThat(health.getDetails()).containsOnlyKeys("up", "upAgain"); assertThat(health.getDetails()).containsOnlyKeys("up", "upAgain");
...@@ -51,10 +52,11 @@ public class HealthEndpointTests { ...@@ -51,10 +52,11 @@ public class HealthEndpointTests {
assertThat(upAgainHealth.getDetails()).containsOnly(entry("second", "2")); assertThat(upAgainHealth.getDetails()).containsOnly(entry("second", "2"));
} }
private HealthIndicator createHealthIndicator( private HealthIndicatorRegistry createHealthIndicatorRegistry(
Map<String, HealthIndicator> healthIndicators) { Map<String, HealthIndicator> healthIndicators) {
return new CompositeHealthIndicatorFactory() HealthIndicatorRegistry registry = new DefaultHealthIndicatorRegistry();
.createHealthIndicator(new OrderedHealthAggregator(), healthIndicators); healthIndicators.forEach(registry::register);
return registry;
} }
} }
...@@ -35,6 +35,7 @@ import org.springframework.test.web.reactive.server.WebTestClient; ...@@ -35,6 +35,7 @@ import org.springframework.test.web.reactive.server.WebTestClient;
* exposed by Jersey, Spring MVC, and WebFlux. * exposed by Jersey, Spring MVC, and WebFlux.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Vedran Pavic
*/ */
@RunWith(WebEndpointRunners.class) @RunWith(WebEndpointRunners.class)
public class HealthEndpointWebIntegrationTests { public class HealthEndpointWebIntegrationTests {
...@@ -66,17 +67,15 @@ public class HealthEndpointWebIntegrationTests { ...@@ -66,17 +67,15 @@ public class HealthEndpointWebIntegrationTests {
@Bean @Bean
public HealthEndpoint healthEndpoint( public HealthEndpoint healthEndpoint(
Map<String, HealthIndicator> healthIndicators) { Map<String, HealthIndicator> healthIndicators) {
return new HealthEndpoint( HealthIndicatorRegistry registry = new DefaultHealthIndicatorRegistry();
new CompositeHealthIndicatorFactory().createHealthIndicator( healthIndicators.forEach(registry::register);
new OrderedHealthAggregator(), healthIndicators)); return new HealthEndpoint(new OrderedHealthAggregator(), registry);
} }
@Bean @Bean
public HealthEndpointWebExtension healthWebEndpointExtension( public HealthEndpointWebExtension healthWebEndpointExtension(
Map<String, HealthIndicator> healthIndicators) { HealthEndpoint healthEndpoint) {
return new HealthEndpointWebExtension( return new HealthEndpointWebExtension(healthEndpoint,
new CompositeHealthIndicatorFactory().createHealthIndicator(
new OrderedHealthAggregator(), healthIndicators),
new HealthWebEndpointResponseMapper(new HealthStatusHttpMapper(), new HealthWebEndpointResponseMapper(new HealthStatusHttpMapper(),
ShowDetails.ALWAYS, ShowDetails.ALWAYS,
new HashSet<>(Arrays.asList("ACTUATOR")))); new HashSet<>(Arrays.asList("ACTUATOR"))));
......
...@@ -726,13 +726,14 @@ configuration must permit access to the health endpoint for both authenticated a ...@@ -726,13 +726,14 @@ configuration must permit access to the health endpoint for both authenticated a
unauthenticated users. unauthenticated users.
Health information is collected from all Health information is collected from all
{sc-spring-boot-actuator}/health/HealthIndicator.{sc-ext}[`HealthIndicator`] beans {sc-spring-boot-actuator}/health/HealthIndicator.{sc-ext}[`HealthIndicator`] instances
defined in your `ApplicationContext`. Spring Boot includes a number of auto-configured registered with {sc-spring-boot-actuator}/health/HealthIndicatorRegistry.{sc-ext}[`HealthIndicatorRegistry`].
`HealthIndicators`, and you can also write your own. By default, the final system state Spring Boot includes a number of auto-configured `HealthIndicators` and you can also write
is derived by the `HealthAggregator`, which sorts the statuses from each your own. By default, the final system state is
`HealthIndicator` based on an ordered list of statuses. The first status in the sorted derived by the `HealthAggregator` which sorts the statuses from each `HealthIndicator`
list is used as the overall health status. If no `HealthIndicator` returns a status that based on an ordered list of statuses. The first status in the sorted list is used as the
is known to the `HealthAggregator`, an `UNKNOWN` status is used. overall health status. If no `HealthIndicator` returns a status that is known to the
`HealthAggregator`, an `UNKNOWN` status is used.
...@@ -818,6 +819,9 @@ NOTE: The identifier for a given `HealthIndicator` is the name of the bean witho ...@@ -818,6 +819,9 @@ NOTE: The identifier for a given `HealthIndicator` is the name of the bean witho
`HealthIndicator` suffix, if it exists. In the preceding example, the health information `HealthIndicator` suffix, if it exists. In the preceding example, the health information
is available in an entry named `my`. is available in an entry named `my`.
Additionally, you can register (and unregister) `HealthIndicator` instances in runtime
using {sc-spring-boot-actuator}/health/HealthIndicatorRegistry.{sc-ext}[`HealthIndicatorRegistry`].
In addition to Spring Boot's predefined In addition to Spring Boot's predefined
{sc-spring-boot-actuator}/health/Status.{sc-ext}[`Status`] types, it is also possible for {sc-spring-boot-actuator}/health/Status.{sc-ext}[`Status`] types, it is also possible for
`Health` to return a custom `Status` that represents a new system state. In such cases, a `Health` to return a custom `Status` that represents a new system state. In such cases, a
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment