【发布时间】:2021-04-07 22:26:36
【问题描述】:
我想写一个方面或类似的东西,每当请求到达控制器时,它就会将请求和响应保存到数据库。
第一个问题是我应该在我的实体中使用什么类型来请求和响应(字符串、blob 等)
第二个问题,如何获取request、response及其控制器名来创建实体保存到数据库?
最后,是否可以计算控制器的响应时间(在控制器中花费的时间)?
【问题讨论】:
标签: java spring spring-boot logging aop
我想写一个方面或类似的东西,每当请求到达控制器时,它就会将请求和响应保存到数据库。
第一个问题是我应该在我的实体中使用什么类型来请求和响应(字符串、blob 等)
第二个问题,如何获取request、response及其控制器名来创建实体保存到数据库?
最后,是否可以计算控制器的响应时间(在控制器中花费的时间)?
【问题讨论】:
标签: java spring spring-boot logging aop
添加这个依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
然后,为您的控制器方法的执行创建一个Around 方面:
@Around("within(path.to.your.controller.*)")
public Object pointcutWithin(ProceedingJoinPoint joinPoint) throws Throwable {
logger.info(" ###### pointcutWithin() before");
long start = System.nanoTime();
Object result = joinPoint.proceed();
logger.info(" ###### pointcutWithin() after");
long end = System.nanoTime();
long timeElapsedInMillis = (end - start) / 1000000;
logger.info(" ###### elapsed time in millis: "+timeElapsedInMillis);
return result;
}
至于持久化:首先,像这样获取 req 和 resp:
MyRequest req = (MyRequest) joinPoint.getArgs()[0];
MyResponse resp = (MyResponse) result;
那么,你真正想要坚持什么取决于你。对于具有简单字段的类,我会使用 varchar,只要记住覆盖它们的 toString 方法即可。
【讨论】:
第一个问题是我应该在我的实体中使用什么类型来请求和 响应(字符串、blob 等)
主要取决于数据库供应商和请求/响应长度。
某些供应商可能会限制字符串,因此需要 blob。
另一方面,在 blob 上的匹配速度较慢。
另一种选择是使用 nosql 格式,例如 JSON。
第二个问题,如何获取request、response及其控制器名 创建要保存到数据库的实体?
确实有几种方法。
您可以利用内置的 Spring Boot http tracing features,但它有一个限制:发布/接收的请求/响应不可用。
5.8。 HTTP 跟踪
可以通过提供一个 bean 类型来启用 HTTP 跟踪 应用程序配置中的 HttpTraceRepository。为了 为了方便,Spring Boot 提供了一个 InMemoryHttpTraceRepository 默认情况下,存储最近 100 次请求-响应交换的跟踪。 与其他跟踪相比,InMemoryHttpTraceRepository 受到限制 解决方案,我们建议仅将其用于开发环境。 对于生产环境,使用生产就绪跟踪或 可观察性解决方案,例如 Zipkin 或 Spring Cloud Sleuth,是 推荐的。或者,创建您自己的 HttpTraceRepository 满足您的需求。
httptrace 端点可用于获取有关 存储在 HttpTraceRepository 中的请求-响应交换。
5.8.1。自定义 HTTP 跟踪
要自定义每个跟踪中包含的项目,请使用 management.trace.http.include 配置属性。对于高级 自定义,考虑注册自己的 HttpExchangeTracer 实施。
替代方案正在为请求/响应实施过滤器并登录。
例如:
@Component
public class RequestResponseStoringFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
long start = System.currentTimeMillis();
try {
chain.doFilter(req, resp);
} finally {
// Measure elapsed time
long elapsed = System.currentTimeMillis() - start;
// store request data and store response data in a DB
.....
}
}
@Override
public void destroy() {}
@Override
public void init(FilterConfig arg0) throws ServletException {}
}
最后,是否可以计算响应时间(在 控制器)的控制器?
实现过滤器的方式可以如上所示。
httptrace 端点方式提供了 timeTaken 字段。
FIY,这是HttpTrace 实例的内容:
【讨论】:
这有点扩展其他答案,但我认为它需要一个单独的答案。
如果您引入了 spring-boot-starter-web,那么您已经引入了 spring-aop。但是,如果您要走切入点路线,我强烈建议您只使用 spring-boot-starter-actuator 附带的 Micrometer @Timed 注释。我已经多次编写了自己的度量切入点,但如果您只是在计时和成功和失败的次数之后,@Timed 效果很好。
我还强烈建议研究使用时间序列数据库(例如 influx)来存储响应时间和其他性能指标等内容。将原始有效负载和其他可能的审计问题保存在单独的数据库中。您可以使用 influx 做一些非常强大的事情,并在其上运行 Grafana 或 Chronograf。毫无疑问,我目前的公司多年来所做的最好的事情之一就是采用 Influx/Chronograf。
关于请求/响应捕获,我的工作流程中有一个奇怪的边缘情况,曾经 http 跟踪无法满足一些硬性要求。您可以使用ContentCachingRequestWrapper 直接在链过滤器中捕获内容
然后您可以通过以下方式访问它们:
@Component
class MyPayloadCapturingFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request)
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response)
filterChain.doFilter(requestWrapper, responseWrapper)
def requestBody = new String(requestWrapper.contentAsByteArray)
def responseBody = new String(responseWrapper.contentAsByteArray)
//..do something with them
}
}
注意OncePerRequestFilter,我发现我的过滤器多次触发同一请求。这可以防止这种情况发生。
【讨论】: