Skip to content

Commit

Permalink
Use Jersey's ObservationRequestEventListener for Jersey observability
Browse files Browse the repository at this point in the history
Closes gh-39633
  • Loading branch information
mhalbritter committed Feb 20, 2024
1 parent 7e43d7b commit 3a565e4
Show file tree
Hide file tree
Showing 3 changed files with 19 additions and 153 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,35 +16,25 @@

package org.springframework.boot.actuate.autoconfigure.metrics.jersey;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.config.MeterFilter;
import org.glassfish.jersey.micrometer.server.AnnotationFinder;
import org.glassfish.jersey.micrometer.server.DefaultJerseyTagsProvider;
import org.glassfish.jersey.micrometer.server.JerseyTagsProvider;
import org.glassfish.jersey.micrometer.server.MetricsApplicationEventListener;
import io.micrometer.observation.ObservationRegistry;
import org.glassfish.jersey.micrometer.server.JerseyObservationConvention;
import org.glassfish.jersey.micrometer.server.ObservationApplicationEventListener;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.monitoring.RequestEvent;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties;
import org.springframework.boot.actuate.autoconfigure.metrics.OnlyOnceLoggingDenyMeterFilter;
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.observation.ObservationProperties;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;

/**
Expand All @@ -53,13 +43,14 @@
* @author Michael Weirauch
* @author Michael Simons
* @author Andy Wilkinson
* @author Moritz Halbritter
* @since 2.1.0
*/
@AutoConfiguration(after = { MetricsAutoConfiguration.class, SimpleMetricsExportAutoConfiguration.class })
@AutoConfiguration(after = { ObservationAutoConfiguration.class })
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass({ ResourceConfig.class, MetricsApplicationEventListener.class })
@ConditionalOnBean({ MeterRegistry.class, ResourceConfig.class })
@EnableConfigurationProperties(MetricsProperties.class)
@ConditionalOnClass({ ResourceConfig.class, ObservationApplicationEventListener.class })
@ConditionalOnBean({ ResourceConfig.class, ObservationRegistry.class })
@EnableConfigurationProperties({ MetricsProperties.class, ObservationProperties.class })
public class JerseyServerMetricsAutoConfiguration {

private final ObservationProperties observationProperties;
Expand All @@ -69,22 +60,11 @@ public JerseyServerMetricsAutoConfiguration(ObservationProperties observationPro
}

@Bean
@SuppressWarnings("deprecation")
@ConditionalOnMissingBean({ JerseyTagsProvider.class,
io.micrometer.core.instrument.binder.jersey.server.JerseyTagsProvider.class })
public DefaultJerseyTagsProvider jerseyTagsProvider() {
return new DefaultJerseyTagsProvider();
}

@Bean
@SuppressWarnings("deprecation")
public ResourceConfigCustomizer jerseyServerMetricsResourceConfigCustomizer(MeterRegistry meterRegistry,
ObjectProvider<JerseyTagsProvider> tagsProvider,
ObjectProvider<io.micrometer.core.instrument.binder.jersey.server.JerseyTagsProvider> micrometerTagsProvider) {
ResourceConfigCustomizer jerseyServerObservationResourceConfigCustomizer(ObservationRegistry observationRegistry,
ObjectProvider<JerseyObservationConvention> jerseyObservationConvention) {
String metricName = this.observationProperties.getHttp().getServer().getRequests().getName();
return (config) -> config.register(new MetricsApplicationEventListener(meterRegistry,
tagsProvider.getIfAvailable(() -> new JerseyTagsProviderAdapter(micrometerTagsProvider.getObject())),
metricName, true, new AnnotationUtilsAnnotationFinder()));
return (config) -> config.register(new ObservationApplicationEventListener(observationRegistry, metricName,
jerseyObservationConvention.getIfAvailable()));
}

@Bean
Expand All @@ -97,38 +77,4 @@ public MeterFilter jerseyMetricsUriTagFilter(MetricsProperties metricsProperties
metricsProperties.getWeb().getServer().getMaxUriTags(), filter);
}

/**
* An {@link AnnotationFinder} that uses {@link AnnotationUtils}.
*/
private static final class AnnotationUtilsAnnotationFinder implements AnnotationFinder {

@Override
public <A extends Annotation> A findAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType) {
return AnnotationUtils.findAnnotation(annotatedElement, annotationType);
}

}

