From 878386f29a331d440679298003c8cde5259b0eb1 Mon Sep 17 00:00:00 2001 From: Giovanni Lovato Date: Tue, 9 Jun 2026 11:21:32 +0200 Subject: [PATCH 01/15] refactor: expose SpringMetricsServiceInitListener as public top-level for reuse by the starter --- .../spring/ObservabilityConfiguration.java | 35 ---------- .../SpringMetricsServiceInitListener.java | 69 +++++++++++++++++++ 2 files changed, 69 insertions(+), 35 deletions(-) create mode 100644 observability-kit-spring/src/main/java/com/vaadin/observability/spring/SpringMetricsServiceInitListener.java diff --git a/observability-kit-spring/src/main/java/com/vaadin/observability/spring/ObservabilityConfiguration.java b/observability-kit-spring/src/main/java/com/vaadin/observability/spring/ObservabilityConfiguration.java index 36a04d6..6a2b3ae 100644 --- a/observability-kit-spring/src/main/java/com/vaadin/observability/spring/ObservabilityConfiguration.java +++ b/observability-kit-spring/src/main/java/com/vaadin/observability/spring/ObservabilityConfiguration.java @@ -15,7 +15,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import com.vaadin.flow.server.VaadinRequest; import com.vaadin.observability.micrometer.MetricsServiceInitListener; import com.vaadin.observability.micrometer.ObservabilitySettings; @@ -119,38 +118,4 @@ MetricsServiceInitListener metricsServiceInitListener( observationRegistry.getIfAvailable(), settings); } - /** - * Spring-aware subclass that skips the default Observation handler - * registration: in Spring/Boot setups the framework already registers a - * {@code DefaultMeterObservationHandler} on the shared - * {@link ObservationRegistry} (via Boot's - * {@code ObservationAutoConfiguration} or the user's own - * {@code @Configuration}), so re-registering here would double-emit Timers. - * It also delegates HTTP observation enrichment to - * {@link SpringHttpObservationEnricher}. - */ - static class SpringMetricsServiceInitListener - extends MetricsServiceInitListener { - - SpringMetricsServiceInitListener(MeterRegistry registry, - ObservationRegistry observationRegistry, - ObservabilitySettings settings) { - super(registry, observationRegistry, settings); - } - - @Override - protected void installDefaultObservationHandlers( - ObservationRegistry observationRegistry, - MeterRegistry registry) { - // No-op: Spring Boot Actuator's ObservationAutoConfiguration - // registers DefaultMeterObservationHandler; re-registering would - // double-emit Timers. - } - - @Override - protected void enrichHttpObservation(VaadinRequest request, - String requestType) { - SpringHttpObservationEnricher.enrich(request, requestType); - } - } } diff --git a/observability-kit-spring/src/main/java/com/vaadin/observability/spring/SpringMetricsServiceInitListener.java b/observability-kit-spring/src/main/java/com/vaadin/observability/spring/SpringMetricsServiceInitListener.java new file mode 100644 index 0000000..a20d0ea --- /dev/null +++ b/observability-kit-spring/src/main/java/com/vaadin/observability/spring/SpringMetricsServiceInitListener.java @@ -0,0 +1,69 @@ +/** + * Copyright (C) 2026 Vaadin Ltd + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * See for the full + * license. + */ +package com.vaadin.observability.spring; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.observation.ObservationRegistry; + +import com.vaadin.flow.server.VaadinRequest; +import com.vaadin.observability.micrometer.MetricsServiceInitListener; +import com.vaadin.observability.micrometer.ObservabilitySettings; + +/** + * Spring/Boot-aware {@link MetricsServiceInitListener} that skips the default + * Observation handler registration and enriches the HTTP server observation. + *

+ * In Spring/Boot setups the framework already registers a + * {@code DefaultMeterObservationHandler} on the shared + * {@link ObservationRegistry} (via Boot's {@code ObservationAutoConfiguration} + * or the user's own {@code @Configuration}), so re-registering here would + * double-emit Timers. HTTP observation enrichment is delegated to + * {@link SpringHttpObservationEnricher}, making the parent HTTP span render as + * e.g. {@code http post /vaadin/uidl} instead of the generic + * {@code http post /**}. + *

+ * This class is declared {@code public} so it can be reused by both + * {@link ObservabilityConfiguration} (plain-Spring import) and the Boot + * auto-configuration starter. + */ +public final class SpringMetricsServiceInitListener + extends MetricsServiceInitListener { + + /** + * Creates a new listener. + * + * @param registry + * the Micrometer meter registry, must not be {@code null} + * @param observationRegistry + * the Micrometer observation registry; may be {@code null} when + * no {@link ObservationRegistry} bean is present — traces will + * be skipped in that case + * @param settings + * the observability settings, must not be {@code null} + */ + public SpringMetricsServiceInitListener(MeterRegistry registry, + ObservationRegistry observationRegistry, + ObservabilitySettings settings) { + super(registry, observationRegistry, settings); + } + + @Override + protected void installDefaultObservationHandlers( + ObservationRegistry observationRegistry, MeterRegistry registry) { + // No-op: Spring Boot Actuator's ObservationAutoConfiguration + // registers DefaultMeterObservationHandler; re-registering would + // double-emit Timers. + } + + @Override + protected void enrichHttpObservation(VaadinRequest request, + String requestType) { + SpringHttpObservationEnricher.enrich(request, requestType); + } +} From c603e982f33abc7f45d2ac0f721c1923cff80eb6 Mon Sep 17 00:00:00 2001 From: Giovanni Lovato Date: Tue, 9 Jun 2026 11:27:11 +0200 Subject: [PATCH 02/15] feat: add observability-kit-starter (Spring Boot auto-configuration) --- observability-kit-starter/pom.xml | 72 +++++++++ .../boot/ObservabilityAutoConfiguration.java | 60 ++++++++ .../spring/boot/ObservabilityProperties.java | 138 ++++++++++++++++++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../ObservabilityAutoConfigurationTest.java | 114 +++++++++++++++ pom.xml | 1 + 6 files changed, 386 insertions(+) create mode 100644 observability-kit-starter/pom.xml create mode 100644 observability-kit-starter/src/main/java/com/vaadin/observability/spring/boot/ObservabilityAutoConfiguration.java create mode 100644 observability-kit-starter/src/main/java/com/vaadin/observability/spring/boot/ObservabilityProperties.java create mode 100644 observability-kit-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports create mode 100644 observability-kit-starter/src/test/java/com/vaadin/observability/spring/boot/ObservabilityAutoConfigurationTest.java diff --git a/observability-kit-starter/pom.xml b/observability-kit-starter/pom.xml new file mode 100644 index 0000000..f6fdad4 --- /dev/null +++ b/observability-kit-starter/pom.xml @@ -0,0 +1,72 @@ + + + + 4.0.0 + + + com.vaadin + observability-kit + 5.0-SNAPSHOT + + + observability-kit-starter + jar + Observability Kit :: Spring Boot Starter + Spring Boot auto-configuration for Observability Kit. + + + + com.vaadin + observability-kit-micrometer + ${project.version} + + + com.vaadin + observability-kit-spring + ${project.version} + + + com.vaadin + vaadin-spring + + + org.springframework.boot + spring-boot-autoconfigure + + + org.springframework.boot + spring-boot-actuator-autoconfigure + true + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.assertj + assertj-core + test + + + org.mockito + mockito-junit-jupiter + test + + + org.junit.jupiter + junit-jupiter + test + + + + diff --git a/observability-kit-starter/src/main/java/com/vaadin/observability/spring/boot/ObservabilityAutoConfiguration.java b/observability-kit-starter/src/main/java/com/vaadin/observability/spring/boot/ObservabilityAutoConfiguration.java new file mode 100644 index 0000000..ba69c71 --- /dev/null +++ b/observability-kit-starter/src/main/java/com/vaadin/observability/spring/boot/ObservabilityAutoConfiguration.java @@ -0,0 +1,60 @@ +/** + * Copyright (C) 2000-2026 Vaadin Ltd + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * See for the full + * license. + */ +package com.vaadin.observability.spring.boot; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.observation.ObservationRegistry; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; + +import com.vaadin.flow.server.VaadinService; +import com.vaadin.observability.micrometer.MetricsServiceInitListener; +import com.vaadin.observability.micrometer.ObservabilitySettings; +import com.vaadin.observability.spring.SpringMetricsServiceInitListener; + +/** + * Auto-configures the Observability Kit {@link MetricsServiceInitListener} when + * a {@link MeterRegistry} is present in the Spring context. + *

+ * Activation is gated by the {@code vaadin.observability.enabled} property + * (default {@code true}); the listener is also skipped when the user supplies + * their own {@link MetricsServiceInitListener} bean. + */ +@AutoConfiguration(afterName = { + "org.springframework.boot.micrometer.metrics.autoconfigure.MetricsAutoConfiguration", + "org.springframework.boot.micrometer.metrics.autoconfigure.CompositeMeterRegistryAutoConfiguration" }) +@ConditionalOnClass({ MeterRegistry.class, VaadinService.class }) +@ConditionalOnProperty(prefix = "vaadin.observability", name = "enabled", havingValue = "true", matchIfMissing = true) +@EnableConfigurationProperties(ObservabilityProperties.class) +public class ObservabilityAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + ObservabilitySettings observabilitySettings( + ObservabilityProperties properties) { + return properties.toSettings(); + } + + @Bean + @ConditionalOnBean(MeterRegistry.class) + @ConditionalOnMissingBean + MetricsServiceInitListener metricsServiceInitListener( + MeterRegistry registry, + ObjectProvider observationRegistry, + ObservabilitySettings settings) { + return new SpringMetricsServiceInitListener(registry, + observationRegistry.getIfAvailable(), settings); + } +} diff --git a/observability-kit-starter/src/main/java/com/vaadin/observability/spring/boot/ObservabilityProperties.java b/observability-kit-starter/src/main/java/com/vaadin/observability/spring/boot/ObservabilityProperties.java new file mode 100644 index 0000000..7b556d2 --- /dev/null +++ b/observability-kit-starter/src/main/java/com/vaadin/observability/spring/boot/ObservabilityProperties.java @@ -0,0 +1,138 @@ +/** + * Copyright (C) 2000-2026 Vaadin Ltd + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * See for the full + * license. + */ +package com.vaadin.observability.spring.boot; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +import com.vaadin.observability.micrometer.ObservabilitySettings; + +/** + * Boot-bound configuration properties under the {@code vaadin.observability} + * prefix. Converted to a plain {@link ObservabilitySettings} via + * {@link #toSettings()}. + */ +@ConfigurationProperties(prefix = "vaadin.observability") +public class ObservabilityProperties { + + private boolean enabled = true; + private boolean sessions = true; + private boolean uis = true; + private boolean navigation = true; + private boolean requests = true; + private boolean errors = true; + private boolean client = true; + private boolean traces = true; + private boolean tracesSessionId = false; + private int routeCardinalityLimit = 200; + private int clientRatePerSession = 100; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isSessions() { + return sessions; + } + + public void setSessions(boolean sessions) { + this.sessions = sessions; + } + + public boolean isUis() { + return uis; + } + + public void setUis(boolean uis) { + this.uis = uis; + } + + public boolean isNavigation() { + return navigation; + } + + public void setNavigation(boolean navigation) { + this.navigation = navigation; + } + + public boolean isRequests() { + return requests; + } + + public void setRequests(boolean requests) { + this.requests = requests; + } + + public boolean isErrors() { + return errors; + } + + public void setErrors(boolean errors) { + this.errors = errors; + } + + public boolean isClient() { + return client; + } + + public void setClient(boolean client) { + this.client = client; + } + + public boolean isTraces() { + return traces; + } + + public void setTraces(boolean traces) { + this.traces = traces; + } + + public boolean isTracesSessionId() { + return tracesSessionId; + } + + public void setTracesSessionId(boolean tracesSessionId) { + this.tracesSessionId = tracesSessionId; + } + + public int getRouteCardinalityLimit() { + return routeCardinalityLimit; + } + + public void setRouteCardinalityLimit(int routeCardinalityLimit) { + this.routeCardinalityLimit = routeCardinalityLimit; + } + + public int getClientRatePerSession() { + return clientRatePerSession; + } + + public void setClientRatePerSession(int clientRatePerSession) { + this.clientRatePerSession = clientRatePerSession; + } + + /** + * Converts these properties to an {@link ObservabilitySettings} instance. + * The {@code enabled} flag is not included in settings; it only gates + * activation of the auto-configuration. + * + * @return a new {@link ObservabilitySettings} built from the current + * property values + */ + public ObservabilitySettings toSettings() { + return ObservabilitySettings.builder().sessions(sessions).uis(uis) + .navigation(navigation).requests(requests).errors(errors) + .client(client).traces(traces).tracesSessionId(tracesSessionId) + .routeCardinalityLimit(routeCardinalityLimit) + .clientRatePerSession(clientRatePerSession).build(); + } +} diff --git a/observability-kit-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/observability-kit-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..4087369 --- /dev/null +++ b/observability-kit-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.vaadin.observability.spring.boot.ObservabilityAutoConfiguration diff --git a/observability-kit-starter/src/test/java/com/vaadin/observability/spring/boot/ObservabilityAutoConfigurationTest.java b/observability-kit-starter/src/test/java/com/vaadin/observability/spring/boot/ObservabilityAutoConfigurationTest.java new file mode 100644 index 0000000..f80f190 --- /dev/null +++ b/observability-kit-starter/src/test/java/com/vaadin/observability/spring/boot/ObservabilityAutoConfigurationTest.java @@ -0,0 +1,114 @@ +/** + * Copyright (C) 2000-2026 Vaadin Ltd + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * See for the full + * license. + */ +package com.vaadin.observability.spring.boot; + +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import com.vaadin.observability.micrometer.MetricsServiceInitListener; +import com.vaadin.observability.micrometer.ObservabilitySettings; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Unit tests for {@link ObservabilityAutoConfiguration} using + * {@link ApplicationContextRunner}. + */ +class ObservabilityAutoConfigurationTest { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations + .of(ObservabilityAutoConfiguration.class)); + + /** + * Default context with a MeterRegistry bean: both ObservabilitySettings and + * MetricsServiceInitListener should be present. + */ + @Test + void defaultConfiguration_withMeterRegistry_registersBeansExpected() { + contextRunner + .withBean(SimpleMeterRegistry.class, SimpleMeterRegistry::new) + .run(context -> { + assertThat(context) + .hasSingleBean(ObservabilitySettings.class); + assertThat(context) + .hasSingleBean(MetricsServiceInitListener.class); + }); + } + + /** + * When vaadin.observability.enabled=false, the auto-configuration should + * not activate and no beans should be registered. + */ + @Test + void disabledProperty_doesNotRegisterBeans() { + contextRunner.withPropertyValues("vaadin.observability.enabled=false") + .run(context -> { + assertThat(context) + .doesNotHaveBean(MetricsServiceInitListener.class); + assertThat(context) + .doesNotHaveBean(ObservabilitySettings.class); + }); + } + + /** + * Property binding: sessions=false and route-cardinality-limit=42 should be + * reflected in the ObservabilitySettings bean. + */ + @Test + void propertyBinding_reflectedInSettings() { + contextRunner + .withBean(SimpleMeterRegistry.class, SimpleMeterRegistry::new) + .withPropertyValues("vaadin.observability.sessions=false", + "vaadin.observability.route-cardinality-limit=42") + .run(context -> { + assertThat(context) + .hasSingleBean(ObservabilitySettings.class); + ObservabilitySettings settings = context + .getBean(ObservabilitySettings.class); + assertThat(settings.isSessions()).isFalse(); + assertThat(settings.getRouteCardinalityLimit()) + .isEqualTo(42); + }); + } + + /** + * Without a MeterRegistry bean, the MetricsServiceInitListener should not + * be registered (gated by @ConditionalOnBean(MeterRegistry.class)). The + * ObservabilitySettings bean may still be present. + */ + @Test + void noMeterRegistry_doesNotRegisterListener() { + contextRunner.run(context -> assertThat(context) + .doesNotHaveBean(MetricsServiceInitListener.class)); + } + + /** + * User-supplied MetricsServiceInitListener bean: our auto-configured + * listener should back off (@ConditionalOnMissingBean), and the custom bean + * should be used. + */ + @Test + void userSuppliedListener_autoConfigBacksOff() { + MetricsServiceInitListener customListener = new MetricsServiceInitListener(); + contextRunner + .withBean("custom", MetricsServiceInitListener.class, + () -> customListener) + .withBean(SimpleMeterRegistry.class, SimpleMeterRegistry::new) + .run(context -> { + assertThat(context) + .hasSingleBean(MetricsServiceInitListener.class); + assertThat( + context.getBean(MetricsServiceInitListener.class)) + .isSameAs(customListener); + }); + } +} diff --git a/pom.xml b/pom.xml index b3c95f9..4b53e3b 100644 --- a/pom.xml +++ b/pom.xml @@ -27,6 +27,7 @@ observability-kit-micrometer observability-kit-spring + observability-kit-starter From 6839a9db4101a497f4d7c47949ca1d49019b64aa Mon Sep 17 00:00:00 2001 From: Giovanni Lovato Date: Tue, 9 Jun 2026 11:36:33 +0200 Subject: [PATCH 03/15] test: cover settings-bean presence + user-settings backoff; trim redundant starter deps --- observability-kit-starter/pom.xml | 19 ---------- .../boot/ObservabilityAutoConfiguration.java | 2 +- .../ObservabilityAutoConfigurationTest.java | 35 ++++++++++++++++--- 3 files changed, 32 insertions(+), 24 deletions(-) diff --git a/observability-kit-starter/pom.xml b/observability-kit-starter/pom.xml index f6fdad4..c6c4aaf 100644 --- a/observability-kit-starter/pom.xml +++ b/observability-kit-starter/pom.xml @@ -27,10 +27,6 @@ observability-kit-spring ${project.version} - - com.vaadin - vaadin-spring - org.springframework.boot spring-boot-autoconfigure @@ -52,21 +48,6 @@ spring-boot-starter-test test - - org.assertj - assertj-core - test - - - org.mockito - mockito-junit-jupiter - test - - - org.junit.jupiter - junit-jupiter - test - diff --git a/observability-kit-starter/src/main/java/com/vaadin/observability/spring/boot/ObservabilityAutoConfiguration.java b/observability-kit-starter/src/main/java/com/vaadin/observability/spring/boot/ObservabilityAutoConfiguration.java index ba69c71..58e4cb0 100644 --- a/observability-kit-starter/src/main/java/com/vaadin/observability/spring/boot/ObservabilityAutoConfiguration.java +++ b/observability-kit-starter/src/main/java/com/vaadin/observability/spring/boot/ObservabilityAutoConfiguration.java @@ -49,7 +49,7 @@ ObservabilitySettings observabilitySettings( @Bean @ConditionalOnBean(MeterRegistry.class) - @ConditionalOnMissingBean + @ConditionalOnMissingBean(MetricsServiceInitListener.class) MetricsServiceInitListener metricsServiceInitListener( MeterRegistry registry, ObjectProvider observationRegistry, diff --git a/observability-kit-starter/src/test/java/com/vaadin/observability/spring/boot/ObservabilityAutoConfigurationTest.java b/observability-kit-starter/src/test/java/com/vaadin/observability/spring/boot/ObservabilityAutoConfigurationTest.java index f80f190..860e37e 100644 --- a/observability-kit-starter/src/test/java/com/vaadin/observability/spring/boot/ObservabilityAutoConfigurationTest.java +++ b/observability-kit-starter/src/test/java/com/vaadin/observability/spring/boot/ObservabilityAutoConfigurationTest.java @@ -83,12 +83,37 @@ void propertyBinding_reflectedInSettings() { /** * Without a MeterRegistry bean, the MetricsServiceInitListener should not * be registered (gated by @ConditionalOnBean(MeterRegistry.class)). The - * ObservabilitySettings bean may still be present. + * ObservabilitySettings bean is NOT gated on a MeterRegistry, so it must + * still be present. */ @Test void noMeterRegistry_doesNotRegisterListener() { - contextRunner.run(context -> assertThat(context) - .doesNotHaveBean(MetricsServiceInitListener.class)); + contextRunner.run(context -> { + assertThat(context) + .doesNotHaveBean(MetricsServiceInitListener.class); + assertThat(context).hasSingleBean(ObservabilitySettings.class); + }); + } + + /** + * User-supplied ObservabilitySettings bean: our auto-configured settings + * bean should back off (@ConditionalOnMissingBean), and the custom bean + * should be used. + */ + @Test + void userSuppliedSettings_autoConfigBacksOff() { + contextRunner + .withBean(SimpleMeterRegistry.class, + SimpleMeterRegistry::new) + .withBean(ObservabilitySettings.class, + () -> ObservabilitySettings.builder().sessions(false) + .build()) + .run(context -> { + assertThat(context) + .hasSingleBean(ObservabilitySettings.class); + assertThat(context.getBean(ObservabilitySettings.class) + .isSessions()).isFalse(); + }); } /** @@ -98,7 +123,9 @@ void noMeterRegistry_doesNotRegisterListener() { */ @Test void userSuppliedListener_autoConfigBacksOff() { - MetricsServiceInitListener customListener = new MetricsServiceInitListener(); + MetricsServiceInitListener customListener = new MetricsServiceInitListener( + new SimpleMeterRegistry(), + ObservabilitySettings.builder().build()); contextRunner .withBean("custom", MetricsServiceInitListener.class, () -> customListener) From 7048973b3543bb83e1471a3747fe104ac34ec129 Mon Sep 17 00:00:00 2001 From: Giovanni Lovato Date: Tue, 9 Jun 2026 11:42:44 +0200 Subject: [PATCH 04/15] refactor: rename IT modules to observability-kit-tests-* and extract shared test base MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Aggregator artifactId: observability-kit-micrometer-test → observability-kit-tests - New observability-kit-test-common (jar) with AbstractIT in pkg com.vaadin.observability.tests.common; compile-scope testbench deps - Standalone IT dir renamed to observability-kit-tests-micrometer, artifactId observability-kit-tests-micrometer, pkg com.vaadin.observability.tests.micrometer; depends on test-common --- .../observability-kit-test-common/pom.xml | 29 +++++++++++++++++++ .../tests/common}/AbstractIT.java | 5 ++-- .../pom.xml | 10 +++++-- .../src/main/frontend/index.html | 0 .../tests/micrometer}/AppServlet.java | 2 +- .../tests/micrometer}/HelloView.java | 2 +- .../tests/micrometer}/MetricsServlet.java | 2 +- .../tests/micrometer}/MicrometerRegistry.java | 2 +- .../tests/micrometer}/MicrometerSetup.java | 2 +- .../micrometer}/StandaloneMetricsIT.java | 3 +- observability-kit-tests/pom.xml | 7 +++-- 11 files changed, 51 insertions(+), 13 deletions(-) create mode 100644 observability-kit-tests/observability-kit-test-common/pom.xml rename observability-kit-tests/{observability-kit-micrometer-tests/src/test/java/com/vaadin/observability/micrometer/tests => observability-kit-test-common/src/main/java/com/vaadin/observability/tests/common}/AbstractIT.java (96%) rename observability-kit-tests/{observability-kit-micrometer-tests => observability-kit-tests-micrometer}/pom.xml (93%) rename observability-kit-tests/{observability-kit-micrometer-tests => observability-kit-tests-micrometer}/src/main/frontend/index.html (100%) rename observability-kit-tests/{observability-kit-micrometer-tests/src/main/java/com/vaadin/observability/micrometer/tests => observability-kit-tests-micrometer/src/main/java/com/vaadin/observability/tests/micrometer}/AppServlet.java (93%) rename observability-kit-tests/{observability-kit-micrometer-tests/src/main/java/com/vaadin/observability/micrometer/tests => observability-kit-tests-micrometer/src/main/java/com/vaadin/observability/tests/micrometer}/HelloView.java (93%) rename observability-kit-tests/{observability-kit-micrometer-tests/src/main/java/com/vaadin/observability/micrometer/tests => observability-kit-tests-micrometer/src/main/java/com/vaadin/observability/tests/micrometer}/MetricsServlet.java (97%) rename observability-kit-tests/{observability-kit-micrometer-tests/src/main/java/com/vaadin/observability/micrometer/tests => observability-kit-tests-micrometer/src/main/java/com/vaadin/observability/tests/micrometer}/MicrometerRegistry.java (92%) rename observability-kit-tests/{observability-kit-micrometer-tests/src/main/java/com/vaadin/observability/micrometer/tests => observability-kit-tests-micrometer/src/main/java/com/vaadin/observability/tests/micrometer}/MicrometerSetup.java (94%) rename observability-kit-tests/{observability-kit-micrometer-tests/src/test/java/com/vaadin/observability/micrometer/tests => observability-kit-tests-micrometer/src/test/java/com/vaadin/observability/tests/micrometer}/StandaloneMetricsIT.java (97%) diff --git a/observability-kit-tests/observability-kit-test-common/pom.xml b/observability-kit-tests/observability-kit-test-common/pom.xml new file mode 100644 index 0000000..d31d2b7 --- /dev/null +++ b/observability-kit-tests/observability-kit-test-common/pom.xml @@ -0,0 +1,29 @@ + + + 4.0.0 + + com.vaadin + observability-kit-tests + 5.0-SNAPSHOT + + observability-kit-test-common + + jar + Shared test base for Observability-kit integration tests + + + + com.vaadin + vaadin-testbench-core-junit5 + + + org.seleniumhq.selenium + selenium-java + + + org.junit.jupiter + junit-jupiter + + + + diff --git a/observability-kit-tests/observability-kit-micrometer-tests/src/test/java/com/vaadin/observability/micrometer/tests/AbstractIT.java b/observability-kit-tests/observability-kit-test-common/src/main/java/com/vaadin/observability/tests/common/AbstractIT.java similarity index 96% rename from observability-kit-tests/observability-kit-micrometer-tests/src/test/java/com/vaadin/observability/micrometer/tests/AbstractIT.java rename to observability-kit-tests/observability-kit-test-common/src/main/java/com/vaadin/observability/tests/common/AbstractIT.java index ddaaa9f..d08deb2 100644 --- a/observability-kit-tests/observability-kit-micrometer-tests/src/test/java/com/vaadin/observability/micrometer/tests/AbstractIT.java +++ b/observability-kit-tests/observability-kit-test-common/src/main/java/com/vaadin/observability/tests/common/AbstractIT.java @@ -6,7 +6,7 @@ * See for the full * license. */ -package com.vaadin.observability.micrometer.tests; +package com.vaadin.observability.tests.common; import java.lang.management.ManagementFactory; @@ -33,7 +33,8 @@ * test before each test method. */ @Execution(ExecutionMode.SAME_THREAD) -abstract class AbstractIT extends BrowserTestBase implements DriverSupplier { +public abstract class AbstractIT extends BrowserTestBase + implements DriverSupplier { static final int SERVER_PORT = Integer.getInteger("serverPort", 8080); diff --git a/observability-kit-tests/observability-kit-micrometer-tests/pom.xml b/observability-kit-tests/observability-kit-tests-micrometer/pom.xml similarity index 93% rename from observability-kit-tests/observability-kit-micrometer-tests/pom.xml rename to observability-kit-tests/observability-kit-tests-micrometer/pom.xml index 90a7e56..feac412 100644 --- a/observability-kit-tests/observability-kit-micrometer-tests/pom.xml +++ b/observability-kit-tests/observability-kit-tests-micrometer/pom.xml @@ -3,10 +3,10 @@ 4.0.0 com.vaadin - observability-kit-micrometer-test + observability-kit-tests 5.0-SNAPSHOT - observability-kit-micrometer-tests + observability-kit-tests-micrometer war Test Vaadin Micrometer integration on a real Jetty deployment @@ -17,6 +17,12 @@ observability-kit-micrometer ${project.version} + + com.vaadin + observability-kit-test-common + ${project.version} + test + com.vaadin vaadin-testbench-core-junit5 diff --git a/observability-kit-tests/observability-kit-micrometer-tests/src/main/frontend/index.html b/observability-kit-tests/observability-kit-tests-micrometer/src/main/frontend/index.html similarity index 100% rename from observability-kit-tests/observability-kit-micrometer-tests/src/main/frontend/index.html rename to observability-kit-tests/observability-kit-tests-micrometer/src/main/frontend/index.html diff --git a/observability-kit-tests/observability-kit-micrometer-tests/src/main/java/com/vaadin/observability/micrometer/tests/AppServlet.java b/observability-kit-tests/observability-kit-tests-micrometer/src/main/java/com/vaadin/observability/tests/micrometer/AppServlet.java similarity index 93% rename from observability-kit-tests/observability-kit-micrometer-tests/src/main/java/com/vaadin/observability/micrometer/tests/AppServlet.java rename to observability-kit-tests/observability-kit-tests-micrometer/src/main/java/com/vaadin/observability/tests/micrometer/AppServlet.java index c24c770..e945af0 100644 --- a/observability-kit-tests/observability-kit-micrometer-tests/src/main/java/com/vaadin/observability/micrometer/tests/AppServlet.java +++ b/observability-kit-tests/observability-kit-tests-micrometer/src/main/java/com/vaadin/observability/tests/micrometer/AppServlet.java @@ -6,7 +6,7 @@ * See for the full * license. */ -package com.vaadin.observability.micrometer.tests; +package com.vaadin.observability.tests.micrometer; import jakarta.servlet.annotation.WebServlet; diff --git a/observability-kit-tests/observability-kit-micrometer-tests/src/main/java/com/vaadin/observability/micrometer/tests/HelloView.java b/observability-kit-tests/observability-kit-tests-micrometer/src/main/java/com/vaadin/observability/tests/micrometer/HelloView.java similarity index 93% rename from observability-kit-tests/observability-kit-micrometer-tests/src/main/java/com/vaadin/observability/micrometer/tests/HelloView.java rename to observability-kit-tests/observability-kit-tests-micrometer/src/main/java/com/vaadin/observability/tests/micrometer/HelloView.java index 4666140..065cd89 100644 --- a/observability-kit-tests/observability-kit-micrometer-tests/src/main/java/com/vaadin/observability/micrometer/tests/HelloView.java +++ b/observability-kit-tests/observability-kit-tests-micrometer/src/main/java/com/vaadin/observability/tests/micrometer/HelloView.java @@ -6,7 +6,7 @@ * See for the full * license. */ -package com.vaadin.observability.micrometer.tests; +package com.vaadin.observability.tests.micrometer; import com.vaadin.flow.component.html.Div; import com.vaadin.flow.component.html.Span; diff --git a/observability-kit-tests/observability-kit-micrometer-tests/src/main/java/com/vaadin/observability/micrometer/tests/MetricsServlet.java b/observability-kit-tests/observability-kit-tests-micrometer/src/main/java/com/vaadin/observability/tests/micrometer/MetricsServlet.java similarity index 97% rename from observability-kit-tests/observability-kit-micrometer-tests/src/main/java/com/vaadin/observability/micrometer/tests/MetricsServlet.java rename to observability-kit-tests/observability-kit-tests-micrometer/src/main/java/com/vaadin/observability/tests/micrometer/MetricsServlet.java index 6a0e832..4893b28 100644 --- a/observability-kit-tests/observability-kit-micrometer-tests/src/main/java/com/vaadin/observability/micrometer/tests/MetricsServlet.java +++ b/observability-kit-tests/observability-kit-tests-micrometer/src/main/java/com/vaadin/observability/tests/micrometer/MetricsServlet.java @@ -6,7 +6,7 @@ * See for the full * license. */ -package com.vaadin.observability.micrometer.tests; +package com.vaadin.observability.tests.micrometer; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; diff --git a/observability-kit-tests/observability-kit-micrometer-tests/src/main/java/com/vaadin/observability/micrometer/tests/MicrometerRegistry.java b/observability-kit-tests/observability-kit-tests-micrometer/src/main/java/com/vaadin/observability/tests/micrometer/MicrometerRegistry.java similarity index 92% rename from observability-kit-tests/observability-kit-micrometer-tests/src/main/java/com/vaadin/observability/micrometer/tests/MicrometerRegistry.java rename to observability-kit-tests/observability-kit-tests-micrometer/src/main/java/com/vaadin/observability/tests/micrometer/MicrometerRegistry.java index e115078..2730dec 100644 --- a/observability-kit-tests/observability-kit-micrometer-tests/src/main/java/com/vaadin/observability/micrometer/tests/MicrometerRegistry.java +++ b/observability-kit-tests/observability-kit-tests-micrometer/src/main/java/com/vaadin/observability/tests/micrometer/MicrometerRegistry.java @@ -6,7 +6,7 @@ * See for the full * license. */ -package com.vaadin.observability.micrometer.tests; +package com.vaadin.observability.tests.micrometer; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; diff --git a/observability-kit-tests/observability-kit-micrometer-tests/src/main/java/com/vaadin/observability/micrometer/tests/MicrometerSetup.java b/observability-kit-tests/observability-kit-tests-micrometer/src/main/java/com/vaadin/observability/tests/micrometer/MicrometerSetup.java similarity index 94% rename from observability-kit-tests/observability-kit-micrometer-tests/src/main/java/com/vaadin/observability/micrometer/tests/MicrometerSetup.java rename to observability-kit-tests/observability-kit-tests-micrometer/src/main/java/com/vaadin/observability/tests/micrometer/MicrometerSetup.java index adb77ec..b9bda05 100644 --- a/observability-kit-tests/observability-kit-micrometer-tests/src/main/java/com/vaadin/observability/micrometer/tests/MicrometerSetup.java +++ b/observability-kit-tests/observability-kit-tests-micrometer/src/main/java/com/vaadin/observability/tests/micrometer/MicrometerSetup.java @@ -6,7 +6,7 @@ * See for the full * license. */ -package com.vaadin.observability.micrometer.tests; +package com.vaadin.observability.tests.micrometer; import jakarta.servlet.ServletContextEvent; import jakarta.servlet.ServletContextListener; diff --git a/observability-kit-tests/observability-kit-micrometer-tests/src/test/java/com/vaadin/observability/micrometer/tests/StandaloneMetricsIT.java b/observability-kit-tests/observability-kit-tests-micrometer/src/test/java/com/vaadin/observability/tests/micrometer/StandaloneMetricsIT.java similarity index 97% rename from observability-kit-tests/observability-kit-micrometer-tests/src/test/java/com/vaadin/observability/micrometer/tests/StandaloneMetricsIT.java rename to observability-kit-tests/observability-kit-tests-micrometer/src/test/java/com/vaadin/observability/tests/micrometer/StandaloneMetricsIT.java index 1410e1c..2761704 100644 --- a/observability-kit-tests/observability-kit-micrometer-tests/src/test/java/com/vaadin/observability/micrometer/tests/StandaloneMetricsIT.java +++ b/observability-kit-tests/observability-kit-tests-micrometer/src/test/java/com/vaadin/observability/tests/micrometer/StandaloneMetricsIT.java @@ -6,7 +6,7 @@ * See for the full * license. */ -package com.vaadin.observability.micrometer.tests; +package com.vaadin.observability.tests.micrometer; import java.io.BufferedReader; import java.io.IOException; @@ -20,6 +20,7 @@ import org.junit.jupiter.api.Assertions; import com.vaadin.flow.component.html.testbench.SpanElement; +import com.vaadin.observability.tests.common.AbstractIT; import com.vaadin.testbench.BrowserTest; /** diff --git a/observability-kit-tests/pom.xml b/observability-kit-tests/pom.xml index 62c31ab..7a83d44 100644 --- a/observability-kit-tests/pom.xml +++ b/observability-kit-tests/pom.xml @@ -6,13 +6,14 @@ observability-kit 5.0-SNAPSHOT - observability-kit-micrometer-test + observability-kit-tests pom - Integration tests for Observability-kit Micrometer integration + Integration tests for Observability-kit - observability-kit-micrometer-tests + observability-kit-test-common + observability-kit-tests-micrometer From 59bfbad26e44d94f89702e0933770ccc97812b98 Mon Sep 17 00:00:00 2001 From: Giovanni Lovato Date: Tue, 9 Jun 2026 11:50:03 +0200 Subject: [PATCH 05/15] docs: generalize AbstractIT javadoc for all IT flavors --- .../com/vaadin/observability/tests/common/AbstractIT.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/observability-kit-tests/observability-kit-test-common/src/main/java/com/vaadin/observability/tests/common/AbstractIT.java b/observability-kit-tests/observability-kit-test-common/src/main/java/com/vaadin/observability/tests/common/AbstractIT.java index d08deb2..6fb97c0 100644 --- a/observability-kit-tests/observability-kit-test-common/src/main/java/com/vaadin/observability/tests/common/AbstractIT.java +++ b/observability-kit-tests/observability-kit-test-common/src/main/java/com/vaadin/observability/tests/common/AbstractIT.java @@ -28,9 +28,9 @@ import com.vaadin.testbench.TestBench; /** - * Base class for the Micrometer integration tests. Spins up a headless Chrome - * driver (or connects to a hub when configured) and navigates to the view under - * test before each test method. + * Base class for Observability Kit integration tests. Spins up a headless + * Chrome driver (or connects to a hub when configured) and navigates to the + * view under test before each test method. */ @Execution(ExecutionMode.SAME_THREAD) public abstract class AbstractIT extends BrowserTestBase From 3423217f17d49e7c60e39b246b124a7833203ab2 Mon Sep 17 00:00:00 2001 From: Giovanni Lovato Date: Tue, 9 Jun 2026 11:56:10 +0200 Subject: [PATCH 06/15] test: add plain-Spring integration test module (observability-kit-tests-spring) --- .../observability-kit-tests-spring/pom.xml | 188 ++++++++++++++++++ .../src/main/frontend/index.html | 23 +++ .../tests/spring/AppConfiguration.java | 34 ++++ .../tests/spring/AppWebAppInitializer.java | 25 +++ .../observability/tests/spring/HelloView.java | 27 +++ .../tests/spring/MetricsServlet.java | 71 +++++++ .../tests/spring/SpringMetricsIT.java | 90 +++++++++ observability-kit-tests/pom.xml | 1 + 8 files changed, 459 insertions(+) create mode 100644 observability-kit-tests/observability-kit-tests-spring/pom.xml create mode 100644 observability-kit-tests/observability-kit-tests-spring/src/main/frontend/index.html create mode 100644 observability-kit-tests/observability-kit-tests-spring/src/main/java/com/vaadin/observability/tests/spring/AppConfiguration.java create mode 100644 observability-kit-tests/observability-kit-tests-spring/src/main/java/com/vaadin/observability/tests/spring/AppWebAppInitializer.java create mode 100644 observability-kit-tests/observability-kit-tests-spring/src/main/java/com/vaadin/observability/tests/spring/HelloView.java create mode 100644 observability-kit-tests/observability-kit-tests-spring/src/main/java/com/vaadin/observability/tests/spring/MetricsServlet.java create mode 100644 observability-kit-tests/observability-kit-tests-spring/src/test/java/com/vaadin/observability/tests/spring/SpringMetricsIT.java diff --git a/observability-kit-tests/observability-kit-tests-spring/pom.xml b/observability-kit-tests/observability-kit-tests-spring/pom.xml new file mode 100644 index 0000000..b625f3a --- /dev/null +++ b/observability-kit-tests/observability-kit-tests-spring/pom.xml @@ -0,0 +1,188 @@ + + + 4.0.0 + + com.vaadin + observability-kit-tests + 5.0-SNAPSHOT + + observability-kit-tests-spring + + war + Test Vaadin Observability Kit on a plain-Spring (non-Boot) Jetty deployment + + Integration test that wires observability-kit-spring into a plain Spring MVC WAR + (no Spring Boot) and verifies session / UI / request metrics are recorded. + + + + + + com.vaadin + observability-kit-spring + ${project.version} + + + + org.springframework + spring-webmvc + + + + io.micrometer + micrometer-core + + + + com.vaadin + flow-server + ${flow.version} + + + com.vaadin + flow-client + ${flow.version} + + + com.vaadin + flow-html-components + ${flow.version} + + + jakarta.servlet + jakarta.servlet-api + ${servlet.api.version} + provided + + + + + com.vaadin + observability-kit-test-common + ${project.version} + test + + + com.vaadin + vaadin-testbench-core-junit5 + test + + + com.vaadin + flow-html-components-testbench + ${flow.version} + test + + + org.seleniumhq.selenium + selenium-java + test + + + org.junit.jupiter + junit-jupiter + test + + + org.assertj + assertj-core + test + + + + + jetty:run + + + + com.vaadin + flow-maven-plugin + ${flow.version} + + + + prepare-frontend + build-frontend + + compile + + + + + + org.eclipse.jetty.ee10 + jetty-ee10-maven-plugin + + 12.1.8 + + + 8090 + observability-kit-spring + 5 + + + + start-jetty + + start + + pre-integration-test + + + stop-jetty + + stop + + post-integration-test + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.6.3 + + + enforce-no-spring-boot + + enforce + + validate + + + + + + org.springframework.boot + + + + true + + + + + + + + diff --git a/observability-kit-tests/observability-kit-tests-spring/src/main/frontend/index.html b/observability-kit-tests/observability-kit-tests-spring/src/main/frontend/index.html new file mode 100644 index 0000000..eb0c53b --- /dev/null +++ b/observability-kit-tests/observability-kit-tests-spring/src/main/frontend/index.html @@ -0,0 +1,23 @@ + + + + + + + + + + + + +

+ + diff --git a/observability-kit-tests/observability-kit-tests-spring/src/main/java/com/vaadin/observability/tests/spring/AppConfiguration.java b/observability-kit-tests/observability-kit-tests-spring/src/main/java/com/vaadin/observability/tests/spring/AppConfiguration.java new file mode 100644 index 0000000..9fd69cc --- /dev/null +++ b/observability-kit-tests/observability-kit-tests-spring/src/main/java/com/vaadin/observability/tests/spring/AppConfiguration.java @@ -0,0 +1,34 @@ +/** + * Copyright (C) 2000-2026 Vaadin Ltd + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * See for the full + * license. + */ +package com.vaadin.observability.tests.spring; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import com.vaadin.observability.spring.ObservabilityConfiguration; + +/** + * Plain Spring (non-Boot) configuration that imports the Observability Kit + * Spring wiring and provides a {@link MeterRegistry}. Component-scans the test + * package so the views and the metrics servlet are picked up. + */ +@Configuration +@ComponentScan +@Import(ObservabilityConfiguration.class) +public class AppConfiguration { + + @Bean + public MeterRegistry meterRegistry() { + return new SimpleMeterRegistry(); + } +} diff --git a/observability-kit-tests/observability-kit-tests-spring/src/main/java/com/vaadin/observability/tests/spring/AppWebAppInitializer.java b/observability-kit-tests/observability-kit-tests-spring/src/main/java/com/vaadin/observability/tests/spring/AppWebAppInitializer.java new file mode 100644 index 0000000..e5e06de --- /dev/null +++ b/observability-kit-tests/observability-kit-tests-spring/src/main/java/com/vaadin/observability/tests/spring/AppWebAppInitializer.java @@ -0,0 +1,25 @@ +/** + * Copyright (C) 2000-2026 Vaadin Ltd + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * See for the full + * license. + */ +package com.vaadin.observability.tests.spring; + +import java.util.Collection; +import java.util.List; + +import com.vaadin.flow.spring.VaadinMVCWebAppInitializer; + +/** + * Bootstraps the plain-Spring MVC context with {@link AppConfiguration}. + */ +public class AppWebAppInitializer extends VaadinMVCWebAppInitializer { + + @Override + protected Collection> getConfigurationClasses() { + return List.of(AppConfiguration.class); + } +} diff --git a/observability-kit-tests/observability-kit-tests-spring/src/main/java/com/vaadin/observability/tests/spring/HelloView.java b/observability-kit-tests/observability-kit-tests-spring/src/main/java/com/vaadin/observability/tests/spring/HelloView.java new file mode 100644 index 0000000..8ac4e85 --- /dev/null +++ b/observability-kit-tests/observability-kit-tests-spring/src/main/java/com/vaadin/observability/tests/spring/HelloView.java @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2000-2026 Vaadin Ltd + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * See for the full + * license. + */ +package com.vaadin.observability.tests.spring; + +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.html.Span; +import com.vaadin.flow.router.Route; + +/** + * Simple landing view; opening it drives a real Vaadin session, UI and + * navigation through the framework so the Micrometer binders fire. + */ +@Route("") +public class HelloView extends Div { + + public HelloView() { + Span greeting = new Span("Hello micrometer spring"); + greeting.setId("greeting"); + add(greeting); + } +} diff --git a/observability-kit-tests/observability-kit-tests-spring/src/main/java/com/vaadin/observability/tests/spring/MetricsServlet.java b/observability-kit-tests/observability-kit-tests-spring/src/main/java/com/vaadin/observability/tests/spring/MetricsServlet.java new file mode 100644 index 0000000..6c5ae82 --- /dev/null +++ b/observability-kit-tests/observability-kit-tests-spring/src/main/java/com/vaadin/observability/tests/spring/MetricsServlet.java @@ -0,0 +1,71 @@ +/** + * Copyright (C) 2000-2026 Vaadin Ltd + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * See for the full + * license. + */ +package com.vaadin.observability.tests.spring; + +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.concurrent.TimeUnit; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Timer; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; + +/** + * Dumps the Spring-managed {@link MeterRegistry} as a deterministic, sorted + * plain-text format that the IT can scrape via HTTP. + */ +@WebServlet(urlPatterns = "/metrics", asyncSupported = false) +public class MetricsServlet extends HttpServlet { + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws IOException { + WebApplicationContext ctx = WebApplicationContextUtils + .getRequiredWebApplicationContext(getServletContext()); + MeterRegistry registry = ctx.getBean(MeterRegistry.class); + resp.setContentType("text/plain; charset=utf-8"); + try (PrintWriter writer = resp.getWriter()) { + registry.getMeters().stream() + .sorted((a, b) -> a.getId().getName() + .compareTo(b.getId().getName())) + .forEach(meter -> writeMeter(writer, meter)); + } + } + + private void writeMeter(PrintWriter writer, Meter meter) { + String id = formatId(meter); + if (meter instanceof Counter c) { + writer.printf("%s count=%.0f%n", id, c.count()); + } else if (meter instanceof Gauge g) { + writer.printf("%s value=%.0f%n", id, g.value()); + } else if (meter instanceof Timer t) { + writer.printf("%s count=%d total_ms=%.3f%n", id, t.count(), + t.totalTime(TimeUnit.MILLISECONDS)); + } + } + + private String formatId(Meter meter) { + StringBuilder sb = new StringBuilder(meter.getId().getName()); + for (Tag tag : meter.getId().getTagsAsIterable()) { + sb.append(' ').append(tag.getKey()).append('=') + .append(tag.getValue()); + } + return sb.toString(); + } +} diff --git a/observability-kit-tests/observability-kit-tests-spring/src/test/java/com/vaadin/observability/tests/spring/SpringMetricsIT.java b/observability-kit-tests/observability-kit-tests-spring/src/test/java/com/vaadin/observability/tests/spring/SpringMetricsIT.java new file mode 100644 index 0000000..62c3b59 --- /dev/null +++ b/observability-kit-tests/observability-kit-tests-spring/src/test/java/com/vaadin/observability/tests/spring/SpringMetricsIT.java @@ -0,0 +1,90 @@ +/** + * Copyright (C) 2000-2026 Vaadin Ltd + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * See for the full + * license. + */ +package com.vaadin.observability.tests.spring; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.assertj.core.api.Assertions; + +import com.vaadin.flow.component.html.testbench.SpanElement; +import com.vaadin.observability.tests.common.AbstractIT; +import com.vaadin.testbench.BrowserTest; + +/** + * Drives a real plain-Spring Vaadin Flow page in Chrome and asserts that the + * Spring-managed {@code MeterRegistry} (provided through + * {@code ObservabilityConfiguration}) recorded session / UI / request activity. + */ +public class SpringMetricsIT extends AbstractIT { + + @Override + protected String getTestPath() { + return "/"; + } + + @BrowserTest + public void viewLoadDrivesSessionAndUiMetrics() throws IOException { + SpanElement greeting = $(SpanElement.class).id("greeting"); + Assertions.assertThat(greeting.getText()) + .isEqualTo("Hello micrometer spring"); + + String metrics = fetchMetrics(); + + Assertions + .assertThat( + meterValue(metrics, "vaadin.sessions.created", "count")) + .as("vaadin.sessions.created count") + .isGreaterThanOrEqualTo(1.0); + Assertions.assertThat(meterValue(metrics, "vaadin.ui.created", "count")) + .as("vaadin.ui.created count").isGreaterThanOrEqualTo(1.0); + Assertions + .assertThat( + meterValue(metrics, "vaadin.sessions.active", "value")) + .as("vaadin.sessions.active value").isGreaterThanOrEqualTo(1.0); + Assertions.assertThat(metrics).as("vaadin.request.duration present") + .contains("vaadin.request.duration"); + } + + private String fetchMetrics() throws IOException { + HttpURLConnection conn = (HttpURLConnection) URI + .create(getRootURL() + "/metrics").toURL().openConnection(); + conn.setRequestMethod("GET"); + Assertions.assertThat(conn.getResponseCode()).isEqualTo(200); + StringBuilder out = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader( + conn.getInputStream(), StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + out.append(line).append('\n'); + } + } + return out.toString(); + } + + /** + * Finds the numeric value of {@code field} on the first line that starts + * with {@code meterName}. Returns {@code -1.0} if not present. + */ + private static double meterValue(String metricsBody, String meterName, + String field) { + Pattern pattern = Pattern.compile( + "^" + Pattern.quote(meterName) + "(?:\\s.*)?\\s" + + Pattern.quote(field) + "=([0-9]+(?:\\.[0-9]+)?)", + Pattern.MULTILINE); + Matcher m = pattern.matcher(metricsBody); + return m.find() ? Double.parseDouble(m.group(1)) : -1.0; + } +} diff --git a/observability-kit-tests/pom.xml b/observability-kit-tests/pom.xml index 7a83d44..ce4cffd 100644 --- a/observability-kit-tests/pom.xml +++ b/observability-kit-tests/pom.xml @@ -14,6 +14,7 @@ observability-kit-test-common observability-kit-tests-micrometer + observability-kit-tests-spring From 0ae33dbd08f2466c28385216094cacc9abe9bedf Mon Sep 17 00:00:00 2001 From: Giovanni Lovato Date: Tue, 9 Jun 2026 12:04:55 +0200 Subject: [PATCH 07/15] docs: clarify MetricsServlet is container-registered, not Spring-managed --- .../vaadin/observability/tests/spring/AppConfiguration.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/observability-kit-tests/observability-kit-tests-spring/src/main/java/com/vaadin/observability/tests/spring/AppConfiguration.java b/observability-kit-tests/observability-kit-tests-spring/src/main/java/com/vaadin/observability/tests/spring/AppConfiguration.java index 9fd69cc..119fe1b 100644 --- a/observability-kit-tests/observability-kit-tests-spring/src/main/java/com/vaadin/observability/tests/spring/AppConfiguration.java +++ b/observability-kit-tests/observability-kit-tests-spring/src/main/java/com/vaadin/observability/tests/spring/AppConfiguration.java @@ -20,7 +20,8 @@ /** * Plain Spring (non-Boot) configuration that imports the Observability Kit * Spring wiring and provides a {@link MeterRegistry}. Component-scans the test - * package so the views and the metrics servlet are picked up. + * package for Spring-managed beans; the {@code @WebServlet} metrics servlet is + * registered by the servlet container, not by Spring. */ @Configuration @ComponentScan From 51f2c3a02a4385ad1f3e865a2c2942a89ab30bd0 Mon Sep 17 00:00:00 2001 From: Giovanni Lovato Date: Tue, 9 Jun 2026 12:10:16 +0200 Subject: [PATCH 08/15] test: add Spring Boot + Prometheus integration test module (observability-kit-tests-starter) --- .../observability-kit-tests-starter/pom.xml | 163 ++++++++++++++++++ .../src/main/frontend/index.html | 39 +++++ .../tests/starter/Application.java | 20 +++ .../tests/starter/HelloView.java | 27 +++ .../src/main/resources/application.properties | 3 + .../tests/starter/BootMetricsIT.java | 93 ++++++++++ observability-kit-tests/pom.xml | 1 + 7 files changed, 346 insertions(+) create mode 100644 observability-kit-tests/observability-kit-tests-starter/pom.xml create mode 100644 observability-kit-tests/observability-kit-tests-starter/src/main/frontend/index.html create mode 100644 observability-kit-tests/observability-kit-tests-starter/src/main/java/com/vaadin/observability/tests/starter/Application.java create mode 100644 observability-kit-tests/observability-kit-tests-starter/src/main/java/com/vaadin/observability/tests/starter/HelloView.java create mode 100644 observability-kit-tests/observability-kit-tests-starter/src/main/resources/application.properties create mode 100644 observability-kit-tests/observability-kit-tests-starter/src/test/java/com/vaadin/observability/tests/starter/BootMetricsIT.java diff --git a/observability-kit-tests/observability-kit-tests-starter/pom.xml b/observability-kit-tests/observability-kit-tests-starter/pom.xml new file mode 100644 index 0000000..9e08b22 --- /dev/null +++ b/observability-kit-tests/observability-kit-tests-starter/pom.xml @@ -0,0 +1,163 @@ + + + 4.0.0 + + com.vaadin + observability-kit-tests + 5.0-SNAPSHOT + + observability-kit-tests-starter + + jar + Test Vaadin Observability Kit Starter on Spring Boot + Prometheus + + Integration test that boots a Spring Boot application using observability-kit-starter + auto-configuration and verifies session / UI / request metrics are exported via the + Actuator Prometheus endpoint. + + + + + + com.vaadin + observability-kit-starter + ${project.version} + + + com.vaadin + vaadin-spring + + + com.vaadin + flow-server + ${flow.version} + + + com.vaadin + flow-html-components + ${flow.version} + + + + + org.springframework.boot + spring-boot-starter-webmvc + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + org.springframework.boot + spring-boot-starter-jetty + + + + + org.springframework.boot + spring-boot-starter-actuator + + + io.micrometer + micrometer-registry-prometheus + + + + + com.vaadin + observability-kit-test-common + ${project.version} + test + + + com.vaadin + vaadin-testbench-core-junit5 + test + + + com.vaadin + flow-html-components-testbench + ${flow.version} + test + + + org.seleniumhq.selenium + selenium-java + test + + + org.junit.jupiter + junit-jupiter + test + + + org.assertj + assertj-core + test + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + + + + + com.vaadin + flow-maven-plugin + ${flow.version} + + + + prepare-frontend + build-frontend + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + pre-integration-test + + start + + + + post-integration-test + + stop + + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + + + + + diff --git a/observability-kit-tests/observability-kit-tests-starter/src/main/frontend/index.html b/observability-kit-tests/observability-kit-tests-starter/src/main/frontend/index.html new file mode 100644 index 0000000..7e3cdd4 --- /dev/null +++ b/observability-kit-tests/observability-kit-tests-starter/src/main/frontend/index.html @@ -0,0 +1,39 @@ + + + + + + + + + + + + +
+ + diff --git a/observability-kit-tests/observability-kit-tests-starter/src/main/java/com/vaadin/observability/tests/starter/Application.java b/observability-kit-tests/observability-kit-tests-starter/src/main/java/com/vaadin/observability/tests/starter/Application.java new file mode 100644 index 0000000..1b213e1 --- /dev/null +++ b/observability-kit-tests/observability-kit-tests-starter/src/main/java/com/vaadin/observability/tests/starter/Application.java @@ -0,0 +1,20 @@ +/** + * Copyright (C) 2000-2026 Vaadin Ltd + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * See for the full + * license. + */ +package com.vaadin.observability.tests.starter; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/observability-kit-tests/observability-kit-tests-starter/src/main/java/com/vaadin/observability/tests/starter/HelloView.java b/observability-kit-tests/observability-kit-tests-starter/src/main/java/com/vaadin/observability/tests/starter/HelloView.java new file mode 100644 index 0000000..9942811 --- /dev/null +++ b/observability-kit-tests/observability-kit-tests-starter/src/main/java/com/vaadin/observability/tests/starter/HelloView.java @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2000-2026 Vaadin Ltd + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * See for the full + * license. + */ +package com.vaadin.observability.tests.starter; + +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.html.Span; +import com.vaadin.flow.router.Route; + +/** + * Simple landing view; opening it drives a real Vaadin session, UI and + * navigation through the framework so the Micrometer binders fire. + */ +@Route("") +public class HelloView extends Div { + + public HelloView() { + Span greeting = new Span("Hello micrometer boot"); + greeting.setId("greeting"); + add(greeting); + } +} diff --git a/observability-kit-tests/observability-kit-tests-starter/src/main/resources/application.properties b/observability-kit-tests/observability-kit-tests-starter/src/main/resources/application.properties new file mode 100644 index 0000000..3d946f4 --- /dev/null +++ b/observability-kit-tests/observability-kit-tests-starter/src/main/resources/application.properties @@ -0,0 +1,3 @@ +server.port=8080 +management.endpoints.web.exposure.include=prometheus,metrics +management.endpoint.prometheus.access=unrestricted diff --git a/observability-kit-tests/observability-kit-tests-starter/src/test/java/com/vaadin/observability/tests/starter/BootMetricsIT.java b/observability-kit-tests/observability-kit-tests-starter/src/test/java/com/vaadin/observability/tests/starter/BootMetricsIT.java new file mode 100644 index 0000000..b1bedf7 --- /dev/null +++ b/observability-kit-tests/observability-kit-tests-starter/src/test/java/com/vaadin/observability/tests/starter/BootMetricsIT.java @@ -0,0 +1,93 @@ +/** + * Copyright (C) 2000-2026 Vaadin Ltd + * + * This program is available under Vaadin Commercial License and Service Terms. + * + * See for the full + * license. + */ +package com.vaadin.observability.tests.starter; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.assertj.core.api.Assertions; + +import com.vaadin.flow.component.html.testbench.SpanElement; +import com.vaadin.observability.tests.common.AbstractIT; +import com.vaadin.testbench.BrowserTest; + +/** + * Drives a Spring Boot + {@code observability-kit-starter} app in Chrome and + * asserts the auto-configured binders move Vaadin meters into the Prometheus + * registry, scraping results via the Actuator Prometheus endpoint. + * + *

+ * Prometheus / OpenMetrics treats the {@code _created} suffix as a special + * timestamp marker and drops it from counter names, so the Micrometer counter + * {@code vaadin.sessions.created} is emitted as {@code vaadin_sessions_total} + * (and {@code vaadin.ui.created} as {@code vaadin_ui_total}). + */ +public class BootMetricsIT extends AbstractIT { + + @Override + protected String getTestPath() { + return "/"; + } + + @BrowserTest + public void viewLoadDrivesMetricsExposedViaActuator() throws IOException { + SpanElement greeting = $(SpanElement.class).id("greeting"); + Assertions.assertThat(greeting.getText()) + .isEqualTo("Hello micrometer boot"); + + String body = fetchPrometheus(); + + Assertions.assertThat(meterValue(body, "vaadin_sessions_total")) + .as("vaadin_sessions_total").isGreaterThanOrEqualTo(1.0); + Assertions.assertThat(meterValue(body, "vaadin_ui_total")) + .as("vaadin_ui_total").isGreaterThanOrEqualTo(1.0); + Assertions.assertThat(meterValue(body, "vaadin_sessions_active")) + .as("vaadin_sessions_active").isGreaterThanOrEqualTo(1.0); + Assertions.assertThat(body) + .as("vaadin_request_duration_seconds present") + .contains("vaadin_request_duration_seconds"); + } + + private String fetchPrometheus() throws IOException { + HttpURLConnection conn = (HttpURLConnection) URI + .create(getRootURL() + "/actuator/prometheus").toURL() + .openConnection(); + conn.setRequestMethod("GET"); + Assertions.assertThat(conn.getResponseCode()).isEqualTo(200); + StringBuilder out = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader( + conn.getInputStream(), StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + out.append(line).append('\n'); + } + } + return out.toString(); + } + + /** + * Returns the numeric value of the first Prometheus sample line whose + * metric name matches {@code meterName}. Lines with or without labels are + * accepted. Returns {@code -1.0} if no match is found. + */ + private static double meterValue(String prometheusBody, String meterName) { + Pattern pattern = Pattern.compile( + "^" + Pattern.quote(meterName) + "(?:\\{[^}]*\\})?\\s+" + + "([0-9]+(?:\\.[0-9]+)?(?:[eE][-+]?[0-9]+)?)", + Pattern.MULTILINE); + Matcher m = pattern.matcher(prometheusBody); + return m.find() ? Double.parseDouble(m.group(1)) : -1.0; + } +} diff --git a/observability-kit-tests/pom.xml b/observability-kit-tests/pom.xml index ce4cffd..13c3919 100644 --- a/observability-kit-tests/pom.xml +++ b/observability-kit-tests/pom.xml @@ -15,6 +15,7 @@ observability-kit-test-common observability-kit-tests-micrometer observability-kit-tests-spring + observability-kit-tests-starter From b7ed8ef70497429219fc8357fa46a02ae5f6c8c1 Mon Sep 17 00:00:00 2001 From: Giovanni Lovato Date: Tue, 9 Jun 2026 12:16:16 +0200 Subject: [PATCH 09/15] refactor: rename observability-kit-test-common to observability-kit-tests-common Align the shared IT base module with the agreed observability-kit-tests-* naming scheme (it was the lone module still using the singular 'test-' prefix). Updates the directory, artifactId, aggregator module entry, and the three dependent test poms. --- .../pom.xml | 2 +- .../java/com/vaadin/observability/tests/common/AbstractIT.java | 0 .../observability-kit-tests-micrometer/pom.xml | 2 +- observability-kit-tests/observability-kit-tests-spring/pom.xml | 2 +- observability-kit-tests/observability-kit-tests-starter/pom.xml | 2 +- observability-kit-tests/pom.xml | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) rename observability-kit-tests/{observability-kit-test-common => observability-kit-tests-common}/pom.xml (94%) rename observability-kit-tests/{observability-kit-test-common => observability-kit-tests-common}/src/main/java/com/vaadin/observability/tests/common/AbstractIT.java (100%) diff --git a/observability-kit-tests/observability-kit-test-common/pom.xml b/observability-kit-tests/observability-kit-tests-common/pom.xml similarity index 94% rename from observability-kit-tests/observability-kit-test-common/pom.xml rename to observability-kit-tests/observability-kit-tests-common/pom.xml index d31d2b7..f52dc27 100644 --- a/observability-kit-tests/observability-kit-test-common/pom.xml +++ b/observability-kit-tests/observability-kit-tests-common/pom.xml @@ -6,7 +6,7 @@ observability-kit-tests 5.0-SNAPSHOT - observability-kit-test-common + observability-kit-tests-common jar Shared test base for Observability-kit integration tests diff --git a/observability-kit-tests/observability-kit-test-common/src/main/java/com/vaadin/observability/tests/common/AbstractIT.java b/observability-kit-tests/observability-kit-tests-common/src/main/java/com/vaadin/observability/tests/common/AbstractIT.java similarity index 100% rename from observability-kit-tests/observability-kit-test-common/src/main/java/com/vaadin/observability/tests/common/AbstractIT.java rename to observability-kit-tests/observability-kit-tests-common/src/main/java/com/vaadin/observability/tests/common/AbstractIT.java diff --git a/observability-kit-tests/observability-kit-tests-micrometer/pom.xml b/observability-kit-tests/observability-kit-tests-micrometer/pom.xml index feac412..816302e 100644 --- a/observability-kit-tests/observability-kit-tests-micrometer/pom.xml +++ b/observability-kit-tests/observability-kit-tests-micrometer/pom.xml @@ -19,7 +19,7 @@ com.vaadin - observability-kit-test-common + observability-kit-tests-common ${project.version} test diff --git a/observability-kit-tests/observability-kit-tests-spring/pom.xml b/observability-kit-tests/observability-kit-tests-spring/pom.xml index b625f3a..f4d8722 100644 --- a/observability-kit-tests/observability-kit-tests-spring/pom.xml +++ b/observability-kit-tests/observability-kit-tests-spring/pom.xml @@ -60,7 +60,7 @@ com.vaadin - observability-kit-test-common + observability-kit-tests-common ${project.version} test diff --git a/observability-kit-tests/observability-kit-tests-starter/pom.xml b/observability-kit-tests/observability-kit-tests-starter/pom.xml index 9e08b22..3d00266 100644 --- a/observability-kit-tests/observability-kit-tests-starter/pom.xml +++ b/observability-kit-tests/observability-kit-tests-starter/pom.xml @@ -67,7 +67,7 @@ com.vaadin - observability-kit-test-common + observability-kit-tests-common ${project.version} test diff --git a/observability-kit-tests/pom.xml b/observability-kit-tests/pom.xml index 13c3919..0a925ab 100644 --- a/observability-kit-tests/pom.xml +++ b/observability-kit-tests/pom.xml @@ -12,7 +12,7 @@ Integration tests for Observability-kit - observability-kit-test-common + observability-kit-tests-common observability-kit-tests-micrometer observability-kit-tests-spring observability-kit-tests-starter From 5ae33d5143d574bac6c571b7cce1d912409cc90f Mon Sep 17 00:00:00 2001 From: Giovanni Lovato Date: Tue, 9 Jun 2026 12:16:47 +0200 Subject: [PATCH 10/15] chore: gitignore .claude tooling directory --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 9db6b66..a23e096 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,6 @@ Thumbs.db docs/superpowers/ *.bundle **/src/main/frontend/generated + +# Claude Code tooling (worktrees, local settings) +.claude/ From 62ce363e0502062c1335e666e1b16c9806e4257a Mon Sep 17 00:00:00 2001 From: Giovanni Lovato Date: Tue, 9 Jun 2026 12:28:50 +0200 Subject: [PATCH 11/15] fix: align plain-Spring traces-session-id property key ObservabilityConfiguration bound the session-id traces flag from the dotted key vaadin.observability.traces.session-id, the lone non-flat key in the module. The Boot starter binds the same flag from the flat vaadin.observability.traces-session-id (the canonical key in the design spec), so a user copying properties between the two flavours would have the setting silently dropped. Switch the plain-Spring @Value to the flat key so both modules and the spec agree. --- .../vaadin/observability/spring/ObservabilityConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/observability-kit-spring/src/main/java/com/vaadin/observability/spring/ObservabilityConfiguration.java b/observability-kit-spring/src/main/java/com/vaadin/observability/spring/ObservabilityConfiguration.java index 6a2b3ae..ef8fb4f 100644 --- a/observability-kit-spring/src/main/java/com/vaadin/observability/spring/ObservabilityConfiguration.java +++ b/observability-kit-spring/src/main/java/com/vaadin/observability/spring/ObservabilityConfiguration.java @@ -77,7 +77,7 @@ public class ObservabilityConfiguration { @Value("${vaadin.observability.requests:true}") boolean requests, @Value("${vaadin.observability.errors:true}") boolean errors, @Value("${vaadin.observability.traces:true}") boolean traces, - @Value("${vaadin.observability.traces.session-id:false}") boolean tracesSessionId, + @Value("${vaadin.observability.traces-session-id:false}") boolean tracesSessionId, @Value("${vaadin.observability.route-cardinality-limit:200}") int routeCardinalityLimit, @Value("${vaadin.observability.client:true}") boolean client, @Value("${vaadin.observability.client-rate-per-session:100}") int clientRatePerSession) { From cb21476ccfe420e9d94b81205d1fcd93afa32e6f Mon Sep 17 00:00:00 2001 From: Giovanni Lovato Date: Tue, 9 Jun 2026 12:28:51 +0200 Subject: [PATCH 12/15] docs: clarify Jetty stop-port comment in spring IT pom The unique value isolates the shutdown handshake, not the HTTP listen port (which stays Jetty's default 8080); the reactor runs IT modules sequentially. --- .../observability-kit-tests-spring/pom.xml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/observability-kit-tests/observability-kit-tests-spring/pom.xml b/observability-kit-tests/observability-kit-tests-spring/pom.xml index f4d8722..6d5cf62 100644 --- a/observability-kit-tests/observability-kit-tests-spring/pom.xml +++ b/observability-kit-tests/observability-kit-tests-spring/pom.xml @@ -119,8 +119,10 @@ (jetty/jetty.project#15135) --> 12.1.8 - + 8090 observability-kit-spring 5 From 5c5a9a296d66d453827bd1347e8ac035cffe9589 Mon Sep 17 00:00:00 2001 From: Giovanni Lovato Date: Tue, 9 Jun 2026 13:46:30 +0200 Subject: [PATCH 13/15] feat: bring Boot Micrometer metrics autoconfig so the starter works out of the box MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit observability-kit-starter only attached to a pre-existing MeterRegistry; it never set Micrometer up, so the kit was not a real Micrometer starter — users had to wire metrics themselves. Depend on Boot's spring-boot-starter-micrometer-metrics (compile) so MetricsAutoConfiguration + CompositeMeterRegistryAutoConfiguration are present: a MeterRegistry is wired out of the box and the Vaadin instrumentation activates with no further setup. The registry backend (Prometheus, OTLP, ...) and Actuator endpoint exposure remain the application's choice. With those autoconfig classes now guaranteed on the classpath, switch @AutoConfiguration(afterName = {strings}) to the type-safe after = {MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class}. Drop the unused optional spring-boot-actuator-autoconfigure dependency (the starter references no actuator types). Add an ApplicationContextRunner test asserting that with the metrics autoconfig present and no hand-supplied registry, both a MeterRegistry and the listener appear. --- observability-kit-starter/pom.xml | 9 ++++++-- .../boot/ObservabilityAutoConfiguration.java | 15 ++++++++++--- .../ObservabilityAutoConfigurationTest.java | 22 +++++++++++++++++++ 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/observability-kit-starter/pom.xml b/observability-kit-starter/pom.xml index c6c4aaf..1ffe3b4 100644 --- a/observability-kit-starter/pom.xml +++ b/observability-kit-starter/pom.xml @@ -31,10 +31,15 @@ org.springframework.boot spring-boot-autoconfigure + org.springframework.boot - spring-boot-actuator-autoconfigure - true + spring-boot-starter-micrometer-metrics org.springframework.boot diff --git a/observability-kit-starter/src/main/java/com/vaadin/observability/spring/boot/ObservabilityAutoConfiguration.java b/observability-kit-starter/src/main/java/com/vaadin/observability/spring/boot/ObservabilityAutoConfiguration.java index 58e4cb0..fb450ef 100644 --- a/observability-kit-starter/src/main/java/com/vaadin/observability/spring/boot/ObservabilityAutoConfiguration.java +++ b/observability-kit-starter/src/main/java/com/vaadin/observability/spring/boot/ObservabilityAutoConfiguration.java @@ -17,6 +17,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.micrometer.metrics.autoconfigure.CompositeMeterRegistryAutoConfiguration; +import org.springframework.boot.micrometer.metrics.autoconfigure.MetricsAutoConfiguration; import org.springframework.context.annotation.Bean; import com.vaadin.flow.server.VaadinService; @@ -28,13 +30,20 @@ * Auto-configures the Observability Kit {@link MetricsServiceInitListener} when * a {@link MeterRegistry} is present in the Spring context. *

