java - log - Spring Boot: ¿cómo registrar todas las solicitudes y respuestas con excepciones en un solo lugar?
spring interceptor (18)
Estoy trabajando en la API de descanso con arranque de primavera. Necesito registrar todas las solicitudes con parámetros de entrada (con métodos, por ejemplo, GET, POST, etc.), ruta de solicitud, cadena de consulta, método de clase correspondiente de esta solicitud, también respuesta de esta acción, éxito y errores.
Para un ejemplo:
solicitud exitosa:
http://example.com/api/users/1
El registro debe verse más o menos así:
{
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: "[email protected]"
}
},
exceptions: []
}
O solicite con error:
http://example.com/api/users/9999
El registro debería ser algo como esto:
{
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: ...................
]
}
Deseo que Solicitud / Respuesta sea una entidad única, con información personalizada relacionada con esta entidad, tanto en casos exitosos como de error.
¿Cuál es la mejor práctica en primavera para lograr esto, puede ser con filtros? En caso afirmativo, ¿puede dar un ejemplo concreto?
(He jugado con @ControllerAdvice y @ExceptionHandler, pero como mencioné, necesito manejar todas las solicitudes de éxito y error en un solo lugar (y un único registro)).
La respuesta de @ hahn requirió un poco de modificación para que funcionara para mí, pero es, con mucho, la cosa más personalizable que pude obtener.
No funcionó para mí, probablemente porque también tengo un HandlerInterceptorAdapter [??] pero recibí una mala respuesta del servidor en esa versión. Aquí está mi modificación.
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);
}
}
Actualmente Spring Boot tiene la función Actuador para obtener los registros de solicitudes y respuestas.
Pero también puede obtener los registros utilizando Aspect.
Aspecto que ofrece con anotaciones como:
@Before
,
@AfterReturning
,
@AfterThrowing
etc.
@Before
registra la solicitud,
@AfterReturning
registra la respuesta y
@AfterThrowing
registra el mensaje de error. Es posible que no necesite el registro de todos los puntos finales, por lo que puede aplicar algunos filtros en los paquetes.
Aquí hay algunos ejemplos :
A petición:
@Before("within(your.package.where.endpoints.are..*)")
public void endpointBefore(JoinPoint p) {
if (log.isTraceEnabled()) {
log.trace(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " START");
Object[] signatureArgs = p.getArgs();
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
try {
if (signatureArgs[0] != null) {
log.trace("/nRequest object: /n" + mapper.writeValueAsString(signatureArgs[0]));
}
} catch (JsonProcessingException e) {
}
}
}
Aquí
@Before("within(your.package.where.endpoints.are..*)")
tiene la ruta del paquete.
Todos los puntos finales dentro de este paquete generarán el registro.
Para respuesta:
@AfterReturning(value = ("within(your.package.where.endpoints.are..*)"),
returning = "returnValue")
public void endpointAfterReturning(JoinPoint p, Object returnValue) {
if (log.isTraceEnabled()) {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
try {
log.trace("/nResponse object: /n" + mapper.writeValueAsString(returnValue));
} catch (JsonProcessingException e) {
System.out.println(e.getMessage());
}
log.trace(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " END");
}
}
Aquí
@AfterReturning("within(your.package.where.endpoints.are..*)")
tiene la ruta del paquete.
Todos los puntos finales dentro de este paquete generarán el registro.
También
Object returnValue
contiene la respuesta.
Por excepción:
@AfterThrowing(pointcut = ("within(your.package.where.endpoints.are..*)"), throwing = "e")
public void endpointAfterThrowing(JoinPoint p, Exception e) throws DmoneyException {
if (log.isTraceEnabled()) {
System.out.println(e.getMessage());
e.printStackTrace();
log.error(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " " + e.getMessage());
}
}
Aquí
@AfterThrowing(pointcut = ("within(your.package.where.endpoints.are..*)"), throwing = "e")
tiene la ruta del paquete.
Todos los puntos finales dentro de este paquete generarán el registro.
También
Exception e
contiene la respuesta de error.
Aquí está el código completo:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Aspect
@Order(1)
@Component
@ConditionalOnExpression("${endpoint.aspect.enabled:true}")
public class EndpointAspect {
static Logger log = Logger.getLogger(EndpointAspect.class);
@Before("within(your.package.where.is.endpoint..*)")
public void endpointBefore(JoinPoint p) {
if (log.isTraceEnabled()) {
log.trace(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " START");
Object[] signatureArgs = p.getArgs();
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
try {
if (signatureArgs[0] != null) {
log.trace("/nRequest object: /n" + mapper.writeValueAsString(signatureArgs[0]));
}
} catch (JsonProcessingException e) {
}
}
}
@AfterReturning(value = ("within(your.package.where.is.endpoint..*)"),
returning = "returnValue")
public void endpointAfterReturning(JoinPoint p, Object returnValue) {
if (log.isTraceEnabled()) {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
try {
log.trace("/nResponse object: /n" + mapper.writeValueAsString(returnValue));
} catch (JsonProcessingException e) {
System.out.println(e.getMessage());
}
log.trace(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " END");
}
}
@AfterThrowing(pointcut = ("within(your.package.where.is.endpoint..*)"), throwing = "e")
public void endpointAfterThrowing(JoinPoint p, Exception e) throws Exception {
if (log.isTraceEnabled()) {
System.out.println(e.getMessage());
e.printStackTrace();
log.error(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " " + e.getMessage());
}
}
}
Aquí, usando
@ConditionalOnExpression("${endpoint.aspect.enabled:true}")
usted puede habilitar / deshabilitar el registro.
sólo tiene que añadir
endpoint.aspect.enabled:true
a la
application.property
y controlar el registro
Más información sobre la visita de AOP aquí:
Aquí mi solución (Spring 2.0.x)
Agregue la dependencia de Maven:
@Bean
public CommonsRequestLoggingFilter requestLoggingFilter() {
CommonsRequestLoggingFilter loggingFilter = new CommonsRequestLoggingFilter();
loggingFilter.setIncludeClientInfo(false);
loggingFilter.setIncludeQueryString(false);
loggingFilter.setIncludePayload(true);
loggingFilter.setIncludeHeaders(false);
loggingFilter.setMaxPayloadLength(500);
return loggingFilter;
}
Edite application.properties y agregue la siguiente línea:
@Component
public class LoggingFilter extends OncePerRequestFilter {
private static final List<MediaType> VISIBLE_TYPES = Arrays.asList(
MediaType.valueOf("text/*"),
MediaType.APPLICATION_FORM_URLENCODED,
MediaType.APPLICATION_JSON,
MediaType.APPLICATION_XML,
MediaType.valueOf("application/*+json"),
MediaType.valueOf("application/*+xml"),
MediaType.MULTIPART_FORM_DATA
);
Logger log = LoggerFactory.getLogger(ReqAndResLoggingFilter.class);
private static final Path path = Paths.get("/home/ramesh/loggerReq.txt");
private static BufferedWriter writer = null;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
writer = Files.newBufferedWriter(path, Charset.forName("UTF-8"));
if (isAsyncDispatch(request)) {
filterChain.doFilter(request, response);
} else {
doFilterWrapped(wrapRequest(request), wrapResponse(response), filterChain);
}
}finally {
writer.close();
}
}
protected void doFilterWrapped(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response, FilterChain filterChain) throws ServletException, IOException {
try {
beforeRequest(request, response);
filterChain.doFilter(request, response);
}
finally {
afterRequest(request, response);
response.copyBodyToResponse();
}
}
protected void beforeRequest(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response) throws IOException {
if (log.isInfoEnabled()) {
logRequestHeader(request, request.getRemoteAddr() + "|>");
}
}
protected void afterRequest(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response) throws IOException {
if (log.isInfoEnabled()) {
logRequestBody(request, request.getRemoteAddr() + "|>");
logResponse(response, request.getRemoteAddr() + "|<");
}
}
private void logRequestHeader(ContentCachingRequestWrapper request, String prefix) throws IOException {
String queryString = request.getQueryString();
if (queryString == null) {
printLines(prefix,request.getMethod(),request.getRequestURI());
log.info("{} {} {}", prefix, request.getMethod(), request.getRequestURI());
} else {
printLines(prefix,request.getMethod(),request.getRequestURI(),queryString);
log.info("{} {} {}?{}", prefix, request.getMethod(), request.getRequestURI(), queryString);
}
Collections.list(request.getHeaderNames()).forEach(headerName ->
Collections.list(request.getHeaders(headerName)).forEach(headerValue ->
log.info("{} {}: {}", prefix, headerName, headerValue)));
printLines(prefix);
printLines(RequestContextHolder.currentRequestAttributes().getSessionId());
log.info("{}", prefix);
log.info(" Session ID: ", RequestContextHolder.currentRequestAttributes().getSessionId());
}
private void printLines(String ...args) throws IOException {
try {
for(String varArgs:args) {
writer.write(varArgs);
writer.newLine();
}
}catch(IOException ex){
ex.printStackTrace();
}
}
private void logRequestBody(ContentCachingRequestWrapper request, String prefix) {
byte[] content = request.getContentAsByteArray();
if (content.length > 0) {
logContent(content, request.getContentType(), request.getCharacterEncoding(), prefix);
}
}
private void logResponse(ContentCachingResponseWrapper response, String prefix) throws IOException {
int status = response.getStatus();
printLines(prefix, String.valueOf(status), HttpStatus.valueOf(status).getReasonPhrase());
log.info("{} {} {}", prefix, status, HttpStatus.valueOf(status).getReasonPhrase());
response.getHeaderNames().forEach(headerName ->
response.getHeaders(headerName).forEach(headerValue ->
log.info("{} {}: {}", prefix, headerName, headerValue)));
printLines(prefix);
log.info("{}", prefix);
byte[] content = response.getContentAsByteArray();
if (content.length > 0) {
logContent(content, response.getContentType(), response.getCharacterEncoding(), prefix);
}
}
private void logContent(byte[] content, String contentType, String contentEncoding, String prefix) {
MediaType mediaType = MediaType.valueOf(contentType);
boolean visible = VISIBLE_TYPES.stream().anyMatch(visibleType -> visibleType.includes(mediaType));
if (visible) {
try {
String contentString = new String(content, contentEncoding);
Stream.of(contentString.split("/r/n|/r|/n")).forEach(line -> {
try {
printLines(line);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
});
// log.info("{} {}", prefix, line));
} catch (UnsupportedEncodingException e) {
log.info("{} [{} bytes content]", prefix, content.length);
}
} else {
log.info("{} [{} bytes content]", prefix, content.length);
}
}
private static ContentCachingRequestWrapper wrapRequest(HttpServletRequest request) {
if (request instanceof ContentCachingRequestWrapper) {
return (ContentCachingRequestWrapper) request;
} else {
return new ContentCachingRequestWrapper(request);
}
}
private static ContentCachingResponseWrapper wrapResponse(HttpServletResponse response) {
if (response instanceof ContentCachingResponseWrapper) {
return (ContentCachingResponseWrapper) response;
} else {
return new ContentCachingResponseWrapper(response);
}
}
}
Una vez que se inicia su aplicación de arranque de primavera, puede rastrear las últimas 100 solicitudes http llamando a esta url: http://localhost:8070/actuator/httptrace
Consulte el siguiente enlace para obtener la respuesta real https://gist.github.com/int128/e47217bebdb4c402b2ffa7cc199307ba
Si se realizan algunos cambios en la solución mencionada anteriormente, la solicitud y la respuesta también se registrarán en la consola y en el archivo si el nivel del registrador es información. podemos imprimir en consola o archivo.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Salida en archivo:
management.endpoints.web.exposure.include=*
Para registrar solicitudes que resultan en 400 solamente:
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.io.FileUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.AbstractRequestLoggingFilter;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.WebUtils;
/**
* Implementation is partially copied from {@link AbstractRequestLoggingFilter} and modified to output request information only if request resulted in 400.
* Unfortunately {@link AbstractRequestLoggingFilter} is not smart enough to expose {@link HttpServletResponse} value in afterRequest() method.
*/
@Component
public class RequestLoggingFilter extends OncePerRequestFilter {
public static final String DEFAULT_AFTER_MESSAGE_PREFIX = "After request [";
public static final String DEFAULT_AFTER_MESSAGE_SUFFIX = "]";
private final boolean includeQueryString = true;
private final boolean includeClientInfo = true;
private final boolean includeHeaders = true;
private final boolean includePayload = true;
private final int maxPayloadLength = (int) (2 * FileUtils.ONE_MB);
private final String afterMessagePrefix = DEFAULT_AFTER_MESSAGE_PREFIX;
private final String afterMessageSuffix = DEFAULT_AFTER_MESSAGE_SUFFIX;
/**
* The default value is "false" so that the filter may log a "before" message
* at the start of request processing and an "after" message at the end from
* when the last asynchronously dispatched thread is exiting.
*/
@Override
protected boolean shouldNotFilterAsyncDispatch() {
return false;
}
@Override
protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain)
throws ServletException, IOException {
final boolean isFirstRequest = !isAsyncDispatch(request);
HttpServletRequest requestToUse = request;
if (includePayload && isFirstRequest && !(request instanceof ContentCachingRequestWrapper)) {
requestToUse = new ContentCachingRequestWrapper(request, maxPayloadLength);
}
final boolean shouldLog = shouldLog(requestToUse);
try {
filterChain.doFilter(requestToUse, response);
} finally {
if (shouldLog && !isAsyncStarted(requestToUse)) {
afterRequest(requestToUse, response, getAfterMessage(requestToUse));
}
}
}
private String getAfterMessage(final HttpServletRequest request) {
return createMessage(request, this.afterMessagePrefix, this.afterMessageSuffix);
}
private String createMessage(final HttpServletRequest request, final String prefix, final String suffix) {
final StringBuilder msg = new StringBuilder();
msg.append(prefix);
msg.append("uri=").append(request.getRequestURI());
if (includeQueryString) {
final String queryString = request.getQueryString();
if (queryString != null) {
msg.append(''?'').append(queryString);
}
}
if (includeClientInfo) {
final String client = request.getRemoteAddr();
if (StringUtils.hasLength(client)) {
msg.append(";client=").append(client);
}
final HttpSession session = request.getSession(false);
if (session != null) {
msg.append(";session=").append(session.getId());
}
final String user = request.getRemoteUser();
if (user != null) {
msg.append(";user=").append(user);
}
}
if (includeHeaders) {
msg.append(";headers=").append(new ServletServerHttpRequest(request).getHeaders());
}
if (includeHeaders) {
final ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
if (wrapper != null) {
final byte[] buf = wrapper.getContentAsByteArray();
if (buf.length > 0) {
final int length = Math.min(buf.length, maxPayloadLength);
String payload;
try {
payload = new String(buf, 0, length, wrapper.getCharacterEncoding());
} catch (final UnsupportedEncodingException ex) {
payload = "[unknown]";
}
msg.append(";payload=").append(payload);
}
}
}
msg.append(suffix);
return msg.toString();
}
private boolean shouldLog(final HttpServletRequest request) {
return true;
}
private void afterRequest(final HttpServletRequest request, final HttpServletResponse response, final String message) {
if (response.getStatus() == HttpStatus.BAD_REQUEST.value()) {
logger.warn(message);
}
}
}
Si alguien aún lo necesita aquí, es una implementación simple con Spring HttpTrace Actuator. Pero como le han dicho a Upper, no registra cuerpos.
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.springframework.boot.actuate.trace.http.HttpTrace;
import org.springframework.boot.actuate.trace.http.InMemoryHttpTraceRepository;
import org.springframework.stereotype.Repository;
@Slf4j
@Repository
public class LoggingInMemoryHttpTraceRepository extends InMemoryHttpTraceRepository {
public void add(HttpTrace trace) {
super.add(trace);
log.info("Trace:" + ToStringBuilder.reflectionToString(trace));
log.info("Request:" + ToStringBuilder.reflectionToString(trace.getRequest()));
log.info("Response:" + ToStringBuilder.reflectionToString(trace.getResponse()));
}
}
Si solo ve parte de la carga útil de su solicitud, debe llamar a la
setMaxPayloadLength
función, ya que por defecto muestra solo 50 caracteres en el cuerpo de la solicitud.
Además, establecer
setIncludeHeaders
en falso es una buena idea si no desea registrar sus encabezados de autenticación.
127.0.0.1|>
POST
/createUser
127.0.0.1|>
session Id:C0793464532E7F0C7154913CBA018B2B
Request:
{
"name": "asdasdas",
"birthDate": "2018-06-21T17:11:15.679+0000"
}
127.0.0.1|<
200
OK
127.0.0.1|<
Response:
{"name":"asdasdas","birthDate":"2018-06-21T17:11:15.679+0000","id":4}
También puede configurar un interceptor Spring personalizado
HandlerInterceptorAdapter
para una implementación simplificada de
interceptores
pre-solo / post-solo:
@Component
public class CustomHttpInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle (final HttpServletRequest request, final HttpServletResponse response,
final Object handler)
throws Exception {
// Logs here
return super.preHandle(request, response, handler);
}
@Override
public void afterCompletion(final HttpServletRequest request, final HttpServletResponse response,
final Object handler, final Exception ex) {
// Logs here
}
}
Luego, registra tantos interceptores como desee:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
CustomHttpInterceptor customHttpInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(customHttpInterceptor).addPathPatterns("/endpoints");
}
}
Nota:
tal como lo indicó @Robert
, debe prestar atención a las implementaciones específicas
HttpServletRequest
y a las
HttpServletResponse
aplicaciones que utiliza.
Por ejemplo, para las aplicaciones que usan
ShallowEtagHeaderFilter
, la implementación de la respuesta sería a
ContentCachingResponseWrapper
, por lo que tendría:
@Component
public class CustomHttpInterceptor extends HandlerInterceptorAdapter {
private static final Logger LOGGER = LoggerFactory.getLogger(CustomHttpInterceptor.class);
private static final int MAX_PAYLOAD_LENGTH = 1000;
@Override
public void afterCompletion(final HttpServletRequest request, final HttpServletResponse response,
final Object handler, final Exception ex) {
final byte[] contentAsByteArray = ((ContentCachingResponseWrapper) response).getContentAsByteArray();
LOGGER.info("Request body:/n" + getContentAsString(contentAsByteArray, response.getCharacterEncoding()));
}
private String getContentAsString(byte[] buf, String charsetName) {
if (buf == null || buf.length == 0) {
return "";
}
try {
int length = Math.min(buf.length, MAX_PAYLOAD_LENGTH);
return new String(buf, 0, length, charsetName);
} catch (UnsupportedEncodingException ex) {
return "Unsupported Encoding";
}
}
}
si usa Tomcat en su aplicación de arranque, aquí está
org.apache.catalina.filters.RequestDumperFilter
en una ruta de clase para usted.
(pero no le proporcionará "con excepciones en un solo lugar").
Así es como lo hago en reposo de datos de primavera usando
org.springframework.web.util.ContentCachingRequestWrapper
y
org.springframework.web.util.ContentCachingResponseWrapper
spring-aop, aspectjrt, aspectjweaver
Después de agregar
Actuator
a la aplicación basada en Spring Boot, tiene disponible el punto final
/trace
con las últimas solicitudes de información.
Este punto final funciona en función de
TraceRepository
y la implementación predeterminada es
InMemoryTraceRepository
que guarda las últimas 100 llamadas.
Puede cambiar esto implementando esta interfaz usted mismo y ponerla a disposición como un bean Spring.
Por ejemplo, para registrar todas las solicitudes de registro (y aún usar la implementación predeterminada como almacenamiento básico para servir información sobre
/trace
punto final de
/trace
) Estoy usando este tipo de implementación:
logging.level.org.springframework.web=DEBUG
logging.level.org.hibernate.SQL=INFO
logging.file=D:/log/myapp.log
Este
traceInfo
mapa contiene informaciones básicas sobre la solicitud y la respuesta en este tipo de formulario:
{method=GET, path=/api/hello/John, headers={request={host=localhost:8080, user-agent=curl/7.51.0, accept=*/*}, response={X-Application-Context=application, Content-Type=text/plain;charset=UTF-8, Content-Length=10, Date=Wed, 29 Mar 2017 20:41:21 GMT, status=200}}}
.
No hay contenido de respuesta aquí.
¡EDITAR! Registro de datos POST
Puede acceder a los datos POST anulando github.com/spring-projects/spring-boot/blob/master/… , pero no piense que es una buena idea (por ejemplo, todo el contenido del archivo cargado irá a los registros). Aquí hay un código de muestra, pero no lo use:
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import javax.servlet.*;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.output.TeeOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class HttpLoggingFilter implements Filter {
private static final Logger log = LoggerFactory.getLogger(HttpLoggingFilter.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
try {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
Map<String, String> requestMap = this
.getTypesafeRequestMap(httpServletRequest);
BufferedRequestWrapper bufferedRequest = new BufferedRequestWrapper(
httpServletRequest);
BufferedResponseWrapper bufferedResponse = new BufferedResponseWrapper(
httpServletResponse);
final StringBuilder logMessage = new StringBuilder(
"REST Request - ").append("[HTTP METHOD:")
.append(httpServletRequest.getMethod())
.append("] [PATH INFO:")
.append(httpServletRequest.getServletPath())
.append("] [REQUEST PARAMETERS:").append(requestMap)
.append("] [REQUEST BODY:")
.append(bufferedRequest.getRequestBody())
.append("] [REMOTE ADDRESS:")
.append(httpServletRequest.getRemoteAddr()).append("]");
chain.doFilter(bufferedRequest, bufferedResponse);
logMessage.append(" [RESPONSE:")
.append(bufferedResponse.getContent()).append("]");
log.debug(logMessage.toString());
} catch (Throwable a) {
log.error(a.getMessage());
}
}
private Map<String, String> getTypesafeRequestMap(HttpServletRequest request) {
Map<String, String> typesafeRequestMap = new HashMap<String, String>();
Enumeration<?> requestParamNames = request.getParameterNames();
while (requestParamNames.hasMoreElements()) {
String requestParamName = (String) requestParamNames.nextElement();
String requestParamValue;
if (requestParamName.equalsIgnoreCase("password")) {
requestParamValue = "********";
} else {
requestParamValue = request.getParameter(requestParamName);
}
typesafeRequestMap.put(requestParamName, requestParamValue);
}
return typesafeRequestMap;
}
@Override
public void destroy() {
}
private static final class BufferedRequestWrapper extends
HttpServletRequestWrapper {
private ByteArrayInputStream bais = null;
private ByteArrayOutputStream baos = null;
private BufferedServletInputStream bsis = null;
private byte[] buffer = null;
public BufferedRequestWrapper(HttpServletRequest req)
throws IOException {
super(req);
// Read InputStream and store its content in a buffer.
InputStream is = req.getInputStream();
this.baos = new ByteArrayOutputStream();
byte buf[] = new byte[1024];
int read;
while ((read = is.read(buf)) > 0) {
this.baos.write(buf, 0, read);
}
this.buffer = this.baos.toByteArray();
}
@Override
public ServletInputStream getInputStream() {
this.bais = new ByteArrayInputStream(this.buffer);
this.bsis = new BufferedServletInputStream(this.bais);
return this.bsis;
}
String getRequestBody() throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(
this.getInputStream()));
String line = null;
StringBuilder inputBuffer = new StringBuilder();
do {
line = reader.readLine();
if (null != line) {
inputBuffer.append(line.trim());
}
} while (line != null);
reader.close();
return inputBuffer.toString().trim();
}
}
private static final class BufferedServletInputStream extends
ServletInputStream {
private ByteArrayInputStream bais;
public BufferedServletInputStream(ByteArrayInputStream bais) {
this.bais = bais;
}
@Override
public int available() {
return this.bais.available();
}
@Override
public int read() {
return this.bais.read();
}
@Override
public int read(byte[] buf, int off, int len) {
return this.bais.read(buf, off, len);
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readListener) {
}
}
public class TeeServletOutputStream extends ServletOutputStream {
private final TeeOutputStream targetStream;
public TeeServletOutputStream(OutputStream one, OutputStream two) {
targetStream = new TeeOutputStream(one, two);
}
@Override
public void write(int arg0) throws IOException {
this.targetStream.write(arg0);
}
public void flush() throws IOException {
super.flush();
this.targetStream.flush();
}
public void close() throws IOException {
super.close();
this.targetStream.close();
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setWriteListener(WriteListener writeListener) {
}
}
public class BufferedResponseWrapper implements HttpServletResponse {
HttpServletResponse original;
TeeServletOutputStream tee;
ByteArrayOutputStream bos;
public BufferedResponseWrapper(HttpServletResponse response) {
original = response;
}
public String getContent() {
return bos.toString();
}
public PrintWriter getWriter() throws IOException {
return original.getWriter();
}
public ServletOutputStream getOutputStream() throws IOException {
if (tee == null) {
bos = new ByteArrayOutputStream();
tee = new TeeServletOutputStream(original.getOutputStream(),
bos);
}
return tee;
}
@Override
public String getCharacterEncoding() {
return original.getCharacterEncoding();
}
@Override
public String getContentType() {
return original.getContentType();
}
@Override
public void setCharacterEncoding(String charset) {
original.setCharacterEncoding(charset);
}
@Override
public void setContentLength(int len) {
original.setContentLength(len);
}
@Override
public void setContentLengthLong(long l) {
original.setContentLengthLong(l);
}
@Override
public void setContentType(String type) {
original.setContentType(type);
}
@Override
public void setBufferSize(int size) {
original.setBufferSize(size);
}
@Override
public int getBufferSize() {
return original.getBufferSize();
}
@Override
public void flushBuffer() throws IOException {
tee.flush();
}
@Override
public void resetBuffer() {
original.resetBuffer();
}
@Override
public boolean isCommitted() {
return original.isCommitted();
}
@Override
public void reset() {
original.reset();
}
@Override
public void setLocale(Locale loc) {
original.setLocale(loc);
}
@Override
public Locale getLocale() {
return original.getLocale();
}
@Override
public void addCookie(Cookie cookie) {
original.addCookie(cookie);
}
@Override
public boolean containsHeader(String name) {
return original.containsHeader(name);
}
@Override
public String encodeURL(String url) {
return original.encodeURL(url);
}
@Override
public String encodeRedirectURL(String url) {
return original.encodeRedirectURL(url);
}
@SuppressWarnings("deprecation")
@Override
public String encodeUrl(String url) {
return original.encodeUrl(url);
}
@SuppressWarnings("deprecation")
@Override
public String encodeRedirectUrl(String url) {
return original.encodeRedirectUrl(url);
}
@Override
public void sendError(int sc, String msg) throws IOException {
original.sendError(sc, msg);
}
@Override
public void sendError(int sc) throws IOException {
original.sendError(sc);
}
@Override
public void sendRedirect(String location) throws IOException {
original.sendRedirect(location);
}
@Override
public void setDateHeader(String name, long date) {
original.setDateHeader(name, date);
}
@Override
public void addDateHeader(String name, long date) {
original.addDateHeader(name, date);
}
@Override
public void setHeader(String name, String value) {
original.setHeader(name, value);
}
@Override
public void addHeader(String name, String value) {
original.addHeader(name, value);
}
@Override
public void setIntHeader(String name, int value) {
original.setIntHeader(name, value);
}
@Override
public void addIntHeader(String name, int value) {
original.addIntHeader(name, value);
}
@Override
public void setStatus(int sc) {
original.setStatus(sc);
}
@SuppressWarnings("deprecation")
@Override
public void setStatus(int sc, String sm) {
original.setStatus(sc, sm);
}
@Override
public String getHeader(String arg0) {
return original.getHeader(arg0);
}
@Override
public Collection<String> getHeaderNames() {
return original.getHeaderNames();
}
@Override
public Collection<String> getHeaders(String arg0) {
return original.getHeaders(arg0);
}
@Override
public int getStatus() {
return original.getStatus();
}
}
}
Este código funciona para mí en una aplicación Spring Boot, solo regístralo como filtro
package info.fingo.nuntius.acuate.trace;
import org.apache.commons.io.IOUtils;
import org.springframework.boot.actuate.trace.TraceProperties;
import org.springframework.boot.actuate.trace.TraceRepository;
import org.springframework.boot.actuate.trace.WebRequestTraceFilter;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.LinkedHashMap;
import java.util.Map;
@Component
public class CustomWebTraceFilter extends WebRequestTraceFilter {
public CustomWebTraceFilter(TraceRepository repository, TraceProperties properties) {
super(repository, properties);
}
@Override
protected Map<String, Object> getTrace(HttpServletRequest request) {
Map<String, Object> trace = super.getTrace(request);
String multipartHeader = request.getHeader("content-type");
if (multipartHeader != null && multipartHeader.startsWith("multipart/form-data")) {
Map<String, Object> parts = new LinkedHashMap<>();
try {
request.getParts().forEach(
part -> {
try {
parts.put(part.getName(), IOUtils.toString(part.getInputStream(), Charset.forName("UTF-8")));
} catch (IOException e) {
e.printStackTrace();
}
}
);
} catch (IOException | ServletException e) {
e.printStackTrace();
}
if (!parts.isEmpty()) {
trace.put("multipart-content-map", parts);
}
}
return trace;
}
}
Había definido el nivel de registro en
application.properties
para imprimir solicitudes / respuestas, url del método en el archivo de registro
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.actuate.trace.InMemoryTraceRepository;
import org.springframework.boot.actuate.trace.Trace;
import org.springframework.boot.actuate.trace.TraceRepository;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
@Component
public class LoggingTraceRepository implements TraceRepository {
private static final Logger LOG = LoggerFactory.getLogger(LoggingTraceRepository.class);
private final TraceRepository delegate = new InMemoryTraceRepository();
@Override
public List<Trace> findAll() {
return delegate.findAll();
}
@Override
public void add(Map<String, Object> traceInfo) {
LOG.info(traceInfo.toString());
this.delegate.add(traceInfo);
}
}
Había usado Spring Boot.
La biblioteca Logbook está hecha específicamente para registrar solicitudes y respuestas HTTP. Es compatible con Spring Boot utilizando una biblioteca de inicio especial.
Para habilitar el inicio de sesión en Spring Boot, todo lo que necesita hacer es agregar la biblioteca a las dependencias de su proyecto. Por ejemplo, suponiendo que está utilizando Maven:
<dependency>
<groupId>org.zalando</groupId>
<artifactId>logbook-spring-boot-starter</artifactId>
<version>1.5.0</version>
</dependency>
Por defecto, la salida de registro se ve así:
{
"origin" : "local",
"correlation" : "52e19498-890c-4f75-a06c-06ddcf20836e",
"status" : 200,
"headers" : {
"X-Application-Context" : [
"application:8088"
],
"Content-Type" : [
"application/json;charset=UTF-8"
],
"Transfer-Encoding" : [
"chunked"
],
"Date" : [
"Sun, 24 Dec 2017 13:10:45 GMT"
]
},
"body" : {
"thekey" : "some_example"
},
"duration" : 105,
"protocol" : "HTTP/1.1",
"type" : "response"
}
Sin embargo, no muestra el nombre de clase que maneja la solicitud. La biblioteca tiene algunas interfaces para escribir registradores personalizados.
No escriba interceptores, filtros, componentes, aspectos, etc., este es un problema muy común y se ha resuelto muchas veces.
Spring Boot tiene un módulo llamado
Actuator
que proporciona el registro de solicitudes HTTP fuera de la caja.
Hay un punto final asignado a
/trace
(SB1.x) o
/actuator/httptrace
(SB2.0 +) que le mostrará las últimas 100 solicitudes HTTP.
Puede personalizarlo para registrar cada solicitud o escribir en una base de datos.
Para obtener los puntos finales que desea, necesitará la dependencia del actuador springboot, y también para "incluir en la lista blanca" los puntos finales que está buscando, y posiblemente configurar o deshabilitar la seguridad.
Además, ¿dónde se ejecutará esta aplicación? ¿Usarás un PaaS? Los proveedores de alojamiento, Heroku, por ejemplo, proporcionan el registro de solicitudes como parte de su servicio y no es necesario que realice ninguna codificación en ese momento.
Puede usar
javax.servlet.Filter
si no hubiera un requisito para registrar el método java que se ejecutó.
Pero con este requisito, debe acceder a la información almacenada en
handlerMapping
de
DispatcherServlet
.
Dicho esto, puede anular
DispatcherServlet
para lograr el registro del par de solicitud / respuesta.
A continuación se muestra un ejemplo de idea que se puede mejorar y adoptar según sus necesidades.
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
: contiene información sobre el controlador de solicitudes.
Luego puede registrar este despachador de la siguiente manera:
@Bean
public ServletRegistrationBean dispatcherRegistration() {
return new ServletRegistrationBean(dispatcherServlet());
}
@Bean(name = DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet() {
return new LoggableDispatcherServlet();
}
Y aquí está la muestra de registros:
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"}''}
ACTUALIZAR
En caso de errores, Spring realiza el manejo automático de errores.
Por lo tanto,
BasicErrorController#error
se muestra como un controlador de solicitud.
Si desea conservar el controlador de solicitud original, puede anular este comportamiento en
spring-webmvc-4.2.5.RELEASE-sources.jar!/org/springframework/web/servlet/DispatcherServlet.java:971
antes de
#processDispatchResult
, para almacenar en caché el controlador original.
Si no le importa probar Spring AOP, esto es algo que he estado explorando con fines de registro y me funciona bastante bien. Sin embargo, no registrará solicitudes que no hayan sido definidas e intentos fallidos de solicitud.
Agregue estas tres dependencias
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface EnableLogging {
ActionType actionType();
}
Agregue esto a su archivo de configuración xml
<aop:aspectj-autoproxy/>
Cree una anotación que se pueda usar como un punto de corte
@EnableLogging(actionType = ActionType.SOME_EMPLOYEE_ACTION)
@Override
public Response getEmployees(RequestDto req, final String param) {
...
}
Ahora anote todos sus métodos de API de descanso que desea registrar
@Aspect
@Component
public class Aspects {
@AfterReturning(pointcut = "execution(@co.xyz.aspect.EnableLogging * *(..)) && @annotation(enableLogging) && args(reqArg, reqArg1,..)", returning = "result")
public void auditInfo(JoinPoint joinPoint, Object result, EnableLogging enableLogging, Object reqArg, String reqArg1) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
.getRequest();
if (result instanceof Response) {
Response responseObj = (Response) result;
String requestUrl = request.getScheme() + "://" + request.getServerName()
+ ":" + request.getServerPort() + request.getContextPath() + request.getRequestURI()
+ "?" + request.getQueryString();
String clientIp = request.getRemoteAddr();
String clientRequest = reqArg.toString();
int httpResponseStatus = responseObj.getStatus();
responseObj.getEntity();
// Can log whatever stuff from here in a single spot.
}
@AfterThrowing(pointcut = "execution(@co.xyz.aspect.EnableLogging * *(..)) && @annotation(enableLogging) && args(reqArg, reqArg1,..)", throwing="exception")
public void auditExceptionInfo(JoinPoint joinPoint, Throwable exception, EnableLogging enableLogging, Object reqArg, String reqArg1) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
.getRequest();
String requestUrl = request.getScheme() + "://" + request.getServerName()
+ ":" + request.getServerPort() + request.getContextPath() + request.getRequestURI()
+ "?" + request.getQueryString();
exception.getMessage();
exception.getCause();
exception.printStackTrace();
exception.getLocalizedMessage();
// Can log whatever exceptions, requests, etc from here in a single spot.
}
}
Ahora al Aspecto. escanee el paquete en el que se encuentra esta clase.
/**
* Doogies very cool HTTP request logging
*
* There is also {@link org.springframework.web.filter.CommonsRequestLoggingFilter} but it cannot log request method
* And it cannot easily be extended.
*
* https://mdeinum.wordpress.com/2015/07/01/spring-framework-hidden-gems/
* http://.com/questions/8933054/how-to-read-and-copy-the-http-servlet-response-output-stream-content-for-logging
*/
public class DoogiesRequestLogger extends OncePerRequestFilter {
private boolean includeResponsePayload = true;
private int maxPayloadLength = 1000;
private String getContentAsString(byte[] buf, int maxLength, String charsetName) {
if (buf == null || buf.length == 0) return "";
int length = Math.min(buf.length, this.maxPayloadLength);
try {
return new String(buf, 0, length, charsetName);
} catch (UnsupportedEncodingException ex) {
return "Unsupported Encoding";
}
}
/**
* Log each request and respponse with full Request URI, content payload and duration of the request in ms.
* @param request the request
* @param response the response
* @param filterChain chain of filters
* @throws ServletException
* @throws IOException
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
long startTime = System.currentTimeMillis();
StringBuffer reqInfo = new StringBuffer()
.append("[")
.append(startTime % 10000) // request ID
.append("] ")
.append(request.getMethod())
.append(" ")
.append(request.getRequestURL());
String queryString = request.getQueryString();
if (queryString != null) {
reqInfo.append("?").append(queryString);
}
if (request.getAuthType() != null) {
reqInfo.append(", authType=")
.append(request.getAuthType());
}
if (request.getUserPrincipal() != null) {
reqInfo.append(", principalName=")
.append(request.getUserPrincipal().getName());
}
this.logger.debug("=> " + reqInfo);
// ========= Log request and response payload ("body") ========
// We CANNOT simply read the request payload here, because then the InputStream would be consumed and cannot be read again by the actual processing/server.
// String reqBody = DoogiesUtil._stream2String(request.getInputStream()); // THIS WOULD NOT WORK!
// So we need to apply some stronger magic here :-)
ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response);
filterChain.doFilter(wrappedRequest, wrappedResponse); // ======== This performs the actual request!
long duration = System.currentTimeMillis() - startTime;
// I can only log the request''s body AFTER the request has been made and ContentCachingRequestWrapper did its work.
String requestBody = this.getContentAsString(wrappedRequest.getContentAsByteArray(), this.maxPayloadLength, request.getCharacterEncoding());
if (requestBody.length() > 0) {
this.logger.debug(" Request body:/n" +requestBody);
}
this.logger.debug("<= " + reqInfo + ": returned status=" + response.getStatus() + " in "+duration + "ms");
if (includeResponsePayload) {
byte[] buf = wrappedResponse.getContentAsByteArray();
this.logger.debug(" Response body:/n"+getContentAsString(buf, this.maxPayloadLength, response.getCharacterEncoding()));
}
wrappedResponse.copyBodyToResponse(); // IMPORTANT: copy content of response back into original response
}
}
El consejo @AfterReturning se ejecuta cuando la ejecución de un método coincidente vuelve normalmente.
@AfterThrowing se ejecuta cuando se ejecuta una ejecución de método coincidente lanzando una excepción.
Si quieres leer en detalle, lee esto. http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html
Spring ya proporciona un filtro que hace este trabajo. Agregue el siguiente bean a su configuración
@Bean
public CommonsRequestLoggingFilter requestLoggingFilter() {
CommonsRequestLoggingFilter loggingFilter = new CommonsRequestLoggingFilter();
loggingFilter.setIncludeClientInfo(true);
loggingFilter.setIncludeQueryString(true);
loggingFilter.setIncludePayload(true);
return loggingFilter;
}
No olvide cambiar el nivel de registro de
org.springframework.web.filter.CommonsRequestLoggingFilter
a
DEBUG
.