개요
프로젝트를 진행하면서, 개발자는 로그를 남기게 됩니다. 로그를 남기는 이유는 다양합니다. 저 같은 경우엔 주로 값이 잘 넘어갔는지, 값이 다시 잘 왔는지, 오류가 났는지, 오류가 났다면 어디서 났는지... 등의 이유로 로그를 남기곤 했습니다.
꼼꼼하게 로그를 남긴다면 오류가 발생했을 때 빠르게 원인파악이 가능해서 대응에 도움이 될 것입니다. 추가로 사용자들이 어떻게 서비스를 이용하는지에 대한 데이터를 확보할 수 있을 겁니다.
이처럼 중요한 정보를 담고 있는 로그를 저는 지금까지 별 생각없이 매번 Spring이 해주는 기본 설정으로 로그를 사용했었는데요, 기본 설정을 벗어나, 로그 라이브러리를 제대로 사용하는 방법을 공부해야 할 필요성을 느꼈습니다.
Spring Boot에서 기본으로 사용한다는 `Logback`이라는 라이브러리를 활용해 로그를 맛깔나게 찍는 방법에 대해서 알아봅시다.
Log란?
로그를 찍기 전에 가볍게 로그가 무엇인지에 대해서 알아봅시다. 로그에 대해서는 다들 어렴풋이 알고 계시겠지만, AWS 선생님들이 설명하는 로그는 아래와 같습니다.
로그 파일은 애플리케이션, 서버 또는 IT 시스템의 작업, 활동 및 사용 패턴에 대한 정보를 포함하는 소프트웨어 생성 파일입니다. 여기에는 모든 프로세스, 이벤트 및 메시지에 대한 기록과 함께 타임스탬프와 같은 추가적인 설명 데이터가 포함되어 이 정보를 상황에 맞게 설명해드립니다. 타임스탬프는 시스템 내부에서 발생한 상황과 발생 시기를 보여줍니다. 따라서 시스템에 문제가 발생한 경우에는 사고 발생 전의 모든 조치에 대한 상세한 기록을 확인할 수 있습니다. - AWS. '로그 파일이란 무엇인가요?'
음.. 역시 로그의 중요성에 대해서 다시 한 번 확인할 수 있었습니다.
Log level
로그에는 단계가 있습니다. 단계별로 그 중요성이나 역할을 부여하고, 그에 해당하는 정보를 기록하면 보기 편하겠죠?
Spring에서 SLF4J 인터페이스를 이용해 로그를 남길 경우 레벨은 5가지로 `trace`, `debug`, `info`, `warn`, `error`가 있습니다.
Spring에서 기본으로 설정되어 콘솔에 출력되는 로그 레벨은 `info`입니다. `application.yml`이나 이후 소개할 `logback.xml` 파일을 통해 수정할 수 있습니다.
각각의 로그 레벨에 대해서 어떤 정보를 설정해야 할지, 저는 잘 모르니 다른 유명인의 발언을 인용하겠습니다.
향로님의 1. 효율적으로 로그 모니터링하기 - 로그 레벨 구분하기의 내용을 짧게 요약하겠습니다. (더 자세한 내용은 들어가서 확인하면 이해가 잘 갈 겁니다)
로그 레벨은 그 메세지의 중요성을 나타낸다. 이 로그레벨에 따라서 개발자인 우리가 당장 노트북을 켜야 하는지, 자도 되는지가 결정된다.
일반적인 로그 라이브러리에는 Debug, Info, Warn, Error 단계가 존재한다.각 단계에 대해서는 다음과 같은 상황에서 이용하면 된다.
`Debug`개발이나 테스트 상황에서 함수의 인자나 리턴 값이 정확하게 넘어오는지에 대한 기록을 한다. 운영 상황에서는 사용하지 않는다.
`Info`Application이 제대로 서비스되고 있는지에 대해서 기록한다. ex) 유저가 인증을 성공하고, 어떤 인증 정보를 가져갔는지에 대해서 기록한다. 운영 상황에서 사용 가능하다.
`Warn`
앞으로 잠재적인 위협이 될 수 있는 경우를 기록한다. 주로 사용자의 입력 실패나 외부 API에서 발생하는 문제에 대해서 기록한다.ex) 사용자가 로그인하려고 할 때 Id나 PW를 잘못 입력하는 경우.
`Error`Application에서 발생한 심각한 오류나 예외를 말한다. 기능 자체가 제대로 동작하지 않는 경우이고 즉시 조치를 취해야 하는 경우를 말한다. ex) DB 연결 실패
프로젝트를 진행할 때에 어떤 내용이 어느 수준의 중요도를 갖게 될지에 대해서 이야기해보는 것도 중요하겠네요.
Logback
지금까지 로그에 대해서 간단하게 알아봤습니다. 이젠 실제로 어떻게 적용하는지에 대해서 알아봅시다.
로그에 대한 설정을 하기 전에 우리는 `Logback`이라는 라이브러리를 사용하겠습니다.
`Logback`은 이미 `spring-boot-starter`에 의존되어 있기 때문에, 별도로 추가하지 않아도 사용이 가능합니다.
관련한 내용은 SpringBoot의 로깅 관련 페이지에서도 살펴볼 수 있습니다.
Spring Boot uses Commons Logging for all internal logging but leaves the underlying log implementation open. Default configurations are provided for Java Util Logging, Log4j2, and Logback. In each case, loggers are pre-configured to use console output with optional file output also available.
By default, if you use the “Starters”, Logback is used for logging. - Logging::SpringBoot
마지막 줄을 보니 Starters를 이용하면 Logback이 사용될 것이라는 사실을 알 수 있죠?
설정 방법
`Logback`을 이용해서 로깅을 설정하기 위해선 `application.yml` 이나 `logback.xml` or `logback-spring.xml` 파일을 수정하면 됩니다. `application.yml`의 경우는 `Logback`의 복잡한 기능을 이용할 수 없어서 많은 사람들이 `logback.xml` 파일을 만들어서 적용하는 것 같습니다.
다양한 Appender
Appender는 무엇일까요? 이미 프로그래밍을 해보신 분들은 append라는 동사를 많이 들어보셨을 겁니다.
append를 사전에 검색하면 다음과 같이 나옵니다.
ap·pend
1.덧붙이다2.첨부하다 - 네이버 영어사전
이 뜻과 log를 합쳐서 생각해 보면, 로그를 어딘가에 덧붙인다고 생각하면 될 것입니다. 그럼 Appender는 로그를 어디엔가 붙여주는 역할을 하는 도구라고 생각하면 이해가 되겠죠?
`Logback`에는 굉장히 많은 Appender가 존재합니다. `Console`, `File`, `RollingFile`, `SMTP`, `DB`, `Async`, `Socket`, `SSLSocket` 등이 있습니다. `Logback-Core`에 속한 `Console`, `File`, `RollingFile` 만이 Spring에서 직접적으로 설정 가능하기 때문에 우선 이 3가지 Appender를 위주로 설명하고, 그 외의 것들에 대해서는 추후 다른 포스팅으로 다뤄보겠습니다.
ConsoleAppender
이름에서 유추할 수 있듯이, console에 로그를 남기는 Appender입니다. 많은 글이나 강의를 보면 로그를 `System.out.println()` 으로 찍지 말라고들 하죠. 로그 라이브러리를 활용해서 로그를 작성할 경우, 사전에 작성된 포맷에 찍힌다는 장점도 있지만, `Logback`의 ConsoleAppender는 내부적으로 OutputStreamWriter로 감싸져서 작성하기 때문에, buffer를 활용해 출력에 이점이 있다고 합니다.
FileAppender
하나의 File에 로그를 기록하는 Appender입니다. 기록할 파일의 이름을 작성하면, 그 위치에 log 파일이 생성되고 지속적으로 기록을 합니다. `append`라는 설정은 로그 파일에 데이터를 이어서 작성할지, 아니면 덮어 쓸지를 정하는 키워드로 기본은 이어서 작성하는 `true`가 설정되어 있지만, `false`를 입력할 경우 덮어쓰게 됩니다.
아래와 같이 작성한 2개의 appender에 대해서 append 옵션을 다르게 해 봤습니다.
<appender name="APPEND_TRUE_FILE" class="ch.qos.logback.core.FileAppender">
<file>./log/file/file-log-append-true.log</file>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<appender name="APPEND_FALSE_FILE" class="ch.qos.logback.core.FileAppender">
<file>./log/file/file-log-append-false.log</file>
<append>false</append>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
</appender>
그 결과로 append false를 설정한 로그 파일은 현재 실행 중인 로그의 기록만 남는다는 것을 확인할 수 있었습니다.
RollingFileAppender
RollingFileAppender는 FileAppender를 상속받아 만들어진 클래스입니다. FileAppender가 하나의 파일로만 로그를 관리하기 때문에, 불편함이 있었습니다. RollingFileAppender는 정해진 조건을 통해 여러 개의 파일로 나누어서 로그를 관리해 줍니다.
파일을 나누는 기준은 3가지가 있습니다.
나누는 기준을 뜻하는 `RollingPolicy`라는 인터페이스를 구현한 `FixedWindowBaseRollingPolicy`, `TimeBaseRollingPolicy`, `SizeAndTimeBaseRollingPolicy` 가 있습니다. 각각 FixedWindow 알고리즘, 시간, 시간과 사이즈를 기준으로 나누게 됩니다.
가장 많이 사용하는 `SizeAndTimeBaseRollingPolicy`에 대해서 설명하겠습니다.
`SizeAndTimeBaseRollingPolicy`는 설정한 시간과, 한 파일의 사이즈에 한계를 두어서 로그 파일을 여러 개를 생성하는 규칙입니다.
`SizeAndTimeBaseRollingPolicy`을 사용할 때엔 3가지 설정을 해주면 됩니다.
첫 번째는 각 파일이 가질 수 있는 최대 크기를 설정하는 `maxFileSize`입니다. 설정한 값을 넘어가면, 다음 로그 파일을 생성한 후 로그를 기록합니다.
두 번째는 로그 파일의 보관 기간을 설정하는 `maxHistory `입니다. 서비스를 오랫동안 운영하면 많은 로그가 쌓이게 될 텐데, 이렇게 될 경우 로그 파일 관리가 어렵게 되겠죠. `maxHistory`에 값을 설정하면 딱 그 기간까지만 로그를 저장하고, 지나면 삭제하도록 해줍니다.
마지막 설정은 전체 로그 파일의 최대 용량을 설정하는 `totalSizeCap`입니다. 여러 개의 로그 파일의 용량의 총합이 설정한 값을 넘어서면 가장 오래된 것부터 삭제를 진행합니다.
아래 코드는 `RollingFileAppender`에 대한 `Logback`의 예제 코드입니다. 앞서 소개한 설정을 전부 담고 있고, 그에 대한 해설이 주석으로 작성되어 있습니다. 100MB까지 보관하고, 각 로그 파일은 60일 동안, 전체 로그 파일은 20GB까지 저장을 하도록 설정이 돼있는 것을 확인할 수 있습니다.
<appender name="ROLLING" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>mylog.txt</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- rollover daily -->
<fileNamePattern>mylog-%d{yyyy-MM-dd}.%i.txt</fileNamePattern>
<!-- each file should be at most 100MB, keep 60 days worth of history, but at most 20GB -->
<maxFileSize>100MB</maxFileSize>
<maxHistory>60</maxHistory>
<totalSizeCap>20GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
RollingFile을 적용하면 아래 사진처럼 여러 파일로 로그를 나눠서 보관할 수 있습니다.
logback.xml 설정하기
지금까지 로그와 `Logback`에 대해서 알아봤습니다. 결국 이 `Logback`을 어떻게 이용하냐 이게 중요하죠. 그럼 같이 한번 `logback.xml` 파일을 설정해 보면서 적용해 봅시다.
저는 우리에게 가장 익숙한 spring에서 이용하는 형태의 패턴을 이용하도록 하겠습니다. spring에서 이용하고 있는 설정은 `defaults.xml`이라는 파일에 기록되어 있습니다.
이 설정을 그대로 사용하기 위해 내부의 내용을 복사한 후 우리 프로젝트의 resources 디렉토리 하위에 생성을 해줍니다. 그런 다음 아래와 같이 입력하면, 스프링에서 사용하는 설정인 콘솔의 출력 형태, 색상 적용, 인코딩 설정 등을 그대로 이용할 수 있습니다.
<include resource="defaults.xml"/>
그다음에는 Appender에 대한 설정을 하겠습니다.
저는 `ConsoleAppender`, `RollingFileAppender` 두 가지 Appender를 이용할 것입니다. 그리고 `ConsoleAppender`의 경우는 INFO 레벨까지 출력을 하고, `RollingFileAppender`의 경우는 Debug Level까지 출력을 하겠습니다.
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>./log/rolling/%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>60</maxHistory>
<totalSizeCap>20GB</totalSizeCap>
</rollingPolicy>
</appender>
위의 코드 중 생소한 부분으로는 `${~~~PATTERN}` 과 `<filter></filter>`가 있습니다. `${}`는 사전에 선언한 변수로 앞선 `defaults.xml`에서 설정된 아래 `<property>` 에 선언된 값을 그대로 이용할 수 있습니다.
<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr(%applicationName[%15.15t]){faint} %clr(${LOG_CORRELATION_PATTERN:-}){faint}%clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
그리고 `<filter></filter>`는 해당 appender가 특정 조건에 해당하는 로그를 남기게 됩니다. 대표적인 필터로는 `LevelFilter`와 제가 작성한 `ThresholdFilter` 그리고 `EvaluatorFilter`가 있습니다.
먼저 `LevelFilter`의 경우 정확히 매칭되는 로그 레벨에 대해서 작동합니다. `ThresholdFilter`의 경우는 설정한 레벨 이상의 범위에 대해서 동작을 하게 해 줍니다. 마지막으로 `EvaluatorFilter`의 경우 표현식, 코드를 통해서 동작을 설정할 수 있습니다.
그리고 filter는 `onMatch`, `onMistmatch`라는 키워드로 동작을 설정할 수 있습니다. 이때 사용되는 키워드는 3가지로 `ACCEPT`, `DENY`, `NEUTRAL`이 있습니다. 필터가 레벨에 맞을 때, 혹은 맞지 않았을 때 `ACCEPT`는 로그를 작성 후 다음 필터에게 넘기고, `NEUTRAL`는 별다른 처리를 하지 않은 후 다음 필터에게 전달하고, `DENY`는 필터 작동을 중단합니다.
이제는 필터 적용하는 방법에 대해서 이해할 수 있을 것입니다.
마지막으로 스프링의 프로필 별로 로그를 작성하는 방법에 대해서 알아봅시다.
굉장히 간단합니다. 스프링 Docs에 있는 내용을 따라 해봅시다.
아래 코드처럼 `<springProfile name="OO"></springProfile>` 블럭에 어떤 Appender를 적용할 것인지 설정을 하면 프로필마다 설정을 할 수 있습니다.
<springProfile name="staging">
<!-- configuration to be enabled when the "staging" profile is active -->
</springProfile>
<springProfile name="dev | staging">
<!-- configuration to be enabled when the "dev" or "staging" profiles are active -->
</springProfile>
<springProfile name="!production">
<!-- configuration to be enabled when the "production" profile is not active -->
</springProfile>
프로필을 local, dev, prod 3가지로 구분을 짓고, local은 콘솔만, dev는 콘솔과 파일, prod는 파일만 남도록 설정은 아래와 같이 하시면 됩니다.
<springProfile name="local">
<logger name="com.kk.log" level="DEBUG" />
<root level="INFO">
<appender-ref ref="CONSOLE" />
</root>
</springProfile>
<springProfile name="dev">
<logger name="com.kk.log" level="INFO" />
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="ROLLING_FILE" />
</root>
</springProfile>
<springProfile name="prod">
<logger name="com.kk.log" level="INFO" />
<root level="INFO">
<appender-ref ref="ROLLING_FILE" />
</root>
</springProfile>
정리
지금까지 로그가 어떤 것인지, 왜 사용해야 하는지, 어떻게 사용하는지에 대해서 알아보았습니다. 저는 지금까지 디버깅 용도로만 로그를 남기곤 했는데, 앞으로 더 상세하고 목적에 맞게 로그를 작성하도록 해야겠습니다.
하지만 인스턴스가 여러 개가 있어서 로그 파일을 관리하기 어렵기도 하고, 필요한 정보에 대해서 찾아보기가 어렵기도 합니다. 다음 시간엔 로그를 어떻게 수집하고, 확인하는지에 대해서 알아보도록 합시다.
참고 자료
AWS - 로그 파일이란 무엇인가요?
향로 - 1. 효율적으로 로그 모니터링하기 - 로그 레벨 구분하기
https://breakcoding.tistory.com/400