System.out.println() 과 비교했을 때, 로깅의 장점은 다음과 같다.
출력 형식을 지정 가능
로그 레벨에 따라, 남기고 싶은 로그를 별도로 지정 가능
콘솔, 파일이나 네트워크 등... 로그를 별도의 위치에 남길 수 있다
성능이 System.out 보다 좋다.
프론트와 백엔드 협업 시, 백엔드에서 발생한 에러가 아니라는 것을 증명할 수 있다.
서버 구동 전
→ 디버깅!
예외 사항을 가장 잘 파악할 수 있다.
변수의 값, 메모리 주소 등... 을 breakPoint가 걸린 시점에서 확인할 수 있기 때문
서버 구동 중
→ 로깅!
그러나, 실제 서버 구동 중에 디버깅을 하기엔 무리가 있다.
실제 디버깅을 하기 어려운 환경에서는 로깅이 최선의 선택이다.
Error를 기준으로
예시
다양한 로깅 프레임워크에 대한 추상화(인터페이스) 역할
추상 로깅 프레임워크이기 때문에, 단독으로 사용 불가능
최종 사용자가 배포시 원하는 구현체를 선택해서 사용
Bridge 모듈
SLF4J 외의 다른 로깅 API로의 Logger 호출을 SLF4J 인터페이스로 연결하여, SJF4J API 가 대신 처리할 수 있도록 하는 일종의 어댑터 역할의 라이브러리
이전의 레거시 로깅 프레임워크를 위한 라이브러리
(레거시 : 현대에서도 사용되고 있는 낡은 기술이나 방법론, 컴퓨터 시스템, 소프트웨어 등)
Binding모듈에서 사용될 프레임워크와 달라야 한다
SLF4J API 모듈
로깅에 대한 인터페이스를 제공
결론적으로, 로깅 동작에 대한 역할을 수행할 추상메서드를 제공
하나의 API 모듈에 하나의 Binding 모듈이 필요
Binding 모듈
SLF4J API 를 로깅 구현체와 연결하는 어댑터 역할을 하는 모듈
(어댑터 :컴퓨터가 네트워크를 통해 다른 컴퓨터 장치와 통신할 수 있도록 하는 하드웨어 구성요소)
SLF4J를 구현한 클래스에서 Binding으로 연결된 Logger의 API를 호출
Logback
로깅 구현체의 종류 中 하나
SpringBoot 가 디폴트로 사용하는 라이브러리
Logback은 3가지 모듈로 나뉜다.
logback-core
다른 두 모듈을 위한 기반 역할을 하는 모듈
Appender 와 Layout 인터페이스가 여기에 속함
logback-classic
logback-core 에서 확장된 모듈
logback-core 를 가지고 SLF4J API 를 구현
Logger 클래스가 여기에 속함
logback-access
Servlet 컨테이너와 통합되어, HTTP 액세스에 대한 로깅 기능을 제공
logback-core
웹 애플리케이션 레벨이 아닌 컨테이너 레벨에서 설치되어야 한다.
Logger
실제 로깅을 수행하는 구성요소
출력 레벨 : TRACE > DEBUG > INFO > WARN > ERROR
Appender
로그 메시지가 출력될 대상을 결정하는 요소
consoleAppender, FileAppender, RollingFileAppender 등...이 있다
Layout(Encoder)
로그 이벤트를 바이트 배열로 변환하고, 해당 바이트 배열을 OutputStream 에 쓰는 작업을 담당
Appender 에 포함되어, 사용자가 지정한 형식으로 표현될 로그 메시지를 변환하는 역할을 담당하는 요소
FileAppender와 하위 클래스는 encoder 를 필요로 한다
(현재 layout은 사용하지 않기 때문에, Encoder 을 사용)
dependencies {
// logback
implementation 'ch.qos.logback:logback-classic:1.4.7'
}
main/resource
에 생성해주면 된다.
로그 설정에 사용되는 패턴이다.
('5. 분리하여 설정하는 방법' 에서 property 태그를 보면 LOG_PATTERN 과 해당 패턴을 설정하는 value 를 확인 할 수 있다.)
%logger
: 패키지 포함 클래스 정보
%logger{0}
: 패키지를 제외한 클래스 이름만 출력
%logger{length}
: Logger name을 축약할 수 있음. {length}는 최대 자리 수, ex)logger{35}
%-5level
: 로그 레벨, -5는 출력의 고정폭 값(5글자), 로깅레벨이 Info일 경우 빈칸 하나 추가
${PID:-}
: 프로세스 아이디
%d
: 로그 기록시간 출력
%p
: 로깅 레벨 출력
%F
: 로깅이 발생한 프로그램 파일명 출력
%M
: 로깅일 발생한 메소드의 명 출력
%line
: 로깅이 발생한 호출지의 라인
%L
: 로깅이 발생한 호출지의 라인
%thread
: 현재 Thread 명
%t
: 로깅이 발생한 Thread 명
%c
: 로깅이 발생한 카테고리
%C
: 로깅이 발생한 클래스 명 (%C{2}는 somePackage.SomeClass 가 출력됨)
%m
: 로그 메시지
%msg
: - 로그 메시지 (=%message)
%n
: 줄바꿈(new line)
%%
: %를 출력
%r
: 애플리케이션 시작 이후부터 로깅이 발생한 시점까지의 시간(ms)
%d{yyyy-MM-dd-HH:mm:ss:sss}
: %d는 date를 의미하며 중괄호에 들어간 문자열은 dateformat을 의미. 따라서 [2021-07-12 12:42:78]과 같은 날짜가 로그에 출력됨.
%-4relative
: %relative는 초 아래 단위 시간(밀리초)을 나타냄. -4를하면 4칸의 출력폼을 고정으로 가지고 출력. 따라서 숫자에 따라 [2021-07-12 12:42:78:232] 혹은 [2021-07-12 12:42:78:2332]와 같이 표현됨
가독성이 떨어진다는 단점이 있다.
appender 태그를 기준으로 분리하면 가독성 있는 logback 설정을 해 줄 수 있다.
메인 파일이며, 분리된 appender.xml 파일들을 inclue 한다
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- key : 변수이름, datePattern : 년-월-일 을 나타낸다 -->
<timestamp key="BY_DATE" datePattern="yyyy-MM-dd"/>
<!-- property : 변수를 저장해주는 곳 -->
<property name="LOG_PATTERN"
value="[%d{yyyy-MM-dd HH:mm:ss}:%-4relative] %green([%thread]) %highlight(%-5level) %boldWhite([%C.%M:%yellow(%L)]) - %msg%n"/>
<!-- springProfile -->
<!-- logback 에서는 여러 개의 프로파일 설정이 가능 -->
<!-- 단, 이경우는 prod 환경이 아닐 때 사용하는 설정 -->
<springProfile name="!prod">
<include resource="console-appender.xml"/>
<!-- root -->
<!-- 전체 프로젝트의 로그 레벨을 설정 -->
<!-- 등록되어 있는 로그들의 최상위 클래스로 레벨을 정하는 곳 -->
<!-- 여기서는 INFO 로 설정했기 때문에, 전체 로그가 이제 error, warn, info 만 찍히게 된다 -->
<!-- appender-ref 를 통해 위에서 작성한 appender 을 넣어준다 -->
<root level="INFO">
<!-- appender-ref -->
<!-- 사용하고자 하는 appender 을 등록 -->
<!-- 위에서 작성한 appender 의 name 을 적어주면 된다 -->
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
<!-- springProfile -->
<!-- logback 에서는 여러 개의 프로파일 설정이 가능 -->
<!-- 단, 이경우는 prod 환경이 아닐 때 사용하는 설정 -->
<springProfile name="prod">
<include resource="file-info-appender.xml"/>
<include resource="file-warn-appender.xml"/>
<include resource="file-error-appender.xml"/>
<!-- root -->
<!-- 전체 프로젝트의 로그 레벨을 설정 -->
<!-- 등록되어 있는 로그들의 최상위 클래스로 레벨을 정하는 곳 -->
<!-- 여기서는 INFO 로 설정했기 때문에, 전체 로그가 이제 error, warn, info 만 찍히게 된다 -->
<!-- appender-ref 를 통해 위에서 작성한 appender 을 넣어준다 -->
<root level="INFO">
<!-- appender-ref -->
<!-- 사용하고자 하는 appender 을 등록 -->
<!-- 위에서 작성한 appender 의 name 을 적어주면 된다 -->
<appender-ref ref="FILE-INFO"/>
<appender-ref ref="FILE-WARN"/>
<appender-ref ref="FILE-ERROR"/>
</root>
</springProfile>
</configuration>
<included>
<!-- appender -->
<!-- 어디다가 쓸지 정하는 부분 -->
<!-- 여기서는 consoleAppender 을 사용함 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!-- Encoder -->
<!-- 로그 이벤트를 바이트 배열로 변환하고, 해당 바이트 배열을 OutputStream 에 쓰는 작업을 담당 -->
<!-- Appender 에 포함되어 사용자가 지정한 형식으로 표현 될 로그메시지를 변환하는 역할을 담당-->
<!-- 여기서는 pattern 을 지정 형식으로 주었음 -->
<!-- Log Pattern 을 사용하는 곳 -->
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
</included>
info, warn, erorr 로 구분되었다
기존 logback-spring.xml 파일에서 appender 부분만 드러내서 파일을 따로 만들고 included 태그로 감싸주면 된다.
appender (여기서는 RollingFileAppender 를 등록했다)
<included>
<appender name="FILE-INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- file : 파일의 저장 위치-->
<!-- logback-spring.xml 파일에서 BY_DATE 키 값으로 저장된 datePattern 에 따라 파일명이 날짜별로 바뀌어서 저장됨 -->
<file>./log/info/info-${BY_DATE}.log</file>
<!-- filter -->
<!-- if문 으로 간주하면 된다 -->
<!-- 여기서는 로그 레벨에 따른 필터 LevelFilter를 사용 -->
<filter class = "ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level> <!-- level : 필터의 조건이 되는 로그 레벨을 입력 -->
<onMatch>ACCEPT</onMatch> <!-- onMatch : ACCEPT 는 위에 조건이 되는 레벨이 되면 실행 -->
<onMismatch>DENY</onMismatch> <!-- onMismatch : DENY 는 위 조건이 아닐 경우 실행되지 않게 하는 것 -->
</filter>
<!-- Encoder -->
<!-- 로그 이벤트를 바이트 배열로 변환하고, 해당 바이트 배열을 OutputStream 에 쓰는 작업을 담당 -->
<!-- Appender 에 포함되어 사용자가 지정한 형식으로 표현 될 로그메시지를 변환하는 역할을 담당-->
<!-- 여기서는 pattern 을 지정 형식으로 주었음 -->
<!-- Log Pattern 을 사용하는 곳 -->
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
<!-- rollingPolicy -->
<!-- RollingFileAppender 를 어떻게 사용할지에 대한 정책을 정하는 곳 -->
<!-- 보통 TimeBasedRollingPolicy 와 SizeAndTimeBasedRollingPolicy 를 사용 -->
<!-- SizeAndTimeBasedRollingPolicy : 시간과 사이즈를 기준하는 정책 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--fileNamePattern -->
<!-- 처음에 로그들은 file(파일의 저장 위치) 에 저장되다가 maxFileSize, maxHistory, totalSizeCap 옵션 중 어느 하나에 일치하게 되면 fileNamePattern대로 파일이 옮겨지게 되고, 다시 생성되는 로그는 file로 설정한 위치에 저장된다 -->
<fileNamePattern> ./backup/info/info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>100MB</maxFileSize> <!-- maxFileSize : 파일 분할 용량으로 KB, MB, GB가 있다 -->
<maxHistory>30</maxHistory> <!-- maxHistory : 파일이 저장될 수 있는 기간을 설정 -->
<totalSizeCap>3GB</totalSizeCap> <!-- totalSizeCap : 전체 파일 크기를 제어해서, 전체 크기 제한을 초과하면 가장 오래된 파일을 삭제 -->
</rollingPolicy>
</appender>
</included>
<included>
<appender name="FILE-WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- file : 파일의 저장 위치-->
<!-- logback-spring.xml 파일에서 BY_DATE 키 값으로 저장된 datePattern 에 따라 파일명이 날짜별로 바뀌어서 저장됨 -->
<file>./log/warn/warn-${BY_DATE}.log</file>
<!-- filter -->
<!-- if문 으로 간주하면 된다 -->
<!-- 여기서는 로그 레벨에 따른 필터 LevelFilter를 사용 -->
<filter class = "ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level> <!-- level : 필터의 조건이 되는 로그 레벨을 입력 -->
<onMatch>ACCEPT</onMatch> <!-- onMatch : ACCEPT 는 위에 조건이 되는 레벨이 되면 실행 -->
<onMismatch>DENY</onMismatch> <!-- onMismatch : DENY 는 위 조건이 아닐 경우 실행되지 않게 하는 것 -->
</filter>
<!-- Encoder -->
<!-- 로그 이벤트를 바이트 배열로 변환하고, 해당 바이트 배열을 OutputStream 에 쓰는 작업을 담당 -->
<!-- Appender 에 포함되어 사용자가 지정한 형식으로 표현 될 로그메시지를 변환하는 역할을 담당-->
<!-- 여기서는 pattern 을 지정 형식으로 주었음 -->
<!-- Log Pattern 을 사용하는 곳 -->
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
<!-- rollingPolicy -->
<!-- RollingFileAppender 를 어떻게 사용할지에 대한 정책을 정하는 곳 -->
<!-- 보통 TimeBasedRollingPolicy 와 SizeAndTimeBasedRollingPolicy 를 사용 -->
<!-- SizeAndTimeBasedRollingPolicy : 시간과 사이즈를 기준하는 정책 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--fileNamePattern -->
<!-- 처음에 로그들은 file(파일의 저장 위치) 에 저장되다가 maxFileSize, maxHistory, totalSizeCap 옵션 중 어느 하나에 일치하게 되면 fileNamePattern대로 파일이 옮겨지게 되고, 다시 생성되는 로그는 file로 설정한 위치에 저장된다 -->
<fileNamePattern> ./backup/warn/warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>100MB</maxFileSize> <!-- maxFileSize : 파일 분할 용량으로 KB, MB, GB가 있다 -->
<maxHistory>30</maxHistory> <!-- maxHistory : 파일이 저장될 수 있는 기간을 설정 -->
<totalSizeCap>3GB</totalSizeCap> <!-- totalSizeCap : 전체 파일 크기를 제어해서, 전체 크기 제한을 초과하면 가장 오래된 파일을 삭제 -->
</rollingPolicy>
</appender>
</included>
<included>
<appender name="FILE-ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- file : 파일의 저장 위치-->
<!-- logback-spring.xml 파일에서 BY_DATE 키 값으로 저장된 datePattern 에 따라 파일명이 날짜별로 바뀌어서 저장됨 -->
<file>./log/error/error-${BY_DATE}.log</file>
<!-- filter -->
<!-- if문 으로 간주하면 된다 -->
<!-- 여기서는 로그 레벨에 따른 필터 LevelFilter를 사용 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level> <!-- level : 필터의 조건이 되는 로그 레벨을 입력 -->
<onMatch>ACCEPT</onMatch> <!-- onMatch : ACCEPT 는 위에 조건이 되는 레벨이 되면 실행 -->
<onMismatch>DENY</onMismatch> <!-- onMismatch : DENY 는 위 조건이 아닐 경우 실행되지 않게 하는 것 -->
</filter>
<!-- Encoder -->
<!-- 로그 이벤트를 바이트 배열로 변환하고, 해당 바이트 배열을 OutputStream 에 쓰는 작업을 담당 -->
<!-- Appender 에 포함되어 사용자가 지정한 형식으로 표현 될 로그메시지를 변환하는 역할을 담당-->
<!-- 여기서는 pattern 을 지정 형식으로 주었음 -->
<!-- Log Pattern 을 사용하는 곳 -->
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
<!-- rollingPolicy -->
<!-- RollingFileAppender 를 어떻게 사용할지에 대한 정책을 정하는 곳 -->
<!-- 보통 TimeBasedRollingPolicy 와 SizeAndTimeBasedRollingPolicy 를 사용 -->
<!-- SizeAndTimeBasedRollingPolicy : 시간과 사이즈를 기준하는 정책 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--fileNamePattern -->
<!-- 처음에 로그들은 file(파일의 저장 위치) 에 저장되다가 maxFileSize, maxHistory, totalSizeCap 옵션 중 어느 하나에 일치하게 되면 fileNamePattern대로 파일이 옮겨지게 되고, 다시 생성되는 로그는 file로 설정한 위치에 저장된다 -->
<fileNamePattern>./backup/error/error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>100MB</maxFileSize> <!-- maxFileSize : 파일 분할 용량으로 KB, MB, GB가 있다 -->
<maxHistory>30</maxHistory> <!-- maxHistory : 파일이 저장될 수 있는 기간을 설정 -->
<totalSizeCap>3GB</totalSizeCap> <!-- totalSizeCap : 전체 파일 크기를 제어해서, 전체 크기 제한을 초과하면 가장 오래된 파일을 삭제 -->
</rollingPolicy>
</appender>
</included>
4,5 번 中 어느 방법으로든 로그백 설정을 마치면, 아래 예시와 같은 방법으로 코드 중간 중간에 로그를 설정해주면 된다.
@Slf4j
@RestController
public class LogController {
@GetMapping("/log")
public String logTest(){
String name = "spring";
// {}는 쉼표 뒤에 파라미터가 치환되는 것
log.error("error log={}",name);
log.warn("warn log={}",name);
log.info("info log={}",name);
log.debug("debug log={}",name);
log.trace("trace log={}",name);
return "ok";
}
}
@Service
@Slf4j
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
@Autowired
UserRepository userRepository;
@Override
public UserEntity searchUser() {
return userMapper.selectUser();
}
@Override
public void deleteUser(Map<String, Object> param) throws Exception {
log.debug("deleteUser");
UserEntity entity = new UserEntity();
BeanUtils.populate(entity,param);
userRepository.delete( entity);
}
}
참고: Spring 로그 설정하기 - Logback
참고: [SPRING/환경구축] 스프링에서 LOGBACK을 설정하는 방법을 알아보자!