서버에서 로그를 상세히 적어두면 디버깅이나 서버 오류가 발생했을 때 쉽게 파악할 수 있어 유지보수에 있어서 중요하다.
로그는 총 7단계로 나뉘는데 설명을 요약하자면 아래와 같다.
로그 레벨과 설명
레벨 | 설명 |
ALL | custom level을 포함한 모든 로그를 저장하는 레벨이다. |
DEBUG | 프로그램 상 아주 단위의 행동들을 기록하는 레벨이다. |
INFO | 프로그램 상 큰 단위, 덩어리채로 정보를 기록하는 레벨이다. |
WARN | 잠재적으로 위험할 수 있는 상태가 될 수 있는 정보를 기록하는 레벨이다. |
ERROR | 원래 설정된 error로 프로그램 구동에는 영향을 미치지 않을 수 있는 것들을 기록하는 로그레벨이다. |
FATAL | 원래 설정된 error이긴 한데 프로그램 중단을 야기시키는 것만 기록하는 로그레벨이다. |
OFF | 프로그램이 멈추는 상태로 로깅시스템도 꺼질 수 있는 로그만을 기록하는 로그레벨이다. |
이러한 로그를 spring에서 작성하는 방식은 여러가지가 있는데 그 중에 내가 사용한 로깅 방식은 SLF4J라는 방식이다.
SLF4J
Simple Logging Facace for Java 라는 명칭의 facade로 여러 로깅 라이브러리를 쓰는 형식을 통일해주는 구글 홈팟 같은 느낌의 facade이다.
다음 그림은 SLF4J와 구조도이다.
즉, 로깅 라이브러리가 얼마나 다양해지냐에 상관없이 우리는 하늘색만 사용하기 때문에 여러 로깅 라이브러리를 사용할 수 있게 된 것이다.
lombok을 설치하고 진행해서 아무런 추가 설정없이 진행했다.
SLF4J를 사용해서 로깅을 하는 방식을 정의해줘야한다. 이를 위해서 resource 폴더에 우리가 원하는 방식의 logback.xml 파일을 작성해야한다.
다음 logback.xml파일은 이번 프로젝트에서 사용한 logback.xml파일이다
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>./logs/ServiceLog.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>./logs/access-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<Pattern>%d{HH:mm} %-5level %logger{36} - %msg%n</Pattern>
</encoder>
</appender>
<appender name="SECURITY" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>./logs/securityCheckFail.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>./logs/security-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<Pattern>%d{HH:mm} %-5level %logger{36} - %msg%n</Pattern>
</encoder>
</appender>
<logger name="com.demo.controller" level="debug">
<appender-ref ref = "FILE" />
</logger>
<logger name = "com.demo.service" level = "debug">
<appender-ref ref = "FILE"/>
</logger>
<logger name = "com.demo.exception" level = "debug">
<appender-ref ref = "FILE"/>
</logger>
<logger name = "com.demo.security" level = "warn">
<appender-ref ref = "SECURITY"/>
</logger>
</configuration>
하나씩 뜯어보자
1. Configuration
<configuration scan="true" scanPeriod="30 seconds">
...
</configuration>
이거 약간 html 삘 나는거 같다. 난 저 <></> 가 사실 맘에 안 든다. 뭐 컴파일 할 때는 편하겠다만은 그냥 별로다. 귀찮고 거슬린다.
여튼 저 scan이라는 놈이 아주 좋은 놈인데 저렇게 설정하면 내가 서버가 실행되는 중에 logging방식에 사용되는 logback.xml을 수정할 수 있다. 약간 react에서 ctrl+s하면 웹페이지 수정되는 거랑 비슷하다고 볼 수 있다. 당연히 뒤에 오는 scanPeriod는 logback 업데이트 주기이다.
근데 쓰면서 생각해보니깐 요놈은 그럼 런타임 바인딩을 하겠네? 라는 생각이 든다.
2. Appender
appender는 이제 우리가 설정할 파일에 진또배기를 설정하는 부분이다.
어디에! 어떤! 포맷으로 로그를 남길것인지 결정하는 부분이라는 것이다.
이 appender에는 어디에 로그를 작성하느냐에 따라 3가지가 방식이 존재한다.
콘솔 : Console Appender
이는 콘솔, 즉 터미널이나 cmd창에 로그를 출력을 할때 설정하는 방법으로 우리가 기본적으로 아무런 설정없이 spring을 실행하면 뭐가 많이 나오는데 그런 부분을 어떻게 출력하게 할 것인지 설정해주는 부분이다.
ConsoleAppender를 사용해서 로그를 설정하는 방식은 다음과 같다.
<appender name = "CONSOLE" class = "ch.qos.logback.core.ConsoleAppender">
<encoder>
<Pattern> %d {HH:mm} %-5level %logger{36} - %msg%n</Pattern>
</encoder>
</appender>
어디에는 정했고 그럼 어떤 포맷으로 남길지를 결정해야한다.
그래서 <Pattern>을 정의한다. Pattern에서 정의되는 요소는 다음과 같다.
Pattern | 의미 |
%d{HH:mm} | 로그가 출력되는 시간, 중괄호 안은 이 시간의 포맷을 의미한다. |
%-5level | 로그 레벨의 폭을 결정하는 부분이다. 앞에서 말한 로그 레벨 중에 Debug에서 Fatal까지를 의미한다. |
%logger{36} | 로거의 이름을 축약해서 출력하는 부분으로 중괄호 안에는 이를 표현할때 사용되는 최대 자릿수를 의미한다. |
%msg | 사용자가 남긴 로그를 출력할 때 사용된다. |
%n | 줄 바꿈을 의미한다. |
이건 내가 작성한 로그가 cmd창에서 출력되는 것을 가져와 봤다.
보면 내가 작성한 대로 출력되는 것을 볼 수 있었다.
시간 - 출력 레벨 - 로그가 발생한 클래스 - 로그 메시지 - 줄바꿈
이렇게 출력되는 것을 볼 수 있다.
그리고 하나더!
%msg 앞에 - 추가해주니 message앞에 -가 나왔다.
파일 : FileAppender
이는 파일에 로그를 작성하는 방법이다. 아니 근데 사람들이 RollingFile Appender예시만 잔뜩 만들어 놓고 File Appender는 예시를 안 만들어 놓는다. 왜 그럴까?
알고보니 RollingFileAppender는 File Appender의 확장자로 이 때문에 대부분 RollingFileAppender로 갈아탄 것같다.
그럼 나도 RollingFileAppender만 설명을 만들어야겠다.
여러 파일 : RollingFileAppender
앞썬 FileAppender와 달리 로그 파일이 백업되는 구조를 가지는 Appender이다.
이러한 FileAppender를 설정해주는 방식은 다음과 같다.
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>info.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>./logs/access-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<Pattern>%d{HH:mm} %-5level %logger{36} - %msg%n</Pattern>
</encoder>
</appender>
현재 로그가 작성될 log 파일 이름
<file> info.log </file>
현재 로그가 작성될 로그 파일의 이름이다.
로그가 벡업될때 파일이름과 최대 갯수
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>./logs/access-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
fileNamePattern은 벡업 될 로그 파일의 형식이다
maxHistory는 이러한 벡업 파일을 최대 몇개까지 유지할 것인가에 대한 정보를 가지고 있다. 당연한 소리겠지만 이게 없다면 앞에서 말한 fileappender와의 차이점은 로그가 나뉘어서 저장이 된다 밖에 없고 메모리적으로는 로그가 무한정으로 생기는 끔찍한 일이 생길 수 있기 때문에 이를 설정해주는 것으로 생각이 된다.
필터
LevelFilter
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
정확한 로그 레벨 일치를 기반으로 이벤트를 필터링한다.
onMatch와 onMismatch를 통해 해당 level에 대한 승인과 거부를 설정할 수 있다.
ThresholdFilter
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
지정된 임계값 아래 이벤트를 필터링한다. 이보다 낮은 레벨의 경우 필터링 되어 이벤트가 거부된다.
logger
로거는 어떤 패키지 이하의 클래스에서 어떤 레벨 이상의 로그를 출력할지 결정할 때 사용한다.
<logger name = "com.demo.service" level = "debug">
<appender-ref ref = "FILE"/>
</logger>
이는 org.springframework로 시작하는 패키지에 속한 클래스에서 출력하는 로그가 info이상의 레벨에 해당하는 것으로 출력하라는 의미이다.
root
위의 설정 파일에는 안 나와있지만 logger는 어떤 패키지 이하의 클래스에서 적용되는 것에 반해 root 태그에 있는 appender는 모든 패키지에 적용되는 것을 의미한다. level은 해당 레벨 이상의 것들에 대해 적용하겠다는 의미로 해석하면 된다. 미적용시 debug를 기본 값으로 사용한다.
<root level="DEBUG">
<appender-ref ref="FILE"/>
</root>
긴 글 읽어주셔서 감사합니다.
틀린 부분이 있으면 댓글을 달아주시면 감사하겠습니다.
📧 : may3210@g.skku.edu
'개발 > Spring' 카테고리의 다른 글
[Spring] SpringSecurity와 Jwt : 이론편 (0) | 2023.01.06 |
---|---|
[Spring]JUnit에서 Async메소드의 비정상적인 종료 - 2편 (0) | 2022.02.15 |
[Spring]JUnit에서 Async메소드의 비정상적인 종료 - 1편 (0) | 2022.02.10 |
[Spring] Rest Docs 빌드 부터 사용까지 (2) | 2022.01.24 |
[Spring] Spring과 Paging - [1] (0) | 2022.01.20 |