记录每一个请求的header,body以及响应的header,body等等信息。
AccessLogFilter
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Enumeration;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
/**
*
* 访问日志记录
*
* @author KevinBlandy
*
*/
@WebFilter(urlPatterns = "/**")
@Component
@Order(-8000)
public class AccessLogFilter extends HttpFilter {
/**
*
*/
private static final long serialVersionUID = 2067392425765043106L;
public static final Logger LOGGER = LoggerFactory.getLogger(AccessLogFilter.class);
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
// 请求日志的专用 GSON 格式化
private Gson logGson;
@Override
public void init() throws ServletException {
super.init();
this.logGson = new GsonBuilder()
.serializeNulls() // null 参数也序列化
.setPrettyPrinting() // 格式化后输出
.disableHtmlEscaping() // 不编码HTML
.create();
}
@Override
protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = null;
HttpServletResponse response = null;
// Multipart 请求,不包装Request
String contentType = req.getHeader(HttpHeaders.CONTENT_TYPE);
if (StringUtils.hasLength(contentType) && contentType.startsWith(MediaType.MULTIPART_FORM_DATA_VALUE)) {
request = req;
} else {
request = new ContentCachingRequestWrapper(req);
}
// 文件下载,图片验证码 不包装Response
// TODO 判断方式待优化
String requestUir = req.getRequestURI();
if (requestUir.contains("down") || requestUir.contains("captcha") || requestUir.contains("favicon.ico")) {
response = res;
} else {
response = new ContentCachingResponseWrapper(res);
}
try {
Instant start = Instant.now();
super.doFilter(request, response, chain);
Instant end = Instant.now();
response.setIntHeader(io.springcloud.constant.HttpHeaders.X_RESPONSE_TIME, (int) (end.toEpochMilli() - start.toEpochMilli()));
} catch (Exception e) {
LOGGER.error("access error: {}", e.getMessage());
throw e;
}
this.onLog(request, response, this.accessLog(request, response));
if (response instanceof ContentCachingResponseWrapper){
((ContentCachingResponseWrapper) response).copyBodyToResponse(); // 把缓存的数据响应给客户端
}
}
/**
* 访问日志解析
* @param req
* @param resp
* @return
*/
private JsonObject accessLog (HttpServletRequest req, HttpServletResponse resp) {
JsonObject accessLog = new JsonObject();
// 请求ID
accessLog.addProperty("requestId", req.getHeader(io.springcloud.constant.HttpHeaders.X_REQUEST_ID));
// client address
accessLog.addProperty("remoteAddress", req.getRemoteAddr());
// request method
accessLog.addProperty("method", req.getMethod());
// request url
accessLog.addProperty("requestUrl", req.getRequestURL().toString());
// query params
accessLog.addProperty("queryParam", req.getQueryString());
// request header
Enumeration<String> nameEnumeration = req.getHeaderNames();
JsonObject requestHeader = new JsonObject();
while (nameEnumeration.hasMoreElements()) {
String name = nameEnumeration.nextElement();
Enumeration<String> valueEnumeration = req.getHeaders(name);
JsonArray headers = new JsonArray(1);
while (valueEnumeration.hasMoreElements()) {
headers.add(valueEnumeration.nextElement());
}
requestHeader.add(name, headers);
}
accessLog.add("requestHeader", requestHeader);
// requestBody
/**
* TODO 在Undertow环境下,如果是请求体超出限制(server.undertow.max-http-post-size),而抛出异常:RequestTooBigException。那么RequestBody读取不到,为空字符串
*/
String requestBody = null;
if (req instanceof ContentCachingRequestWrapper){
requestBody = new String(((ContentCachingRequestWrapper)req).getContentAsByteArray(), StandardCharsets.UTF_8);
}
accessLog.addProperty("requestBody", requestBody);
// response header
JsonObject responseHeader = new JsonObject();
for (String headerName : resp.getHeaderNames()) {
JsonArray jsonArray = new JsonArray(1);
resp.getHeaders(headerName).forEach(jsonArray::add);
responseHeader.add(headerName, jsonArray);
}
// response status
accessLog.addProperty("responseStatus", resp.getStatus());
// response header
accessLog.add("responseHeader", responseHeader);
String responseBody = null;
if (resp instanceof ContentCachingResponseWrapper){
responseBody = new String(((ContentCachingResponseWrapper)resp).getContentAsByteArray(), StandardCharsets.UTF_8);
}
// response body
accessLog.addProperty("responseBody", responseBody);
return accessLog;
}
protected void onLog(HttpServletRequest request, HttpServletResponse response, JsonObject accessLog) {
String requestId = request.getHeader(io.springcloud.constant.HttpHeaders.X_REQUEST_ID);
// 解析请求日志
String log = logGson.toJson(accessLog);
LOGGER.debug("access log: {}{}{}", requestId, LINE_SEPARATOR, log);
}
}