Polish "Add support for GET requests for /actuator/startup"

See gh-24717
pull/24734/head
Stephane Nicoll 4 years ago
parent 4b8d6efc12
commit 632c1239e6

@ -4,11 +4,27 @@
The `startup` endpoint provides information about the application's startup sequence. The `startup` endpoint provides information about the application's startup sequence.
[[startup-retrieving]] [[startup-retrieving]]
== Retrieving the Application Startup steps == 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`).
[[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-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[] include::{snippets}/startup/curl-request.adoc[]
@ -16,14 +32,10 @@ The resulting response is similar to the following:
include::{snippets}/startup/http-response.adoc[] include::{snippets}/startup/http-response.adoc[]
NOTE: The above call resets the application startup steps buffer - subsequent calls to the endpoint will
not include the returned steps. To retrieve a snapshot of the steps recorded so far without removing them
from the startup buffer, make a `GET` request to `/actuator/startup`.
[[startup-retrieving-response-structure]] [[startup-retrieving-response-structure]]
=== 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: The following table describes the structure of the response:
[cols="2,1,3"] [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.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.core.metrics.StartupStep; import org.springframework.core.metrics.StartupStep;
import org.springframework.restdocs.payload.FieldDescriptor;
import org.springframework.restdocs.payload.JsonFieldType; 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.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; 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.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 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}. * Tests for generating documentation describing {@link StartupEndpoint}.
* *
* @author Brian Clozel * @author Brian Clozel
* @author Stephane Nicoll
*/ */
class StartupEndpointDocumentationTests extends MockMvcEndpointDocumentationTests { class StartupEndpointDocumentationTests extends MockMvcEndpointDocumentationTests {
@ -53,9 +55,20 @@ class StartupEndpointDocumentationTests extends MockMvcEndpointDocumentationTest
instantiate.end(); instantiate.end();
} }
@Test
void startupSnapshot() throws Exception {
this.mockMvc.perform(get("/actuator/startup")).andExpect(status().isOk())
.andDo(document("startup-snapshot", PayloadDocumentation.responseFields(responseFields())));
}
@Test @Test
void startup() throws Exception { 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) fieldWithPath("springBootVersion").type(JsonFieldType.STRING)
.description("Spring Boot version for this application.").optional(), .description("Spring Boot version for this application.").optional(),
fieldWithPath("timeline.startTime").description("Start time of the application."), fieldWithPath("timeline.startTime").description("Start time of the application."),
@ -73,10 +86,7 @@ class StartupEndpointDocumentationTests extends MockMvcEndpointDocumentationTest
fieldWithPath("timeline.events.[].startupStep.tags[].key") fieldWithPath("timeline.events.[].startupStep.tags[].key")
.description("The key of the StartupStep Tag."), .description("The key of the StartupStep Tag."),
fieldWithPath("timeline.events.[].startupStep.tags[].value") fieldWithPath("timeline.events.[].startupStep.tags[].value")
.description("The value of the StartupStep Tag.")); .description("The value of the StartupStep Tag.") };
this.mockMvc.perform(post("/actuator/startup")).andExpect(status().isOk())
.andDo(document("startup", responseFields));
} }
@Configuration(proxyBeanMethods = false) @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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -46,18 +46,18 @@ public class StartupEndpoint {
this.applicationStartup = applicationStartup; this.applicationStartup = applicationStartup;
} }
@WriteOperation
public StartupResponse startup() {
StartupTimeline startupTimeline = this.applicationStartup.drainBufferedTimeline();
return new StartupResponse(startupTimeline);
}
@ReadOperation @ReadOperation
public StartupResponse startupSnapshot() { public StartupResponse startupSnapshot() {
StartupTimeline startupTimeline = this.applicationStartup.getBufferedTimeline(); StartupTimeline startupTimeline = this.applicationStartup.getBufferedTimeline();
return new StartupResponse(startupTimeline); return new StartupResponse(startupTimeline);
} }
@WriteOperation
public StartupResponse startup() {
StartupTimeline startupTimeline = this.applicationStartup.drainBufferedTimeline();
return new StartupResponse(startupTimeline);
}
/** /**
* A description of an application startup, primarily intended for serialization to * A description of an application startup, primarily intended for serialization to
* JSON. * JSON.

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,13 +16,17 @@
package org.springframework.boot.actuate.startup; package org.springframework.boot.actuate.startup;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.SpringBootVersion; 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.context.metrics.buffering.BufferingApplicationStartup;
import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.metrics.ApplicationStartup;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -37,11 +41,8 @@ class StartupEndpointTests {
@Test @Test
void startupEventsAreFound() { void startupEventsAreFound() {
BufferingApplicationStartup applicationStartup = new BufferingApplicationStartup(256); BufferingApplicationStartup applicationStartup = new BufferingApplicationStartup(256);
ApplicationContextRunner contextRunner = new ApplicationContextRunner() testStartupEndpoint(applicationStartup, (startupEndpoint) -> {
.withInitializer((context) -> context.setApplicationStartup(applicationStartup)) StartupResponse startup = startupEndpoint.startup();
.withUserConfiguration(EndpointConfiguration.class);
contextRunner.run((context) -> {
StartupEndpoint.StartupResponse startup = context.getBean(StartupEndpoint.class).startup();
assertThat(startup.getSpringBootVersion()).isEqualTo(SpringBootVersion.getVersion()); assertThat(startup.getSpringBootVersion()).isEqualTo(SpringBootVersion.getVersion());
assertThat(startup.getTimeline().getStartTime()) assertThat(startup.getTimeline().getStartTime())
.isEqualTo(applicationStartup.getBufferedTimeline().getStartTime()); .isEqualTo(applicationStartup.getBufferedTimeline().getStartTime());
@ -49,28 +50,32 @@ class StartupEndpointTests {
} }
@Test @Test
void bufferIsDrained() { void bufferWithGetIsNotDrained() {
BufferingApplicationStartup applicationStartup = new BufferingApplicationStartup(256); BufferingApplicationStartup applicationStartup = new BufferingApplicationStartup(256);
ApplicationContextRunner contextRunner = new ApplicationContextRunner() testStartupEndpoint(applicationStartup, (startupEndpoint) -> {
.withInitializer((context) -> context.setApplicationStartup(applicationStartup)) StartupResponse startup = startupEndpoint.startupSnapshot();
.withUserConfiguration(EndpointConfiguration.class);
contextRunner.run((context) -> {
StartupEndpoint.StartupResponse startup = context.getBean(StartupEndpoint.class).startup();
assertThat(startup.getTimeline().getEvents()).isNotEmpty(); assertThat(startup.getTimeline().getEvents()).isNotEmpty();
assertThat(applicationStartup.getBufferedTimeline().getEvents()).isEmpty(); assertThat(applicationStartup.getBufferedTimeline().getEvents()).isNotEmpty();
}); });
} }
@Test @Test
void bufferIsNotDrained() { void bufferWithPostIsDrained() {
BufferingApplicationStartup applicationStartup = new BufferingApplicationStartup(256); 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() ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withInitializer((context) -> context.setApplicationStartup(applicationStartup)) .withInitializer((context) -> context.setApplicationStartup(applicationStartup))
.withUserConfiguration(EndpointConfiguration.class); .withUserConfiguration(EndpointConfiguration.class);
contextRunner.run((context) -> { contextRunner.run((context) -> {
StartupEndpoint.StartupResponse startup = context.getBean(StartupEndpoint.class).startupSnapshot(); assertThat(context).hasSingleBean(StartupEndpoint.class);
assertThat(startup.getTimeline().getEvents()).isNotEmpty(); startupEndpoint.accept(context.getBean(StartupEndpoint.class));
assertThat(applicationStartup.getBufferedTimeline().getEvents()).isNotEmpty();
}); });
} }

Loading…
Cancel
Save