error, warn, info 레벨로 나눠서 기록
error파일에 출력되는 로그 레벨 error, warn, info
warn파일에 출력되는 로그 레벨 warn, info
info파일에 출력되는 로그 레벨 info
1.
spring-logback.xml
<configuration>
<appender name="error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>error</level>
<onMatch>NEUTRAL</onMatch>
<onMismatch>ACCEPT</onMismatch>
</filter>
<file>./log/error.log</file>
<encoder>
<pattern>[%X{request_id:-startup}] %d{HH:mm:ss.SSS} [%t] %-5level [%logger{0}:%line] - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>error.log.%d{yyyy-MM-dd}.gz</fileNamePattern>
<maxHistory>1</maxHistory>
<totalSizeCap>100MB</totalSizeCap>
</rollingPolicy>
</appender>
<appender name="warn" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level> error</level>
<onMatch>DENY</onMatch>
<onMismatch>NEUTRAL</onMismatch>
</filter>
<file>./log/warn.log</file>
<encoder>
<pattern>[%X{request_id:-startup}] %d{HH:mm:ss.SSS} [%t] %-5level [%logger{0}:%line] - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>warn.log.%d{yyyy-MM-dd}.gz</fileNamePattern>
<maxHistory>1</maxHistory>
<totalSizeCap>100MB</totalSizeCap>
</rollingPolicy>
</appender>
<appender name="info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>info</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<file>./log/info.log</file>
<encoder>
<pattern>[%X{request_id:-startup}] %d{HH:mm:ss.SSS} [%t] %-5level [%logger{0}:%line] - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>info.log.%d{yyyy-MM-dd}.gz</fileNamePattern>
<maxHistory>1</maxHistory>
<totalSizeCap>100MB</totalSizeCap>
</rollingPolicy>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>[%X{request_id:-startup}] %d{HH:mm:ss.SSS} [%t] %-5level [%logger{0}:%line] - %msg%n</pattern>
</layout>
</appender>
<root level="info">
<appender-ref ref="error" />
<appender-ref ref="warn" />
<appender-ref ref="info" />
<appender-ref ref="STDOUT"/>
</root>
</configuration>
2. graphQL 로깅도 하고 싶은 경우
@Component
public class CustomServletWrappingFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
ContentCachingRequestWrapper wrappingRequest = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper wrappingResponse = new ContentCachingResponseWrapper(response);
filterChain.doFilter(wrappingRequest, wrappingResponse);
wrappingResponse.copyBodyToResponse();
}
}
@Component
public class LoggingInterceptor extends HandlerInterceptorAdapter {
private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class);
private final ObjectMapper objectMapper;
public LoggingInterceptor(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (request.getClass().getName().contains("SecurityContextHolderAwareRequestWrapper")) return true;
String request_id = UUID.randomUUID().toString();
MDC.put("request_id", request_id);
final ContentCachingRequestWrapper cachingRequest = new ContentCachingRequestWrapper(request);
logger.info("Request URL : {}", ServletUriComponentsBuilder.fromRequest(cachingRequest).toUriString());
logger.info("Request Method : {}", cachingRequest.getMethod());
logger.info("Request Header : {}", objectMapper.writeValueAsString(cachingRequest.getHeaderNames()));
// GraphQL 요청인 경우
if (cachingRequest.getRequestURI().startsWith("/graphql")) {
logger.info("GraphQL Request Body : {}", new String(cachingRequest.getContentAsByteArray()));
}
// REST API 요청인 경우
else {
logger.info("REST API Request Body : {}", new String(cachingRequest.getContentAsByteArray()));
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
if (request.getClass().getName().contains("SecurityContextHolderAwareRequestWrapper")) return;
final ContentCachingResponseWrapper cachingResponse = (ContentCachingResponseWrapper) response;
logger.info("Response Status : {}", cachingResponse.getStatus());
logger.info("Response Header : {}", objectMapper.writeValueAsString(cachingResponse.getHeaderNames()));
// GraphQL 요청인 경우
if (request.getRequestURI().startsWith("/graphql")) {
logger.info("GraphQL Response Body : {}", new String(cachingResponse.getContentAsByteArray()));
}
// REST API 요청인 경우
else {
logger.info("REST API Response Body : {}", new String(cachingResponse.getContentAsByteArray()));
}
cachingResponse.copyBodyToResponse();
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
MDC.remove("request_id");
}
}
`LoggingInterceptor` 클래스는 Spring의 `HandlerInterceptorAdapter` 인터페이스를 구현합니다.
이 클래스는 요청 처리 전, 후 및 완료 후 로깅을 수행합니다.
`ObjectMapper` 클래스를 사용하여 요청 및 응답의 JSON 데이터를 파싱하고, `ContentCachingRequestWrapper` 및 `ContentCachingResponseWrapper` 클래스를 사용하여 요청 및 응답을 버퍼링합니다.
또한, Mapped Diagnostic Context(MDC)를 사용하여 각 로깅 이벤트에 대한 고유 식별자를 생성합니다.
`CustomServletWrappingFilter` 클래스는 Spring의 `OncePerRequestFilter` 인터페이스를 구현합니다.
이 클래스는 `ContentCachingRequestWrapper` 및 `ContentCachingResponseWrapper` 클래스를 사용하여 모든 요청과 응답을 버퍼링합니다.
또한, `doFilterInternal()` 메소드에서 요청을 처리한 후, 버퍼링된 응답 내용을 클라이언트에 전송하기 전에 복사(copy)합니다.