우린 어떤 로그를 써야할까?

디우·2022년 7월 29일
0

모아모아

목록 보기
13/17

이번 스프린트3의 백엔드 요구사항은 다음과 같다.

드디어 운영 서버와 Dev 서버의 구분이 필요해지게 된 것이다.
그리고 운영 서버에서 로그를 남기는 작업 또한 리소스를 잡아먹는 행위이기 때문에 어떤 라이브버리를 그리고 어떤 포맷으로 사용해야 그나마 서버에 부하를 덜 줄지에 대해 정리해보려고 한다.

참고한 책 : 자바 최적화(벤저민 J.에번스, 제임스 고프, 크리스 뉴랜드)


책의 14챕터를 보면, 글 시작에서 개발자들은 대부분 프로젝트에서 로깅 라이브러리를 그리 신경쓰지 않고 그 선택 과정도 대충 일사천리로 진행하는 경우가 많습니다. 라는 말이 나온다.

나도 그랬던 것 같다.
우선 실제 운영으로 연결된 프로젝트가 거의 없다시피 하기 때문에 로그를 남기고 관리하는 경험이 한 두번으로 없을 뿐 아니라, 로깅 라이브러리가 다양하다는 사실도 잘 몰랐었다. 그냥 SLF4j 의 구현체이며 Spring Boot 환경에서 별도의 의존성 주입 없이 사용가능한 Logback 구현체를 사용하였었다.

이번 프로젝트 에서는 어떤 로깅 라이브러리들이 있고, 어떤 것이 우리의 상황에 조금 더 맞는지를 고민해보는 시간을 가졌으면 해서 각 로깅 라이브러리를 비교해보려고 한다.


로깅을 왜 할까?

해당 질문에 대한 내용은 Tecoble의 글에 잘 정리가 되어 있어 해당 글 내용을 인용하려고 한다.

로깅이란 시스템이 동작할 때 시스템의 상태 및 동작 정보를 시간 경과에 따라 기록하는 것을 의미한다. 로깅을 통해 개발자는 개발 과정 혹은 개발 후에 발생할 수 있는 예상치 못한 애플리케이션의 문제를 진단할 수 있고, 다양한 정보를 수집할 수 있다. 사용자 로그의 경우 분석 데이터로 활용할 수 있다. 하지만 로깅을 하는 단계에서 적절한 수준의 로그 기록 기준을 잡지 못하면 방대한 양의 로그 파일이 생성되는 문제를 겪거나, 의미 있는 로그를 쌓지 못하는 경우가 발생할 수 있다. 결국 효율적으로 로깅을 하는 방법을 이해하는 것이 중요하다.
출처: Logback 으로 쉽고 편리하게 로그 관리를 해볼까요? ⚙️


로깅 포맷

Logback 포맷

14:18:17.635 [Name Of Thread] INFO c.e.NameOfLogger - Log message

java.tuil.logging 포맷

July 29, 2022 2:36:11 PM com.exmaple.NameOfLogger nameOfMethod
INFO : Log message

Log4j 포맷

2022-07-29 11:36:02,651 [Name Of Thread] INFO com.example.NameOfLogger - message

로깅 벤치마크

책에서 다양한 로그 패턴을 이용해서 가장 많이 쓰이는 세가지 로거 (Logback, Log4j, java.util.loggin) 의 성능을 공정하게 비교하는 벤치마크를 보여준다.

참고 : https://github.com/stephenc/java-logging-benchmarks

저자는 세가지 로거를 비교하기 위해 아이맥과 AWS EC2 t2.2xlarge 인스턴스에서 벤치마크를 하였으며, 이렇게 두 가지 컴퓨터에서 진행한 이유는 맥 OS는 절전 기술이 문제가 될 수 있고, EC2 인스턴스는 다른 컨테이너가 벤치마크 결과에 영향을 미칠 수 있기 때문이다.
이말은 즉, 우리 모아모아팀 상황에 맞게 한 번 테스트하는 것이 유의미한 데이터를 얻을 수 있다는 것과 같다는 생각이 든다.

저자가 iMac에서 테스트한 결과의 경우 java.util.logging 포맷을 사용한 경우에 성능이 가장 나빴으며, 같은 포맷으로 Log4j를 사용할 때 2.5배 이상 성능이 안좋았다. 결론적으로 벤치마크 결과를 보면 Logback이 전반적으로 성능이 가장 우수하고 로깅 포맷이 Log4j일 때 최고였다.

AWS EC2 에서도 결과는 비슷해보였다. LogbackLog4j 보다 약간 더 빠른 결과가 나왔지만, 이번에는 Logback 포맷일 때 가장 빨랐다.


우린 어떤 로깅 라이브러리를 쓸까?

로그를 남기는 데 드는 자원이 우리가 처리해야하는 비즈니스에 영향을 주면 안된다는 생각이 든다. 따라서 초대한 성능에 영향이 적은 로거를 사용해야할 것이다. (포맷도 포함해서)

