Skip to content

Commit 7a9cec0

Browse files
committed
Improving error-handling and logging for queying actuator-index
1 parent 9de9f11 commit 7a9cec0

File tree

2 files changed

+112
-71
lines changed

2 files changed

+112
-71
lines changed

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

Lines changed: 47 additions & 23 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.
@@ -28,13 +28,18 @@
2828
import java.util.List;
2929
import java.util.Map;
3030
import java.util.Objects;
31+
import java.util.function.Function;
3132
import java.util.stream.Collectors;
33+
import org.slf4j.Logger;
34+
import org.slf4j.LoggerFactory;
3235
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
3336
import org.springframework.http.MediaType;
37+
import org.springframework.web.reactive.function.client.ClientResponse;
3438
import com.fasterxml.jackson.annotation.JsonCreator;
3539
import com.fasterxml.jackson.annotation.JsonProperty;
3640

3741
public class QueryIndexEndpointStrategy implements EndpointDetectionStrategy {
42+
private static final Logger log = LoggerFactory.getLogger(QueryIndexEndpointStrategy.class);
3843
private final InstanceWebClient instanceWebClient;
3944
private static final MediaType actuatorMediaType = MediaType.parseMediaType(ActuatorMediaType.V2_JSON);
4045

@@ -47,39 +52,58 @@ public Mono<Endpoints> detectEndpoints(Instance instance) {
4752
Registration registration = instance.getRegistration();
4853
String managementUrl = registration.getManagementUrl();
4954
if (managementUrl == null || Objects.equals(registration.getServiceUrl(), managementUrl)) {
55+
log.debug("Querying actuator-index for instance {} omitted.", instance.getId());
5056
return Mono.empty();
5157
}
5258

53-
return instanceWebClient.instance(instance)
54-
.get()
55-
.uri(managementUrl)
56-
.exchange()
57-
.flatMap(response -> {
58-
if (response.statusCode().is2xxSuccessful() &&
59-
response.headers()
60-
.contentType()
61-
.map(actuatorMediaType::isCompatibleWith)
62-
.orElse(false)) {
63-
return response.bodyToMono(Response.class);
64-
} else {
65-
return response.bodyToMono(Void.class).then(Mono.empty());
66-
}
67-
})
68-
.flatMap(this::convert);
59+
return this.instanceWebClient.instance(instance)
60+
.get()
61+
.uri(managementUrl)
62+
.exchange()
63+
.flatMap(this.convert(instance, managementUrl))
64+
.onErrorResume(e -> {
65+
log.warn("Querying actuator-index for instance {} on '{}' failed: {}",
66+
instance.getId(),
67+
managementUrl,
68+
e.getMessage()
69+
);
70+
log.debug("Querying actuator-index for instance {} on '{}' failed.",
71+
instance.getId(),
72+
managementUrl,
73+
e
74+
);
75+
return Mono.empty();
76+
});
6977
}
7078

71-
private Mono<Endpoints> convert(Response response) {
79+
private Function<ClientResponse, Mono<Endpoints>> convert(Instance instance, String managementUrl) {
80+
return response -> {
81+
if (response.statusCode().is2xxSuccessful() &&
82+
response.headers().contentType().map(actuatorMediaType::isCompatibleWith).orElse(false)) {
83+
log.debug("Querying actuator-index for instance {} on '{}' successful.",
84+
instance.getId(),
85+
managementUrl
86+
);
87+
return response.bodyToMono(Response.class).flatMap(this::convertResponse);
88+
} else {
89+
log.debug("Querying actuator-index for instance {} on '{}' failed with status {}.",
90+
instance.getId(),
91+
managementUrl,
92+
response.rawStatusCode()
93+
);
94+
return response.bodyToMono(Void.class).then(Mono.empty());
95+
}
96+
};
97+
}
98+
99+
private Mono<Endpoints> convertResponse(Response response) {
72100
List<Endpoint> endpoints = response.getLinks()
73101
.entrySet()
74102
.stream()
75103
.filter(e -> !e.getKey().equals("self") && !e.getValue().isTemplated())
76104
.map(e -> Endpoint.of(e.getKey(), e.getValue().getHref()))
77105
.collect(Collectors.toList());
78-
if (endpoints.isEmpty()) {
79-
return Mono.empty();
80-
} else {
81-
return Mono.just(Endpoints.of(endpoints));
82-
}
106+
return endpoints.isEmpty() ? Mono.empty() : Mono.just(Endpoints.of(endpoints));
83107
}
84108

85109
@Data

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

Lines changed: 65 additions & 48 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.
@@ -29,6 +29,7 @@
2929
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
3030
import org.springframework.http.MediaType;
3131
import com.github.tomakehurst.wiremock.core.Options;
32+
import com.github.tomakehurst.wiremock.http.Fault;
3233
import com.github.tomakehurst.wiremock.junit.WireMockRule;
3334

3435
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
@@ -47,28 +48,25 @@ public class QueryIndexEndpointStrategyTest {
4748
public WireMockRule wireMock = new WireMockRule(Options.DYNAMIC_PORT);
4849

4950
private InstanceWebClient instanceWebClient = InstanceWebClient.builder()
50-
.retries(singletonMap(
51-
Endpoint.ACTUATOR_INDEX,
52-
1
53-
))
51+
.retries(singletonMap(Endpoint.ACTUATOR_INDEX, 1))
5452
.build();
5553

5654
@Test
5755
public void should_return_endpoints() {
5856
//given
5957
Instance instance = Instance.create(InstanceId.of("id"))
60-
.register(Registration.create("test", wireMock.url("/mgmt/health"))
61-
.managementUrl(wireMock.url("/mgmt"))
58+
.register(Registration.create("test", this.wireMock.url("/mgmt/health"))
59+
.managementUrl(this.wireMock.url("/mgmt"))
6260
.build());
6361

6462
String body = "{\"_links\":{\"metrics-requiredMetricName\":{\"templated\":true,\"href\":\"\\/mgmt\\/metrics\\/{requiredMetricName}\"},\"self\":{\"templated\":false,\"href\":\"\\/mgmt\"},\"metrics\":{\"templated\":false,\"href\":\"\\/mgmt\\/stats\"},\"info\":{\"templated\":false,\"href\":\"\\/mgmt\\/info\"}}}";
6563

66-
wireMock.stubFor(get("/mgmt").willReturn(ok(body).withHeader("Content-Type", ActuatorMediaType.V2_JSON)
67-
.withHeader("Content-Length",
68-
Integer.toString(body.length())
69-
)));
64+
this.wireMock.stubFor(get("/mgmt").willReturn(ok(body).withHeader("Content-Type", ActuatorMediaType.V2_JSON)
65+
.withHeader("Content-Length",
66+
Integer.toString(body.length())
67+
)));
7068

71-
QueryIndexEndpointStrategy strategy = new QueryIndexEndpointStrategy(instanceWebClient);
69+
QueryIndexEndpointStrategy strategy = new QueryIndexEndpointStrategy(this.instanceWebClient);
7270

7371
//when
7472
StepVerifier.create(strategy.detectEndpoints(instance))
@@ -81,17 +79,17 @@ public void should_return_endpoints() {
8179
public void should_return_empty_on_empty_endpoints() {
8280
//given
8381
Instance instance = Instance.create(InstanceId.of("id"))
84-
.register(Registration.create("test", wireMock.url("/mgmt/health"))
85-
.managementUrl(wireMock.url("/mgmt"))
82+
.register(Registration.create("test", this.wireMock.url("/mgmt/health"))
83+
.managementUrl(this.wireMock.url("/mgmt"))
8684
.build());
8785

8886
String body = "{\"_links\":{}}";
89-
wireMock.stubFor(get("/mgmt").willReturn(okJson(body).withHeader("Content-Type", ActuatorMediaType.V2_JSON)
90-
.withHeader("Content-Length",
91-
Integer.toString(body.length())
92-
)));
87+
this.wireMock.stubFor(get("/mgmt").willReturn(okJson(body).withHeader("Content-Type", ActuatorMediaType.V2_JSON)
88+
.withHeader("Content-Length",
89+
Integer.toString(body.length())
90+
)));
9391

94-
QueryIndexEndpointStrategy strategy = new QueryIndexEndpointStrategy(instanceWebClient);
92+
QueryIndexEndpointStrategy strategy = new QueryIndexEndpointStrategy(this.instanceWebClient);
9593

9694
//when
9795
StepVerifier.create(strategy.detectEndpoints(instance))
@@ -103,35 +101,54 @@ public void should_return_empty_on_empty_endpoints() {
103101
public void should_return_empty_on_not_found() {
104102
//given
105103
Instance instance = Instance.create(InstanceId.of("id"))
106-
.register(Registration.create("test", wireMock.url("/mgmt/health"))
107-
.managementUrl(wireMock.url("/mgmt"))
104+
.register(Registration.create("test", this.wireMock.url("/mgmt/health"))
105+
.managementUrl(this.wireMock.url("/mgmt"))
108106
.build());
109107

110-
wireMock.stubFor(get("/mgmt").willReturn(notFound()));
108+
this.wireMock.stubFor(get("/mgmt").willReturn(notFound()));
111109

112-
QueryIndexEndpointStrategy strategy = new QueryIndexEndpointStrategy(instanceWebClient);
110+
QueryIndexEndpointStrategy strategy = new QueryIndexEndpointStrategy(this.instanceWebClient);
113111

114112
//when
115113
StepVerifier.create(strategy.detectEndpoints(instance))
116114
//then
117115
.verifyComplete();
118116
}
119117

118+
@Test
119+
public void should_return_empty_on_error() {
120+
//given
121+
Instance instance = Instance.create(InstanceId.of("id"))
122+
.register(Registration.create("test", this.wireMock.url("/mgmt/health"))
123+
.managementUrl(this.wireMock.url("/mgmt"))
124+
.build());
125+
126+
this.wireMock.stubFor(get("/mgmt").willReturn(aResponse().withFault(Fault.EMPTY_RESPONSE)));
127+
128+
QueryIndexEndpointStrategy strategy = new QueryIndexEndpointStrategy(this.instanceWebClient);
129+
130+
//when
131+
StepVerifier.create(strategy.detectEndpoints(instance))
132+
//then
133+
.verifyComplete();
134+
}
135+
136+
120137
@Test
121138
public void should_return_empty_on_wrong_content_type() {
122139
//given
123140
Instance instance = Instance.create(InstanceId.of("id"))
124-
.register(Registration.create("test", wireMock.url("/mgmt/health"))
125-
.managementUrl(wireMock.url("/mgmt"))
141+
.register(Registration.create("test", this.wireMock.url("/mgmt/health"))
142+
.managementUrl(this.wireMock.url("/mgmt"))
126143
.build());
127144

128145
String body = "HELLOW WORLD";
129-
wireMock.stubFor(get("/mgmt").willReturn(ok(body).withHeader("Content-Type", MediaType.TEXT_PLAIN_VALUE)
130-
.withHeader("Content-Length",
131-
Integer.toString(body.length())
132-
)));
146+
this.wireMock.stubFor(get("/mgmt").willReturn(ok(body).withHeader("Content-Type", MediaType.TEXT_PLAIN_VALUE)
147+
.withHeader("Content-Length",
148+
Integer.toString(body.length())
149+
)));
133150

134-
QueryIndexEndpointStrategy strategy = new QueryIndexEndpointStrategy(instanceWebClient);
151+
QueryIndexEndpointStrategy strategy = new QueryIndexEndpointStrategy(this.instanceWebClient);
135152

136153
//when
137154
StepVerifier.create(strategy.detectEndpoints(instance))
@@ -143,41 +160,41 @@ public void should_return_empty_on_wrong_content_type() {
143160
public void should_return_empty_when_mgmt_equals_service_url() {
144161
//given
145162
Instance instance = Instance.create(InstanceId.of("id"))
146-
.register(Registration.create("test", wireMock.url("/app/health"))
147-
.managementUrl(wireMock.url("/app"))
148-
.serviceUrl(wireMock.url("/app"))
163+
.register(Registration.create("test", this.wireMock.url("/app/health"))
164+
.managementUrl(this.wireMock.url("/app"))
165+
.serviceUrl(this.wireMock.url("/app"))
149166
.build());
150167

151-
QueryIndexEndpointStrategy strategy = new QueryIndexEndpointStrategy(instanceWebClient);
168+
QueryIndexEndpointStrategy strategy = new QueryIndexEndpointStrategy(this.instanceWebClient);
152169

153170
//when/then
154171
StepVerifier.create(strategy.detectEndpoints(instance)).verifyComplete();
155-
wireMock.verify(0, anyRequestedFor(urlPathEqualTo("/app")));
172+
this.wireMock.verify(0, anyRequestedFor(urlPathEqualTo("/app")));
156173
}
157174

158175
@Test
159176
public void should_retry() {
160177
//given
161178
Instance instance = Instance.create(InstanceId.of("id"))
162-
.register(Registration.create("test", wireMock.url("/mgmt/health"))
163-
.managementUrl(wireMock.url("/mgmt"))
179+
.register(Registration.create("test", this.wireMock.url("/mgmt/health"))
180+
.managementUrl(this.wireMock.url("/mgmt"))
164181
.build());
165182

166183
String body = "{\"_links\":{\"metrics-requiredMetricName\":{\"templated\":true,\"href\":\"\\/mgmt\\/metrics\\/{requiredMetricName}\"},\"self\":{\"templated\":false,\"href\":\"\\/mgmt\"},\"metrics\":{\"templated\":false,\"href\":\"\\/mgmt\\/stats\"},\"info\":{\"templated\":false,\"href\":\"\\/mgmt\\/info\"}}}";
167184

168-
wireMock.stubFor(get("/mgmt").inScenario("retry")
169-
.whenScenarioStateIs(STARTED)
170-
.willReturn(aResponse().withFixedDelay(5000))
171-
.willSetStateTo("recovered"));
185+
this.wireMock.stubFor(get("/mgmt").inScenario("retry")
186+
.whenScenarioStateIs(STARTED)
187+
.willReturn(aResponse().withFixedDelay(5000))
188+
.willSetStateTo("recovered"));
172189

173-
wireMock.stubFor(get("/mgmt").inScenario("retry")
174-
.whenScenarioStateIs("recovered")
175-
.willReturn(ok(body).withHeader("Content-Type", ActuatorMediaType.V2_JSON)
176-
.withHeader("Content-Length",
177-
Integer.toString(body.length())
178-
)));
190+
this.wireMock.stubFor(get("/mgmt").inScenario("retry")
191+
.whenScenarioStateIs("recovered")
192+
.willReturn(ok(body).withHeader("Content-Type", ActuatorMediaType.V2_JSON)
193+
.withHeader("Content-Length",
194+
Integer.toString(body.length())
195+
)));
179196

180-
QueryIndexEndpointStrategy strategy = new QueryIndexEndpointStrategy(instanceWebClient);
197+
QueryIndexEndpointStrategy strategy = new QueryIndexEndpointStrategy(this.instanceWebClient);
181198

182199
//when
183200
StepVerifier.create(strategy.detectEndpoints(instance))

0 commit comments

Comments
 (0)