diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfiguration.java index c6885e11d4..62c4b0af56 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/HealthIndicatorAutoConfiguration.java @@ -30,6 +30,8 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.actuate.health.ApplicationHealthIndicator; import org.springframework.boot.actuate.health.CompositeHealthIndicator; import org.springframework.boot.actuate.health.DataSourceHealthIndicator; +import org.springframework.boot.actuate.health.DiskSpaceHealthIndicator; +import org.springframework.boot.actuate.health.DiskSpaceHealthIndicatorProperties; import org.springframework.boot.actuate.health.HealthAggregator; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.MongoHealthIndicator; @@ -52,6 +54,7 @@ import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; import org.springframework.boot.autoconfigure.mongo.MongoDataAutoConfiguration; import org.springframework.boot.autoconfigure.redis.RedisAutoConfiguration; import org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration; +import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.mongodb.core.MongoTemplate; @@ -256,4 +259,22 @@ public class HealthIndicatorAutoConfiguration { } } + @Configuration + @ConditionalOnExpression("${health.diskspace.enabled:true}") + public static class DiskSpaceHealthIndicatorConfiguration { + + @Bean + @ConditionalOnMissingBean(name = "diskSpaceHealthIndicator") + public HealthIndicator diskSpaceHealthIndicator( + DiskSpaceHealthIndicatorProperties properties) { + return new DiskSpaceHealthIndicator(properties); + } + + @Bean + @ConfigurationProperties("health.diskspace") + public DiskSpaceHealthIndicatorProperties diskSpaceHealthIndicatorProperties() { + return new DiskSpaceHealthIndicatorProperties(); + } + } + } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DiskSpaceHealthIndicator.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DiskSpaceHealthIndicator.java new file mode 100644 index 0000000000..37c129e359 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DiskSpaceHealthIndicator.java @@ -0,0 +1,60 @@ +/* + * Copyright 2014 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.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * A {@link HealthIndicator} that checks available disk space and reports a status of + * {@link Status#DOWN} when it drops below a configurable threshold. + * + * @author Mattias Severson + * @author Andy Wilkinson + * @since 1.2.0 + */ +public class DiskSpaceHealthIndicator extends AbstractHealthIndicator { + + private static Log logger = LogFactory.getLog(DiskSpaceHealthIndicator.class); + + private final DiskSpaceHealthIndicatorProperties properties; + + /** + * Create a new {@code DiskSpaceHealthIndicator} + */ + @Autowired + public DiskSpaceHealthIndicator(DiskSpaceHealthIndicatorProperties properties) { + this.properties = properties; + } + + @Override + protected void doHealthCheck(Health.Builder builder) throws Exception { + long diskFreeInBytes = this.properties.getPath().getFreeSpace(); + if (diskFreeInBytes >= this.properties.getThreshold()) { + builder.up(); + } + else { + logger.warn(String.format("Free disk space below threshold. " + + "Available: %d bytes (threshold: %d bytes)", diskFreeInBytes, + this.properties.getThreshold())); + builder.down(); + } + builder.withDetail("free", diskFreeInBytes).withDetail("threshold", + this.properties.getThreshold()); + } +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DiskSpaceHealthIndicatorProperties.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DiskSpaceHealthIndicatorProperties.java new file mode 100644 index 0000000000..2881eb55ff --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DiskSpaceHealthIndicatorProperties.java @@ -0,0 +1,60 @@ +/* + * Copyright 2012-2014 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.io.File; + +import org.springframework.util.Assert; + +/** + * External configuration properties for {@link DiskSpaceHealthIndicator} + * + * @author Andy Wilkinson + * @since 1.2.0 + */ +public class DiskSpaceHealthIndicatorProperties { + + private File path = new File("."); + + private long threshold = 10 * 1024 * 1024; + + public File getPath() { + return this.path; + } + + public void setPath(File path) { + if (!path.exists()) { + throw new IllegalArgumentException(String.format("Path '%s' does not exist", + path)); + } + if (!path.canRead()) { + throw new IllegalStateException(String.format("Path '%s' cannot be read", + path)); + } + this.path = path; + } + + public long getThreshold() { + + return this.threshold; + } + + public void setThreshold(long threshold) { + Assert.isTrue(threshold >= 0, "threshold must be greater than 0"); + this.threshold = threshold; + } +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DiskSpaceHealthIndicatorTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DiskSpaceHealthIndicatorTests.java new file mode 100644 index 0000000000..3797884f97 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DiskSpaceHealthIndicatorTests.java @@ -0,0 +1,84 @@ +/* + * Copyright 2014 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.io.File; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +/** + * Tests for {@link DiskSpaceHealthIndicator}. + * + * @author Mattias Severson + */ +@RunWith(MockitoJUnitRunner.class) +public class DiskSpaceHealthIndicatorTests { + + static final long THRESHOLD_BYTES = 1024; + + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Mock + File fileMock; + + HealthIndicator healthIndicator; + + @Before + public void setUp() throws Exception { + when(this.fileMock.exists()).thenReturn(true); + when(this.fileMock.canRead()).thenReturn(true); + this.healthIndicator = new DiskSpaceHealthIndicator(createProperties( + this.fileMock, THRESHOLD_BYTES)); + } + + @Test + public void diskSpaceIsUp() throws Exception { + when(this.fileMock.getFreeSpace()).thenReturn(THRESHOLD_BYTES + 10); + + Health health = this.healthIndicator.health(); + assertEquals(Status.UP, health.getStatus()); + assertEquals(THRESHOLD_BYTES, health.getDetails().get("threshold")); + assertEquals(THRESHOLD_BYTES + 10, health.getDetails().get("free")); + } + + @Test + public void diskSpaceIsDown() throws Exception { + when(this.fileMock.getFreeSpace()).thenReturn(THRESHOLD_BYTES - 10); + + Health health = this.healthIndicator.health(); + assertEquals(Status.DOWN, health.getStatus()); + assertEquals(THRESHOLD_BYTES, health.getDetails().get("threshold")); + assertEquals(THRESHOLD_BYTES - 10, health.getDetails().get("free")); + } + + private DiskSpaceHealthIndicatorProperties createProperties(File path, long threshold) { + DiskSpaceHealthIndicatorProperties properties = new DiskSpaceHealthIndicatorProperties(); + properties.setPath(path); + properties.setThreshold(threshold); + return properties; + } +} \ No newline at end of file diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 0bc598304c..f3789bfe6c 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -387,6 +387,10 @@ content into your application; rather pick only the properties that you need. endpoints.trace.sensitive=true endpoints.trace.enabled=true + # HEALTH INDICATORS + health.diskspace.path=. + health.diskspace.threshold=10485760 + # MVC ONLY ENDPOINTS endpoints.jolokia.path=jolokia endpoints.jolokia.sensitive=true diff --git a/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc b/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc index 0994f1b8a4..179d3b1816 100644 --- a/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc @@ -165,10 +165,10 @@ To provide custom health information you can register a Spring bean that impleme Spring Boot provides a {sc-spring-boot-actuator}/health/DataSourceHealthIndicator.{sc-ext}[`DataSourceHealthIndicator`] implementation that attempts a simple database test (reusing the validation query set on the data -source, if any) as well as implementations for Redis, MongoDB and RabbitMQ. - -Spring Boot adds the `HealthIndicator` instances automatically if beans of type `DataSource`, -`MongoTemplate`, `RedisConnectionFactory`, `RabbitTemplate` are present in the `ApplicationContext`. +source, if any) as well as implementations for Redis, MongoDB and RabbitMQ. Spring Boot adds the +`HealthIndicator` instances automatically if beans of type `DataSource`, `MongoTemplate`, +`RedisConnectionFactory`, and `RabbitTemplate` respectively are present in the +`ApplicationContext`. A health indicator that checks free disk space is also provided. Besides implementing custom a `HealthIndicator` type and using out-of-box {sc-spring-boot-actuator}/health/Status.{sc-ext}[`Status`] types, it is also possible to introduce custom `Status` types for different or more complex system