Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,6 @@ Thumbs.db
docs/superpowers/
*.bundle
**/src/main/frontend/generated

# Claude Code tooling (worktrees, local settings)
.claude/
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -78,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) {
Expand Down Expand Up @@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* Copyright (C) 2026 Vaadin Ltd
*
* This program is available under Vaadin Commercial License and Service Terms.
*
* See <https://vaadin.com/commercial-license-and-service-terms> 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.
* <p>
* 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 /**}.
* <p>
* 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);
}
}
52 changes: 52 additions & 0 deletions observability-kit-starter/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.vaadin</groupId>
<artifactId>observability-kit</artifactId>
<version>5.0-SNAPSHOT</version>
</parent>

<artifactId>observability-kit-starter</artifactId>
<packaging>jar</packaging>
<name>Observability Kit :: Spring Boot Starter</name>
<description>Spring Boot auto-configuration for Observability Kit.</description>

<dependencies>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>observability-kit-micrometer</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>observability-kit-spring</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-micrometer-metrics</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* Copyright (C) 2000-2026 Vaadin Ltd
*
* This program is available under Vaadin Commercial License and Service Terms.
*
* See <https://vaadin.com/commercial-license-and-service-terms> 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.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;
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.
* <p>
* 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.
* <p>
* 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(after = { MetricsAutoConfiguration.class,
CompositeMeterRegistryAutoConfiguration.class })
@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.class)
MetricsServiceInitListener metricsServiceInitListener(
MeterRegistry registry,
ObjectProvider<ObservationRegistry> observationRegistry,
ObservabilitySettings settings) {
return new SpringMetricsServiceInitListener(registry,
observationRegistry.getIfAvailable(), settings);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/**
* Copyright (C) 2000-2026 Vaadin Ltd
*
* This program is available under Vaadin Commercial License and Service Terms.
*
* See <https://vaadin.com/commercial-license-and-service-terms> 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();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
com.vaadin.observability.spring.boot.ObservabilityAutoConfiguration
Loading
Loading