로그를 남기는것은 아주 중요한 일이다. 추후 오류를 찾을때 추적하기도 쉬우며...(주절주절)
로그가 중요하다는것은 모두 알거고, 그럼 어떠한 방식으로 로그를 남기는게 좋을까? 라는 다음 단계가 있을수 있겠다.
사내에서는 다양한 프레임워크를 사용하고 있기에, 전사적으로 사용하기 위해 처음 구상한 내용은, 로깅용 데이터베이스를 따로 구축하여(nosql
) 카프카 등 이벤트로 처리하도록 하는걸 생각해봤었다.
하지만 트래픽이 적지도않을뿐더러, 솔루션마다 주요하게 저장할 로깅 데이터가 달라 정형화하기 힘들다 라는 이슈...
전사적으로 가장 많이 채택되어 사용되고있는 스프링 모듈로부터 따로 생각해봤다.
그리고 아주 편리하게 사용할수있는 AbstractRequestLoggingFilter
이 있다고 할수 있겠다.
OncePerRequestFilter
을 상속받고 있으며, 해당 클래스를 조금만 손보면 요청 전후로 로그를 받을수 있다.
해당 모듈을 구현하며, 사용자가 커스텀하게 할수있도록 하고 싶었다. 예를들면 특정 사용자별 로그를 찍고싶지 않은 엔드포인트를 정하는느낌으로!
그리고 AbstractRequestLoggingFilter
에서 커스텀하게 지정해줄수 있는 세팅값들은 다음과 같다. (message prefix, suffix 데코레이터는 제외!)
public void setIncludeQueryString(boolean includeQueryString) {
this.includeQueryString = includeQueryString;
}
public void setIncludeClientInfo(boolean includeClientInfo) {
this.includeClientInfo = includeClientInfo;
}
public void setIncludeHeaders(boolean includeHeaders) {
this.includeHeaders = includeHeaders;
}
public void setIncludePayload(boolean includePayload) {
this.includePayload = includePayload;
}
public void setHeaderPredicate(@Nullable Predicate<String> headerPredicate) {
this.headerPredicate = headerPredicate;
}
public void setMaxPayloadLength(int maxPayloadLength) {
Assert.isTrue(maxPayloadLength >= 0, "'maxPayloadLength' must be greater than or equal to 0");
this.maxPayloadLength = maxPayloadLength;
}
다들 메서드가 직관적이라 이해하기가 아주 좋다.
그리고 사용자가 설정값을 커스텀하게 할수 있도록 ConfigurationProperties
으로 받도록 구성하였다.
@Component
@ConfigurationProperties(LoggingProperties.LOGGING_PREFIX)
data class LoggingProperties(
var excludePath: MutableList<String> = mutableListOf("**/health/**"),
var maxPayloadLength: Int = 1000,
var includeHeaders: Boolean = true,
var includePayload: Boolean = true,
var includeQueryString: Boolean = true,
){
companion object{
const val LOGGING_PREFIX="logging.config"
}
}
그리고 AbstractRequestLoggingFilter
를 구현해주도록 한다. 구현해야할 주요 메서드들은 다음 세개가 있다.
로그를 찍을지 여부 판단
protected boolean shouldLog(HttpServletRequest request)
before 로그
protected abstract void beforeRequest(HttpServletRequest request, String message)
after 로그
protected abstract void afterRequest(HttpServletRequest request, String message)
sholudLog
메서드를 재정의하지 않는다면 k8s에서 health 체크등 불필요한 요청들도 다 찍혀서 오히려 보기가 힘들수 있기에, 제외하고싶은 엔드포인트들을 받을수 있도록 하였다.
class RequestLoggingFilter(
private val excludePath: List<String>
): AbstractRequestLoggingFilter() {
private val matcher = AntPathMatcher()
override fun shouldLog(request: HttpServletRequest): Boolean {
return !excludePath.any {
matcher.match(it, request.requestURI)
}
}
override fun beforeRequest(request: HttpServletRequest, message: String) {
this.logger.info(message)
}
override fun afterRequest(request: HttpServletRequest, message: String) {
this.logger.info(message)
}
}
이후 해당 로깅 내용을 Bean 설정까지만 해주면 뚝딱!
@Configuration
class RequestLoggingConfig{
@Bean
fun requestLoggingFilter(
properties: LoggingProperties
):RequestLoggingFilter{
val filter = RequestLoggingFilter(properties.excludePath);
filter.setMaxPayloadLength(properties.maxPayloadLength)
filter.setIncludeHeaders(properties.includeHeaders)
filter.setIncludePayload(properties.includePayload)
filter.setIncludeQueryString(properties.includeQueryString)
return filter
}
}
해당 모듈을 사용자가 가져오기만 하더라도 기본적인 로깅이 찍히도록 구성하였다. 물론 모듈을 만들때 사용자가 커스텀하게 설정하는것도 @ConfigurationProperties
value로 설정한 값들을 yml 에 설정해줄수 있다고 할 수 있다.