@SuppressWarnings("deprecation")
static final class JerseyTagsProviderAdapter implements JerseyTagsProvider {

private final io.micrometer.core.instrument.binder.jersey.server.JerseyTagsProvider delegate;

private JerseyTagsProviderAdapter(
io.micrometer.core.instrument.binder.jersey.server.JerseyTagsProvider delegate) {
this.delegate = delegate;
}

@Override
public Iterable<Tag> httpRequestTags(RequestEvent event) {
return this.delegate.httpRequestTags(event);
}

@Override
public Iterable<Tag> httpLongRequestTags(RequestEvent event) {
return this.delegate.httpLongRequestTags(event);
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,18 @@
package org.springframework.boot.actuate.autoconfigure.metrics.jersey;

import java.net.URI;
import java.util.Set;

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Timer;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.glassfish.jersey.micrometer.server.DefaultJerseyTagsProvider;
import org.glassfish.jersey.micrometer.server.JerseyTagsProvider;
import org.glassfish.jersey.micrometer.server.MetricsApplicationEventListener;
import org.glassfish.jersey.micrometer.server.ObservationApplicationEventListener;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.monitoring.RequestEvent;
import org.junit.jupiter.api.Test;

import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.jersey.JerseyServerMetricsAutoConfiguration.JerseyTagsProviderAdapter;
import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun;
import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigurations;
Expand All @@ -58,6 +51,7 @@
*
* @author Michael Weirauch
* @author Michael Simons
* @author Moritz Halbritter
*/
class JerseyServerMetricsAutoConfigurationTests {

Expand All @@ -80,32 +74,10 @@ void shouldOnlyBeActiveInWebApplicationContext() {

@Test
void shouldProvideAllNecessaryBeans() {
this.webContextRunner.run((context) -> assertThat(context).hasSingleBean(DefaultJerseyTagsProvider.class)
this.webContextRunner.run((context) -> assertThat(context).hasBean("jerseyMetricsUriTagFilter")
.hasSingleBean(ResourceConfigCustomizer.class));
}

@Test
void shouldHonorExistingTagProvider() {
this.webContextRunner.withUserConfiguration(CustomJerseyTagsProviderConfiguration.class)
.run((context) -> assertThat(context).hasSingleBean(CustomJerseyTagsProvider.class));
}

@Test
@Deprecated(since = "3.3.0", forRemoval = true)
void shouldHonorExistingMicrometerTagProvider() {
this.webContextRunner.withUserConfiguration(CustomMicrometerJerseyTagsProviderConfiguration.class)
.run((context) -> {
assertThat(context).hasSingleBean(CustomMicrometerJerseyTagsProvider.class);
ResourceConfig config = new ResourceConfig();
context.getBean(ResourceConfigCustomizer.class).customize(config);
Set<Object> instances = config.getInstances();
assertThat(instances).hasSize(1)
.first(InstanceOfAssertFactories.type(MetricsApplicationEventListener.class))
.satisfies((listener) -> assertThat(listener).extracting("tagsProvider")
.isInstanceOf(JerseyTagsProviderAdapter.class));
});
}

@Test
void httpRequestsAreTimed() {
this.webContextRunner.run((context) -> {
Expand All @@ -118,10 +90,9 @@ void httpRequestsAreTimed() {

@Test
void noHttpRequestsTimedWhenJerseyInstrumentationMissingFromClasspath() {
this.webContextRunner.withClassLoader(new FilteredClassLoader(MetricsApplicationEventListener.class))
this.webContextRunner.withClassLoader(new FilteredClassLoader(ObservationApplicationEventListener.class))
.run((context) -> {
doRequest(context);

MeterRegistry registry = context.getBean(MeterRegistry.class);
assertThat(registry.find("http.server.requests").timer()).isNull();
});
Expand All @@ -144,7 +115,7 @@ ResourceConfig resourceConfig() {
}

@Path("/users")
public class TestResource {
public static class TestResource {

@GET
@Path("/{id}")
Expand All @@ -156,55 +127,4 @@ public String getUser(@PathParam("id") String id) {

}

@Configuration(proxyBeanMethods = false)
static class CustomJerseyTagsProviderConfiguration {

@Bean
JerseyTagsProvider customJerseyTagsProvider() {
return new CustomJerseyTagsProvider();
}

}

static class CustomJerseyTagsProvider implements JerseyTagsProvider {

@Override
public Iterable<Tag> httpRequestTags(RequestEvent event) {
return null;
}

@Override
public Iterable<Tag> httpLongRequestTags(RequestEvent event) {
return null;
}

}

@SuppressWarnings("deprecation")
@Configuration(proxyBeanMethods = false)
static class CustomMicrometerJerseyTagsProviderConfiguration {

@Bean
io.micrometer.core.instrument.binder.jersey.server.JerseyTagsProvider customJerseyTagsProvider() {
return new CustomMicrometerJerseyTagsProvider();
}

}

@SuppressWarnings("deprecation")
static class CustomMicrometerJerseyTagsProvider
implements io.micrometer.core.instrument.binder.jersey.server.JerseyTagsProvider {

@Override
public Iterable<Tag> httpRequestTags(RequestEvent event) {
return null;
}

@Override
public Iterable<Tag> httpLongRequestTags(RequestEvent event) {
return null;
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -815,7 +815,7 @@ By default, Jersey server metrics are tagged with the following information:
| The request's URI template prior to variable substitution, if possible (for example, `/api/person/\{id}`)
|===

To customize the tags, provide a `@Bean` that implements `JerseyTagsProvider`.
To customize the tags, provide a `@Bean` that implements `JerseyObservationConvention`.



Expand Down

0 comments on commit 3a565e4

Please sign in to comment.