需求:
1.记录用户请求记录,需要记录请求的参数,在异常情况下可以复现异常。
2.记录每个请求的响应记录。
实现:
记录请求日志
1.使用log4j输出记录到单独日志文件中,配置log4j
#配置logger和日志输出等级
log4j.logger.com.jsz.peini.interceptor.AccessLogFilter= INFO,accessLog
#配置不继承父类appender 它是 子Logger 是否继承 父Logger 的 输出源(appender) 的标志位。具体说,默认情况下子#Logger会继承父Logger的appender,也就是说子Logger会在父Logger的appender里输出。若是additivity设为false,则子#Logger只会在自己的appender里输出,而不会在父Logger的appender里输出。
log4j.additivity.com.jsz.peini.interceptor.AccessLogFilter=false
#配置按天滚动日志文件
log4j.appender.accessLog=org.apache.log4j.DailyRollingFileAppender
#日志文件位置
log4j.appender.accessLog.File=d:/peinilog/peini20access.log
#文件名
log4j.appender.accessLog.DatePattern=yyyy-MM-dd'.log'
log4j.appender.accessLog.layout=org.apache.log4j.PatternLayout
log4j.appender.accessLog.layout.ConversionPattern=%-d{yyyy-MM-dd HH\:mm\:ss} - %m%n
log4j.additivity.accessLog=false
2.请求参数分两种类型,一种存放在url中一种放在requestBody中。
在url中的参数使用request.getParameterMap();即可获取,不会对以后的使用造成影响,而requestBody中的数据,如果在过滤器中getInputStream后就会被消耗掉,后面的程序就无法再次获取。所以需要将request对象包装一下,缓存住inputstream中的内容。
实现HttpServletRequestWrapper类 将数据缓存到byte数组中,重写getInputStream和getReader方法。
package com.jsz.peini.interceptor;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
public class PnRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body; // 报文
final static int BUFFER_SIZE = 4096;
public PnRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
body = InputStreamTOByte(request.getInputStream());
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
};
}
// 将InputStream转换成byte数组
public static byte[] InputStreamTOByte(InputStream in) throws IOException {
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
byte[] data = new byte[BUFFER_SIZE];
int count = -1;
while ((count = in.read(data, 0, BUFFER_SIZE)) != -1)
outStream.write(data, 0, count);
data = null;
return outStream.toByteArray();
}
}
3.解决了前面的问题后实现filter
package com.jsz.peini.interceptor;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSONObject;
import com.jsz.peini.common.util.PeiniUtils;
import com.jsz.peini.common.util.ThreadUtil;
public class AccessLogFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(AccessLogFilter.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
/**
* 判断请求
* get请求不用多余处理,获取请求参数后放行。
* post请求判断
* content-type:keyvalue获取requestmap记录
* json 包装request获取inputstream 记录
* 放行
*
* 记录参数:访问时间-响应时间-请求的接口-请求的类型-请求的userid-请求的token-请求的ip-请求useragent-请求的parameter-请求的inputstream
*/
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
long startTime = System.currentTimeMillis();
String method = httpServletRequest.getMethod();
String accessIp = getIpAddr(httpServletRequest);
String accessUrl = httpServletRequest.getRequestURI();
//从header获取token 如果token未获取到,从parameter获取token(userId)
String token = httpServletRequest.getHeader("token");
String userId = "";
if(StringUtils.isBlank(token)){
token = request.getParameter("userId");
if(StringUtils.isNotBlank(token)){
JSONObject obj = PeiniUtils.validToken(token);
if (obj.getInteger("resultCode") != 1) {
} else {
userId = obj.getString("userId");
}
}else{
userId = token;
}
}else{
userId = ThreadUtil.getThreadInfo().getUserId();
}
String userAgent = httpServletRequest.getHeader("User-Agent");
String requestParameter = "";
String requestBodyJson = "";
ServletRequest requestWrapper = null;
if("GET".equals(method)){
requestParameter = getRequestParameter(httpServletRequest);
}else if("POST".equals(method)){
if(request instanceof HttpServletRequest&&"application/json".equals(((HttpServletRequest) request).getHeader("Content-Type"))) {
requestWrapper = new PnRequestWrapper((HttpServletRequest) request);
}
if(requestWrapper == null) {
requestParameter = getRequestParameter(httpServletRequest);
}else{
InputStream is = requestWrapper.getInputStream();
StringBuilder requsetBodyBuilder = new StringBuilder ();
BufferedReader streamReader = new BufferedReader (new InputStreamReader (is,"UTF-8"));
String inputStr;
while ((inputStr = streamReader.readLine ()) != null){
requsetBodyBuilder.append (inputStr);
}
requestBodyJson = requsetBodyBuilder.toString();
}
}
if(requestWrapper == null) {
chain.doFilter(request, response);
}else{
chain.doFilter(requestWrapper, response);
}
long endTime = System.currentTimeMillis();
long execteTime = endTime - startTime;
logger.info("{} {} {} {} {} {} {} {} {}",execteTime,accessUrl,method,userId,token,accessIp,userAgent,requestParameter,requestBodyJson);
}
@Override
public void destroy() {
}
/**
* 获取当前网络ip
* @param request
* @return
*/
public String getIpAddr(HttpServletRequest request){
String ipAddress = request.getHeader("x-forwarded-for");
if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
if(ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")){
//根据网卡取本机配置的IP
InetAddress inet=null;
try {
inet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
ipAddress= inet.getHostAddress();
}
}
//对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
if(ipAddress!=null && ipAddress.length()>15){ //"***.***.***.***".length() = 15
if(ipAddress.indexOf(",")>0){
ipAddress = ipAddress.substring(0,ipAddress.indexOf(","));
}
}
return ipAddress;
}
public String getRequestParameter(HttpServletRequest request){
StringBuilder requestParameter = new StringBuilder();
Map<String, String[]> pa = request.getParameterMap();
for (Map.Entry<String, String[]> entry : pa.entrySet()) {
requestParameter.append(entry.getKey()+":"+entry.getValue()[0]);
}
return requestParameter.toString();
}
}
记录响应日志
记录响应内容,已开始困扰了我很久找不到合适的解决方案,拿不到response的输出流中的数据。
平时开发时除了mybatis sql有异常时日志等级设置为debug等级一般都是info等级,有次设置问debug等级后发现spring mvc会输出响应内容,追踪后是RequestResponseBodyMethodProcessor输出的。
在log4j的配置文件中为这个类配置为debug的日志等级,使用accessLog这个logger,additivity设置问false,既可以实现在日志文件中记录响应内容
log4j.logger.org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor=DEBUG,accessLog
log4j.additivity.org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor=false