mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-09-03 04:26:12 +08:00
Return objects from trace, audit event, and thread dump endpoints
Closes gh-7648
This commit is contained in:
parent
a6b30a3aab
commit
ad4ce9cf57
@ -18,15 +18,11 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.web;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.ConditionalOnEnabledEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.AuditEventsWebEndpointExtension;
|
||||
import org.springframework.boot.actuate.endpoint.web.HeapDumpWebEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.LogFileWebEndpoint;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
|
||||
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
@ -53,15 +49,6 @@ public class WebEndpointManagementContextConfiguration {
|
||||
return new HeapDumpWebEndpoint();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnEnabledEndpoint
|
||||
@ConditionalOnBean(value = AuditEventsEndpoint.class, search = SearchStrategy.CURRENT)
|
||||
public AuditEventsWebEndpointExtension auditEventsWebEndpointExtension(
|
||||
AuditEventsEndpoint delegate) {
|
||||
return new AuditEventsWebEndpointExtension(delegate);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@Conditional(LogFileCondition.class)
|
||||
|
@ -42,9 +42,28 @@ public class AuditEventsEndpoint {
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public List<AuditEvent> eventsWithPrincipalDateAfterAndType(String principal,
|
||||
public AuditEventsDescriptor eventsWithPrincipalDateAfterAndType(String principal,
|
||||
Date after, String type) {
|
||||
return this.auditEventRepository.find(principal, after, type);
|
||||
return new AuditEventsDescriptor(
|
||||
this.auditEventRepository.find(principal, after, type));
|
||||
}
|
||||
|
||||
/**
|
||||
* A description of an application's {@link AuditEvent audit events}. Primarily
|
||||
* intended for serialization to JSON.
|
||||
*/
|
||||
public static final class AuditEventsDescriptor {
|
||||
|
||||
private final List<AuditEvent> events;
|
||||
|
||||
private AuditEventsDescriptor(List<AuditEvent> events) {
|
||||
this.events = events;
|
||||
}
|
||||
|
||||
public List<AuditEvent> getEvents() {
|
||||
return this.events;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -28,14 +28,32 @@ import org.springframework.boot.endpoint.ReadOperation;
|
||||
* {@link Endpoint} to expose thread info.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@Endpoint(id = "threaddump")
|
||||
public class ThreadDumpEndpoint {
|
||||
|
||||
@ReadOperation
|
||||
public List<ThreadInfo> threadDump() {
|
||||
return Arrays
|
||||
.asList(ManagementFactory.getThreadMXBean().dumpAllThreads(true, true));
|
||||
public ThreadDumpDescriptor threadDump() {
|
||||
return new ThreadDumpDescriptor(Arrays
|
||||
.asList(ManagementFactory.getThreadMXBean().dumpAllThreads(true, true)));
|
||||
}
|
||||
|
||||
/**
|
||||
* A description of a thread dump. Primarily intended for serialization to JSON.
|
||||
*/
|
||||
public static final class ThreadDumpDescriptor {
|
||||
|
||||
private final List<ThreadInfo> threads;
|
||||
|
||||
private ThreadDumpDescriptor(List<ThreadInfo> threads) {
|
||||
this.threads = threads;
|
||||
}
|
||||
|
||||
public List<ThreadInfo> getThreads() {
|
||||
return this.threads;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -44,8 +44,26 @@ public class TraceEndpoint {
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public List<Trace> traces() {
|
||||
return this.repository.findAll();
|
||||
public TraceDescriptor traces() {
|
||||
return new TraceDescriptor(this.repository.findAll());
|
||||
}
|
||||
|
||||
/**
|
||||
* A description of an application's {@link Trace} entries. Primarily intended for
|
||||
* serialization to JSON.
|
||||
*/
|
||||
public static final class TraceDescriptor {
|
||||
|
||||
private final List<Trace> traces;
|
||||
|
||||
private TraceDescriptor(List<Trace> traces) {
|
||||
this.traces = traces;
|
||||
}
|
||||
|
||||
public List<Trace> getTraces() {
|
||||
return this.traces;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -17,10 +17,9 @@
|
||||
package org.springframework.boot.actuate.endpoint.jmx;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.actuate.audit.AuditEvent;
|
||||
import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint.AuditEventsDescriptor;
|
||||
import org.springframework.boot.endpoint.ReadOperation;
|
||||
import org.springframework.boot.endpoint.jmx.JmxEndpointExtension;
|
||||
|
||||
@ -41,12 +40,12 @@ public class AuditEventsJmxEndpointExtension {
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public List<AuditEvent> eventsWithDateAfter(Date dateAfter) {
|
||||
public AuditEventsDescriptor eventsWithDateAfter(Date dateAfter) {
|
||||
return this.delegate.eventsWithPrincipalDateAfterAndType(null, dateAfter, null);
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public List<AuditEvent> eventsWithPrincipalAndDateAfter(String principal,
|
||||
public AuditEventsDescriptor eventsWithPrincipalAndDateAfter(String principal,
|
||||
Date dateAfter) {
|
||||
return this.delegate.eventsWithPrincipalDateAfterAndType(principal, dateAfter,
|
||||
null);
|
||||
|
@ -1,51 +0,0 @@
|
||||
/*
|
||||
* 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.endpoint.web;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.actuate.audit.AuditEvent;
|
||||
import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint;
|
||||
import org.springframework.boot.endpoint.ReadOperation;
|
||||
import org.springframework.boot.endpoint.web.WebEndpointExtension;
|
||||
|
||||
/**
|
||||
* Web-specific extension of the {@link AuditEventsEndpoint}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@WebEndpointExtension(endpoint = AuditEventsEndpoint.class)
|
||||
public class AuditEventsWebEndpointExtension {
|
||||
|
||||
private final AuditEventsEndpoint delegate;
|
||||
|
||||
public AuditEventsWebEndpointExtension(AuditEventsEndpoint delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public Map<String, List<AuditEvent>> eventsWithPrincipalDateAfterAndType(
|
||||
String principal, Date after, String type) {
|
||||
return Collections.singletonMap("events", this.delegate
|
||||
.eventsWithPrincipalDateAfterAndType(principal, after, type));
|
||||
}
|
||||
|
||||
}
|
@ -24,7 +24,6 @@ import org.springframework.boot.actuate.audit.AuditEventRepository;
|
||||
import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.HealthEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.StatusEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.AuditEventsWebEndpointExtension;
|
||||
import org.springframework.boot.actuate.endpoint.web.HealthReactiveWebEndpointExtension;
|
||||
import org.springframework.boot.actuate.endpoint.web.HealthWebEndpointExtension;
|
||||
import org.springframework.boot.actuate.endpoint.web.HeapDumpWebEndpoint;
|
||||
@ -143,9 +142,10 @@ public class WebEndpointManagementContextConfigurationTests {
|
||||
public void reactiveHealthWebEndpointExtensionCanBeDisabled() {
|
||||
reactiveWebContextRunner(HealthEndpointConfiguration.class)
|
||||
.withPropertyValues("endpoints.health.enabled=false").run((context) -> {
|
||||
assertThat(context).doesNotHaveBean(HealthReactiveWebEndpointExtension.class);
|
||||
assertThat(context).doesNotHaveBean(HealthWebEndpointExtension.class);
|
||||
});
|
||||
assertThat(context)
|
||||
.doesNotHaveBean(HealthReactiveWebEndpointExtension.class);
|
||||
assertThat(context).doesNotHaveBean(HealthWebEndpointExtension.class);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@ -175,22 +175,10 @@ public class WebEndpointManagementContextConfigurationTests {
|
||||
public void reactiveStatusWebEndpointExtensionCanBeDisabled() {
|
||||
reactiveWebContextRunner(StatusEndpointConfiguration.class)
|
||||
.withPropertyValues("endpoints.status.enabled=false").run((context) -> {
|
||||
assertThat(context).doesNotHaveBean(StatusReactiveWebEndpointExtension.class);
|
||||
assertThat(context).doesNotHaveBean(StatusWebEndpointExtension.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void auditEventsWebEndpointExtensionIsAutoConfigured() {
|
||||
beanIsAutoConfigured(AuditEventsWebEndpointExtension.class,
|
||||
AuditEventsEndpointConfiguration.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void auditEventsWebEndpointExtensionCanBeDisabled() {
|
||||
beanIsNotAutoConfiguredWhenEndpointIsDisabled(
|
||||
AuditEventsWebEndpointExtension.class, "auditevents",
|
||||
AuditEventsEndpointConfiguration.class);
|
||||
assertThat(context)
|
||||
.doesNotHaveBean(StatusReactiveWebEndpointExtension.class);
|
||||
assertThat(context).doesNotHaveBean(StatusWebEndpointExtension.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -209,7 +197,8 @@ public class WebEndpointManagementContextConfigurationTests {
|
||||
|
||||
@Test
|
||||
public void logFileWebEndpointIsAutoConfiguredWhenExternalFileIsSet() {
|
||||
webContextRunner().withPropertyValues("endpoints.logfile.external-file:external.log")
|
||||
webContextRunner()
|
||||
.withPropertyValues("endpoints.logfile.external-file:external.log")
|
||||
.run((context) -> assertThat(
|
||||
context.getBeansOfType(LogFileWebEndpoint.class)).hasSize(1));
|
||||
}
|
||||
|
@ -47,8 +47,8 @@ public class AuditEventsEndpointTests {
|
||||
public void eventsWithType() {
|
||||
given(this.repository.find(null, null, "type"))
|
||||
.willReturn(Collections.singletonList(this.event));
|
||||
List<AuditEvent> result = this.endpoint.eventsWithPrincipalDateAfterAndType(null,
|
||||
null, "type");
|
||||
List<AuditEvent> result = this.endpoint
|
||||
.eventsWithPrincipalDateAfterAndType(null, null, "type").getEvents();
|
||||
assertThat(result).isEqualTo(Collections.singletonList(this.event));
|
||||
}
|
||||
|
||||
@ -57,8 +57,8 @@ public class AuditEventsEndpointTests {
|
||||
Date date = new Date();
|
||||
given(this.repository.find(null, date, null))
|
||||
.willReturn(Collections.singletonList(this.event));
|
||||
List<AuditEvent> result = this.endpoint.eventsWithPrincipalDateAfterAndType(null,
|
||||
date, null);
|
||||
List<AuditEvent> result = this.endpoint
|
||||
.eventsWithPrincipalDateAfterAndType(null, date, null).getEvents();
|
||||
assertThat(result).isEqualTo(Collections.singletonList(this.event));
|
||||
}
|
||||
|
||||
@ -67,7 +67,7 @@ public class AuditEventsEndpointTests {
|
||||
given(this.repository.find("Joan", null, null))
|
||||
.willReturn(Collections.singletonList(this.event));
|
||||
List<AuditEvent> result = this.endpoint
|
||||
.eventsWithPrincipalDateAfterAndType("Joan", null, null);
|
||||
.eventsWithPrincipalDateAfterAndType("Joan", null, null).getEvents();
|
||||
assertThat(result).isEqualTo(Collections.singletonList(this.event));
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,8 @@ public class ThreadDumpEndpointTests {
|
||||
|
||||
@Test
|
||||
public void dumpThreads() throws Exception {
|
||||
assertThat(new ThreadDumpEndpoint().threadDump().size()).isGreaterThan(0);
|
||||
assertThat(new ThreadDumpEndpoint().threadDump().getThreads().size())
|
||||
.isGreaterThan(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ public class TraceEndpointTests {
|
||||
public void trace() throws Exception {
|
||||
TraceRepository repository = new InMemoryTraceRepository();
|
||||
repository.add(Collections.<String, Object>singletonMap("a", "b"));
|
||||
Trace trace = new TraceEndpoint(repository).traces().get(0);
|
||||
Trace trace = new TraceEndpoint(repository).traces().getTraces().get(0);
|
||||
assertThat(trace.getInfo().get("a")).isEqualTo("b");
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,7 @@ public class AuditEventsJmxEndpointExtensionTests {
|
||||
Date date = new Date();
|
||||
given(this.repository.find(null, date, null))
|
||||
.willReturn(Collections.singletonList(this.event));
|
||||
List<AuditEvent> result = this.extension.eventsWithDateAfter(date);
|
||||
List<AuditEvent> result = this.extension.eventsWithDateAfter(date).getEvents();
|
||||
assertThat(result).isEqualTo(Collections.singletonList(this.event));
|
||||
}
|
||||
|
||||
@ -59,8 +59,8 @@ public class AuditEventsJmxEndpointExtensionTests {
|
||||
Date date = new Date();
|
||||
given(this.repository.find("Joan", date, null))
|
||||
.willReturn(Collections.singletonList(this.event));
|
||||
List<AuditEvent> result = this.extension.eventsWithPrincipalAndDateAfter("Joan",
|
||||
date);
|
||||
List<AuditEvent> result = this.extension
|
||||
.eventsWithPrincipalAndDateAfter("Joan", date).getEvents();
|
||||
assertThat(result).isEqualTo(Collections.singletonList(this.event));
|
||||
}
|
||||
|
||||
|
@ -33,8 +33,8 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link AuditEventsEndpoint} and
|
||||
* {@link AuditEventsWebEndpointExtension} exposed by Jersey, Spring MVC, and WebFlux.
|
||||
* Integration tests for {@link AuditEventsEndpoint} exposed by Jersey, Spring MVC, and
|
||||
* WebFlux.
|
||||
*
|
||||
* @author Vedran Pavic
|
||||
* @author Andy Wilkinson
|
||||
@ -95,11 +95,6 @@ public class AuditEventsEndpointWebIntegrationTests {
|
||||
return new AuditEventsEndpoint(auditEventsRepository());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuditEventsWebEndpointExtension auditEventsWebEndpointExtension() {
|
||||
return new AuditEventsWebEndpointExtension(auditEventsEndpoint());
|
||||
}
|
||||
|
||||
private AuditEvent createEvent(String instant, String principal, String type) {
|
||||
return new AuditEvent(Date.from(Instant.parse(instant)), principal, type,
|
||||
Collections.<String, Object>emptyMap());
|
||||
|
@ -1,60 +0,0 @@
|
||||
/*
|
||||
* 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.endpoint.web;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.actuate.audit.AuditEvent;
|
||||
import org.springframework.boot.actuate.audit.AuditEventRepository;
|
||||
import org.springframework.boot.actuate.endpoint.AuditEventsEndpoint;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link AuditEventsWebEndpointExtension}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class AuditEventsWebEndpointExtensionTests {
|
||||
|
||||
private final AuditEventRepository repository = mock(AuditEventRepository.class);
|
||||
|
||||
private final AuditEventsWebEndpointExtension extension = new AuditEventsWebEndpointExtension(
|
||||
new AuditEventsEndpoint(this.repository));
|
||||
|
||||
private final AuditEvent event = new AuditEvent("principal", "type",
|
||||
Collections.singletonMap("a", "alpha"));
|
||||
|
||||
@Test
|
||||
public void delegatesResponseIsAvailableFromEventsKeyInMap() {
|
||||
Date date = new Date();
|
||||
given(this.repository.find("principal", date, "type"))
|
||||
.willReturn(Collections.singletonList(this.event));
|
||||
Map<String, List<AuditEvent>> result = this.extension
|
||||
.eventsWithPrincipalDateAfterAndType("principal", date, "type");
|
||||
assertThat(result).hasSize(1);
|
||||
assertThat(result).containsEntry("events", Collections.singletonList(this.event));
|
||||
}
|
||||
|
||||
}
|
@ -163,35 +163,35 @@ public class SampleActuatorApplicationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testTrace() throws Exception {
|
||||
this.restTemplate.getForEntity("/health", String.class);
|
||||
@SuppressWarnings("rawtypes")
|
||||
ResponseEntity<List> entity = this.restTemplate
|
||||
ResponseEntity<Map> entity = this.restTemplate
|
||||
.withBasicAuth("user", getPassword())
|
||||
.getForEntity("/application/trace", List.class);
|
||||
.getForEntity("/application/trace", Map.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> list = entity.getBody();
|
||||
Map<String, Object> trace = list.get(list.size() - 1);
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> body = entity.getBody();
|
||||
Map<String, Object> trace = ((List<Map<String, Object>>) body.get("traces"))
|
||||
.get(0);
|
||||
Map<String, Object> map = (Map<String, Object>) ((Map<String, Object>) ((Map<String, Object>) trace
|
||||
.get("info")).get("headers")).get("response");
|
||||
assertThat(map.get("status")).isEqualTo("200");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void traceWithParameterMap() throws Exception {
|
||||
this.restTemplate.withBasicAuth("user", getPassword())
|
||||
.getForEntity("/application/health?param1=value1", String.class);
|
||||
@SuppressWarnings("rawtypes")
|
||||
ResponseEntity<List> entity = this.restTemplate
|
||||
ResponseEntity<Map> entity = this.restTemplate
|
||||
.withBasicAuth("user", getPassword())
|
||||
.getForEntity("/application/trace", List.class);
|
||||
.getForEntity("/application/trace", Map.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> list = entity.getBody();
|
||||
Map<String, Object> trace = list.get(0);
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> body = entity.getBody();
|
||||
Map<String, Object> trace = ((List<Map<String, Object>>) body.get("traces"))
|
||||
.get(0);
|
||||
Map<String, Object> map = (Map<String, Object>) ((Map<String, Object>) trace
|
||||
.get("info")).get("parameters");
|
||||
assertThat(map.get("param1")).isNotNull();
|
||||
|
Loading…
Reference in New Issue
Block a user