ELK日志收集時(shí),我們需要在Filter中統(tǒng)一的打印請(qǐng)求報(bào)文虫碉。便于在Kibina中查詢
遇見兩個(gè)問題:
- Servlet流是單向的寓免,在Filter中解析那么后續(xù)會(huì)出現(xiàn)異常;
- 對(duì)于請(qǐng)求的不同Method(GET/POST等)如何在Filter中解析為字符串盹沈;
解決方案:
1.1 將單向流設(shè)置為可重復(fù)讀流
定義一個(gè)優(yōu)先級(jí)高的Filter龄章,實(shí)現(xiàn)流的轉(zhuǎn)換吃谣。
@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();
//對(duì)于文件上傳,直接放行做裙。
if (contentType != null && contentType.toLowerCase().contains("multipart")) {
//直接放行
chain.doFilter(request, response);
return;
}
//重寫流對(duì)象
if (!(request instanceof BackupRequestWrapper)) {
req = new BackupRequestWrapper(req);
}
chain.doFilter(req, response);
}
}
原理是:繼承HttpServletRequestWrapper岗憋,內(nèi)部使用ByteArrayInputStream
來實(shí)現(xiàn)流的可重復(fù)讀。
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);
}
// 對(duì)外提供讀取流的方法
@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參數(shù)讀取為JSON
提供一個(gè)工具類:來針對(duì)GET/POST請(qǐng)求來單獨(dú)處理锚贱。
@Slf4j
public class RequestJsonUtil {
/***
* 獲取 request 中 json 字符串的內(nèi)容
*
* @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("獲取請(qǐng)求參數(shù)異常", e);
}
return null;
}
/**
* 描述:獲取 post 請(qǐng)求的 byte[] 數(shù)組
* <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 請(qǐng)求內(nèi)容
* <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打印數(shù)據(jù)仔戈,然后配置logback.xml等日志配置文件來將這個(gè)pv Filter類的日志輸出打印到一個(gè)單獨(dú)日志文件中。使用Elk來解析收集展示pv日志拧廊。