최근 프로젝트와 외부활동 이것 저것 하다보니 바쁘다는 핑계 반, 귀찮다는 핑계 반으로 블로그 정리를 안했었다... 과거의 멍청했던 나를 반성하며 그동안 프로젝트에 적용하면서 노션에만 정리해뒀던 내용을 다시 블로그에 정리해보려 한다.
모니터에 이상한 글자들이 주르륵 올라가고 그것을 유심히 보는 해커 또는 프로그래머. 아마 영화에서 그런 장면 한번 쯤은 본 적 있을 것이다. 여기서 올라가는 글자들이 대부분 로그이다.
서비스를 개발하는 단계이든, 운영중인 단계이든 로깅을 하는 작업은 매우 중요하다.
이 로그를 통해서 프로그램이 어떻게 작동하고 있는지, 어떤 어디서 오류가 발생했는지 쉽게 파악할 수 있기 때문.
System.out.println("Hello, world");
System.err.println("Error!!")
자바 공부를 처음 시작하고 Hello, world 찍을 때 Sysyem.out를 사용한 경험이 있을 것이다. 이 방법으로 로그를 찍으면 될까??
Sysyem.out은 상세한 로그를 찍기도 힘들고 로그 레벨을 관리, 로그를 제어하거나 필터링 할 수가 없다. 그렇다면 Spring boot에서는 어떤 방식으로 로그 처리를 할 수 있을까?
Logback은 오픈소스 로깅 프레임워크이며 SLF4J의 구현체이자, 스프링부트에 기본으로 내장되어 있는 로깅 라이브러리이다.
log4j보다 좋은 성능을 보여준다고 한다.(log4j는 2015년에 지원을 중단했으니 당연한 결과겠지만)
그래서 대부분 log4j2와 logback 중 자신의 프로젝트에 맞는 것을 선택해서 사용하게 된다.
log4j2는 얼마전에 큰 보안 이슈가 터졌었는데 logback을 사용해 로깅을 한 시스템들은 해당 이슈에 대응할 필요가 없었을 것이다.
물론 log4j2는 멀티 쓰레드 환경에서 비동기 로 처리가 아주 빠르다는 장점이 있어서 보안 취약점이 개선된 지금은 아무 문제 없이 선택해도 된다.
하지만 오늘의 주제는 logback이므로 logback에 대해서만 다뤄보도록 하겠다.
우선 log4j2와 logback은 slf4j의 구현체라고 했는데 이 slf4j가 무엇일까?
slf4j는 여러 로깅 라이브러리들을 하나의 통일된 방식으로 사용하도록 방법을 제공하기 위한 것이다.
즉, 로깅 추상 레이어를 제공하는 인터페이스이다.
이 slf4j 덕분에 애플리케이션은 어떤 로깅 라이브러리를 사용하던 같은 방법으로 로그를 남길 수 있는 것.
그래서 로그 라이브러리를 교체하는 일이 발생하더라도 애플리케이션의 코드가 변경될 필요는 없다.
이제 logback을 설정하는 방법에 대해 알아보자.
Appender는 어디에 어떤 포멧으로 로그를 남길지를 설정하는 부분이다.
위 3가지 Appender의 Pattern 요소에는 출력하고 싶은 포멧을 적는데, 보통 날짜와 시간, 로그의 레벨을 기록하는 편이다.
RollingFileAppender의 rollingPolicy 옵션에는 파일이 언제 백업될지 설정할 수 있다.
하루단위 로그 파일이 생성되며, maxHistory 옵션 개수만큼 생성되고 해당 개수를 초과하면 이전 로그 파일은 삭제된다.
로그 레벨은
가 있는데, 위로 갈 수록 레벨이 높은 로그이다.
logger는 실제 로그 기능을 수행하는 객체로 각 Logger마다 "name"을 통해 구분한다. 최상위 로거인 Root Logger를 설정하면 이를 계층적으로 어떤 패키지 이하의 클래스에서는 어떤 레벨 이상의 로그만 출력할지 설정 할 수 있다.
class 에서 로그를 출력하는데 사용된 logger가 존재하지 않는다면, 부모 로거를 찾는다.
여기서 debug 이하 레벨은 주로 개발 과정에서만 쓰이게 되고, error 레벨은 애플리케이션이 멈출 수 있는 치명적인 에러인 경우가 많으므로 잘 쓰이지 않는다고 한다.
그래서 현재 진행중인 프로젝트에 로그백을 세팅할 때에도 개발 과정에 확인이 필요한 로그는 debug를 사용했고, 그 외에는 주로 info 레벨과 warn 레벨을 사용했다.
logger.info("{} {} 출력", "값1", "값2");
주의할 점은 문자열을 연결하기 위해 '+' 를 사용하면 안된다는 점이다.
'+'를 사용하면 Sysyem.out.print처럼 문자열 연결을 되지만 문자열 연산이 먼저 일어나서 문자열 연산만큼의 성능 악화가 발생할 수 있다.
그래서 문자열을 하나로 길게 적되 {}를 사용해서 변수가 들어갈 곳을 지정해주고 ,(콤마) 뒤에 순서대로 변수를 넣어주면 된다.
위에서 말한 Appender, logger등을 사용해서 필요한 설정을 해주어야 하는데, 검색을 해보아도 아마 대부분 xml 파일을 이용해서 설정하는 예제일 것이다. 하지만 이 설정 파일은 당연히 자바 코드로도 설정이 가능하다.
나는 현재 프로젝트에 자바 코드로 설정을 했는데, xml 방식은 다른 블로그에 정리된 예제가 많이 있기 때문에 자바 코드로 설정하는 방법을 소개하려 한다.
우선 xml 방식을 살펴보자.
logback.xml을 사용할 수도, logback-spring.xml을 사용할 수도 있는데 Spring boot 에서는 logback.xml로 설정하면 스프링 부트에대한 설정 전에 로그백 설정이 되므로 제어 할 수가 없다고 한다.
따라서 xml방식을 사용할 땐 logback-spring.xml을 이용하도록 하자.
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<springProperty name="SLACK_WEBHOOK_URI" source="logging.slack.webhook-uri"/>
<appender name="SLACK" class="com.github.maricn.logback.SlackAppender">
<webhookUri>${SLACK_WEBHOOK_URI}</webhookUri>
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %msg %n</pattern>
</layout>
<username>Sslc-Server-log</username>
<iconEmoji>:stuck_out_tongue_winking_eye:</iconEmoji>
<colorCoding>true</colorCoding>
</appender>
<!-- Console appender 설정 -->
<appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<Pattern>%d %-5level %logger{35} - %msg%n</Pattern>
</encoder>
</appender>
<appender name="ASYNC_SLACK" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="SLACK"/>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
</appender>
<root level="INFO">
<appender-ref ref="Console"/>
<appender-ref ref="ASYNC_SLACK"/>
</root>
</configuration>
위 xml 파일은 콘솔 로그를 패턴을 설정하는 부분과 slack-appender 라이브러리를 사용해 슬랙으로 에러 로그만 필터링해서 보내기 위한 설정 부분이다.
개인적으로 xml 파일 형식을 별로 좋아하지 않아 가능한 최소한으로 사용하자는 생각을 갖고 있기 때문에 위 코드는 정말 맘에 들지 않는다. 굳이 xml로 써야할 이유도 없고...
그렇다면 자바 코드로 설정을 하려면 어떻게 사용해야 할까?
이번에는 자바 코드 방식으로 콘솔 로그 패턴을 지정하는 것과 로그 필터링을 해서 롤링 후 로그파일로 저장하는 것을 알아보겠다.
우선 콘솔 로그 패턴을 지정하는 방식을 보자.
@Configuration
public class LogBackConfig {
// 공통 필드, 어펜더 별 설정을 달리 할 경우 지역변수로 변경 하면 됨
private final LoggerContext logCtx = (LoggerContext) LoggerFactory.getILoggerFactory();
private final String pattern = "%d{yyyy-MM-dd HH:mm:ss.SSS} %magenta([%thread]) %highlight([%-3level]) %logger{5} - %msg %n";
// 어펜더 목록, 다른 어펜더가 필요할 경우 추가하면 됨
private ConsoleAppender<ILoggingEvent> consoleAppender;
@Bean
public void logConfig() {
consoleAppender = getLogConsoleAppender();
createLoggers();
}
private void createLogger(String loggerName, Level logLevel, Boolean additive) {
Logger logger = logCtx.getLogger(loggerName);
logger.setAdditive(additive);
logger.setLevel(logLevel);
logger.addAppender(consoleAppender);
}
private void createLoggers() {
// 로거 이름, 로깅 레벨, 상위 로깅 설정 상속 여부 설정
createLogger("root", INFO, true);
createLogger("jdbc", OFF, false);
createLogger("jdbc.sqlonly", DEBUG, false);
createLogger("jdbc.sqltiming", DEBUG, false);
createLogger("{패키지 경로}", INFO, false);
createLogger("{패키지 경로}.*.controller", DEBUG, false);
createLogger("{패키지 경로}.*.service", WARN, false);
createLogger("{패키지 경로}.*.repository", INFO, false);
createLogger("{패키지 경로}.*.security", DEBUG, false);
}
/**
* 콘솔 로그 어펜더 생성
* @return 콘솔 로그 어펜더
*/
private ConsoleAppender<ILoggingEvent> getLogConsoleAppender() {
final String appenderName = "STDOUT";
PatternLayoutEncoder consoleLogEncoder = createLogEncoder(pattern);
return createLogConsoleAppender(appenderName, consoleLogEncoder);
}
private PatternLayoutEncoder createLogEncoder(String pattern) {
PatternLayoutEncoder encoder = new PatternLayoutEncoder();
encoder.setContext(logCtx);
encoder.setPattern(pattern);
encoder.start();
return encoder;
}
private ConsoleAppender<ILoggingEvent> createLogConsoleAppender(String appenderName, PatternLayoutEncoder consoleLogEncoder) {
ConsoleAppender<ILoggingEvent> logConsoleAppender = new ConsoleAppender<>();
logConsoleAppender.setName(appenderName);
logConsoleAppender.setContext(logCtx);
logConsoleAppender.setEncoder(consoleLogEncoder);
logConsoleAppender.start();
return logConsoleAppender;
}
}
설정 클래스이므로 @Configuration을 붙여서 생성해주고
logger에 로깅 레벨을 지정. 인코더에 로깅 패턴을 지정해 준 뒤, 빈으로 등록해준다.
위 설정으로 실제 애플리케이션을 작동시켜서 로그를 찍고 확인을 해보면 내가 설정한 패턴대로 로그가 잘 나오는 것을 확인해볼 수 있다.(로그에 색깔 지정도 가능. 패턴에서 쓰레드와 로그 레벨에 색깔을 지정해주었다.)
위 로그는 인터셉터를 통해 요청이 들어올 때와 어느 곳에서 어떤 메서드가 처리되는지 확인하기 위해 로그를 찍어 둔 것이다.
다음은 이 로그들을 RollingAppender를 통해 일 단위로 로그 파일로 저장하는 것을 해보자.
[계속 작성중...]
감사합니다. 로그백이 뭔지 궁금했는데 배워가요 ㅎㅎ