ELK日志收集时,我们需要在Filter中统一的打印请求报文。便于在Kibina中查询
遇见两个问题:
- Servlet流是单向的,在Filter中解析那么后续会出现异常;
- 对于请求的不同Method(GET/POST等)如何在Filter中解析为字符串;
解决方案:
1.1 将单向流设置为可重复读流
定义一个优先级高的Filter,实现流的转换。
@Order(Integer.MIN_VALUE + 1)
@Slf4j
@Service
public class RepeatableFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
HttpServletRequest req = (HttpServletRequest) request;
String contentType = req.getContentType();
//对于文件上传,直接放行。
if (contentType != null && contentType.toLowerCase().contains("multipart")) {
//直接放行
chain.doFilter(request, response);
return;
}
//重写流对象
if (!(request instanceof BackupRequestWrapper)) {
req = new BackupRequestWrapper(req);
}
chain.doFilter(req, response);
}
}
原理是:继承HttpServletRequestWrapper,内部使用ByteArrayInputStream
来实现流的可重复读。
public class BackupRequestWrapper extends HttpServletRequestWrapper {
private final byte[] buffer;
public BackupRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
InputStream is = request.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int read;
byte[] buff = new byte[1024];
while ((read = is.read(buff)) > 0) {
baos.write(buff, 0, read);
}
this.buffer = baos.toByteArray();
}
@Override
public ServletInputStream getInputStream() throws IOException {
return new BufferedServletInputStream(this.buffer);
}
// 对外提供读取流的方法
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
}
class BufferedServletInputStream extends ServletInputStream {
private final ByteArrayInputStream inputStream;
public BufferedServletInputStream(byte[] buffer) {
this.inputStream = new ByteArrayInputStream(buffer);
}
@Override
public int available() throws IOException {
return inputStream.available();
}
@Override
public int read() throws IOException {
return inputStream.read();
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
return inputStream.read(b, off, len);
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
}
1.2 将request参数读取为JSON
提供一个工具类:来针对GET/POST请求来单独处理。
@Slf4j
public class RequestJsonUtil {
/***
* 获取 request 中 json 字符串的内容
*
* @return : <code>byte[]</code>
* @throws IOException
*/
public static String getRequestJsonString(HttpServletRequest request) throws IOException {
String submitMehtod = request.getMethod();
// GET
String s = new String("".getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8).replaceAll("%22", "\"");
if (submitMehtod.equals("GET")) {
if (StringUtils.isNotEmpty(request.getQueryString())) {
return new String(request.getQueryString().getBytes(StandardCharsets.ISO_8859_1),
StandardCharsets.UTF_8).replaceAll("%22", "\"");
}
return s;
}
// POST
String requestString = getRequestPostStr(request);
if (StringUtils.isNotBlank(requestString)) {
return requestString;
}
if (StringUtils.isNotEmpty(request.getQueryString())) {
return new String(request.getQueryString().getBytes(StandardCharsets.ISO_8859_1),
StandardCharsets.UTF_8).replaceAll("%22", "\"");
}
return s;
}
public static String getRequestJsonStringNoException(HttpServletRequest request) {
try {
return getRequestJsonString(request);
} catch (IOException e) {
log.error("获取请求参数异常", e);
}
return null;
}
/**
* 描述:获取 post 请求的 byte[] 数组
* <pre>
* 举例:
* </pre>
*/
public static byte[] getRequestPostBytes(HttpServletRequest request)
throws IOException {
int contentLength = request.getContentLength();
if (contentLength < 0) {
return null;
}
byte[] buffer;
buffer = new byte[contentLength];
int i = 0;
while (i < contentLength) {
int readlen = request.getInputStream().read(buffer, i,
contentLength - i);
if (readlen == -1) {
break;
}
i += readlen;
}
return buffer;
}
/**
* 描述:获取 post 请求内容
* <pre>
* 举例:
* </pre>
*/
public static String getRequestPostStr(HttpServletRequest request)
throws IOException {
byte[] buffer = getRequestPostBytes(request);
String charEncoding = request.getCharacterEncoding();
if (charEncoding == null) {
charEncoding = "UTF-8";
}
if (null == buffer) {
return null;
} else {
return new String(buffer, charEncoding);
}
}
}
然后在利用Filter打印数据,然后配置logback.xml等日志配置文件来将这个pv Filter类的日志输出打印到一个单独日志文件中。使用Elk来解析收集展示pv日志。
网友评论