From 4046b5af0f7854811aa77dc20fdd078ce428cae0 Mon Sep 17 00:00:00 2001 From: pangdayuan Date: Tue, 19 Dec 2023 20:52:35 +0800 Subject: [PATCH] feat: add Interceptor to record payload info --- arex-storage-config/pom.xml | 2 +- arex-storage-model/pom.xml | 2 +- arex-storage-web-api/pom.xml | 2 +- .../CachingInterceptorConfiguration.java | 47 +++++++++ .../beans/StorageAutoConfiguration.java | 1 + .../storage/filter/ContentCachingFilter.java | 58 +++++++++++ .../interceptor/MetricInterceptor.java | 97 +++++++++++++++++++ .../storage/metric/MetricListener.java | 7 +- pom.xml | 2 +- 9 files changed, 213 insertions(+), 5 deletions(-) create mode 100644 arex-storage-web-api/src/main/java/com/arextest/storage/beans/CachingInterceptorConfiguration.java create mode 100644 arex-storage-web-api/src/main/java/com/arextest/storage/filter/ContentCachingFilter.java create mode 100644 arex-storage-web-api/src/main/java/com/arextest/storage/interceptor/MetricInterceptor.java diff --git a/arex-storage-config/pom.xml b/arex-storage-config/pom.xml index bae2b983..ef9ee108 100644 --- a/arex-storage-config/pom.xml +++ b/arex-storage-config/pom.xml @@ -45,7 +45,7 @@ arex-storage-service com.arextest - 1.0.57 + 1.0.58 diff --git a/arex-storage-model/pom.xml b/arex-storage-model/pom.xml index c9c4a48c..bc7ccc92 100644 --- a/arex-storage-model/pom.xml +++ b/arex-storage-model/pom.xml @@ -7,7 +7,7 @@ arex-storage-service com.arextest - 1.0.57 + 1.0.58 diff --git a/arex-storage-web-api/pom.xml b/arex-storage-web-api/pom.xml index c6d04b6c..e556cd8f 100644 --- a/arex-storage-web-api/pom.xml +++ b/arex-storage-web-api/pom.xml @@ -123,7 +123,7 @@ arex-storage-service com.arextest - 1.0.57 + 1.0.58 diff --git a/arex-storage-web-api/src/main/java/com/arextest/storage/beans/CachingInterceptorConfiguration.java b/arex-storage-web-api/src/main/java/com/arextest/storage/beans/CachingInterceptorConfiguration.java new file mode 100644 index 00000000..5f847b35 --- /dev/null +++ b/arex-storage-web-api/src/main/java/com/arextest/storage/beans/CachingInterceptorConfiguration.java @@ -0,0 +1,47 @@ +package com.arextest.storage.beans; + +import com.arextest.storage.filter.ContentCachingFilter; +import com.arextest.storage.interceptor.MetricInterceptor; +import com.arextest.storage.metric.MetricListener; +import java.util.List; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * Configuration class that registers and sets up the ContentCachingFilter interceptor. + * created by xinyuan_wang on 2023/12/25 + */ +@Configuration +public class CachingInterceptorConfiguration implements WebMvcConfigurer { + + private final MetricInterceptor metricInterceptor; + public final List metricListeners; + + public CachingInterceptorConfiguration(MetricInterceptor metricInterceptor, List metricListeners) { + this.metricInterceptor = metricInterceptor; + this.metricListeners = metricListeners; + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + if (CollectionUtils.isEmpty(metricListeners)) { + return; + } + registry.addInterceptor(metricInterceptor); + } + + /** + * Register the ContentCachingFilter filter and add it to the Filter chain + */ + @Bean + public FilterRegistrationBean contentCachingFilter() { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(new ContentCachingFilter(metricListeners)); + registrationBean.addUrlPatterns("/*"); + return registrationBean; + } +} diff --git a/arex-storage-web-api/src/main/java/com/arextest/storage/beans/StorageAutoConfiguration.java b/arex-storage-web-api/src/main/java/com/arextest/storage/beans/StorageAutoConfiguration.java index d1621209..ece517b0 100644 --- a/arex-storage-web-api/src/main/java/com/arextest/storage/beans/StorageAutoConfiguration.java +++ b/arex-storage-web-api/src/main/java/com/arextest/storage/beans/StorageAutoConfiguration.java @@ -7,6 +7,7 @@ import com.arextest.model.mock.AREXMocker; import com.arextest.model.mock.MockCategoryType; import com.arextest.storage.converter.ZstdJacksonMessageConverter; +import com.arextest.storage.interceptor.RequestResponseMetricInterceptor; import com.arextest.storage.metric.AgentWorkingMetricService; import com.arextest.storage.metric.MatchStrategyMetricService; import com.arextest.storage.metric.MetricListener; diff --git a/arex-storage-web-api/src/main/java/com/arextest/storage/filter/ContentCachingFilter.java b/arex-storage-web-api/src/main/java/com/arextest/storage/filter/ContentCachingFilter.java new file mode 100644 index 00000000..4d9f4dc7 --- /dev/null +++ b/arex-storage-web-api/src/main/java/com/arextest/storage/filter/ContentCachingFilter.java @@ -0,0 +1,58 @@ +package com.arextest.storage.filter; + +import com.arextest.storage.metric.MetricListener; +import java.io.IOException; +import java.util.List; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.util.ContentCachingRequestWrapper; +import org.springframework.web.util.ContentCachingResponseWrapper; + +/** + * Cache the response and record the size of the request and response + * created by xinyuan_wang on 2023/12/25 + */ +public class ContentCachingFilter implements Filter { + private static final String GET_METHOD = "GET"; + private final List metricListeners; + public ContentCachingFilter(List metricListeners) { + this.metricListeners = metricListeners; + } + + @Override + public void init(FilterConfig filterConfig) { + // do nothing + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + if (CollectionUtils.isEmpty(metricListeners)) { + chain.doFilter(request, response); + return; + } + if (skipMetric(((HttpServletRequest) request).getMethod())) { + chain.doFilter(request, response); + return; + } + chain.doFilter(new ContentCachingRequestWrapper((HttpServletRequest) request), + new ContentCachingResponseWrapper((HttpServletResponse) response)); + } + + @Override + public void destroy() { + // do nothing + } + + private boolean skipMetric(String method) { + return StringUtils.isEmpty(method) || GET_METHOD.equalsIgnoreCase(method); + } +} diff --git a/arex-storage-web-api/src/main/java/com/arextest/storage/interceptor/MetricInterceptor.java b/arex-storage-web-api/src/main/java/com/arextest/storage/interceptor/MetricInterceptor.java new file mode 100644 index 00000000..7a21a5b6 --- /dev/null +++ b/arex-storage-web-api/src/main/java/com/arextest/storage/interceptor/MetricInterceptor.java @@ -0,0 +1,97 @@ +package com.arextest.storage.interceptor; + +import com.arextest.storage.metric.MetricListener; +import com.google.common.collect.Maps; +import java.util.List; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.lang.Nullable; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.util.ContentCachingRequestWrapper; +import org.springframework.web.util.ContentCachingResponseWrapper; + +/** + * record request and response interceptor + * created by xinyuan_wang on 2023/12/25 + */ +@Component +public class MetricInterceptor implements HandlerInterceptor { + private static final String CLIENT_APP_HEADER = "client-app"; + private static final String CATEGORY_TYPE_HEADER = "category-type"; + private static final String START_TIME = "startTime"; + private static final String ENTRY_PAYLOAD_NAME = "service.entry.payload"; + private static final String TYPE = "type"; + private static final String REQUEST_TAG = "request"; + private static final String RESPONSE_TAG = "response"; + private static final String CLIENT_APP_ID = "clientAppId"; + private static final String PATH = "path"; + private static final String CATEGORY = "category"; + public final List metricListeners; + + public MetricInterceptor(List metricListeners) { + this.metricListeners = metricListeners; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { + request.setAttribute(START_TIME, System.currentTimeMillis()); + return true; + } + + @Override + public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, + @Nullable ModelAndView modelAndView) throws Exception { + if (!(request instanceof ContentCachingRequestWrapper && response instanceof ContentCachingResponseWrapper)) { + return; + } + + long endTime = System.currentTimeMillis(); + + byte[] requestBody = ((ContentCachingRequestWrapper) request).getContentAsByteArray(); + int requestLength = requestBody.length; + + ContentCachingResponseWrapper responseWrapper = (ContentCachingResponseWrapper) response; + int responseLength = responseWrapper.getContentSize(); + responseWrapper.copyBodyToResponse(); + + long startTime = (Long) request.getAttribute(START_TIME); + + String clientApp = request.getHeader(CLIENT_APP_HEADER); + String category = request.getHeader(CATEGORY_TYPE_HEADER); + recordPayloadInfo(clientApp, category, request.getRequestURI(), requestLength, responseLength, endTime - startTime); + } + + public void recordPayloadInfo(String clientApp, String category, + String path, int requestLength, int responseLength, long executeMillis) { + if (CollectionUtils.isEmpty(metricListeners)) { + return; + } + + Map tags = Maps.newHashMapWithExpectedSize(5); + putIfValueNotEmpty(clientApp, CLIENT_APP_ID, tags); + putIfValueNotEmpty(category, CATEGORY, tags); + putIfValueNotEmpty(path, PATH, tags); + for (MetricListener metricListener : metricListeners) { + if (executeMillis > 0) { + metricListener.recordTime(ENTRY_PAYLOAD_NAME, tags, executeMillis); + } + + tags.put(TYPE, REQUEST_TAG); + metricListener.recordSize(ENTRY_PAYLOAD_NAME, tags, requestLength); + tags.put(TYPE, RESPONSE_TAG); + metricListener.recordSize(ENTRY_PAYLOAD_NAME, tags, responseLength); + } + } + + private void putIfValueNotEmpty(String value, String tagName, Map tags) { + if (StringUtils.isEmpty(value)) { + return; + } + tags.put(tagName, value); + } +} diff --git a/arex-storage-web-api/src/main/java/com/arextest/storage/metric/MetricListener.java b/arex-storage-web-api/src/main/java/com/arextest/storage/metric/MetricListener.java index 86dde655..5123fde7 100644 --- a/arex-storage-web-api/src/main/java/com/arextest/storage/metric/MetricListener.java +++ b/arex-storage-web-api/src/main/java/com/arextest/storage/metric/MetricListener.java @@ -17,4 +17,9 @@ public interface MetricListener { * record match strategy count with tags. */ void recordMatchingCount(String metricName, Map tags); -} \ No newline at end of file + + /** + * record size with tags. + */ + void recordSize(String metricName, Map tags, int size); +} diff --git a/pom.xml b/pom.xml index 6cea1b21..15049b45 100644 --- a/pom.xml +++ b/pom.xml @@ -408,5 +408,5 @@ https://github.com/arextest/arex-storage - 1.0.57 + 1.0.58 \ No newline at end of file