如何优雅打日志

252 阅读1分钟

接入HTTP日志

整体思路

基于springBoot的spring.factories扩展,加载功能类。

功能类应有的功能如下:

  • 需要添加到拦截器
  • 每次请求需要获取请求参数打印
  • 每次响应需要获取响应体进行打印
  • 打印通过注解灵活控制

代码实现

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
*.TraceAutoConfiguration
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface RequestLog {
​
    /**
     * 是否打印请求日志
     *
     * @return
     */
    boolean value() default true;
​
}
​
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface ResponseLog {
​
    /**
     * 是否打印响应日志
     *
     * @return
     */
    boolean value() default true;
​
}
@Slf4j
@Configuration
@ControllerAdvice
public class HttpLogAdvice extends AbstractHttpLogAdvice {
​
    @Override
    protected void afterRequestRead(HttpInputMessageDelegate message) {
       //进行请求日志输出
    }
​
    @Override
    protected void beforeResponseWrite(ServerHttpRequest request, Object response) {
        //响应日志输出
    }
​
}
public abstract class AbstractHttpLogAdvice extends RequestBodyAdviceAdapter
        implements ResponseBodyAdvice<Object>, HandlerInterceptor, WebMvcConfigurer {
​
    /**
     * 解决线程消息体传递问题
     */
    private final ThreadLocal<HttpInputMessageDelegate> inputMessageLocal = new ThreadLocal<>();
​
    /**
     * 储值方法是否需要打印,请求或响应
     * 存在的问题
     * 1、HashMap 线程安全问题
     * 2、每次执行时,动态校验RequestLog和ResponseLog注解(影响不大)
     */
    private final Map<Method, Boolean> printRequest = new HashMap<>();
    private final Map<Method, Boolean> printResponse = new HashMap<>();
​
    /**
     * 注册拦截器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(this);
    }
​
​
    /**
     * 销毁ThreadLocal
     */
    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler,
                                Exception ex) throws Exception {
        inputMessageLocal.remove();
    }
​
​
    /**
     * 是否支持处理请求数据,无body不走
     */
    @Override
    public boolean supports(MethodParameter methodParameter,
                            Type targetType,
                            Class<? extends HttpMessageConverter<?>> converterType) {
        return supportsRequest(methodParameter.getMethod());
    }
​
    /**
     *  body数据读取之前调用
     */
    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage,
                                           MethodParameter parameter,
                                           Type targetType,
                                           Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        HttpInputMessageDelegate messageDelegate = new HttpInputMessageDelegate(inputMessage);
        inputMessageLocal.set(messageDelegate);
        return messageDelegate;
    }
​
    /**
     * body读取之后操作
     */
    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage,
                                MethodParameter parameter,
                                Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        HttpInputMessageDelegate message = inputMessageLocal.get();
        if (message != null) {
            afterRequestRead(message);
        }
        return body;
    }
​
​
    /**
     * 是否支持处理响应数据
     */
    @Override
    public boolean supports(MethodParameter returnType,
                            Class<? extends HttpMessageConverter<?>> converterType) {
        return supportsResponse(returnType.getMethod());
    }
​
    /**
     *
     */
    @Override
    public Object beforeBodyWrite(Object body,
                                  MethodParameter returnType,
                                  MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                  ServerHttpRequest request,
                                  ServerHttpResponse response) {
        beforeResponseWrite(request, body);
        return body;
    }
​
​
​
    protected <A extends Annotation> A getAnnotation(Method method, Class<A> annType) {
        A ann = method.getAnnotation(annType);
        return ann != null ? ann
                : method.getDeclaringClass().getAnnotation(annType);
    }
​
    protected boolean needPrintRequest(Method method) {
        RequestLog ann = getAnnotation(method, RequestLog.class);
        return ann != null ? ann.value() : true;
    }
​
    protected boolean needPrintResponse(Method method) {
        ResponseLog ann = getAnnotation(method, ResponseLog.class);
        return ann != null ? ann.value() : false;
    }
​
    protected final boolean supportsRequest(Method method) {
        return printRequest.computeIfAbsent(method, m -> needPrintRequest(method));
    }
​
    protected final boolean supportsResponse(Method method) {
        return printResponse.computeIfAbsent(method, m -> needPrintResponse(method));
    }
​
    protected abstract void afterRequestRead(HttpInputMessageDelegate message);
​
    protected abstract void beforeResponseWrite(ServerHttpRequest request, Object response);
​
​
​
}
/**
 * 封装入参实体
 */
public class HttpInputMessageDelegate implements HttpInputMessage {
​
    private final HttpInputMessage target;
    private final ByteArrayOutputStream baos;
​
    public HttpInputMessageDelegate(HttpInputMessage target) throws IOException {
        this.target = target;
        this.baos = new ByteArrayOutputStream(target.getBody().available());
    }
​
    public byte[] getBufferedBody() {
        return baos.toByteArray();
    }
​
    public String getBufferedBodyString() {
        return new String(getBufferedBody(), StandardCharsets.UTF_8);
    }
​
    @Override
    public InputStream getBody() throws IOException {
        InputStream body = target.getBody();
        return new InputStream() {
            @Override
            public int read() throws IOException {
                int read = body.read();
                if (read != -1) {
                    baos.write(read);
                }
                return read;
            }
        };
    }
​
    @Override
    public HttpHeaders getHeaders() {
        return target.getHeaders();
    }
​
}

\