Logging (로깅) & Logback (로그백)

박영준·2023년 8월 29일
0

Spring

목록 보기
57/58

1. 정의

1) 로깅의 필요성

System.out.println() 과 비교했을 때, 로깅의 장점은 다음과 같다.

  • 출력 형식을 지정 가능

  • 로그 레벨에 따라, 남기고 싶은 로그를 별도로 지정 가능

  • 콘솔, 파일이나 네트워크 등... 로그를 별도의 위치에 남길 수 있다

  • 성능이 System.out 보다 좋다.

  • 프론트와 백엔드 협업 시, 백엔드에서 발생한 에러가 아니라는 것을 증명할 수 있다.

2) 로깅 vs 디버깅

서버 구동 전 → 디버깅!
예외 사항을 가장 잘 파악할 수 있다.
변수의 값, 메모리 주소 등... 을 breakPoint가 걸린 시점에서 확인할 수 있기 때문

서버 구동 중 → 로깅!
그러나, 실제 서버 구동 중에 디버깅을 하기엔 무리가 있다.
실제 디버깅을 하기 어려운 환경에서는 로깅이 최선의 선택이다.

3) 로그 레벨

  • Error를 기준으로

    • 위쪽(Fatal) : 시스템 상에서 개발자가 의도하지 않은 예외를 나타낼 때 사용
    • 아래쪽(Warn, Info, Debug, Trace) : 개발자가 의도한 예외를 나타낼 때 사용
  • 예시

    • 회원가입 시 DB에 동일한 email을 가진 회원이 있을 때 예외를 던진다면, 이 이벤트의 로그는 Info
      • 개발자가 의도한 예외이기 때문

4) 로깅 프레임워크 SLF4J

(1) 정의

  • 다양한 로깅 프레임워크에 대한 추상화(인터페이스) 역할

  • 추상 로깅 프레임워크이기 때문에, 단독으로 사용 불가능

  • 최종 사용자가 배포시 원하는 구현체를 선택해서 사용

(2) 동작 과정

Bridge 모듈

  • SLF4J 외의 다른 로깅 API로의 Logger 호출을 SLF4J 인터페이스로 연결하여, SJF4J API 가 대신 처리할 수 있도록 하는 일종의 어댑터 역할의 라이브러리

  • 이전의 레거시 로깅 프레임워크를 위한 라이브러리
    (레거시 : 현대에서도 사용되고 있는 낡은 기술이나 방법론, 컴퓨터 시스템, 소프트웨어 등)

  • Binding모듈에서 사용될 프레임워크와 달라야 한다

SLF4J API 모듈

  • 로깅에 대한 인터페이스를 제공

  • 결론적으로, 로깅 동작에 대한 역할을 수행할 추상메서드를 제공

  • 하나의 API 모듈에 하나의 Binding 모듈이 필요

Binding 모듈

  • SLF4J API 를 로깅 구현체와 연결하는 어댑터 역할을 하는 모듈
    (어댑터 :컴퓨터가 네트워크를 통해 다른 컴퓨터 장치와 통신할 수 있도록 하는 하드웨어 구성요소)

  • SLF4J를 구현한 클래스에서 Binding으로 연결된 Logger의 API를 호출

Logback

  • 로깅 구현체의 종류 中 하나

  • SpringBoot 가 디폴트로 사용하는 라이브러리

5) Logback

(1) 구조

Logback은 3가지 모듈로 나뉜다.

logback-core

  • 다른 두 모듈을 위한 기반 역할을 하는 모듈

  • Appender 와 Layout 인터페이스가 여기에 속함

logback-classic

  • logback-core 에서 확장된 모듈

  • logback-core 를 가지고 SLF4J API 를 구현

  • Logger 클래스가 여기에 속함

logback-access

  • Servlet 컨테이너와 통합되어, HTTP 액세스에 대한 로깅 기능을 제공

  • logback-core

    • logback-access 의 기반 기술이기에 필요하지만,
    • logback-classic 과 SLF4J 와는 무관하다.
  • 웹 애플리케이션 레벨이 아닌 컨테이너 레벨에서 설치되어야 한다.

(2) 설정 요소

Logger

  • 실제 로깅을 수행하는 구성요소

  • 출력 레벨 : TRACE > DEBUG > INFO > WARN > ERROR

Appender

  • 로그 메시지가 출력될 대상을 결정하는 요소

    • Logback 은 로그 이벤트를 쓰는 작업을 Appender 에게 위임
  • consoleAppender, FileAppender, RollingFileAppender 등...이 있다

Layout(Encoder)

  • 로그 이벤트를 바이트 배열로 변환하고, 해당 바이트 배열을 OutputStream 에 쓰는 작업을 담당

  • Appender 에 포함되어, 사용자가 지정한 형식으로 표현될 로그 메시지를 변환하는 역할을 담당하는 요소

  • FileAppender와 하위 클래스는 encoder 를 필요로 한다
    (현재 layout은 사용하지 않기 때문에, Encoder 을 사용)

3. 세팅하기

1) build.gradle

dependencies {
    // logback
    implementation 'ch.qos.logback:logback-classic:1.4.7'
}    

2) 파일 생성 위치

main/resource 에 생성해주면 된다.

3) 로그 패턴

로그 설정에 사용되는 패턴이다.
('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]와 같이 표현됨

4. 한 곳에서 설정하는 방법

가독성이 떨어진다는 단점이 있다.

5. 분리하여 설정하는 방법

appender 태그를 기준으로 분리하면 가독성 있는 logback 설정을 해 줄 수 있다.

1) logback-spring.xml 파일

메인 파일이며, 분리된 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>

2) console-appender.xml 파일

<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>

3) file-xxx-appender.xml 파일들

  • info, warn, erorr 로 구분되었다

  • 기존 logback-spring.xml 파일에서 appender 부분만 드러내서 파일을 따로 만들고 included 태그로 감싸주면 된다.

  • appender (여기서는 RollingFileAppender 를 등록했다)

    • RollingFileAppender
      • FileAppender 를 상속하여 로그 파일을 rollover(타깃 파일을 바꾸는 것)한다.
        • 예시 : 타깃 파일로 log.txt 에 로그 메시지를 append 하다가, 어느 지정한 조건에 다다르면 타킷 파일을 다른 파일로 바꿀 수 있다.

(1) file-info-appender.xml 파일

<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>

(2) file-warn-appender.xml 파일

<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>

(3) file-error-appender.xml 파일

<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>

6. 코드에 적용하기

4,5 번 中 어느 방법으로든 로그백 설정을 마치면, 아래 예시와 같은 방법으로 코드 중간 중간에 로그를 설정해주면 된다.

예시 1

@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";
    }
}
  • 스프링부트의 기본적인 세팅은 info level로 되어있다.
    • 따라서, 실행시켜보면 debug 와 trace는 찍히지 않는다.
      ('3) 로그 레벨'을 보고 확인 할 수 있다.)

예시 2

@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을 설정하는 방법을 알아보자!

profile
개발자로 거듭나기!

0개의 댓글