/**
* Copyright 2017 Pivotal Software, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.metrics.boot;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.condition.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.env.Environment;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.metrics.instrument.MeterRegistry;
import org.springframework.metrics.instrument.scheduling.MetricsSchedulingAspect;
import org.springframework.metrics.instrument.web.*;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.*;
import java.util.ArrayList;
import java.util.regex.Pattern;
/**
* Enable dimensional metrics collection.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(MetricsBoot1Configuration.class)
public @interface EnableMetricsBoot1 {
}
@Configuration
class MetricsBoot1Configuration {
@Bean
@ConditionalOnMissingBean(WebMetricsTagProvider.class)
public WebMetricsTagProvider defaultMetricsTagProvider() {
return new DefaultWebMetricsTagProvider();
}
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@Import(WebMetricsTagProviderConfiguration.class)
static class WebFluxConfiguration {
@Autowired
MeterRegistry registry;
@Bean
public WebfluxMetricsWebFilter webfluxMetrics(WebMetricsTagProvider provider) {
return new WebfluxMetricsWebFilter(registry, provider, "http_server_requests");
}
}
/**
* We continue to use the deprecated WebMvcConfigurerAdapter for backwards compatibility
* with Spring Framework 4.X.
*/
@SuppressWarnings("deprecation")
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@Import(WebMetricsTagProviderConfiguration.class)
static class WebMvcConfiguration extends org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter {
@Autowired
MeterRegistry registry;
@Autowired
WebMetricsTagProvider tagProvider;
@Autowired
Environment environment;
@Bean
WebmvcMetricsHandlerInterceptor webMetricsInterceptor() {
return new WebmvcMetricsHandlerInterceptor(registry, tagProvider,
environment.getProperty("spring.metrics.web.server_requests.name", "http_server_requests"));
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(webMetricsInterceptor());
}
}
@Configuration
@ConditionalOnWebApplication
static class WebMetricsTagProviderConfiguration {
@Bean
@ConditionalOnMissingBean(WebMetricsTagProvider.class)
@ConditionalOnClass(name = "javax.servlet.http.HttpServletRequest")
public WebMetricsTagProvider defaultMetricsTagProvider() {
return new DefaultWebMetricsTagProvider();
}
@Bean
@ConditionalOnMissingBean(WebMetricsTagProvider.class)
@ConditionalOnMissingClass("javax.servlet.http.HttpServletRequest")
public WebMetricsTagProvider emptyMetricsTagProvider() {
return new WebMetricsTagProvider() {};
}
}
/**
* If AOP is not enabled, scheduled interception will not work.
*/
@Bean
@ConditionalOnClass({RestTemplate.class, JoinPoint.class})
@ConditionalOnProperty(value = "spring.aop.enabled", havingValue = "true", matchIfMissing = true)
public MetricsSchedulingAspect metricsSchedulingAspect(MeterRegistry registry) {
return new MetricsSchedulingAspect(registry);
}
/**
* If AOP is not enabled, client request interception will still work, but the URI tag
* will always be evaluated to "none".
*/
@Configuration
@ConditionalOnClass({RestTemplate.class, JoinPoint.class})
@ConditionalOnProperty(value = "spring.aop.enabled", havingValue = "true", matchIfMissing = true)
static class MetricsRestTemplateAspectConfiguration {
@Bean
RestTemplateUrlTemplateCapturingAspect restTemplateUrlTemplateCapturingAspect() {
return new RestTemplateUrlTemplateCapturingAspect();
}
}
@Configuration
@ConditionalOnClass(RestTemplate.class)
static class MetricsRestTemplateConfiguration {
@Bean
MetricsClientHttpRequestInterceptor spectatorLoggingClientHttpRequestInterceptor(MeterRegistry meterRegistry,
WebMetricsTagProvider tagProvider,
Environment environment) {
return new MetricsClientHttpRequestInterceptor(meterRegistry, tagProvider,
environment.getProperty("spring.metrics.web.client_requests.name", "http_client_requests"));
}
@Bean
BeanPostProcessor spectatorRestTemplateInterceptorPostProcessor() {
return new MetricsInterceptorPostProcessor();
}
private static class MetricsInterceptorPostProcessor
implements BeanPostProcessor, ApplicationContextAware {
private ApplicationContext context;
private MetricsClientHttpRequestInterceptor interceptor;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof RestTemplate) {
if (this.interceptor == null) {
this.interceptor = this.context
.getBean(MetricsClientHttpRequestInterceptor.class);
}
RestTemplate restTemplate = (RestTemplate) bean;
// create a new list as the old one may be unmodifiable (ie Arrays.asList())
ArrayList<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
interceptors.add(interceptor);
interceptors.addAll(restTemplate.getInterceptors());
restTemplate.setInterceptors(interceptors);
}
return bean;
}
@Override
public void setApplicationContext(ApplicationContext context)
throws BeansException {
this.context = context;
}
}
}
}
/**
* Captures the still-templated URI because currently the ClientHttpRequestInterceptor
* currently only gives us the means to retrieve the substituted URI.
*
* @author Jon Schneider
*/
@Aspect
class RestTemplateUrlTemplateCapturingAspect {
@Around("execution(* org.springframework.web.client.RestOperations+.*(String, ..))")
Object captureUrlTemplate(ProceedingJoinPoint joinPoint) throws Throwable {
try {
String urlTemplate = (String) joinPoint.getArgs()[0];
RestTemplateUrlTemplateHolder.setRestTemplateUrlTemplate(urlTemplate);
return joinPoint.proceed();
} finally {
RestTemplateUrlTemplateHolder.clear();
}
}
}