场景描述:在异常日中打印请求url和请求传入的参数
采用的方式:使用slf4j+logback管理日志,在进入controller前将参数放入MDC中,在logba.xml中使用<pattern>%d - %X{requestBody} - %X{requestUrl} - %msg%n</pattern>输入日志
遇到问题:1、在获取body需采用获取requestinputStream的方式,但是流被读取后,不会进入对应的controller中,提示stream is closed。
2、通过在过滤器filter中,封装原生的HttpServletRequest请求,将其输入流里的数据保存在字节数组里,最后重写getInputStream方法,使其之后每次读取数据都是从字节数组里读取。出现request.getParameter()无法获取参数。
解决方法:
1、增加过滤器filter,获取请求中的流,将取出来的字符串,再次转换成流,然后把它放入到新request对象中
import org.apache.commons.io.IOUtils;
import org.slf4j.MDC;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
/*@WebFilter(filterName="Servlet3Filter",urlPatterns="/*")*/
public class FilterConfig implements Filter{
@Override
public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
MDC.clear();
ServletRequest requestWrapper = null;
//获取请求中的流,将取出来的字符串,再次转换成流,然后把它放入到新request对象中。
if(request instanceof HttpServletRequest) {
requestWrapper = new RequestReaderHttpServletRequestWrapper((HttpServletRequest) request);
}
// 在chain.doFiler方法中传递新的request对象
if(requestWrapper == null) {
chain.doFilter(request, response);
} else {
Writer writer = null;
IOUtils.copy(request.getInputStream(),writer,"utf-8");
chain.doFilter(requestWrapper, response);
}
}
@Override
public void destroy() {
}
}
2、RequestReaderHttpServletRequestWrapper代码如下:
import org.springframework.web.util.ContentCachingRequestWrapper;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
public class RequestReaderHttpServletRequestWrapper extends ContentCachingRequestWrapper {
private final byte[] body;
/**
* 将body取出存储起来然后再放回去,但是在request.getParameter()时数据就会丢失
* 调用getParameterMap(),目的将参数Map从body中取出,这样后续的任何request.getParamter()都会有值
* @param request request
* @throws IOException io异常
*/
public RequestReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
request.getParameterMap();//此处将body中的parameter取出来,,这样后续的任何request.getParamter()都会有值
body = LogUtils.getBodyString(request).getBytes(Charset.forName("UTF-8"));
}
@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();
}
};
}
}
3、在进入controller前,将请求参数放入MDC中
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
if(StringUtils.isBlank(getBodyString(request))){
MDC.put("requestBody", "requestBody:" + JsonUtils.toJson(request.getParameterMap()));
}else{
MDC.put("requestBody", "requestBody:" + getBodyString(request));
}
}
/**
* 获取请求Body
* @param request 过滤后被的request
* @return 返回body
*/
public static String getBodyString(ServletRequest request) {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = request.getInputStream();
reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (Exception e) {
throw new BusinessException("获取requestBody出错:" + e.getMessage());
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
if (reader != null) {
reader.close();
}
} catch (IOException ignored) {
}
}
return sb.toString();
}
4、最后在logbac.xml中增加输出
!-- ch.qos.logback.core.rolling.RollingFileAppender 异常日志输出 -->
<appender name="pay_log" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--文件路径默认catalina同级目录-->
<file>logs/paylog.log</file>
<Append>false</Append>
<encoder charset="UTF-8">
<!--%X{memberId} memberId是在MDC中put进去的数据 如未put任何数据则显示空格-->
<pattern>%d - %X{memberId} - %X{requestId} - %X{requestBody} - %X{requestUrl} - %msg%n</pattern>
<!--日志缓存,默认值8192 但appender异常或者系统崩溃时可能导致日志丢失,异常日志不建议使用-->
<!--<immediateFlush>false</immediateFlush>-->
</encoder>
<!--定义日志级别-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<!--按天生成日志-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/paylog.log.%d{yyyy-MM-dd}-%i</fileNamePattern>
<!--日志保留最长天数-->
<maxHistory>60</maxHistory>
<!--当天日志超过10MB则生成新的日志日志格式为paylog.2018-11-02-0.log 该属性不能删掉-->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<MaxFileSize>1024MB</MaxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
</appender>