接入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();
}
}
\