【问题标题】:How to log any outgoing responses from @RestController?如何记录来自@RestController 的任何传出响应?
【发布时间】:2017-05-17 13:29:48
【问题描述】:

我正在使用CommonsRequestLoggingFilter 在我的@RestController 上记录任何传入请求。

@RestController
public class MyController {
    @PostMapping("/")
    public MyRsp ping() {
        ...
        return myRsp;
    }
}

用户将发送POST JSON 请求,这些请求已经使用:

@Bean
public CommonsRequestLoggingFilter requestLoggingFilter() {
    CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter();
    filter.setIncludeClientInfo(true);
    filter.setIncludeQueryString(true);
    filter.setIncludePayload(true);
    return filter;
}

问题:我怎样才能为我发回给用户的 JSON 响应实现相同的效果?

【问题讨论】:

  • 如果添加一个可以同时记录请求和响应的拦截器会怎样?
  • 嗯,我认为可能已经有一个现有的解决方案。如上所述,对于请求日志记录,已经有一个使用Filter
  • 写一个@Around 方面。这样,您可以在一个地方同时跟踪请求和响应。在这里查看我的答案。这可能有助于stackoverflow.com/questions/44000449/spring-boot-aspect-around/…
  • 但是我怎样才能访问@Around' method (eg the JSON` 输出中的内容也显示给用户)?
  • 你的意思是你想访问从你的控制器返回的对象(json)吗?当然可以了

标签: java json spring spring-mvc


【解决方案1】:

我不知道为什么 spring 提供了一个使用ContentCachingRequestWrapper 的休息记录器,但不提供响应记录。因为它可以实现和req日志非常相似,如下:

public class MyFilter extends CommonsRequestLoggingFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) {
        //same mechanism as for request caching in superclass
        HttpServletResponse responseToUse = response;
        if (isIncludePayload() && !isAsyncDispatch(request) && !(response instanceof ContentCachingResponseWrapper)) {
            responseToUse = new ContentCachingResponseWrapper(response);
        }

        //outgoing request is logged in superclass
        super.doFilterInternal(request, responseToUse, filterChain);

        //log incoming response
        String rsp = getResponseMessage(responseToUse);
        LOGGER.info(rsp);
    }   

    //equivalent to super.createMessage() for request logging
    private String getResponseMessage(HttpServletResponse rsp) {
        StringBuilder msg = new StringBuilder();

        ContentCachingResponseWrapper wrapper =
                    WebUtils.getNativeResponse(request, ContentCachingResponseWrapper.class);

        if (wrapper != null) {
                byte[] buf = wrapper.getContentAsByteArray();
                if (buf.length > 0) {
                    int length = Math.min(buf.length, getMaxPayloadLength());
                    String payload;
                    try {
                        payload = new String(buf, 0, length, wrapper.getCharacterEncoding());
                    }
                    catch (UnsupportedEncodingException ex) {
                        payload = "[unknown]";
                    }
                    msg.append(";payload=").append(payload);
                }
         }
    } 
}

【讨论】:

  • 它很好,但它似乎消耗了响应,我在客户端得到了空的有效载荷,但日志上的有效载荷很好
【解决方案2】:
@Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void requestMapping() {}

@Pointcut("within(path.to your.controller.package.*)")
public void myController() {}

@Around("requestMapping() || myController()")
public MyRsp logAround(ProceedingJoinPoint joinPoint) throws Throwable {
    joinPoint.getArgs()// This will give you arguments if any being passed to your controller.
   ...............
   MyRsp myRsp = (MyRsp) joinPoint.proceed();
   ...............
   return myRsp;
}

【讨论】:

  • 这没有帮助。因为我不需要访问 BeanDTO,而是需要访问发回给用户的文本内容。
  • 在上面的代码中,从控制器返回的任何内容都会在 Aspect 中捕获,然后再将其发送回用户。
  • 是的,我的控制器将MyRsp 作为对象返回。 Spring 会自动处理其余部分以将其转换为 JSON 响应。我想记录返回给用户的 json 文本响应。但这在周围的剪辑中无法访问?
  • 为什么你不能自动连接 ObjectMapper 并执行 writeValueAsString。事实上,这就是 Spring 在内部为您所做的事情
  • 好吧,我不知道。但是,如果我再次从 bean 序列化到 json,开销又会怎样呢?仅仅阅读/记录已经准备好发送给用户的文本不是更好吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多