我正在用spring boot开发REST API。我需要记录所有的请求与输入参数(与方法,例如。GET, POST等),请求路径,查询字符串,此请求对应的类方法,以及此操作的响应,包括成功和错误。例如:
成功的要求:
http://example.com/api/users/1
Log应该是这样的:
{
HttpStatus: 200,
path: "api/users/1",
method: "GET",
clientIp: "0.0.0.0",
accessToken: "XHGu6as5dajshdgau6i6asdjhgjhg",
method: "UsersController.getUser",
arguments: {
id: 1
},
response: {
user: {
id: 1,
username: "user123",
email: "user123@example.com"
}
},
exceptions: []
}
或请求错误:
http://example.com/api/users/9999
Log应该是这样的:
{
HttpStatus: 404,
errorCode: 101,
path: "api/users/9999",
method: "GET",
clientIp: "0.0.0.0",
accessToken: "XHGu6as5dajshdgau6i6asdjhgjhg",
method: "UsersController.getUser",
arguments: {
id: 9999
},
returns: {
},
exceptions: [
{
exception: "UserNotFoundException",
message: "User with id 9999 not found",
exceptionId: "adhaskldjaso98d7324kjh989",
stacktrace: ...................
]
}
我希望Request/Response是一个单独的实体,在成功和错误的情况下都具有与该实体相关的自定义信息。
春季实现这一目标的最佳做法是什么,可能是使用过滤器吗?如果是,能否提供具体的例子?
我使用过@ControllerAdvice和@ExceptionHandler,但正如我提到的,我需要在一个地方(和单个日志)处理所有成功和错误请求。
@hahn的回答需要一些修改才能为我工作,但这是迄今为止我能得到的最可定制的东西。
它对我不起作用,可能是因为我也有一个HandlerInterceptorAdapter[?? ?但是我一直从那个版本的服务器得到不好的响应。这是我对它的修改。
public class LoggableDispatcherServlet extends DispatcherServlet {
private final Log logger = LogFactory.getLog(getClass());
@Override
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
long startTime = System.currentTimeMillis();
try {
super.doDispatch(request, response);
} finally {
log(new ContentCachingRequestWrapper(request), new ContentCachingResponseWrapper(response),
System.currentTimeMillis() - startTime);
}
}
private void log(HttpServletRequest requestToCache, HttpServletResponse responseToCache, long timeTaken) {
int status = responseToCache.getStatus();
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("httpStatus", status);
jsonObject.addProperty("path", requestToCache.getRequestURI());
jsonObject.addProperty("httpMethod", requestToCache.getMethod());
jsonObject.addProperty("timeTakenMs", timeTaken);
jsonObject.addProperty("clientIP", requestToCache.getRemoteAddr());
if (status > 299) {
String requestBody = null;
try {
requestBody = requestToCache.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
} catch (IOException e) {
e.printStackTrace();
}
jsonObject.addProperty("requestBody", requestBody);
jsonObject.addProperty("requestParams", requestToCache.getQueryString());
jsonObject.addProperty("tokenExpiringHeader",
responseToCache.getHeader(ResponseHeaderModifierInterceptor.HEADER_TOKEN_EXPIRING));
}
logger.info(jsonObject);
}
}
如果不需要记录已执行的java方法,则可以使用javax.servlet.Filter。
但是有了这个要求,您必须访问存储在DispatcherServlet的handlerMapping中的信息。也就是说,您可以重写DispatcherServlet来完成请求/响应对的日志记录。
下面是一个想法的例子,可以进一步加强和采用您的需要。
public class LoggableDispatcherServlet extends DispatcherServlet {
private final Log logger = LogFactory.getLog(getClass());
@Override
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (!(request instanceof ContentCachingRequestWrapper)) {
request = new ContentCachingRequestWrapper(request);
}
if (!(response instanceof ContentCachingResponseWrapper)) {
response = new ContentCachingResponseWrapper(response);
}
HandlerExecutionChain handler = getHandler(request);
try {
super.doDispatch(request, response);
} finally {
log(request, response, handler);
updateResponse(response);
}
}
private void log(HttpServletRequest requestToCache, HttpServletResponse responseToCache, HandlerExecutionChain handler) {
LogMessage log = new LogMessage();
log.setHttpStatus(responseToCache.getStatus());
log.setHttpMethod(requestToCache.getMethod());
log.setPath(requestToCache.getRequestURI());
log.setClientIp(requestToCache.getRemoteAddr());
log.setJavaMethod(handler.toString());
log.setResponse(getResponsePayload(responseToCache));
logger.info(log);
}
private String getResponsePayload(HttpServletResponse response) {
ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
if (wrapper != null) {
byte[] buf = wrapper.getContentAsByteArray();
if (buf.length > 0) {
int length = Math.min(buf.length, 5120);
try {
return new String(buf, 0, length, wrapper.getCharacterEncoding());
}
catch (UnsupportedEncodingException ex) {
// NOOP
}
}
}
return "[unknown]";
}
private void updateResponse(HttpServletResponse response) throws IOException {
ContentCachingResponseWrapper responseWrapper =
WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
responseWrapper.copyBodyToResponse();
}
}
HandlerExecutionChain—包含关于请求处理程序的信息。
然后你可以像下面这样注册这个dispatcher:
@Bean
public ServletRegistrationBean dispatcherRegistration() {
return new ServletRegistrationBean(dispatcherServlet());
}
@Bean(name = DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet() {
return new LoggableDispatcherServlet();
}
下面是log的例子:
http http://localhost:8090/settings/test
i.g.m.s.s.LoggableDispatcherServlet : LogMessage{httpStatus=500, path='/error', httpMethod='GET', clientIp='127.0.0.1', javaMethod='HandlerExecutionChain with handler [public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)] and 3 interceptors', arguments=null, response='{"timestamp":1472475814077,"status":500,"error":"Internal Server Error","exception":"java.lang.RuntimeException","message":"org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.RuntimeException","path":"/settings/test"}'}
http http://localhost:8090/settings/params
i.g.m.s.s.LoggableDispatcherServlet : LogMessage{httpStatus=200, path='/settings/httpParams', httpMethod='GET', clientIp='127.0.0.1', javaMethod='HandlerExecutionChain with handler [public x.y.z.DTO x.y.z.Controller.params()] and 3 interceptors', arguments=null, response='{}'}
http http://localhost:8090/123
i.g.m.s.s.LoggableDispatcherServlet : LogMessage{httpStatus=404, path='/error', httpMethod='GET', clientIp='127.0.0.1', javaMethod='HandlerExecutionChain with handler [public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)] and 3 interceptors', arguments=null, response='{"timestamp":1472475840592,"status":404,"error":"Not Found","message":"Not Found","path":"/123"}'}
更新
如果出现错误,Spring会自动进行错误处理。因此,basicerrorcontroller# error被显示为请求处理程序。如果你想保留原始的请求处理程序,那么你可以在#processDispatchResult被调用之前在spring-webmvc- 4.5.release -sources.jar!/org/springframework/web/servlet/DispatcherServlet.java:971中覆盖这个行为来缓存原始的处理程序。
自从最初的问题发布以来,执行器HTTP跟踪是否有任何发展,即有一种方法来丰富它与响应体?
用来自MDC、Spring-Sleuth或Zipkin的自定义元数据(如traceId和spanId)来丰富它怎么样?
同样对我来说,执行器HTTP跟踪不工作Spring Boot 2.2.3,我在这里找到了修复:https://juplo.de/actuator-httptrace-does-not-work-with-spring-boot-2-2/
pom.xml
<dependency>
<groupId>org.springframework.boot
<artifactId>spring-boot-starter-actuator
</dependency>
application.properties
management.endpoints.web.exposure.include=httptrace
解决办法:
解决这个问题的简单方法是添加一个@Bean类型
InMemoryHttpTraceRepository到你的@Configuration-class:
@Bean
public HttpTraceRepository htttpTraceRepository()
{
return new InMemoryHttpTraceRepository();
}
解释:
The cause of this problem is not a bug, but a legitimate change in the
default configuration. Unfortunately, this change is not noted in the
according section of the documentation. Instead it is burried in the
Upgrade Notes for Spring Boot 2.2
The default-implementation stores the captured data in memory. Hence,
it consumes much memory, without the user knowing, or even worse:
needing it. This is especially undesirable in cluster environments,
where memory is a precious good. And remember: Spring Boot was
invented to simplify cluster deployments!
That is, why this feature is now turned of by default and has to be
turned on by the user explicitly, if needed.