Merge pull request #27878 from bono007
* pr/27878: Polish "Add startup time metrics" Add startup time metrics Closes gh-27878
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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.autoconfigure.metrics.startup;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
|
||||
import org.springframework.boot.actuate.metrics.startup.StartupTimeMetrics;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for startup time metrics.
|
||||
*
|
||||
* @author Chris Bono
|
||||
* @since 2.6.0
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@AutoConfigureAfter({ MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class })
|
||||
@ConditionalOnClass(MeterRegistry.class)
|
||||
@ConditionalOnBean(MeterRegistry.class)
|
||||
public class StartupTimeMetricsAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public StartupTimeMetrics startupTimeMetrics(MeterRegistry meterRegistry) {
|
||||
return new StartupTimeMetrics(meterRegistry);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Auto-configuration for actuator startup time metrics.
|
||||
*/
|
||||
package org.springframework.boot.actuate.autoconfigure.metrics.startup;
|
||||
@@ -75,6 +75,7 @@ org.springframework.boot.actuate.autoconfigure.metrics.mongo.MongoMetricsAutoCon
|
||||
org.springframework.boot.actuate.autoconfigure.metrics.orm.jpa.HibernateMetricsAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.metrics.r2dbc.ConnectionPoolMetricsAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.metrics.redis.LettuceMetricsAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.metrics.startup.StartupTimeMetricsAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.metrics.task.TaskExecutorMetricsAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.metrics.web.client.HttpClientMetricsAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.metrics.web.jetty.JettyMetricsAutoConfiguration,\
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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.autoconfigure.metrics.startup;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.micrometer.core.instrument.TimeGauge;
|
||||
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun;
|
||||
import org.springframework.boot.actuate.metrics.startup.StartupTimeMetrics;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.boot.context.event.ApplicationStartedEvent;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link StartupTimeMetricsAutoConfiguration}.
|
||||
*
|
||||
* @author Chris Bono
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class StartupTimeMetricsAutoConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().with(MetricsRun.simple())
|
||||
.withConfiguration(AutoConfigurations.of(StartupTimeMetricsAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void startupTimeMetricsAreRecorded() {
|
||||
this.contextRunner.run((context) -> {
|
||||
assertThat(context).hasSingleBean(StartupTimeMetrics.class);
|
||||
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
|
||||
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
|
||||
context.getSourceApplicationContext(), Duration.ofMillis(1500)));
|
||||
TimeGauge startedTimeGage = registry.find("application.started.time").timeGauge();
|
||||
assertThat(startedTimeGage).isNotNull();
|
||||
assertThat(startedTimeGage.value(TimeUnit.MILLISECONDS)).isEqualTo(1500L);
|
||||
context.publishEvent(new ApplicationReadyEvent(new SpringApplication(), null,
|
||||
context.getSourceApplicationContext(), Duration.ofMillis(2000)));
|
||||
TimeGauge readyTimeGage = registry.find("application.ready.time").timeGauge();
|
||||
assertThat(readyTimeGage).isNotNull();
|
||||
assertThat(readyTimeGage.value(TimeUnit.MILLISECONDS)).isEqualTo(2000L);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void startupTimeMetricsCanBeDisabled() {
|
||||
this.contextRunner.withPropertyValues("management.metrics.enable.application.started.time:false",
|
||||
"management.metrics.enable.application.ready.time:false").run((context) -> {
|
||||
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
|
||||
context.getSourceApplicationContext(), Duration.ofMillis(2500)));
|
||||
context.publishEvent(new ApplicationReadyEvent(new SpringApplication(), null,
|
||||
context.getSourceApplicationContext(), Duration.ofMillis(3000)));
|
||||
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
|
||||
assertThat(registry.find("application.started.time").timeGauge()).isNull();
|
||||
assertThat(registry.find("application.ready.time").timeGauge()).isNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void customStartupTimeMetricsAreRespected() {
|
||||
this.contextRunner
|
||||
.withBean("customStartupTimeMetrics", StartupTimeMetrics.class, () -> mock(StartupTimeMetrics.class))
|
||||
.run((context) -> assertThat(context).hasSingleBean(StartupTimeMetrics.class)
|
||||
.hasBean("customStartupTimeMetrics"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -35,6 +35,7 @@ import org.springframework.boot.web.embedded.jetty.JettyReactiveWebServerFactory
|
||||
import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory;
|
||||
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext;
|
||||
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.server.reactive.HttpHandler;
|
||||
@@ -57,8 +58,7 @@ class JettyMetricsAutoConfigurationTests {
|
||||
ServletWebServerFactoryAutoConfiguration.class))
|
||||
.withUserConfiguration(ServletWebServerConfiguration.class, MeterRegistryConfiguration.class)
|
||||
.run((context) -> {
|
||||
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
|
||||
context.getSourceApplicationContext()));
|
||||
context.publishEvent(createApplicationStartedEvent(context.getSourceApplicationContext()));
|
||||
assertThat(context).hasSingleBean(JettyServerThreadPoolMetricsBinder.class);
|
||||
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
|
||||
assertThat(registry.find("jetty.threads.config.min").meter()).isNotNull();
|
||||
@@ -72,8 +72,7 @@ class JettyMetricsAutoConfigurationTests {
|
||||
ReactiveWebServerFactoryAutoConfiguration.class))
|
||||
.withUserConfiguration(ReactiveWebServerConfiguration.class, MeterRegistryConfiguration.class)
|
||||
.run((context) -> {
|
||||
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
|
||||
context.getSourceApplicationContext()));
|
||||
context.publishEvent(createApplicationStartedEvent(context.getSourceApplicationContext()));
|
||||
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
|
||||
assertThat(registry.find("jetty.threads.config.min").meter()).isNotNull();
|
||||
});
|
||||
@@ -94,8 +93,7 @@ class JettyMetricsAutoConfigurationTests {
|
||||
ServletWebServerFactoryAutoConfiguration.class))
|
||||
.withUserConfiguration(ServletWebServerConfiguration.class, MeterRegistryConfiguration.class)
|
||||
.run((context) -> {
|
||||
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
|
||||
context.getSourceApplicationContext()));
|
||||
context.publishEvent(createApplicationStartedEvent(context.getSourceApplicationContext()));
|
||||
assertThat(context).hasSingleBean(JettyConnectionMetricsBinder.class);
|
||||
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
|
||||
assertThat(registry.find("jetty.connections.messages.in").meter()).isNotNull();
|
||||
@@ -109,8 +107,7 @@ class JettyMetricsAutoConfigurationTests {
|
||||
ReactiveWebServerFactoryAutoConfiguration.class))
|
||||
.withUserConfiguration(ReactiveWebServerConfiguration.class, MeterRegistryConfiguration.class)
|
||||
.run((context) -> {
|
||||
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
|
||||
context.getSourceApplicationContext()));
|
||||
context.publishEvent(createApplicationStartedEvent(context.getSourceApplicationContext()));
|
||||
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
|
||||
assertThat(registry.find("jetty.connections.messages.in").meter()).isNotNull();
|
||||
});
|
||||
@@ -124,8 +121,7 @@ class JettyMetricsAutoConfigurationTests {
|
||||
.withUserConfiguration(ServletWebServerConfiguration.class, CustomJettyConnectionMetricsBinder.class,
|
||||
MeterRegistryConfiguration.class)
|
||||
.run((context) -> {
|
||||
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
|
||||
context.getSourceApplicationContext()));
|
||||
context.publishEvent(createApplicationStartedEvent(context.getSourceApplicationContext()));
|
||||
assertThat(context).hasSingleBean(JettyConnectionMetricsBinder.class)
|
||||
.hasBean("customJettyConnectionMetricsBinder");
|
||||
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
|
||||
@@ -143,8 +139,7 @@ class JettyMetricsAutoConfigurationTests {
|
||||
.withPropertyValues("server.ssl.enabled: true", "server.ssl.key-store: src/test/resources/test.jks",
|
||||
"server.ssl.key-store-password: secret", "server.ssl.key-password: password")
|
||||
.run((context) -> {
|
||||
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
|
||||
context.getSourceApplicationContext()));
|
||||
context.publishEvent(createApplicationStartedEvent(context.getSourceApplicationContext()));
|
||||
assertThat(context).hasSingleBean(JettySslHandshakeMetricsBinder.class);
|
||||
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
|
||||
assertThat(registry.find("jetty.ssl.handshakes").meter()).isNotNull();
|
||||
@@ -160,8 +155,7 @@ class JettyMetricsAutoConfigurationTests {
|
||||
.withPropertyValues("server.ssl.enabled: true", "server.ssl.key-store: src/test/resources/test.jks",
|
||||
"server.ssl.key-store-password: secret", "server.ssl.key-password: password")
|
||||
.run((context) -> {
|
||||
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
|
||||
context.getSourceApplicationContext()));
|
||||
context.publishEvent(createApplicationStartedEvent(context.getSourceApplicationContext()));
|
||||
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
|
||||
assertThat(registry.find("jetty.ssl.handshakes").meter()).isNotNull();
|
||||
});
|
||||
@@ -177,8 +171,7 @@ class JettyMetricsAutoConfigurationTests {
|
||||
.withPropertyValues("server.ssl.enabled: true", "server.ssl.key-store: src/test/resources/test.jks",
|
||||
"server.ssl.key-store-password: secret", "server.ssl.key-password: password")
|
||||
.run((context) -> {
|
||||
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
|
||||
context.getSourceApplicationContext()));
|
||||
context.publishEvent(createApplicationStartedEvent(context.getSourceApplicationContext()));
|
||||
assertThat(context).hasSingleBean(JettySslHandshakeMetricsBinder.class)
|
||||
.hasBean("customJettySslHandshakeMetricsBinder");
|
||||
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
|
||||
@@ -213,6 +206,10 @@ class JettyMetricsAutoConfigurationTests {
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(JettySslHandshakeMetricsBinder.class));
|
||||
}
|
||||
|
||||
private ApplicationStartedEvent createApplicationStartedEvent(ConfigurableApplicationContext context) {
|
||||
return new ApplicationStartedEvent(new SpringApplication(), null, context, null);
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class MeterRegistryConfiguration {
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactor
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatWebServer;
|
||||
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext;
|
||||
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.server.reactive.HttpHandler;
|
||||
@@ -61,8 +62,7 @@ class TomcatMetricsAutoConfigurationTests {
|
||||
ServletWebServerFactoryAutoConfiguration.class))
|
||||
.withUserConfiguration(ServletWebServerConfiguration.class, MeterRegistryConfiguration.class)
|
||||
.withPropertyValues("server.tomcat.mbeanregistry.enabled=true").run((context) -> {
|
||||
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
|
||||
context.getSourceApplicationContext()));
|
||||
context.publishEvent(createApplicationStartedEvent(context.getSourceApplicationContext()));
|
||||
assertThat(context).hasSingleBean(TomcatMetricsBinder.class);
|
||||
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
|
||||
assertThat(registry.find("tomcat.sessions.active.max").meter()).isNotNull();
|
||||
@@ -78,8 +78,7 @@ class TomcatMetricsAutoConfigurationTests {
|
||||
ReactiveWebServerFactoryAutoConfiguration.class))
|
||||
.withUserConfiguration(ReactiveWebServerConfiguration.class, MeterRegistryConfiguration.class)
|
||||
.withPropertyValues("server.tomcat.mbeanregistry.enabled=true").run((context) -> {
|
||||
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
|
||||
context.getSourceApplicationContext()));
|
||||
context.publishEvent(createApplicationStartedEvent(context.getSourceApplicationContext()));
|
||||
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
|
||||
assertThat(registry.find("tomcat.sessions.active.max").meter()).isNotNull();
|
||||
assertThat(registry.find("tomcat.threads.current").meter()).isNotNull();
|
||||
@@ -109,6 +108,10 @@ class TomcatMetricsAutoConfigurationTests {
|
||||
.hasBean("customTomcatMetrics"));
|
||||
}
|
||||
|
||||
private ApplicationStartedEvent createApplicationStartedEvent(ConfigurableApplicationContext context) {
|
||||
return new ApplicationStartedEvent(new SpringApplication(), null, context, null);
|
||||
}
|
||||
|
||||
private void resetTomcatState() {
|
||||
ReflectionTestUtils.setField(Registry.class, "registry", null);
|
||||
AtomicInteger containerCounter = (AtomicInteger) ReflectionTestUtils.getField(TomcatWebServer.class,
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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.metrics.startup;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.Tag;
|
||||
import io.micrometer.core.instrument.Tags;
|
||||
import io.micrometer.core.instrument.TimeGauge;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.boot.context.event.ApplicationStartedEvent;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.event.SmartApplicationListener;
|
||||
|
||||
/**
|
||||
* Binds application startup metrics in response to {@link ApplicationStartedEvent} and
|
||||
* {@link ApplicationReadyEvent}.
|
||||
*
|
||||
* @author Chris Bono
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public class StartupTimeMetrics implements SmartApplicationListener {
|
||||
|
||||
/**
|
||||
* The default name to use for the application started time metric.
|
||||
*/
|
||||
public static final String APPLICATION_STARTED_TIME_METRIC_NAME = "application.started.time";
|
||||
|
||||
/**
|
||||
* The default name to use for the application ready time metric.
|
||||
*/
|
||||
public static final String APPLICATION_READY_TIME_METRIC_NAME = "application.ready.time";
|
||||
|
||||
private final MeterRegistry meterRegistry;
|
||||
|
||||
private final String applicationStartedTimeMetricName;
|
||||
|
||||
private final String applicationReadyTimeMetricName;
|
||||
|
||||
private final Iterable<Tag> tags;
|
||||
|
||||
/**
|
||||
* Create a new instance using default metric names.
|
||||
* @param meterRegistry the registry to use
|
||||
* @see #APPLICATION_STARTED_TIME_METRIC_NAME
|
||||
* @see #APPLICATION_READY_TIME_METRIC_NAME
|
||||
*/
|
||||
public StartupTimeMetrics(MeterRegistry meterRegistry) {
|
||||
this(meterRegistry, Collections.emptyList(), APPLICATION_STARTED_TIME_METRIC_NAME,
|
||||
APPLICATION_READY_TIME_METRIC_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance using the specified options.
|
||||
* @param meterRegistry the registry to use
|
||||
* @param tags the tags to associate to application startup metrics
|
||||
* @param applicationStartedTimeMetricName the name to use for the application started
|
||||
* time metric
|
||||
* @param applicationReadyTimeMetricName the name to use for the application ready
|
||||
* time metric
|
||||
*/
|
||||
public StartupTimeMetrics(MeterRegistry meterRegistry, Iterable<Tag> tags, String applicationStartedTimeMetricName,
|
||||
String applicationReadyTimeMetricName) {
|
||||
this.meterRegistry = meterRegistry;
|
||||
this.tags = (tags != null) ? tags : Collections.emptyList();
|
||||
this.applicationStartedTimeMetricName = applicationStartedTimeMetricName;
|
||||
this.applicationReadyTimeMetricName = applicationReadyTimeMetricName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
|
||||
return ApplicationStartedEvent.class.isAssignableFrom(eventType)
|
||||
|| ApplicationReadyEvent.class.isAssignableFrom(eventType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationEvent event) {
|
||||
if (event instanceof ApplicationStartedEvent) {
|
||||
onApplicationStarted((ApplicationStartedEvent) event);
|
||||
}
|
||||
if (event instanceof ApplicationReadyEvent) {
|
||||
onApplicationReady((ApplicationReadyEvent) event);
|
||||
}
|
||||
}
|
||||
|
||||
private void onApplicationStarted(ApplicationStartedEvent event) {
|
||||
if (event.getStartedTime() == null) {
|
||||
return;
|
||||
}
|
||||
registerGauge(this.applicationStartedTimeMetricName, "Time taken (ms) to start the application",
|
||||
event.getStartedTime(), createTagsFrom(event.getSpringApplication()));
|
||||
}
|
||||
|
||||
private void onApplicationReady(ApplicationReadyEvent event) {
|
||||
if (event.getReadyTime() == null) {
|
||||
return;
|
||||
}
|
||||
registerGauge(this.applicationReadyTimeMetricName,
|
||||
"Time taken (ms) for the application to be ready to serve requests", event.getReadyTime(),
|
||||
createTagsFrom(event.getSpringApplication()));
|
||||
}
|
||||
|
||||
private void registerGauge(String metricName, String description, Duration time, Iterable<Tag> tags) {
|
||||
TimeGauge.builder(metricName, time::toMillis, TimeUnit.MILLISECONDS).tags(tags).description(description)
|
||||
.register(this.meterRegistry);
|
||||
}
|
||||
|
||||
private Iterable<Tag> createTagsFrom(SpringApplication springApplication) {
|
||||
Class<?> mainClass = springApplication.getMainApplicationClass();
|
||||
if (mainClass == null) {
|
||||
return this.tags;
|
||||
}
|
||||
return Tags.concat(this.tags, "main-application-class", mainClass.getName());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Actuator support for startup metrics.
|
||||
*/
|
||||
package org.springframework.boot.actuate.metrics.startup;
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://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.metrics.startup;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.Tags;
|
||||
import io.micrometer.core.instrument.TimeGauge;
|
||||
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.boot.context.event.ApplicationStartedEvent;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link StartupTimeMetrics}.
|
||||
*
|
||||
* @author Chris Bono
|
||||
*/
|
||||
class StartupTimeMetricsTests {
|
||||
|
||||
private MeterRegistry registry;
|
||||
|
||||
private StartupTimeMetrics metrics;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
this.registry = new SimpleMeterRegistry();
|
||||
this.metrics = new StartupTimeMetrics(this.registry);
|
||||
}
|
||||
|
||||
@Test
|
||||
void metricsRecordedWithoutCustomTags() {
|
||||
this.metrics.onApplicationEvent(applicationStartedEvent(2000L));
|
||||
this.metrics.onApplicationEvent(applicationReadyEvent(2200L));
|
||||
assertMetricExistsWithValue("application.started.time", 2000L);
|
||||
assertMetricExistsWithValue("application.ready.time", 2200L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void metricsRecordedWithCustomTagsAndMetricNames() {
|
||||
Tags tags = Tags.of("foo", "bar");
|
||||
this.metrics = new StartupTimeMetrics(this.registry, tags, "m1", "m2");
|
||||
this.metrics.onApplicationEvent(applicationStartedEvent(1000L));
|
||||
this.metrics.onApplicationEvent(applicationReadyEvent(1050L));
|
||||
assertMetricExistsWithCustomTagsAndValue("m1", tags, 1000L);
|
||||
assertMetricExistsWithCustomTagsAndValue("m2", tags, 1050L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void metricRecordedWithoutMainAppClassTag() {
|
||||
SpringApplication application = mock(SpringApplication.class);
|
||||
this.metrics.onApplicationEvent(new ApplicationStartedEvent(application, null, null, Duration.ofSeconds(2)));
|
||||
TimeGauge applicationStartedGague = this.registry.find("application.started.time").timeGauge();
|
||||
assertThat(applicationStartedGague).isNotNull();
|
||||
assertThat(applicationStartedGague.getId().getTags()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void metricRecordedWithoutMainAppClassTagAndAdditionalTags() {
|
||||
SpringApplication application = mock(SpringApplication.class);
|
||||
Tags tags = Tags.of("foo", "bar");
|
||||
this.metrics = new StartupTimeMetrics(this.registry, tags, "started", "ready");
|
||||
this.metrics.onApplicationEvent(new ApplicationReadyEvent(application, null, null, Duration.ofSeconds(2)));
|
||||
TimeGauge applicationReadyGague = this.registry.find("ready").timeGauge();
|
||||
assertThat(applicationReadyGague).isNotNull();
|
||||
assertThat(applicationReadyGague.getId().getTags()).containsExactlyElementsOf(tags);
|
||||
}
|
||||
|
||||
@Test
|
||||
void metricsNotRecordedWhenStartupTimeNotAvailable() {
|
||||
this.metrics.onApplicationEvent(applicationStartedEvent(null));
|
||||
this.metrics.onApplicationEvent(applicationReadyEvent(null));
|
||||
assertThat(this.registry.find("application.started.time").timeGauge()).isNull();
|
||||
assertThat(this.registry.find("application.ready.time").timeGauge()).isNull();
|
||||
}
|
||||
|
||||
private ApplicationStartedEvent applicationStartedEvent(Long startupTimeMs) {
|
||||
SpringApplication application = mock(SpringApplication.class);
|
||||
doReturn(TestMainApplication.class).when(application).getMainApplicationClass();
|
||||
return new ApplicationStartedEvent(application, null, null,
|
||||
(startupTimeMs != null) ? Duration.ofMillis(startupTimeMs) : null);
|
||||
}
|
||||
|
||||
private ApplicationReadyEvent applicationReadyEvent(Long startupTimeMs) {
|
||||
SpringApplication application = mock(SpringApplication.class);
|
||||
doReturn(TestMainApplication.class).when(application).getMainApplicationClass();
|
||||
return new ApplicationReadyEvent(application, null, null,
|
||||
(startupTimeMs != null) ? Duration.ofMillis(startupTimeMs) : null);
|
||||
}
|
||||
|
||||
private void assertMetricExistsWithValue(String metricName, long expectedValueInMillis) {
|
||||
assertMetricExistsWithCustomTagsAndValue(metricName, Tags.empty(), expectedValueInMillis);
|
||||
}
|
||||
|
||||
private void assertMetricExistsWithCustomTagsAndValue(String metricName, Tags expectedCustomTags,
|
||||
Long expectedValueInMillis) {
|
||||
assertThat(this.registry.find(metricName)
|
||||
.tags(Tags.concat(expectedCustomTags, "main-application-class", TestMainApplication.class.getName()))
|
||||
.timeGauge()).isNotNull().extracting((m) -> m.value(TimeUnit.MILLISECONDS))
|
||||
.isEqualTo(expectedValueInMillis.doubleValue());
|
||||
}
|
||||
|
||||
static class TestMainApplication {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -110,7 +110,7 @@ class RestartApplicationListenerTests {
|
||||
listener.onApplicationEvent(new ApplicationFailedEvent(application, ARGS, context, new RuntimeException()));
|
||||
}
|
||||
else {
|
||||
listener.onApplicationEvent(new ApplicationReadyEvent(application, ARGS, context));
|
||||
listener.onApplicationEvent(new ApplicationReadyEvent(application, ARGS, context, null));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -656,6 +656,17 @@ The following system metrics are provided:
|
||||
|
||||
|
||||
|
||||
[[actuator.metrics.supported.application-startup]]
|
||||
==== Application Startup Metrics
|
||||
Auto-configuration exposes application startup time metrics:
|
||||
|
||||
* `application.started.time`: time taken to start the application.
|
||||
* `application.ready.time`: time taken for the application to be ready to serve requests.
|
||||
|
||||
Metrics are tagged by the fully qualified name of the application class.
|
||||
|
||||
|
||||
|
||||
[[actuator.metrics.supported.logger]]
|
||||
==== Logger Metrics
|
||||
Auto-configuration enables the event metrics for both Logback and Log4J2.
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.boot;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@@ -150,6 +151,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Madhura Bhave
|
||||
* @author Brian Clozel
|
||||
* @author Ethan Rubinson
|
||||
* @author Chris Bono
|
||||
* @since 1.0.0
|
||||
* @see #run(Class, String[])
|
||||
* @see #run(Class[], String[])
|
||||
@@ -302,10 +304,12 @@ public class SpringApplication {
|
||||
refreshContext(context);
|
||||
afterRefresh(context, applicationArguments);
|
||||
stopWatch.stop();
|
||||
Duration startedTime = Duration.ofMillis(stopWatch.getTotalTimeMillis());
|
||||
stopWatch.start();
|
||||
if (this.logStartupInfo) {
|
||||
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
|
||||
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), startedTime);
|
||||
}
|
||||
listeners.started(context);
|
||||
listeners.started(context, startedTime);
|
||||
callRunners(context, applicationArguments);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
@@ -314,7 +318,8 @@ public class SpringApplication {
|
||||
}
|
||||
|
||||
try {
|
||||
listeners.running(context);
|
||||
stopWatch.stop();
|
||||
listeners.running(context, Duration.ofMillis(stopWatch.getTotalTimeMillis()));
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
handleRunFailure(context, ex, null);
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.boot;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
@@ -31,6 +33,7 @@ import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
* @author Phillip Webb
|
||||
* @author Dave Syer
|
||||
* @author Andy Wilkinson
|
||||
* @author Chris Bono
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public interface SpringApplicationRunListener {
|
||||
@@ -75,8 +78,25 @@ public interface SpringApplicationRunListener {
|
||||
* ApplicationRunners} have not been called.
|
||||
* @param context the application context.
|
||||
* @since 2.0.0
|
||||
* @deprecated since 2.6.0 for removal in 2.8.0 in favour of
|
||||
* {@link #started(ConfigurableApplicationContext, Duration)}
|
||||
*/
|
||||
@Deprecated
|
||||
default void started(ConfigurableApplicationContext context) {
|
||||
started(context, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* The context has been refreshed and the application has started but
|
||||
* {@link CommandLineRunner CommandLineRunners} and {@link ApplicationRunner
|
||||
* ApplicationRunners} have not been called.
|
||||
* @param context the application context.
|
||||
* @param startedTime the time taken to start the application or {@code null} if
|
||||
* unknown
|
||||
* @since 2.6.0
|
||||
*/
|
||||
default void started(ConfigurableApplicationContext context, Duration startedTime) {
|
||||
started(context);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,9 +104,26 @@ public interface SpringApplicationRunListener {
|
||||
* been refreshed and all {@link CommandLineRunner CommandLineRunners} and
|
||||
* {@link ApplicationRunner ApplicationRunners} have been called.
|
||||
* @param context the application context.
|
||||
* @deprecated since 2.6.0 for removal in 2.8.0 in favour of
|
||||
* {@link #running(ConfigurableApplicationContext, Duration)}
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Deprecated
|
||||
default void running(ConfigurableApplicationContext context) {
|
||||
running(context, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called immediately before the run method finishes, when the application context has
|
||||
* been refreshed and all {@link CommandLineRunner CommandLineRunners} and
|
||||
* {@link ApplicationRunner ApplicationRunners} have been called.
|
||||
* @param context the application context.
|
||||
* @param readyTime the time taken for the application to be ready to service requests
|
||||
* or {@code null} if unknown
|
||||
* @since 2.6.0
|
||||
*/
|
||||
default void running(ConfigurableApplicationContext context, Duration readyTime) {
|
||||
running(context);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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,6 +16,7 @@
|
||||
|
||||
package org.springframework.boot;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -33,6 +34,8 @@ import org.springframework.util.ReflectionUtils;
|
||||
* A collection of {@link SpringApplicationRunListener}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
* @author Chris Bono
|
||||
*/
|
||||
class SpringApplicationRunListeners {
|
||||
|
||||
@@ -71,12 +74,12 @@ class SpringApplicationRunListeners {
|
||||
doWithListeners("spring.boot.application.context-loaded", (listener) -> listener.contextLoaded(context));
|
||||
}
|
||||
|
||||
void started(ConfigurableApplicationContext context) {
|
||||
doWithListeners("spring.boot.application.started", (listener) -> listener.started(context));
|
||||
void started(ConfigurableApplicationContext context, Duration startupTime) {
|
||||
doWithListeners("spring.boot.application.started", (listener) -> listener.started(context, startupTime));
|
||||
}
|
||||
|
||||
void running(ConfigurableApplicationContext context) {
|
||||
doWithListeners("spring.boot.application.running", (listener) -> listener.running(context));
|
||||
void running(ConfigurableApplicationContext context, Duration startupTime) {
|
||||
doWithListeners("spring.boot.application.running", (listener) -> listener.running(context, startupTime));
|
||||
}
|
||||
|
||||
void failed(ConfigurableApplicationContext context, Throwable exception) {
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.boot;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.net.InetAddress;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
@@ -29,7 +30,6 @@ import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StopWatch;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -56,9 +56,9 @@ class StartupInfoLogger {
|
||||
applicationLog.debug(LogMessage.of(this::getRunningMessage));
|
||||
}
|
||||
|
||||
void logStarted(Log applicationLog, StopWatch stopWatch) {
|
||||
void logStarted(Log applicationLog, Duration startupTime) {
|
||||
if (applicationLog.isInfoEnabled()) {
|
||||
applicationLog.info(getStartedMessage(stopWatch));
|
||||
applicationLog.info(getStartedMessage(startupTime));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,12 +83,12 @@ class StartupInfoLogger {
|
||||
return message;
|
||||
}
|
||||
|
||||
private CharSequence getStartedMessage(StopWatch stopWatch) {
|
||||
private CharSequence getStartedMessage(Duration startupTime) {
|
||||
StringBuilder message = new StringBuilder();
|
||||
message.append("Started ");
|
||||
appendApplicationName(message);
|
||||
message.append(" in ");
|
||||
message.append(stopWatch.getTotalTimeMillis() / 1000.0);
|
||||
message.append(startupTime.toMillis() / 1000.0);
|
||||
message.append(" seconds");
|
||||
try {
|
||||
double uptime = ManagementFactory.getRuntimeMXBean().getUptime() / 1000.0;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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,6 +16,8 @@
|
||||
|
||||
package org.springframework.boot.context.event;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
@@ -26,6 +28,7 @@ import org.springframework.context.ConfigurableApplicationContext;
|
||||
* have been completed by then.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Chris Bono
|
||||
* @since 1.3.0
|
||||
* @see ApplicationFailedEvent
|
||||
*/
|
||||
@@ -34,15 +37,34 @@ public class ApplicationReadyEvent extends SpringApplicationEvent {
|
||||
|
||||
private final ConfigurableApplicationContext context;
|
||||
|
||||
private final Duration readyTime;
|
||||
|
||||
/**
|
||||
* Create a new {@link ApplicationReadyEvent} instance.
|
||||
* @param application the current application
|
||||
* @param args the arguments the application is running with
|
||||
* @param context the context that was being created
|
||||
* @deprecated since 2.6.0 for removal in 2.8.0 in favor of
|
||||
* {@link #ApplicationReadyEvent(SpringApplication, String[], ConfigurableApplicationContext, Duration)}
|
||||
*/
|
||||
@Deprecated
|
||||
public ApplicationReadyEvent(SpringApplication application, String[] args, ConfigurableApplicationContext context) {
|
||||
this(application, args, context, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ApplicationReadyEvent} instance.
|
||||
* @param application the current application
|
||||
* @param args the arguments the application is running with
|
||||
* @param context the context that was being created
|
||||
* @param readyTime the time taken to get the application ready to service requests
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public ApplicationReadyEvent(SpringApplication application, String[] args, ConfigurableApplicationContext context,
|
||||
Duration readyTime) {
|
||||
super(application, args);
|
||||
this.context = context;
|
||||
this.readyTime = readyTime;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,4 +75,13 @@ public class ApplicationReadyEvent extends SpringApplicationEvent {
|
||||
return this.context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the time taken for the application to be ready to service requests, or
|
||||
* {@code null} if unknown.
|
||||
* @return the time taken to be ready to service requests
|
||||
*/
|
||||
public Duration getReadyTime() {
|
||||
return this.readyTime;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.boot.context.event;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
@@ -34,16 +36,35 @@ public class ApplicationStartedEvent extends SpringApplicationEvent {
|
||||
|
||||
private final ConfigurableApplicationContext context;
|
||||
|
||||
private final Duration startedTime;
|
||||
|
||||
/**
|
||||
* Create a new {@link ApplicationStartedEvent} instance.
|
||||
* @param application the current application
|
||||
* @param args the arguments the application is running with
|
||||
* @param context the context that was being created
|
||||
* @deprecated since 2.6.0 for removal in 2.8.0 in favor of
|
||||
* {@link #ApplicationStartedEvent(SpringApplication, String[], ConfigurableApplicationContext, Duration)}
|
||||
*/
|
||||
@Deprecated
|
||||
public ApplicationStartedEvent(SpringApplication application, String[] args,
|
||||
ConfigurableApplicationContext context) {
|
||||
this(application, args, context, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ApplicationStartedEvent} instance.
|
||||
* @param application the current application
|
||||
* @param args the arguments the application is running with
|
||||
* @param context the context that was being created
|
||||
* @param startedTime the time taken to start the application
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public ApplicationStartedEvent(SpringApplication application, String[] args, ConfigurableApplicationContext context,
|
||||
Duration startedTime) {
|
||||
super(application, args);
|
||||
this.context = context;
|
||||
this.startedTime = startedTime;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,4 +75,12 @@ public class ApplicationStartedEvent extends SpringApplicationEvent {
|
||||
return this.context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the time taken to start the application, or {@code null} if unknown.
|
||||
* @return the startup time
|
||||
*/
|
||||
public Duration getStartedTime() {
|
||||
return this.startedTime;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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,6 +16,8 @@
|
||||
|
||||
package org.springframework.boot.context.event;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
@@ -46,6 +48,7 @@ import org.springframework.util.ErrorHandler;
|
||||
* @author Andy Wilkinson
|
||||
* @author Artsiom Yudovin
|
||||
* @author Brian Clozel
|
||||
* @author Chris Bono
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
|
||||
@@ -101,14 +104,14 @@ public class EventPublishingRunListener implements SpringApplicationRunListener,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void started(ConfigurableApplicationContext context) {
|
||||
context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
|
||||
public void started(ConfigurableApplicationContext context, Duration startedTime) {
|
||||
context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context, startedTime));
|
||||
AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void running(ConfigurableApplicationContext context) {
|
||||
context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context));
|
||||
public void running(ConfigurableApplicationContext context, Duration readyTime) {
|
||||
context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context, readyTime));
|
||||
AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);
|
||||
}
|
||||
|
||||
|
||||
@@ -88,6 +88,7 @@ import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.event.ApplicationEventMulticaster;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.context.event.SimpleApplicationEventMulticaster;
|
||||
import org.springframework.context.event.SmartApplicationListener;
|
||||
import org.springframework.context.support.AbstractApplicationContext;
|
||||
import org.springframework.context.support.StaticApplicationContext;
|
||||
import org.springframework.core.Ordered;
|
||||
@@ -149,6 +150,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
* @author Artsiom Yudovin
|
||||
* @author Marten Deinum
|
||||
* @author Nguyen Bao Sach
|
||||
* @author Chris Bono
|
||||
*/
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
class SpringApplicationTests {
|
||||
@@ -361,36 +363,18 @@ class SpringApplicationTests {
|
||||
void applicationRunningEventListener() {
|
||||
SpringApplication application = new SpringApplication(ExampleConfig.class);
|
||||
application.setWebApplicationType(WebApplicationType.NONE);
|
||||
final AtomicReference<SpringApplication> reference = new AtomicReference<>();
|
||||
class ApplicationReadyEventListener implements ApplicationListener<ApplicationReadyEvent> {
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationReadyEvent event) {
|
||||
reference.set(event.getSpringApplication());
|
||||
}
|
||||
|
||||
}
|
||||
application.addListeners(new ApplicationReadyEventListener());
|
||||
AtomicReference<ApplicationReadyEvent> reference = setupListener(application, ApplicationReadyEvent.class);
|
||||
this.context = application.run("--foo=bar");
|
||||
assertThat(application).isSameAs(reference.get());
|
||||
assertThat(application).isSameAs(reference.get().getSpringApplication());
|
||||
}
|
||||
|
||||
@Test
|
||||
void contextRefreshedEventListener() {
|
||||
SpringApplication application = new SpringApplication(ExampleConfig.class);
|
||||
application.setWebApplicationType(WebApplicationType.NONE);
|
||||
final AtomicReference<ApplicationContext> reference = new AtomicReference<>();
|
||||
class InitializerListener implements ApplicationListener<ContextRefreshedEvent> {
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ContextRefreshedEvent event) {
|
||||
reference.set(event.getApplicationContext());
|
||||
}
|
||||
|
||||
}
|
||||
application.setListeners(Collections.singletonList(new InitializerListener()));
|
||||
AtomicReference<ContextRefreshedEvent> reference = setupListener(application, ContextRefreshedEvent.class);
|
||||
this.context = application.run("--foo=bar");
|
||||
assertThat(this.context).isSameAs(reference.get());
|
||||
assertThat(this.context).isSameAs(reference.get().getApplicationContext());
|
||||
// Custom initializers do not switch off the defaults
|
||||
assertThat(getEnvironment().getProperty("foo")).isEqualTo("bar");
|
||||
}
|
||||
@@ -417,6 +401,24 @@ class SpringApplicationTests {
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
}
|
||||
|
||||
@Test
|
||||
void applicationStartedEventHasStartedTime() {
|
||||
SpringApplication application = new SpringApplication(ExampleConfig.class);
|
||||
application.setWebApplicationType(WebApplicationType.NONE);
|
||||
AtomicReference<ApplicationStartedEvent> reference = setupListener(application, ApplicationStartedEvent.class);
|
||||
this.context = application.run();
|
||||
assertThat(reference.get()).isNotNull().extracting(ApplicationStartedEvent::getStartedTime).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void applicationReadyEventHasReadyTime() {
|
||||
SpringApplication application = new SpringApplication(ExampleConfig.class);
|
||||
application.setWebApplicationType(WebApplicationType.NONE);
|
||||
AtomicReference<ApplicationReadyEvent> reference = setupListener(application, ApplicationReadyEvent.class);
|
||||
this.context = application.run();
|
||||
assertThat(reference.get()).isNotNull().extracting(ApplicationReadyEvent::getReadyTime).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultApplicationContext() {
|
||||
SpringApplication application = new SpringApplication(ExampleConfig.class);
|
||||
@@ -1250,6 +1252,27 @@ class SpringApplicationTests {
|
||||
&& ((AvailabilityChangeEvent<?>) argument).getState().equals(state);
|
||||
}
|
||||
|
||||
private <T extends ApplicationEvent> AtomicReference<T> setupListener(SpringApplication application,
|
||||
Class<T> targetEventType) {
|
||||
final AtomicReference<T> reference = new AtomicReference<>();
|
||||
class TestEventListener implements SmartApplicationListener {
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void onApplicationEvent(ApplicationEvent event) {
|
||||
reference.set((T) event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
|
||||
return targetEventType.isAssignableFrom(eventType);
|
||||
}
|
||||
|
||||
}
|
||||
application.addListeners(new TestEventListener());
|
||||
return reference;
|
||||
}
|
||||
|
||||
private Condition<ConfigurableEnvironment> matchingPropertySource(final Class<?> propertySourceClass,
|
||||
final String name) {
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.boot;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.time.Duration;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -59,7 +60,7 @@ class StartupInfoLoggerTests {
|
||||
stopWatch.start();
|
||||
given(this.log.isInfoEnabled()).willReturn(true);
|
||||
stopWatch.stop();
|
||||
new StartupInfoLogger(getClass()).logStarted(this.log, stopWatch);
|
||||
new StartupInfoLogger(getClass()).logStarted(this.log, Duration.ofMillis(stopWatch.getTotalTimeMillis()));
|
||||
ArgumentCaptor<Object> captor = ArgumentCaptor.forClass(Object.class);
|
||||
verify(this.log).info(captor.capture());
|
||||
assertThat(captor.getValue().toString()).matches("Started " + getClass().getSimpleName()
|
||||
|
||||
@@ -88,10 +88,10 @@ class SpringApplicationAdminMXBeanRegistrarTests {
|
||||
SpringApplicationAdminMXBeanRegistrar registrar = new SpringApplicationAdminMXBeanRegistrar(OBJECT_NAME);
|
||||
ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class);
|
||||
registrar.setApplicationContext(context);
|
||||
registrar.onApplicationReadyEvent(
|
||||
new ApplicationReadyEvent(new SpringApplication(), null, mock(ConfigurableApplicationContext.class)));
|
||||
registrar.onApplicationReadyEvent(new ApplicationReadyEvent(new SpringApplication(), null,
|
||||
mock(ConfigurableApplicationContext.class), null));
|
||||
assertThat(isApplicationReady(registrar)).isFalse();
|
||||
registrar.onApplicationReadyEvent(new ApplicationReadyEvent(new SpringApplication(), null, context));
|
||||
registrar.onApplicationReadyEvent(new ApplicationReadyEvent(new SpringApplication(), null, context, null));
|
||||
assertThat(isApplicationReady(registrar)).isTrue();
|
||||
}
|
||||
|
||||
|
||||
@@ -187,7 +187,7 @@ class ApplicationPidFileWriterTests {
|
||||
ConfigurableEnvironment environment = createEnvironment(propName, propValue);
|
||||
ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class);
|
||||
given(context.getEnvironment()).willReturn(environment);
|
||||
return new ApplicationReadyEvent(new SpringApplication(), new String[] {}, context);
|
||||
return new ApplicationReadyEvent(new SpringApplication(), new String[] {}, context, null);
|
||||
}
|
||||
|
||||
private ConfigurableEnvironment createEnvironment(String propName, String propValue) {
|
||||
|
||||
@@ -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.
|
||||
@@ -72,9 +72,9 @@ class EventPublishingRunListenerTests {
|
||||
this.runListener.contextLoaded(context);
|
||||
checkApplicationEvents(ApplicationPreparedEvent.class);
|
||||
context.refresh();
|
||||
this.runListener.started(context);
|
||||
this.runListener.started(context, null);
|
||||
checkApplicationEvents(ApplicationStartedEvent.class, AvailabilityChangeEvent.class);
|
||||
this.runListener.running(context);
|
||||
this.runListener.running(context, null);
|
||||
checkApplicationEvents(ApplicationReadyEvent.class, AvailabilityChangeEvent.class);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user