Merge pull request #24717 from bono007

* pr/24717:
  Polish "Add support for GET requests for /actuator/startup"
  Add support for GET requests for /actuator/startup

Closes gh-24717
pull/24734/head
Stephane Nicoll 4 years ago
commit 04a02f0a2d

@ -4,24 +4,38 @@
The `startup` endpoint provides information about the application's startup sequence.
[[startup-retrieving]]
== Retrieving the Application Startup steps
To retrieve the steps recorded so far during the application startup phase , make a `POST` request to `/actuator/startup`, as shown in the following curl-based example:
The application startup steps can either be retrieved as a snapshot (`GET`) or drained from the buffer (`POST`).
include::{snippets}/startup/curl-request.adoc[]
[[startup-retrieving-snapshot]]
=== Retrieving a snapshot of the Application Startup steps
To retrieve the steps recorded so far during the application startup phase , make a `GET` request to `/actuator/startup`, as shown in the following curl-based example:
include::{snippets}/startup-snapshot/curl-request.adoc[]
The resulting response is similar to the following:
include::{snippets}/startup/http-response.adoc[]
include::{snippets}/startup-snapshot/http-response.adoc[]
[[startup-retrieving-drain]]
== Draining the Application Startup steps
To drain and return the steps recorded so far during the application startup phase , make a `POST` request to `/actuator/startup`, as shown in the following curl-based example:
include::{snippets}/startup/curl-request.adoc[]
The resulting response is similar to the following:
include::{snippets}/startup/http-response.adoc[]
[[startup-retrieving-response-structure]]
=== Response Structure
The response contains details of the application startup steps recorded so far by the application.
The response contains details of the application startup steps.
The following table describes the structure of the response:
[cols="2,1,3"]

@ -26,12 +26,13 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.metrics.StartupStep;
import org.springframework.restdocs.payload.FieldDescriptor;
import org.springframework.restdocs.payload.JsonFieldType;
import org.springframework.restdocs.payload.ResponseFieldsSnippet;
import org.springframework.restdocs.payload.PayloadDocumentation;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -39,6 +40,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* Tests for generating documentation describing {@link StartupEndpoint}.
*
* @author Brian Clozel
* @author Stephane Nicoll
*/
class StartupEndpointDocumentationTests extends MockMvcEndpointDocumentationTests {
@ -53,9 +55,20 @@ class StartupEndpointDocumentationTests extends MockMvcEndpointDocumentationTest
instantiate.end();
}
@Test
void startupSnapshot() throws Exception {
this.mockMvc.perform(get("/actuator/startup")).andExpect(status().isOk())
.andDo(document("startup-snapshot", PayloadDocumentation.responseFields(responseFields())));
}
@Test
void startup() throws Exception {
ResponseFieldsSnippet responseFields = responseFields(
this.mockMvc.perform(post("/actuator/startup")).andExpect(status().isOk())
.andDo(document("startup", PayloadDocumentation.responseFields(responseFields())));
}
private FieldDescriptor[] responseFields() {
return new FieldDescriptor[] {
fieldWithPath("springBootVersion").type(JsonFieldType.STRING)
.description("Spring Boot version for this application.").optional(),
fieldWithPath("timeline.startTime").description("Start time of the application."),
@ -73,10 +86,7 @@ class StartupEndpointDocumentationTests extends MockMvcEndpointDocumentationTest
fieldWithPath("timeline.events.[].startupStep.tags[].key")
.description("The key of the StartupStep Tag."),
fieldWithPath("timeline.events.[].startupStep.tags[].value")
.description("The value of the StartupStep Tag."));
this.mockMvc.perform(post("/actuator/startup")).andExpect(status().isOk())
.andDo(document("startup", responseFields));
.description("The value of the StartupStep Tag.") };
}
@Configuration(proxyBeanMethods = false)

