アプリケーションは、クライアントに影響を与えることなく、非同期に(別のスレッドで)以下の情報をログに記録する必要があります。
フィルターでinputstream
を使用する場合、jsonからオブジェクトへのマッピングのために、Springによって再度使用することはできません。入力ストリームからオブジェクトへのマッピング中に、ロガーを接続できますか?
更新:
MessageConverter でロギングコードを上書きできますが、それは良い考えではないようです。
public class MyMappingJackson2MessageConverter extends AbstractHttpMessageConverter<Object> {
...
protected Object readInternal(Class<? extends Object> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
InputStream inputStream = inputMessage.getBody();
String requestBody = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
String method = request.getMethod();
String uri = request.getRequestURI();
LOGGER.debug("{} {}", method, uri);
LOGGER.debug("{}", requestBody);
return objectMapper.readValue(requestBody, clazz);
}
protected void writeInternal(Object o, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
String responseBody = objectMapper.writeValueAsString(o);
LOGGER.debug("{}", responseBody);
outputMessage.getBody().write(responseBody.getBytes(StandardCharsets.UTF_8));
}
}
baeldung.com からの回答:
Springは、ペイロードをログに記録するための組み込みソリューションを提供します。設定を使用してSpringアプリケーションにプラグインすることにより、既製のフィルターを使用できます。 AbstractRequestLoggingFilter は、ロギングの基本機能を提供するフィルターです。サブクラスは、
beforeRequest()
およびafterRequest()
メソッドをオーバーライドして、リクエストに関する実際のロギングを実行する必要があります。 Springフレームワークは、着信リクエストのログに使用できる以下の具体的な実装クラスを提供します。これらは:Spring Bootアプリケーションは、リクエストロギングを有効にするBean定義を追加することで設定できます。
@Configuration public class RequestLoggingFilterConfig { @Bean public CommonsRequestLoggingFilter logFilter() { CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter(); filter.setIncludeQueryString(true); filter.setIncludePayload(true); filter.setMaxPayloadLength(10000); filter.setIncludeHeaders(false); filter.setAfterMessagePrefix("REQUEST DATA : "); return filter; } }
また、このロギングフィルターでは、ログレベルをDEBUGに設定する必要があります。
application.properties
置くlogging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=DEBUG
ロギングを非同期にするには、 非同期アペンダー を使用できます。残念ながら、それはロギング応答ペイロードをサポートしていません。 :(
これは、スプリングアスペクトを使用して実現できます。それはあなたにいくつかの注釈を提供します:@Before , @AfterReturning, @AfterThrowing
など。すべてのエンドポイントログが必要なわけではないため、パッケージに基づいたいくつかのフィルターがあります。ここではいくつかの例を示します。
リクエストの場合:
@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) {
}
}
}
here `@Before("within(your.package.where.is.endpoint..*)")` has the package path. All endpoints within this package will generate the log.
応答の場合:
@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");
}
}
here `@AfterReturning("within(your.package.where.is.endpoint..*)")` has the package path. All endpoints within this package will generate the log. Also Object returnValue has the response.
例外の場合:
@AfterThrowing(pointcut = ("within(your.package.where.is.endpoint..*)"), 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());
}
}
here `@AfterThrowing(pointcut = ("within(your.package.where.is.endpoint..*)"), throwing = "e")` has the package path. All endpoints within this package will generate the log. Also Exception e has the error response.
これが完全なコードです:
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());
}
}
}
AOPの詳細については、こちらをご覧ください。
私は2つの要素を使用します:A LoggingFilter およびSpringの Async サポート。最初の例では、HTTPリクエストをインターセプトする方法をすでに知っているCommonsRequestLoggingFilterを使用し、その設定と非同期の設定を作成します。次のようなことができます:
最初に非同期サポートを有効にします
@Configuration
@EnableAsync
public class SpringAsyncConfig { ... }
次に、loggingFilterを作成します。
public class LoggingFilter extends CommonsRequestLoggingFilter {
@Override
protected void beforeRequest(final HttpServletRequest request, final String message) {
// DO something
myAsyncMethodRequest(request, message)
}
@Override
protected void afterRequest(final HttpServletRequest request, final String message) {
// Do something
myAsyncMethodResponse(request, message)
}
// -----------------------------------------
// Async Methods
// -----------------------------------------
@Async
protected void myAsyncMethodRequest(HttpServletRequest request, String message) {
// Do your thing
// You can use message that has a raw message from the properties
// defined in the logFilter() method.
// Also you can extract it from the HttpServletRequest using:
// IOUtils.toString(request.getReader());
}
@Async
protected void myAsyncMethodResponse(HttpServletRequest request, String message) {
// Do your thing
}
}
次に、作成したフィルターのカスタムロギング構成を作成します。
@Configuration
public class LoggingConfiguration {
@Bean
public LoggingConfiguration logFilter() {
LoggingFilter filter
= new LoggingFilter();
filter.setIncludeQueryString(true);
filter.setIncludePayload(true);
filter.setIncludeHeaders(true);
return filter;
}
}
リクエストからデータを抽出するには、message
パラメータを使用するか、HttpServletRequest
を処理します。例として: