Skip to content

Commit 9f8a262

Browse files
committed
Log a warning when a health indicator takes too long to run
Update `HealthEndpointSupport` so that it logs a warning if a health indicator takes too long to respond. Fixes gh-31231
1 parent 2094722 commit 9f8a262

File tree

16 files changed

+192
-42
lines changed

16 files changed

+192
-42
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointConfiguration.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -88,8 +88,9 @@ HealthContributorRegistry healthContributorRegistry(ApplicationContext applicati
8888

8989
@Bean
9090
@ConditionalOnMissingBean
91-
HealthEndpoint healthEndpoint(HealthContributorRegistry registry, HealthEndpointGroups groups) {
92-
return new HealthEndpoint(registry, groups);
91+
HealthEndpoint healthEndpoint(HealthContributorRegistry registry, HealthEndpointGroups groups,
92+
HealthEndpointProperties properties) {
93+
return new HealthEndpoint(registry, groups, properties.getLogging().getSlowIndicatorThreshold());
9394
}
9495

9596
@Bean

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointProperties.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.health;
1818

19+
import java.time.Duration;
1920
import java.util.LinkedHashMap;
2021
import java.util.Map;
2122
import java.util.Set;
@@ -43,6 +44,8 @@ public class HealthEndpointProperties extends HealthProperties {
4344
*/
4445
private Map<String, Group> group = new LinkedHashMap<>();
4546

47+
private Logging logging = new Logging();
48+
4649
@Override
4750
public Show getShowDetails() {
4851
return this.showDetails;
@@ -56,6 +59,10 @@ public Map<String, Group> getGroup() {
5659
return this.group;
5760
}
5861

62+
public Logging getLogging() {
63+
return this.logging;
64+
}
65+
5966
/**
6067
* A health endpoint group.
6168
*/
@@ -124,4 +131,24 @@ public void setAdditionalPath(String additionalPath) {
124131

125132
}
126133

134+
/**
135+
* Health logging properties.
136+
*/
137+
public static class Logging {
138+
139+
/**
140+
* Threshold after which a warning will be logged for slow health indicators.
141+
*/
142+
Duration slowIndicatorThreshold = Duration.ofSeconds(10);
143+
144+
public Duration getSlowIndicatorThreshold() {
145+
return this.slowIndicatorThreshold;
146+
}
147+
148+
public void setSlowIndicatorThreshold(Duration slowIndicatorThreshold) {
149+
this.slowIndicatorThreshold = slowIndicatorThreshold;
150+
}
151+
152+
}
153+
127154
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointReactiveWebExtensionConfiguration.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,10 @@ class HealthEndpointReactiveWebExtensionConfiguration {
5353
@ConditionalOnMissingBean
5454
@ConditionalOnBean(HealthEndpoint.class)
5555
ReactiveHealthEndpointWebExtension reactiveHealthEndpointWebExtension(
56-
ReactiveHealthContributorRegistry reactiveHealthContributorRegistry, HealthEndpointGroups groups) {
57-
return new ReactiveHealthEndpointWebExtension(reactiveHealthContributorRegistry, groups);
56+
ReactiveHealthContributorRegistry reactiveHealthContributorRegistry, HealthEndpointGroups groups,
57+
HealthEndpointProperties properties) {
58+
return new ReactiveHealthEndpointWebExtension(reactiveHealthContributorRegistry, groups,
59+
properties.getLogging().getSlowIndicatorThreshold());
5860
}
5961

6062
@Configuration(proxyBeanMethods = false)

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointWebExtensionConfiguration.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,9 @@ class HealthEndpointWebExtensionConfiguration {
7272
@Bean
7373
@ConditionalOnMissingBean
7474
HealthEndpointWebExtension healthEndpointWebExtension(HealthContributorRegistry healthContributorRegistry,
75-
HealthEndpointGroups groups) {
76-
return new HealthEndpointWebExtension(healthContributorRegistry, groups);
75+
HealthEndpointGroups groups, HealthEndpointProperties properties) {
76+
return new HealthEndpointWebExtension(healthContributorRegistry, groups,
77+
properties.getLogging().getSlowIndicatorThreshold());
7778
}
7879

7980
private static ExposableWebEndpoint getHealthEndpoint(WebEndpointsSupplier webEndpointsSupplier) {

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscovererTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -113,7 +113,7 @@ TestEndpointWebExtension testEndpointWebExtension() {
113113
HealthEndpoint healthEndpoint() {
114114
HealthContributorRegistry registry = mock(HealthContributorRegistry.class);
115115
HealthEndpointGroups groups = mock(HealthEndpointGroups.class);
116-
return new HealthEndpoint(registry, groups);
116+
return new HealthEndpoint(registry, groups, null);
117117
}
118118

119119
@Bean

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HealthEndpointDocumentationTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -111,7 +111,7 @@ HealthEndpoint healthEndpoint(Map<String, HealthContributor> healthContributors)
111111
HealthContributorRegistry registry = new DefaultHealthContributorRegistry(healthContributors);
112112
HealthEndpointGroup primary = new TestHealthEndpointGroup();
113113
HealthEndpointGroups groups = HealthEndpointGroups.of(primary, Collections.emptyMap());
114-
return new HealthEndpoint(registry, groups);
114+
return new HealthEndpoint(registry, groups, null);
115115
}
116116

117117
@Bean

spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpoint.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.boot.actuate.health;
1818

19+
import java.time.Duration;
1920
import java.util.Map;
2021
import java.util.Set;
2122

@@ -51,9 +52,24 @@ public class HealthEndpoint extends HealthEndpointSupport<HealthContributor, Hea
5152
* Create a new {@link HealthEndpoint} instance.
5253
* @param registry the health contributor registry
5354
* @param groups the health endpoint groups
55+
* @deprecated since 2.6.9 for removal in 3.0.0 in favor of
56+
* {@link #HealthEndpoint(HealthContributorRegistry, HealthEndpointGroups, Duration)}
5457
*/
58+
@Deprecated
5559
public HealthEndpoint(HealthContributorRegistry registry, HealthEndpointGroups groups) {
56-
super(registry, groups);
60+
super(registry, groups, null);
61+
}
62+
63+
/**
64+
* Create a new {@link HealthEndpoint} instance.
65+
* @param registry the health contributor registry
66+
* @param groups the health endpoint groups
67+
* @param slowIndicatorLoggingThreshold duration after which slow health indicator
68+
* logging should occur
69+
*/
70+
public HealthEndpoint(HealthContributorRegistry registry, HealthEndpointGroups groups,
71+
Duration slowIndicatorLoggingThreshold) {
72+
super(registry, groups, slowIndicatorLoggingThreshold);
5773
}
5874

5975
@ReadOperation

spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointSupport.java

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,14 +16,21 @@
1616

1717
package org.springframework.boot.actuate.health;
1818

19+
import java.time.Duration;
20+
import java.time.Instant;
1921
import java.util.LinkedHashMap;
2022
import java.util.Map;
2123
import java.util.Set;
2224
import java.util.stream.Collectors;
2325

26+
import org.apache.commons.logging.Log;
27+
import org.apache.commons.logging.LogFactory;
28+
2429
import org.springframework.boot.actuate.endpoint.ApiVersion;
2530
import org.springframework.boot.actuate.endpoint.SecurityContext;
2631
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
32+
import org.springframework.boot.convert.DurationStyle;
33+
import org.springframework.core.log.LogMessage;
2734
import org.springframework.util.Assert;
2835
import org.springframework.util.StringUtils;
2936

@@ -37,22 +44,30 @@
3744
*/
3845
abstract class HealthEndpointSupport<C, T> {
3946

47+
private static final Log logger = LogFactory.getLog(HealthEndpointSupport.class);
48+
4049
static final Health DEFAULT_HEALTH = Health.up().build();
4150

4251
private final ContributorRegistry<C> registry;
4352

4453
private final HealthEndpointGroups groups;
4554

55+
private Duration slowIndicatorLoggingThreshold;
56+
4657
/**
4758
* Create a new {@link HealthEndpointSupport} instance.
4859
* @param registry the health contributor registry
4960
* @param groups the health endpoint groups
61+
* @param slowIndicatorLoggingThreshold duration after which slow health indicator
62+
* logging should occur
5063
*/
51-
HealthEndpointSupport(ContributorRegistry<C> registry, HealthEndpointGroups groups) {
64+
HealthEndpointSupport(ContributorRegistry<C> registry, HealthEndpointGroups groups,
65+
Duration slowIndicatorLoggingThreshold) {
5266
Assert.notNull(registry, "Registry must not be null");
5367
Assert.notNull(groups, "Groups must not be null");
5468
this.registry = registry;
5569
this.groups = groups;
70+
this.slowIndicatorLoggingThreshold = slowIndicatorLoggingThreshold;
5671
}
5772

5873
HealthResult<T> getHealth(ApiVersion apiVersion, WebServerNamespace serverNamespace,
@@ -127,7 +142,7 @@ private T getContribution(ApiVersion apiVersion, HealthEndpointGroup group, Stri
127142
showDetails, groupNames);
128143
}
129144
if (contributor != null && (name.isEmpty() || group.isMember(name))) {
130-
return getHealth((C) contributor, showDetails);
145+
return getLoggedHealth((C) contributor, name, showDetails);
131146
}
132147
return null;
133148
}
@@ -151,6 +166,25 @@ private T getAggregateContribution(ApiVersion apiVersion, HealthEndpointGroup gr
151166
groupNames);
152167
}
153168

169+
private T getLoggedHealth(C contributor, String name, boolean showDetails) {
170+
Instant start = Instant.now();
171+
try {
172+
return getHealth(contributor, showDetails);
173+
}
174+
finally {
175+
if (logger.isWarnEnabled() && this.slowIndicatorLoggingThreshold != null) {
176+
Duration duration = Duration.between(start, Instant.now());
177+
if (duration.compareTo(this.slowIndicatorLoggingThreshold) > 0) {
178+
String contributorClassName = contributor.getClass().getName();
179+
Object contributorIdentifier = (!StringUtils.hasLength(name)) ? contributorClassName
180+
: contributor.getClass().getName() + " (" + name + ")";
181+
logger.warn(LogMessage.format("Health contributor %s took %s to respond", contributorIdentifier,
182+
DurationStyle.SIMPLE.print(duration)));
183+
}
184+
}
185+
}
186+
}
187+
154188
protected abstract T getHealth(C contributor, boolean includeDetails);
155189

156190
protected abstract T aggregateContributions(ApiVersion apiVersion, Map<String, T> contributions,

spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointWebExtension.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.boot.actuate.health;
1818

19+
import java.time.Duration;
1920
import java.util.Arrays;
2021
import java.util.Map;
2122
import java.util.Set;
@@ -51,9 +52,24 @@ public class HealthEndpointWebExtension extends HealthEndpointSupport<HealthCont
5152
* Create a new {@link HealthEndpointWebExtension} instance.
5253
* @param registry the health contributor registry
5354
* @param groups the health endpoint groups
55+
* @deprecated since 2.6.9 for removal in 3.0.0 in favor of
56+
* {@link #HealthEndpointWebExtension(HealthContributorRegistry, HealthEndpointGroups, Duration)}
5457
*/
58+
@Deprecated
5559
public HealthEndpointWebExtension(HealthContributorRegistry registry, HealthEndpointGroups groups) {
56-
super(registry, groups);
60+
super(registry, groups, null);
61+
}
62+
63+
/**
64+
* Create a new {@link HealthEndpointWebExtension} instance.
65+
* @param registry the health contributor registry
66+
* @param groups the health endpoint groups
67+
* @param slowIndicatorLoggingThreshold duration after which slow health indicator
68+
* logging should occur
69+
*/
70+
public HealthEndpointWebExtension(HealthContributorRegistry registry, HealthEndpointGroups groups,
71+
Duration slowIndicatorLoggingThreshold) {
72+
super(registry, groups, slowIndicatorLoggingThreshold);
5773
}
5874

5975
@ReadOperation

spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthEndpointWebExtension.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.boot.actuate.health;
1818

19+
import java.time.Duration;
1920
import java.util.Arrays;
2021
import java.util.Map;
2122
import java.util.Set;
@@ -51,9 +52,24 @@ public class ReactiveHealthEndpointWebExtension
5152
* Create a new {@link ReactiveHealthEndpointWebExtension} instance.
5253
* @param registry the health contributor registry
5354
* @param groups the health endpoint groups
55+
* @deprecated since 2.6.9 for removal in 3.0.0 in favor of
56+
* {@link #ReactiveHealthEndpointWebExtension(ReactiveHealthContributorRegistry, HealthEndpointGroups, Duration)}
5457
*/
58+
@Deprecated
5559
public ReactiveHealthEndpointWebExtension(ReactiveHealthContributorRegistry registry, HealthEndpointGroups groups) {
56-
super(registry, groups);
60+
super(registry, groups, null);
61+
}
62+
63+
/**
64+
* Create a new {@link ReactiveHealthEndpointWebExtension} instance.
65+
* @param registry the health contributor registry
66+
* @param groups the health endpoint groups
67+
* @param slowIndicatorLoggingThreshold duration after which slow health indicator
68+
* logging should occur
69+
*/
70+
public ReactiveHealthEndpointWebExtension(ReactiveHealthContributorRegistry registry, HealthEndpointGroups groups,
71+
Duration slowIndicatorLoggingThreshold) {
72+
super(registry, groups, slowIndicatorLoggingThreshold);
5773
}
5874

5975
@ReadOperation

spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointSupportTests.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.boot.actuate.health;
1818

19+
import java.time.Duration;
1920
import java.util.Collections;
2021
import java.util.LinkedHashMap;
2122
import java.util.Map;
@@ -34,13 +35,14 @@
3435
/**
3536
* Base class for {@link HealthEndpointSupport} tests.
3637
*
38+
* @param <S> the support type
3739
* @param <R> the registry type
3840
* @param <C> the contributor type
3941
* @param <T> the contributed health component type
4042
* @author Phillip Webb
4143
* @author Madhura Bhave
4244
*/
43-
abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T> {
45+
abstract class HealthEndpointSupportTests<S extends HealthEndpointSupport<C, T>, R extends ContributorRegistry<C>, C, T> {
4446

4547
final R registry;
4648

@@ -352,7 +354,11 @@ void getComponentHealthWhenGroupHasAdditionalPathAndShowComponentsFalse() {
352354
assertThat(result).isEqualTo(null);
353355
}
354356

355-
protected abstract HealthEndpointSupport<C, T> create(R registry, HealthEndpointGroups groups);
357+
protected final S create(R registry, HealthEndpointGroups groups) {
358+
return create(registry, groups, null);
359+
}
360+
361+
protected abstract S create(R registry, HealthEndpointGroups groups, Duration slowIndicatorLoggingThreshold);
356362

357363
protected abstract R createRegistry();
358364

0 commit comments

Comments
 (0)