记录日志的上下文类: ContextHolder
public final class ContextHolder {
private static ThreadLocalContext context = new ThreadLocalContext();
private long interval;
private long startTime;
private Throwable exception;
private String requestBody;
private String responseBody;
private String Method;
private String ServletPath;
// 请求URL参数
private Map<String, String> requestData;
private Map<String, String> cookie;
private Map<String, String> headers;
// 业务具体日志
private Map<String, Object> businessData;
private ContextHolder() {
this.requestData = new ConcurrentHashMap();
this.cookie = new ConcurrentHashMap();
this.headers = new ConcurrentHashMap();
this.businessData = new ConcurrentHashMap();
}
public static ContextHolder ctx() {
return (ContextHolder) context.get();
}
/**
* Remove.
*/
public static void remove() {
context.remove();
}
public static void setContext(ContextHolder contextHolder) {
context.set(contextHolder);
}
public static ThreadLocalContext getContext() {
return context;
}
public void setException(Throwable exception) {
this.exception = exception;
}
public static void setContext(ThreadLocalContext context) {
ContextHolder.context = context;
}
public Throwable getException() {
return exception;
}
public String getRequestBody() {
return requestBody;
}
public void setRequestBody(String requestBody) {
this.requestBody = requestBody;
}
public String getResponseBody() {
return responseBody;
}
public void setResponseBody(String responseBody) {
this.responseBody = responseBody;
}
public String getMethod() {
return Method;
}
public void setMethod(String method) {
Method = method;
}
public String getServletPath() {
return ServletPath;
}
public void setServletPath(String servletPath) {
ServletPath = servletPath;
}
public Map<String, String> getRequestData() {
return requestData;
}
public Map<String, String> getCookie() {
return cookie;
}
public Map<String, String> getHeaders() {
return headers;
}
public Map<String, Object> getBusinessData() {
return businessData;
}
public long getInterval() {
return interval;
}
public void setInterval(long interval) {
this.interval = interval;
}
public long getStartTime() {
return startTime;
}
public void setStartTime(long startTime) {
this.startTime = startTime;
}
private static class ThreadLocalContext extends ThreadLocal<ContextHolder> {
private ThreadLocalContext() {
}
@Override
protected ContextHolder initialValue() {
return new ContextHolder();
}
}
}
request 请求包装类:
RequestWrapper
import org.apache.commons.io.IOUtils;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
public class RequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
public RequestWrapper(HttpServletRequest request) throws IOException {
super(request);
body = IOUtils.toByteArray(request.getInputStream());
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);
ServletInputStream servletInputStream = new ServletInputStream() {
public boolean isFinished() {
return false;
}
public boolean isReady() {
return false;
}
public void setReadListener(ReadListener readListener) {}
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
return servletInputStream;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
public byte[] getBody() {
return this.body;
}
public String getStringBody() throws UnsupportedEncodingException {
return new String(this.body, "utf-8");
}
}
ResponseWrapper
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.*;
/**
* @author :yekeping
* @date :Created in 2020/10/28 9:15 上午
* @description:
* @version: 1.0
*/
public class ResponseWrapper extends HttpServletResponseWrapper {
private ByteArrayOutputStream bytes = new ByteArrayOutputStream();
private HttpServletResponse response;
private PrintWriter pwrite;
public ResponseWrapper(HttpServletResponse response) {
super(response);
this.response = response;
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return new HllServletOutputStream(bytes); // 将数据写到 byte 中
}
/**
* 重写父类的 getWriter() 方法,将响应数据缓存在 PrintWriter 中
*/
@Override
public PrintWriter getWriter() throws IOException {
try {
pwrite = new PrintWriter(new OutputStreamWriter(bytes, "utf-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return pwrite;
}
/**
* 获取缓存在 PrintWriter 中的响应数据
*
* @return
*/
public byte[] getBytes() {
if (null != pwrite) {
pwrite.close();
return bytes.toByteArray();
}
try {
bytes.flush();
} catch (IOException e) {
e.printStackTrace();
}
return bytes.toByteArray();
}
class HllServletOutputStream extends ServletOutputStream {
private ByteArrayOutputStream ostream;
public HllServletOutputStream(ByteArrayOutputStream ostream) {
this.ostream = ostream;
}
@Override
public void write(int b) throws IOException {
ostream.write(b); // 将数据写到 stream 中
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setWriteListener(WriteListener writeListener) {
}
}
}
ReplaceStreamFilter
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author :yekeping
* @date :Created in 2020/10/27 8:02 下午
* @description:
* @version: 1.0
*/
@Component
public class ReplaceStreamFilter implements Filter {
private static Logger logger = LoggerFactory.getLogger(ReplaceStreamFilter.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
ServletRequest requestWrapper = new RequestWrapper((HttpServletRequest) request);
ResponseWrapper responseWrapper = new ResponseWrapper((HttpServletResponse) response);
chain.doFilter(requestWrapper, responseWrapper);
byte[] bytes = responseWrapper.getBytes();
responseWrapper.setContentLength(-1);
ServletOutputStream out = response.getOutputStream();
out.write(bytes);
out.flush();
ContextHolder contextHolder = ContextHolder.ctx();
contextHolder.setResponseBody(new String(bytes,"utf-8"));
// 计算耗时
contextHolder.setInterval(System.currentTimeMillis() - contextHolder.getStartTime());
logger.info(JSON.toJSONString(contextHolder));
// 移除线程上下文记录
ContextHolder.remove();
}
@Override
public void destroy() {
}
}
LogHubInterceptor:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Enumeration;
import java.util.Map;
/**
* @author :yekeping
* @date :Created in 2020/10/23 2:40 下午
* @description:
* @version: 1.0
*/
@Component
public class LogHubInterceptor extends HandlerInterceptorAdapter {
private static Logger logger = LoggerFactory.getLogger(LogHubInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
ContextHolder contextHolder = ContextHolder.ctx();
try {
contextHolder.setStartTime(System.currentTimeMillis());
RequestWrapper requestWrapper = new RequestWrapper(request);
contextHolder.setMethod(request.getMethod());
contextHolder.setServletPath(request.getServletPath());
contextHolder.setRequestBody(requestWrapper.getStringBody());
//如果是spring mvc 方法
if (handler.getClass().isAssignableFrom(HandlerMethod.class)) {
// 检测是否有 @LogHeader 注解
LogHeader logHeader = ((HandlerMethod) handler).getMethodAnnotation(LogHeader.class);
if (logHeader != null) {
if (logHeader.needCooke()) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
Map<String, String> cookiesMap = contextHolder.getCookie();
for (Cookie cookie : cookies) {
cookiesMap.put(cookie.getName(), cookie.getValue());
}
}
}
if (logHeader.needHeader()) {
Enumeration<String> headerNames = request.getHeaderNames();
Map<String, String> headerMap = contextHolder.getHeaders();
while (headerNames.hasMoreElements()) {
String key = headerNames.nextElement();
if ("cookie".equalsIgnoreCase(key)) {
continue;
}
headerMap.put(key, request.getHeader(key));
}
}
}
}
String query = request.getQueryString();
Map<String, String> querysMap = contextHolder.getRequestData();
if (query != null && !query.isEmpty()) {
String[] querys = query.split("\\&");
for (String q : querys) {
String[] value = q.split("\\=");
if (value.length >= 2) {
querysMap.put(value[0], value[1]);
}
}
}
return true;
} catch (Exception e) {
contextHolder.setException(e);
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView
modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception
ex) throws Exception {
}
FilterConfig
@Configuration
public class FilterConfig {
@Autowired
private ReplaceStreamFilter replaceStreamFilter;
/**
* 注册过滤器
*
* @return FilterRegistrationBean
*/
@Bean
public FilterRegistrationBean someFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(replaceStreamFilter);
registration.addUrlPatterns("/*");
registration.setName("streamFilter");
return registration;
}
}
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private LogHubInterceptor logHubInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(logHubInterceptor).addPathPatterns("/**");
}
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/***
* 打印 http 请求头好Cookes 日志
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogHeader {
boolean needCooke() default true;
boolean needHeader() default true;
}