@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 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.
@ -18,6 +18,7 @@ package org.springframework.boot.actuate.startup;
import org.springframework.boot.SpringBootVersion;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
import org.springframework.boot.context.metrics.buffering.StartupTimeline;
@ -28,6 +29,7 @@ import org.springframework.boot.context.metrics.buffering.StartupTimeline;
* application startup}.
*
* @author Brian Clozel
* @author Chris Bono
* @since 2.4.0
*/
@Endpoint(id = "startup")
@ -44,6 +46,12 @@ public class StartupEndpoint {
this.applicationStartup = applicationStartup;
}
@ReadOperation
public StartupResponse startupSnapshot() {
StartupTimeline startupTimeline = this.applicationStartup.getBufferedTimeline();
return new StartupResponse(startupTimeline);
}
@WriteOperation
public StartupResponse startup() {
StartupTimeline startupTimeline = this.applicationStartup.drainBufferedTimeline();

@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 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.
@ -16,13 +16,17 @@
package org.springframework.boot.actuate.startup;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.springframework.boot.SpringBootVersion;
import org.springframework.boot.actuate.startup.StartupEndpoint.StartupResponse;
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.metrics.ApplicationStartup;
import static org.assertj.core.api.Assertions.assertThat;
@ -30,17 +34,15 @@ import static org.assertj.core.api.Assertions.assertThat;
* Tests for {@link StartupEndpoint}.
*
* @author Brian Clozel
* @author Chris Bono
*/
class StartupEndpointTests {
@Test
void startupEventsAreFound() {
BufferingApplicationStartup applicationStartup = new BufferingApplicationStartup(256);
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withInitializer((context) -> context.setApplicationStartup(applicationStartup))
.withUserConfiguration(EndpointConfiguration.class);
contextRunner.run((context) -> {
StartupEndpoint.StartupResponse startup = context.getBean(StartupEndpoint.class).startup();
testStartupEndpoint(applicationStartup, (startupEndpoint) -> {
StartupResponse startup = startupEndpoint.startup();
assertThat(startup.getSpringBootVersion()).isEqualTo(SpringBootVersion.getVersion());
assertThat(startup.getTimeline().getStartTime())
.isEqualTo(applicationStartup.getBufferedTimeline().getStartTime());
@ -48,15 +50,32 @@ class StartupEndpointTests {
}
@Test
void bufferIsDrained() {
void bufferWithGetIsNotDrained() {
BufferingApplicationStartup applicationStartup = new BufferingApplicationStartup(256);
testStartupEndpoint(applicationStartup, (startupEndpoint) -> {
StartupResponse startup = startupEndpoint.startupSnapshot();
assertThat(startup.getTimeline().getEvents()).isNotEmpty();
assertThat(applicationStartup.getBufferedTimeline().getEvents()).isNotEmpty();
});
}
@Test
void bufferWithPostIsDrained() {
BufferingApplicationStartup applicationStartup = new BufferingApplicationStartup(256);
testStartupEndpoint(applicationStartup, (startupEndpoint) -> {
StartupResponse startup = startupEndpoint.startup();
assertThat(startup.getTimeline().getEvents()).isNotEmpty();
assertThat(applicationStartup.getBufferedTimeline().getEvents()).isEmpty();
});
}
private void testStartupEndpoint(ApplicationStartup applicationStartup, Consumer<StartupEndpoint> startupEndpoint) {
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withInitializer((context) -> context.setApplicationStartup(applicationStartup))
.withUserConfiguration(EndpointConfiguration.class);
contextRunner.run((context) -> {
StartupEndpoint.StartupResponse startup = context.getBean(StartupEndpoint.class).startup();
assertThat(startup.getTimeline().getEvents()).isNotEmpty();
assertThat(applicationStartup.getBufferedTimeline().getEvents()).isEmpty();
assertThat(context).hasSingleBean(StartupEndpoint.class);
startupEndpoint.accept(context.getBean(StartupEndpoint.class));
});
}

Loading…
Cancel
Save