Skip to content

Commit 9de9f11

Browse files
committed
Improving error-handling and logging for endpoint probing
1 parent 452b618 commit 9de9f11

File tree

2 files changed

+81
-44
lines changed

2 files changed

+81
-44
lines changed

spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/server/services/endpoints/ProbeEndpointsStrategy.java

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2018 the original author or authors.
2+
* Copyright 2014-2019 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.
@@ -19,6 +19,7 @@
1919
import de.codecentric.boot.admin.server.domain.entities.Instance;
2020
import de.codecentric.boot.admin.server.domain.values.Endpoint;
2121
import de.codecentric.boot.admin.server.domain.values.Endpoints;
22+
import de.codecentric.boot.admin.server.domain.values.InstanceId;
2223
import de.codecentric.boot.admin.server.web.client.InstanceWebClient;
2324
import lombok.Data;
2425
import reactor.core.publisher.Flux;
@@ -53,36 +54,65 @@ public ProbeEndpointsStrategy(InstanceWebClient instanceWebClient, String[] endp
5354

5455
@Override
5556
public Mono<Endpoints> detectEndpoints(Instance instance) {
56-
return Flux.fromIterable(endpoints)
57+
if (instance.getRegistration().getManagementUrl() == null) {
58+
log.debug("Endpoint probe for instance {} omitted. No management-url registered.", instance.getId());
59+
return Mono.empty();
60+
}
61+
62+
return Flux.fromIterable(this.endpoints)
5763
.flatMap(endpoint -> detectEndpoint(instance, endpoint))
5864
.collectList()
5965
.flatMap(this::convert);
6066
}
6167

6268
private Mono<DetectedEndpoint> detectEndpoint(Instance instance, EndpointDefinition endpoint) {
63-
String managementUrl = instance.getRegistration().getManagementUrl();
64-
if (managementUrl == null) {
65-
return Mono.empty();
66-
}
67-
URI uri = UriComponentsBuilder.fromUriString(managementUrl)
69+
URI uri = UriComponentsBuilder.fromUriString(instance.getRegistration().getManagementUrl())
6870
.path("/")
6971
.path(endpoint.getPath())
7072
.build()
7173
.toUri();
72-
return instanceWebClient.instance(instance).options().uri(uri).exchange().flatMap(this.convert(endpoint, uri));
74+
return this.instanceWebClient.instance(instance)
75+
.options()
76+
.uri(uri)
77+
.exchange()
78+
.flatMap(this.convert(instance.getId(), endpoint, uri))
79+
.onErrorResume(e -> {
80+
log.warn(
81+
"Endpoint probe for instance {} on endpoint '{}' failed: {}",
82+
instance.getId(),
83+
uri,
84+
e.getMessage()
85+
);
86+
log.debug(
87+
"Endpoint probe for instance {} on endpoint '{}' failed.",
88+
instance.getId(),
89+
uri,
90+
e
91+
);
92+
return Mono.empty();
93+
});
7394
}
7495

75-
private Function<ClientResponse, Mono<DetectedEndpoint>> convert(EndpointDefinition endpointDefinition, URI uri) {
96+
private Function<ClientResponse, Mono<DetectedEndpoint>> convert(InstanceId instanceId,
97+
EndpointDefinition endpointDefinition,
98+
URI uri) {
7699
return response -> {
77100
Mono<DetectedEndpoint> endpoint = Mono.empty();
78101
if (response.statusCode().is2xxSuccessful()) {
79102
endpoint = Mono.just(DetectedEndpoint.of(endpointDefinition, uri.toString()));
103+
log.debug("Endpoint probe for instance {} on endpoint '{}' successful.", instanceId, uri);
104+
} else {
105+
log.debug(
106+
"Endpoint probe for instance {} on endpoint '{}' failed with status {}.",
107+
instanceId,
108+
uri,
109+
response.rawStatusCode()
110+
);
80111
}
81112
return response.bodyToMono(Void.class).then(endpoint);
82113
};
83114
}
84115

85-
86116
private Mono<Endpoints> convert(List<DetectedEndpoint> endpoints) {
87117
if (endpoints.isEmpty()) {
88118
return Mono.empty();

spring-boot-admin-server/src/test/java/de/codecentric/boot/admin/server/services/endpoints/ProbeEndpointsStrategyTest.java

Lines changed: 41 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2018 the original author or authors.
2+
* Copyright 2014-2019 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.
@@ -30,12 +30,14 @@
3030
import org.junit.Rule;
3131
import org.junit.Test;
3232
import com.github.tomakehurst.wiremock.core.Options;
33+
import com.github.tomakehurst.wiremock.http.Fault;
3334
import com.github.tomakehurst.wiremock.junit.WireMockRule;
3435

3536
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
3637
import static com.github.tomakehurst.wiremock.client.WireMock.notFound;
3738
import static com.github.tomakehurst.wiremock.client.WireMock.ok;
3839
import static com.github.tomakehurst.wiremock.client.WireMock.options;
40+
import static com.github.tomakehurst.wiremock.client.WireMock.serverError;
3941
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
4042
import static com.github.tomakehurst.wiremock.stubbing.Scenario.STARTED;
4143
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -45,8 +47,8 @@ public class ProbeEndpointsStrategyTest {
4547
public WireMockRule wireMock = new WireMockRule(Options.DYNAMIC_PORT);
4648

4749
private InstanceWebClient instanceWebClient = InstanceWebClient.builder()
48-
.connectTimeout(Duration.ofSeconds(2))
49-
.readTimeout(Duration.ofSeconds(2))
50+
.connectTimeout(Duration.ofSeconds(1))
51+
.readTimeout(Duration.ofSeconds(1))
5052
.defaultRetries(1)
5153
.build();
5254

@@ -62,48 +64,52 @@ public static void tearDown() {
6264

6365
@Test
6466
public void invariants() {
65-
assertThatThrownBy(() -> new ProbeEndpointsStrategy(instanceWebClient, null)).isInstanceOf(
67+
assertThatThrownBy(() -> new ProbeEndpointsStrategy(this.instanceWebClient, null)).isInstanceOf(
6668
IllegalArgumentException.class).hasMessage("'endpoints' must not be null.");
67-
assertThatThrownBy(() -> new ProbeEndpointsStrategy(instanceWebClient, new String[]{null})).isInstanceOf(
69+
assertThatThrownBy(() -> new ProbeEndpointsStrategy(this.instanceWebClient, new String[]{null})).isInstanceOf(
6870
IllegalArgumentException.class).hasMessage("'endpoints' must not contain null.");
6971
}
7072

7173
@Test
7274
public void should_return_detect_endpoints() {
7375
//given
7476
Instance instance = Instance.create(InstanceId.of("id"))
75-
.register(Registration.create("test", wireMock.url("/mgmt/health"))
76-
.managementUrl(wireMock.url("/mgmt"))
77+
.register(Registration.create("test", this.wireMock.url("/mgmt/health"))
78+
.managementUrl(this.wireMock.url("/mgmt"))
7779
.build());
7880

79-
wireMock.stubFor(options(urlEqualTo("/mgmt/metrics")).willReturn(ok()));
80-
wireMock.stubFor(options(urlEqualTo("/mgmt/stats")).willReturn(ok()));
81-
wireMock.stubFor(options(urlEqualTo("/mgmt/info")).willReturn(ok()));
82-
wireMock.stubFor(options(urlEqualTo("/mgmt/non-exist")).willReturn(notFound()));
81+
this.wireMock.stubFor(options(urlEqualTo("/mgmt/metrics")).willReturn(ok()));
82+
this.wireMock.stubFor(options(urlEqualTo("/mgmt/stats")).willReturn(ok()));
83+
this.wireMock.stubFor(options(urlEqualTo("/mgmt/info")).willReturn(ok()));
84+
this.wireMock.stubFor(options(urlEqualTo("/mgmt/non-exist")).willReturn(notFound()));
85+
this.wireMock.stubFor(options(urlEqualTo("/mgmt/error")).willReturn(serverError()));
86+
this.wireMock.stubFor(options(urlEqualTo("/mgmt/exception")).willReturn(aResponse().withFault(Fault.EMPTY_RESPONSE)));
8387

84-
ProbeEndpointsStrategy strategy = new ProbeEndpointsStrategy(instanceWebClient,
85-
new String[]{"metrics:stats", "metrics", "info", "non-exist"}
88+
ProbeEndpointsStrategy strategy = new ProbeEndpointsStrategy(this.instanceWebClient,
89+
new String[]{"metrics:stats", "metrics", "info", "non-exist", "error", "exception"}
8690
);
8791

8892
//when
8993
StepVerifier.create(strategy.detectEndpoints(instance))
9094
//then
91-
.expectNext(Endpoints.single("metrics", wireMock.url("/mgmt/stats"))
92-
.withEndpoint("info", wireMock.url("/mgmt/info")))//
95+
.expectNext(Endpoints.single("metrics", this.wireMock.url("/mgmt/stats"))
96+
.withEndpoint("info", this.wireMock.url("/mgmt/info")))//
9397
.verifyComplete();
9498
}
9599

96100
@Test
97101
public void should_return_empty() {
98102
//given
99103
Instance instance = Instance.create(InstanceId.of("id"))
100-
.register(Registration.create("test", wireMock.url("/mgmt/health"))
101-
.managementUrl(wireMock.url("/mgmt"))
104+
.register(Registration.create("test", this.wireMock.url("/mgmt/health"))
105+
.managementUrl(this.wireMock.url("/mgmt"))
102106
.build());
103107

104-
wireMock.stubFor(options(urlEqualTo("/mgmt/stats")).willReturn(aResponse().withStatus(HttpStatus.NOT_FOUND_404)));
108+
this.wireMock.stubFor(options(urlEqualTo("/mgmt/stats")).willReturn(aResponse().withStatus(HttpStatus.NOT_FOUND_404)));
105109

106-
ProbeEndpointsStrategy strategy = new ProbeEndpointsStrategy(instanceWebClient, new String[]{"metrics:stats"});
110+
ProbeEndpointsStrategy strategy = new ProbeEndpointsStrategy(this.instanceWebClient,
111+
new String[]{"metrics:stats"}
112+
);
107113

108114
//when
109115
StepVerifier.create(strategy.detectEndpoints(instance))
@@ -115,31 +121,32 @@ public void should_return_empty() {
115121
public void should_retry() {
116122
//given
117123
Instance instance = Instance.create(InstanceId.of("id"))
118-
.register(Registration.create("test", wireMock.url("/mgmt/health"))
119-
.managementUrl(wireMock.url("/mgmt"))
124+
.register(Registration.create("test", this.wireMock.url("/mgmt/health"))
125+
.managementUrl(this.wireMock.url("/mgmt"))
120126
.build());
121127

122-
wireMock.stubFor(options(urlEqualTo("/mgmt/metrics")).inScenario("retry")
123-
.whenScenarioStateIs(STARTED)
124-
.willReturn(aResponse().withFixedDelay(5000))
125-
.willSetStateTo("recovered"));
128+
this.wireMock.stubFor(options(urlEqualTo("/mgmt/metrics")).inScenario("retry")
129+
.whenScenarioStateIs(STARTED)
130+
.willReturn(aResponse().withFixedDelay(5000))
131+
.willSetStateTo("recovered"));
126132

127-
wireMock.stubFor(options(urlEqualTo("/mgmt/metrics")).inScenario("retry")
128-
.whenScenarioStateIs("recovered")
129-
.willReturn(ok()));
130-
wireMock.stubFor(options(urlEqualTo("/mgmt/stats")).willReturn(ok()));
131-
wireMock.stubFor(options(urlEqualTo("/mgmt/info")).willReturn(ok()));
132-
wireMock.stubFor(options(urlEqualTo("/mgmt/non-exist")).willReturn(notFound()));
133+
this.wireMock.stubFor(options(urlEqualTo("/mgmt/metrics")).inScenario("retry")
134+
.whenScenarioStateIs("recovered")
135+
.willReturn(ok()));
136+
this.wireMock.stubFor(options(urlEqualTo("/mgmt/stats")).willReturn(ok()));
137+
this.wireMock.stubFor(options(urlEqualTo("/mgmt/info")).willReturn(ok()));
138+
this.wireMock.stubFor(options(urlEqualTo("/mgmt/non-exist")).willReturn(notFound()));
133139

134-
ProbeEndpointsStrategy strategy = new ProbeEndpointsStrategy(instanceWebClient,
140+
ProbeEndpointsStrategy strategy = new ProbeEndpointsStrategy(
141+
this.instanceWebClient,
135142
new String[]{"metrics:stats", "metrics", "info", "non-exist"}
136143
);
137144

138145
//when
139146
StepVerifier.create(strategy.detectEndpoints(instance))
140147
//then
141-
.expectNext(Endpoints.single("metrics", wireMock.url("/mgmt/stats"))
142-
.withEndpoint("info", wireMock.url("/mgmt/info")))//
148+
.expectNext(Endpoints.single("metrics", this.wireMock.url("/mgmt/stats"))
149+
.withEndpoint("info", this.wireMock.url("/mgmt/info")))//
143150
.verifyComplete();
144151
}
145152
}

0 commit comments

Comments
 (0)