diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/startup.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/startup.adoc index 76ac18e236..681d07834f 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/startup.adoc +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/startup.adoc @@ -4,11 +4,27 @@ 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`). + +[[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[] @@ -16,12 +32,10 @@ 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"] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/StartupEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/StartupEndpointDocumentationTests.java index 5fe538a4d4..935ee973f7 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/StartupEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/StartupEndpointDocumentationTests.java @@ -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) diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/startup/StartupEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/startup/StartupEndpoint.java index 145d1d7317..51b0412a65 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/startup/StartupEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/startup/StartupEndpoint.java @@ -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(); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/startup/StartupEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/startup/StartupEndpointTests.java index 5f9dcf6eb3..6e9ae9e5b2 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/startup/StartupEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/startup/StartupEndpointTests.java @@ -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) { 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)); }); }