Logging
은 말 그대로 log를 남기는 것으로 시스템이 동작할 때 시스템의 상태 및 동작 정보를 시간 경과에 따라 기록하는 것을 의미한다. 이를 통해 개발자는 개발 과정 혹은 개발 후에 발생할 수 있는 예상치 못한 애플리케이션의 문제를 진달할 수 있고, 다양한 정보를 수집할 수 있다.
하지만 너무 적절한 수준의 로깅을 설정하지 않으면 너무 방대한 로그로 인해 의미있는 결과를 얻을 수 없기에 효율적인 설정이 필요하다.
많이 알려진 Logging 구현체로는 log4j
,log-back
,log4j2
가 있으며 log4j2
의 등장 이후 log4j
는 사용되지 않으며 이 포스트에서는 log-back
의 사용법을 설명하고자 한다.
Error
: 예상하지 못한 심각한 문제가 발생하는 경우, 즉시 조취를 취해야 할 수준의 레벨
Warn
: 로직 상 유효성 확인, 예상 가능한 문제로 인한 예외 처리, 당장 서비스 운영에는 영향이 없지만 주의해야 할 부분
Info
: 운영에 참고할만한 사항, 중요한 비즈니스 프로세스가 완료됨
Debug
: 개발 단계에서 사용하며, SQL 로깅을 할 수 있음
Trace
: 모든 레벨에 대한 로깅이 추적되므로 개발 단계에서 사용함
어떤 클래스에 직접 로그를 출력하도록 설정할 수 있다.
private final Logger logger = LoggerFactory.getLogger(this.getClass());
logger.error("내용");
logger.warn("내용");
logger.info("내용");
logger.debug("내용");
logger.trace("내용");
해당 클래스에 LoggerFactory
를 통해 logger
를 출력할 수 있다.
또한 application.properties
설정을 통해 log-level을 설정하고 파일로 저장할 수 있다.
logging.level.root = info
logging.file.name=경로/test.log
하지만 이 경우 일자별 로그가 남지 않고, 파일 관리가 어렵기 때문에 권장되지 않는다.
application.properties
설정을 통해 로그에 어떤 SQL 문이 실행되었는지 출력할 수 있다.
📌application.properties
spring.jpa.properties.hibernate.format_sql=true
AOP를 생성하기 전 resource
패키지에 logback-spring.xml
파일을 생성한다.
📌logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 로그의 저장 위치 -->
<property name="LOGS" value="D:/logs" />
<!-- appender-> 출력 위치, CONSOLE 표기 패턴-->
<appender name="Console"
class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>
%yellow(%d{ISO8601}) %highlight(%-5level) [%blue(%t)] %magenta(%C{1.}): %msg%n%throwable
</Pattern>
</layout>
</appender>
<!-- 현재 롤링파일 표기 및 아카이브 로깅 규칙 정의 -->
<appender name="RollingFile"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOGS}/now-logFile.log</file>
<encoder
class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
</encoder>
<rollingPolicy
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 분당 로그 파일을 만들고, 10MB 단위로 운영 -->
<!-- %d -> dateTime, %i -> index 둘은 필수 -->
<fileNamePattern>${LOGS}/archive/logFile.%d{yyyy-MM-dd_HH}.%i.log
</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
</appender>
<!--전역 로깅 설정 ROOT 부터 하위로 내려감, additivity-> 상속여부-->
<logger name="com.spring.jpa" level="trace" additivity="false">
<appender-ref ref="RollingFile" />
<appender-ref ref="Console" />
</logger>
<!--데이터 베이스 관련 로깅 설정-->
<!--SQL 보이게 하기-->
<logger name="org.hibernate.SQL" level="DEBUG">
<appender-ref ref="RollingFile" />
<appender-ref ref="Console" />
</logger>
<!--SQL에 들어가는 파라미터 보이게 하기-->
<logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE">
<appender-ref ref="RollingFile" />
<appender-ref ref="Console" />
</logger>
<!--DB 커넥션 표시(선택)-->
<logger name="com.zaxxer.hikari" level="DEBUG">
<!--<appender-ref ref="RollingFile" />-->
<!--<appender-ref ref="Console" />-->
</logger>
</configuration>
👀configuration 기본적인 구조
👀appender 종류
logger
설정이 완료되었다면 aop.java
파일을 생성한다.
📌LoggerAop.java
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
@Aspect
public class LoggerAop {
private static final Logger log = LoggerFactory.getLogger(LogAdvice.class);
@Around("execution(* com.spring.jpa.controller..*Controller.*(..))"
+" || execution(* com.spring.jpa.service..*Service*.*(..))"
+" || execution(* com.spring.jpa.repository..*Repository.*(..))")
public Object logPrint(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//객체명
String type = proceedingJoinPoint.getSignature().getDeclaringTypeName();
//proceedingJoinPoint.getSignature().getName() <- 실행 메서드명
log.info("[[START]]"+type+"."+proceedingJoinPoint.getSignature().getName()+"() <=================");
log.info("Argument/Parameter : "+ Arrays.toString(proceedingJoinPoint.getArgs()));//<-파라미터
log.info("================[[END : "+proceedingJoinPoint.getSignature().getName()+"()]]==================");
return proceedingJoinPoint.proceed();
}
}
클래스에 직접 호출할 때와 마찬가지로 LoggerFactory
를 통해 logger
를 생성하고 Controller
,Service
,Repository
가 사용될 때 로그를 남기도록 설정하였다.
👀log 출력
proceedingJoinPoint.getSignature().getDeclaringTypeName()
: 실행 위치(Controller, Service, Repository)
proceedingJoinPoint.getSignature().getName()
: 실행 메서드
proceedingJoinPoint.getArgs()
: 전달된 파라미터
Log를 위한 AOP까지 설정하면 프로젝트를 실행하면서 메서드의 흐름과 어떤 파라미터가 오고 가는지, 어떤 SQL문이 실행되었는지 확인할 수 있다.
마무리
Log를 사용해서 가장 좋았던 것은 프로그램의 실행 순서를 명확하게 볼 수 있다는 것과 SQL문이 올바르게 사용되었는지 확인 할 수 있다는 점이었다.
확실히 오류 지점을 빠르게 파악할 수 있다는 장점이 있지만 로그 파일을 어떻게 효율적으로 관리할 수 있을지에 대해서는 많은 경험과 고민이 필요할 것 같다.