+ * The starter pulls in Boot's Micrometer metrics auto-configuration, so a + * {@link MeterRegistry} is wired out of the box and this listener activates + * without further setup; the application only chooses the registry backend + * (Prometheus, OTLP, ...) and whether to expose Actuator endpoints. Ordered + * after {@link MetricsAutoConfiguration} and + * {@link CompositeMeterRegistryAutoConfiguration} so that registry is visible + * when this runs. + *

* Activation is gated by the {@code vaadin.observability.enabled} property * (default {@code true}); the listener is also skipped when the user supplies * their own {@link MetricsServiceInitListener} bean. */ -@AutoConfiguration(afterName = { - "org.springframework.boot.micrometer.metrics.autoconfigure.MetricsAutoConfiguration", - "org.springframework.boot.micrometer.metrics.autoconfigure.CompositeMeterRegistryAutoConfiguration" }) +@AutoConfiguration(after = { MetricsAutoConfiguration.class, + CompositeMeterRegistryAutoConfiguration.class }) @ConditionalOnClass({ MeterRegistry.class, VaadinService.class }) @ConditionalOnProperty(prefix = "vaadin.observability", name = "enabled", havingValue = "true", matchIfMissing = true) @EnableConfigurationProperties(ObservabilityProperties.class) diff --git a/observability-kit-starter/src/test/java/com/vaadin/observability/spring/boot/ObservabilityAutoConfigurationTest.java b/observability-kit-starter/src/test/java/com/vaadin/observability/spring/boot/ObservabilityAutoConfigurationTest.java index 860e37e..53a6792 100644 --- a/observability-kit-starter/src/test/java/com/vaadin/observability/spring/boot/ObservabilityAutoConfigurationTest.java +++ b/observability-kit-starter/src/test/java/com/vaadin/observability/spring/boot/ObservabilityAutoConfigurationTest.java @@ -8,9 +8,12 @@ */ package com.vaadin.observability.spring.boot; +import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.micrometer.metrics.autoconfigure.CompositeMeterRegistryAutoConfiguration; +import org.springframework.boot.micrometer.metrics.autoconfigure.MetricsAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import com.vaadin.observability.micrometer.MetricsServiceInitListener; @@ -44,6 +47,25 @@ void defaultConfiguration_withMeterRegistry_registersBeansExpected() { }); } + /** + * Real-starter behavior: with Boot's Micrometer metrics auto-configuration + * on the classpath (as the starter pulls it in) and no manually supplied + * registry, a {@link MeterRegistry} is wired out of the box and our + * listener activates on top of it. + */ + @Test + void metricsAutoConfigurationPresent_wiresRegistryAndListener() { + contextRunner + .withConfiguration( + AutoConfigurations.of(MetricsAutoConfiguration.class, + CompositeMeterRegistryAutoConfiguration.class)) + .run(context -> { + assertThat(context).hasSingleBean(MeterRegistry.class); + assertThat(context) + .hasSingleBean(MetricsServiceInitListener.class); + }); + } + /** * When vaadin.observability.enabled=false, the auto-configuration should * not activate and no beans should be registered. From efc094e3248d62dea9ae67475188c0268f1f7c12 Mon Sep 17 00:00:00 2001 From: Giovanni Lovato Date: Tue, 9 Jun 2026 13:52:03 +0200 Subject: [PATCH 14/15] chore: drop verbose comment on metrics-metrics starter dependency --- observability-kit-starter/pom.xml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/observability-kit-starter/pom.xml b/observability-kit-starter/pom.xml index 1ffe3b4..cac9253 100644 --- a/observability-kit-starter/pom.xml +++ b/observability-kit-starter/pom.xml @@ -31,12 +31,6 @@ org.springframework.boot spring-boot-autoconfigure - org.springframework.boot spring-boot-starter-micrometer-metrics From 4c125665c7b0168c7da918ff8f2f448b0341ea5d Mon Sep 17 00:00:00 2001 From: Giovanni Lovato Date: Tue, 9 Jun 2026 13:58:52 +0200 Subject: [PATCH 15/15] test: address review on IT assertions - Use a descriptive withFailMessage on the request-duration .contains assertions (BootMetricsIT, SpringMetricsIT) so a failure reports what was missing instead of dumping the whole scrape body (caalador review). - Switch both ITs to static AssertJ imports (assertThat), matching the unit test and the project's test conventions. --- .../tests/spring/SpringMetricsIT.java | 22 ++++++++----------- .../tests/starter/BootMetricsIT.java | 21 +++++++++--------- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/observability-kit-tests/observability-kit-tests-spring/src/test/java/com/vaadin/observability/tests/spring/SpringMetricsIT.java b/observability-kit-tests/observability-kit-tests-spring/src/test/java/com/vaadin/observability/tests/spring/SpringMetricsIT.java index 62c3b59..eed3a83 100644 --- a/observability-kit-tests/observability-kit-tests-spring/src/test/java/com/vaadin/observability/tests/spring/SpringMetricsIT.java +++ b/observability-kit-tests/observability-kit-tests-spring/src/test/java/com/vaadin/observability/tests/spring/SpringMetricsIT.java @@ -17,12 +17,12 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.assertj.core.api.Assertions; - import com.vaadin.flow.component.html.testbench.SpanElement; import com.vaadin.observability.tests.common.AbstractIT; import com.vaadin.testbench.BrowserTest; +import static org.assertj.core.api.Assertions.assertThat; + /** * Drives a real plain-Spring Vaadin Flow page in Chrome and asserts that the * Spring-managed {@code MeterRegistry} (provided through @@ -38,23 +38,19 @@ protected String getTestPath() { @BrowserTest public void viewLoadDrivesSessionAndUiMetrics() throws IOException { SpanElement greeting = $(SpanElement.class).id("greeting"); - Assertions.assertThat(greeting.getText()) - .isEqualTo("Hello micrometer spring"); + assertThat(greeting.getText()).isEqualTo("Hello micrometer spring"); String metrics = fetchMetrics(); - Assertions - .assertThat( - meterValue(metrics, "vaadin.sessions.created", "count")) + assertThat(meterValue(metrics, "vaadin.sessions.created", "count")) .as("vaadin.sessions.created count") .isGreaterThanOrEqualTo(1.0); - Assertions.assertThat(meterValue(metrics, "vaadin.ui.created", "count")) + assertThat(meterValue(metrics, "vaadin.ui.created", "count")) .as("vaadin.ui.created count").isGreaterThanOrEqualTo(1.0); - Assertions - .assertThat( - meterValue(metrics, "vaadin.sessions.active", "value")) + assertThat(meterValue(metrics, "vaadin.sessions.active", "value")) .as("vaadin.sessions.active value").isGreaterThanOrEqualTo(1.0); - Assertions.assertThat(metrics).as("vaadin.request.duration present") + assertThat(metrics).withFailMessage( + "expected a vaadin.request.duration sample in the /metrics output") .contains("vaadin.request.duration"); } @@ -62,7 +58,7 @@ private String fetchMetrics() throws IOException { HttpURLConnection conn = (HttpURLConnection) URI .create(getRootURL() + "/metrics").toURL().openConnection(); conn.setRequestMethod("GET"); - Assertions.assertThat(conn.getResponseCode()).isEqualTo(200); + assertThat(conn.getResponseCode()).isEqualTo(200); StringBuilder out = new StringBuilder(); try (BufferedReader reader = new BufferedReader(new InputStreamReader( conn.getInputStream(), StandardCharsets.UTF_8))) { diff --git a/observability-kit-tests/observability-kit-tests-starter/src/test/java/com/vaadin/observability/tests/starter/BootMetricsIT.java b/observability-kit-tests/observability-kit-tests-starter/src/test/java/com/vaadin/observability/tests/starter/BootMetricsIT.java index b1bedf7..3155ace 100644 --- a/observability-kit-tests/observability-kit-tests-starter/src/test/java/com/vaadin/observability/tests/starter/BootMetricsIT.java +++ b/observability-kit-tests/observability-kit-tests-starter/src/test/java/com/vaadin/observability/tests/starter/BootMetricsIT.java @@ -17,12 +17,12 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.assertj.core.api.Assertions; - import com.vaadin.flow.component.html.testbench.SpanElement; import com.vaadin.observability.tests.common.AbstractIT; import com.vaadin.testbench.BrowserTest; +import static org.assertj.core.api.Assertions.assertThat; + /** * Drives a Spring Boot + {@code observability-kit-starter} app in Chrome and * asserts the auto-configured binders move Vaadin meters into the Prometheus @@ -44,19 +44,18 @@ protected String getTestPath() { @BrowserTest public void viewLoadDrivesMetricsExposedViaActuator() throws IOException { SpanElement greeting = $(SpanElement.class).id("greeting"); - Assertions.assertThat(greeting.getText()) - .isEqualTo("Hello micrometer boot"); + assertThat(greeting.getText()).isEqualTo("Hello micrometer boot"); String body = fetchPrometheus(); - Assertions.assertThat(meterValue(body, "vaadin_sessions_total")) + assertThat(meterValue(body, "vaadin_sessions_total")) .as("vaadin_sessions_total").isGreaterThanOrEqualTo(1.0); - Assertions.assertThat(meterValue(body, "vaadin_ui_total")) - .as("vaadin_ui_total").isGreaterThanOrEqualTo(1.0); - Assertions.assertThat(meterValue(body, "vaadin_sessions_active")) + assertThat(meterValue(body, "vaadin_ui_total")).as("vaadin_ui_total") + .isGreaterThanOrEqualTo(1.0); + assertThat(meterValue(body, "vaadin_sessions_active")) .as("vaadin_sessions_active").isGreaterThanOrEqualTo(1.0); - Assertions.assertThat(body) - .as("vaadin_request_duration_seconds present") + assertThat(body).withFailMessage( + "expected a vaadin_request_duration_seconds sample in the Prometheus scrape") .contains("vaadin_request_duration_seconds"); } @@ -65,7 +64,7 @@ private String fetchPrometheus() throws IOException { .create(getRootURL() + "/actuator/prometheus").toURL() .openConnection(); conn.setRequestMethod("GET"); - Assertions.assertThat(conn.getResponseCode()).isEqualTo(200); + assertThat(conn.getResponseCode()).isEqualTo(200); StringBuilder out = new StringBuilder(); try (BufferedReader reader = new BufferedReader(new InputStreamReader( conn.getInputStream(), StandardCharsets.UTF_8))) {