java http打印请求日志_Spring Boot 记录 Http 请求日志

在使用Spring Boot开发 web api 的时候希望把 request,request header ,response reponse header , uri, method 等等的信息记录到我们的日志中,方便我们排查问题,也能对系统的数据做一些统计。

Spring 使用了 DispatcherServlet 来拦截并分发请求,我们只要自己实现一个 DispatcherServlet 并在其中对请求和响应做处理打印到日志中即可。

我们实现一个自己的分发 Servlet ,它继承于 DispatcherServlet,我们实现自己的 doDispatch(HttpServletRequest request, HttpServletResponse response) 方法。

public class LoggableDispatcherServlet extends DispatcherServlet {

private static final Logger logger = LoggerFactory.getLogger("HttpLogger");

private static final ObjectMapper mapper = new ObjectMapper();

@Override

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);

ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);

//创建一个 json 对象,用来存放 http 日志信息

ObjectNode rootNode = mapper.createObjectNode();

rootNode.put("uri", requestWrapper.getRequestURI());

rootNode.put("clientIp", requestWrapper.getRemoteAddr());

rootNode.set("requestHeaders", mapper.valueToTree(getRequestHeaders(requestWrapper)));

String method = requestWrapper.getMethod();

rootNode.put("method", method);

try {

super.doDispatch(requestWrapper, responseWrapper);

} finally {

if(method.equals("GET")) {

rootNode.set("request", mapper.valueToTree(requestWrapper.getParameterMap()));

} else {

JsonNode newNode = mapper.readTree(requestWrapper.getContentAsByteArray());

rootNode.set("request", newNode);

}

rootNode.put("status", responseWrapper.getStatus());

JsonNode newNode = mapper.readTree(responseWrapper.getContentAsByteArray());

rootNode.set("response", newNode);

responseWrapper.copyBodyToResponse();

rootNode.set("responseHeaders", mapper.valueToTree(getResponsetHeaders(responseWrapper)));

logger.info(rootNode.toString());

}

}

private Map getRequestHeaders(HttpServletRequest request) {

Map headers = new HashMap<>();

Enumeration headerNames = request.getHeaderNames();

while (headerNames.hasMoreElements()) {

String headerName = headerNames.nextElement();

headers.put(headerName, request.getHeader(headerName));

}

return headers;

}

private Map getResponsetHeaders(ContentCachingResponseWrapper response) {

Map headers = new HashMap<>();

Collection headerNames = response.getHeaderNames();

for (String headerName : headerNames) {

headers.put(headerName, response.getHeader(headerName));

}

return headers;

}

在 LoggableDispatcherServlet 中,我们可以通过 HttpServletRequest 中的 InputStream 或 reader 来获取请求的数据,但如果我们直接在这里读取了流或内容,到后面的逻辑将无法进行下去,所以需要实现一个可以缓存的 HttpServletRequest。好在 Spring 提供这样的类,就是 ContentCachingRequestWrapper 和 ContentCachingResponseWrapper, 根据官方的文档这两个类正好是来干这个事情的,我们只要将 HttpServletRequest 和 HttpServletResponse 转化即可。

HttpServletRequest wrapper that caches all content read from the input stream and reader, and allows this content to be retrieved via a byte array.

Used e.g. by AbstractRequestLoggingFilter. Note: As of Spring Framework 5.0, this wrapper is built on the Servlet 3.1 API.

HttpServletResponse wrapper that caches all content written to the output stream and writer, and allows this content to be retrieved via a byte array.

Used e.g. by ShallowEtagHeaderFilter. Note: As of Spring Framework 5.0, this wrapper is built on the Servlet 3.1 API.

实现好我们的 LoggableDispatcherServlet后,接下来就是要指定使用 LoggableDispatcherServlet 来分发请求。

@SpringBootApplication

public class SbDemoApplication implements ApplicationRunner {

public static void main(String[] args) {

SpringApplication.run(SbDemoApplication.class, args);

}

@Bean

public ServletRegistrationBean dispatcherRegistration() {

return new ServletRegistrationBean(dispatcherServlet());

}

@Bean(name = DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)

public DispatcherServlet dispatcherServlet() {

return new LoggableDispatcherServlet();

}

}

增加一个简单的 Controller 来测试一下

@RestController

@RequestMapping("/hello")

public class HelloController {

@RequestMapping(value = "/word", method = RequestMethod.POST)

public Object hello(@RequestBody Object object) {

return object;

}

}

使用 curl 发送一个 Post 请求:

$ curl --header "Content-Type: application/json" \

--request POST \

--data '{"username":"xyz","password":"xyz"}' \

http://localhost:8080/hello/word

{"username":"xyz","password":"xyz"}

查看打印的日志:

{

"uri":"/hello/word",

"clientIp":"0:0:0:0:0:0:0:1",

"requestHeaders":{

"content-length":"35",

"host":"localhost:8080",

"content-type":"application/json",

"user-agent":"curl/7.54.0",

"accept":"*/*"

},

"method":"POST",

"request":{

"username":"xyz",

"password":"xyz"

},

"status":200,

"response":{

"username":"xyz",

"password":"xyz"

},

"responseHeaders":{

"Content-Length":"35",

"Date":"Sun, 17 Mar 2019 08:56:50 GMT",

"Content-Type":"application/json;charset=UTF-8"

}

}

当然打印出来是在一行中的,我进行了一下格式化。我们还可以在日志中增加请求的时间,耗费的时间以及异常信息等。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值