앞서 보인 로깅 벤치마크에서는 Log4j가 성능이 Logback보다 낮은 것으로 나왔지만 Log4j 2.6에서 성능이 향상되기는 하였다. (위의 벤치마크에서는 Log4j 2.7버전 기준이다.)
각 로그 메시지마다 임시 객체를 생성하던 로직을 객체를 재사용하는 방향으로 수정하였기 때문이다.
(객체 풀 패턴: 필요한 객체를 바로 생성하지 않고 풀에 요청을 해서 반환받는 식으로 작업을 수행하는 패턴, JDBC 커넥션 풀과 유사)

앞서 인용한 Tecoble 글을 참고해 볼 때 Logback 은 log4j 에 비해 향상된 필터링 정책, 기능, 로그 레벨 변경 등에 대해 서버를 재시작할 필요 없이 자동 리로딩을 지원한다는 장점 이 있고, 책에서도 Logback이 성능상 이점이 있는 것으로 보이기 때문에 (실제 우리 환경에서 테스트를 해보는게 제일 베스트 이지만..우선) 우리팀에서도 Logback 구현 라이브러리를 사용할 것으로 생각된다.

스프링 부트가 제공하는 로그

그렇다면 다음으로 로그 레벨은 어떻게 할까?


로그 레벨은 어떻게 하면 좋을까?

Logback 은 총 5단계의 로그 레벨을 가진다.

  • Error : 예상하지 못한 심각한 문제가 발생하는 경우
  • Warn : 로직 상 유효성 확인, 예상 가능한 문제로 인한 예외 처리
  • Info : 운영에 참고할만한 사항
  • Debug : 개발 단계에서 사용하며 SQL 로깅을 할 수 있다.
  • Trace : 모든 레벨에 대한 로깅이 추적 (개발단계에서 사용)

우리팀은 총 2개의 인스턴스가 있다. Dev 서버와 운영 서버, 따라서 개발서버에서는 debug, 운영 서버에서는 info 로 하는 것이 좋을 것 같다.

개발서버에서 Debug로 하는 이유는 SQL 쿼리까지 확인할 수 있기 때문이다.
아마 jpa 설정(spring.jpa.properties.show_sql)으로도 쿼리를 볼 수 있겠지만, 현재 우리가 Read(읽기, 조회)의 경우에는 쿼리를 직접 날리고 있기 때문에 이 부분은 jpa 설정으로 잡히지 않을 것 같다.

그리고 운영 서버는 앞서 계속 강조한 것 처럼 성능에 영향을 크게 미치지 않는 한에서 정말로 필요한 정보만 로그를 남기면 될 것이라고 생각되기 때문에 Info 로 설정하여 운영에 참고할만한 사항이나 중요한 비즈니스 프로세스가 완료되었는지 여부만 보아도 충분할 것 같다.

(Debug와 Trace레벨을 사용하면 많은 양의 로그가 쌓이므로 운영 단계에서는 사용하지 않는 것이 좋음)

주의할 점 (올바른 로그 사용법)

log.debug("data = " + data)

로그 출력 레벨을 info 로 설정해도 위의 코드에 있는 "data = " + data 가 실제 실행되어 결과적으로 문자열 더하기 연산이 발생한다. 따라서 아래와 같이 설정하는 것이 좋다.

log.debug("data={}", data)

위와 같이 하면 로그 출력 레벨을 info 로 설정하면 아무일도 발생하지 않으며 문자열 덧셈도 발생하지 않는다.


로그를 적용해볼까?

아직 우리팀 프로젝트에 적용을 한 이후 시점이 아니라서 어떻게 적용하면 좋을지 나의 생각을 작성해보려고 한다.

가장 먼저 application.yml 을 통해서도 간단하게 로그 설정이 가능하지만, 세부적인 설정이 불가능하기 때문에 logback-spring.xml 로 관리하는 것이 좋을 것 같다.

logback-spring.xml 은 콘솔, 파일, DB 등 로그를 출력하는 방법을 지정하는 appender 와 출력할 곳을 정하는 logger 로 나눌 수 있다.
출처 : Logback 으로 수비고 편리하게 로그 관리를 해볼까요? ⚙️

가장 먼저 configuration 설정을 시작한다.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
 <!-- 이 곳에 추가할 기능을 넣는다. -->
</configuration>

그리고 우리는 현재 일정 기간 별로 나눠서 로그 파일을 관리하고 싶고,
다음으로 appender 에서 콘솔에 출력되는 형식을 지정한다.
이 때, <fileNamePattern> 에 어떻게 파일명을 할지 패턴을 적어주면 된다. (예시 : ./was-logs/info.%d{yyyy-MM-dd}.%i.log.gz)

그리고 <maxFileSize> 로 일정 파일 용량이 되면 앞서 설정한 fileNamePattern 에 맞게 .gz 로 저장되게 할 수 있으며 <maxHistory> 로 하나의 파일의 최대 저장 기한을 지정해 줄 수 있다.

profile
꾸준함에서 의미를 찾자!

0개의 댓글