로깅(Logging)

YongJin·2024년 5월 26일


n:n 채팅방을 만들면서 나의 잘못된 습관들을 알 수 있었다

  • 테스트 코드 작성 안 함
  • 로깅에 대해 무지하고 뭔가 남기려고 하면 System.out.println()으로 해결하려 함, 디버깅 사용은 하는데 정확히 모름

이렇게 되니 뭔가 문제가 터지면 문제 상횡을 반복하고 내가 짠 코드를 계속 들여다보면서 시간을 보내고만 있는다
이렇게 되니 해결도 잘하지 못하고 설령 해결했다 하더라고 시간이 너무 오래 걸린다

하여 이번 글은 일단 로깅에 대해 설명하려고 한다

로그? 로깅?

  • 로그 : 프로그램의 동작 및 상태를 관찰할 수 있도록 제공하는 정보
  • 로깅 : 로그를 시간의 경과에 따라 기록하는 것, 프로그램 동작 시 발생하는 일을 기록하는 것

로깅을 해야하는 이유

디버깅을 제대로 사용하지는 못하는 수준이지만(범위 지정에 대해서)
(개인적안 생각) 프로그램이 실행 중인 서버에 문제가 생겼으면 서버를 멈추고 프로그램을 디버깅 할 수는 없을 것이다.
로깅을 남겨서 배포된 프로그램의 로그를 보고 문제를 해결하여 다시 배포해야 한다.

운영 상 어느 기능이 많이 쓰이는지 고객 문의에 대한 대응이 필요한 경우가 있을 것이다 (과장하면, 로그가 없으면 고객의 컴플레인에 무조건 사과 후 보상을 해야 하는 경우가 있을 수 있다) 이를 위해서도 로깅을 해야 한다

정리하자면,
기능 개발 중 오류 원인 파악 / 비정상적 동작 감지(외부의 공격 등) / 사용자의 활동 기록(운영 정보 수집 > 고객 문의에 대한 대응, 개인에게 물품 추천을 위한 정보 수집)를 위해 로그를 남겨야 한다

System.out.println()은 왜 더이상 사용하면 안 될까? > 로그의 장점

  • 로깅은 출력 형식을 지정할 수 있다.

  • 로그 레벨에 따라 남기고 싶은 로그를 별도로 지정이 가능하다.
    (밑의 필요한 로그만 볼 수 있도록 해보는 xml파일 설정 참고)

  • 콘솔 뿐만 아니라 로그를 파일이나 DB등 별도에 위치에 남길 수 있다.

  • 로그 설정을 변경하려면 코드를 수정하지 않고 설정 파일만 수정하면 된다.
    (System.out.println() 은 로그 레벨을 지원하지 않아 유지보수가 어렵다)

그리고 System.out.println()은 내부적으로 Synchronized가 걸려있다.
이것이 성능 문제와 직결된다

LogLevel (SLf4j기준)

Logback 은 5단계의 로그 레벨을 가진다.
심각도 수준은 Error > Warn > Info > Debug > Trace 이다.

⛔️ Error : 예상하지 못한 심각한 문제가 발생하는 경우, 즉시 조취를 취해야 할 수준의 레벨

⚠ ️Warn : 로직 상 유효성 확인, 예상 가능한 문제로 인한 예외 처리, 당장 서비스 운영에는 영향이 없지만 주의해야 할 부분

✅ Info : 운영에 참고할만한 사항, 중요한 비즈니스 프로세스가 완료됨

⚙️ Debug : 개발 단계에서 사용하며, SQL 로깅을 할 수 있음

📝 Trace : 모든 레벨에 대한 로깅이 추적되므로 개발 단계에서 사용함

Debug 와 Trace 레벨은 많은 양의 로그가 쌓이므로 자칫 운영 단계에서 해당 레벨의 로깅을 할 경우 용량 감당이 안 될 수 있다. 그렇기 때문에 중요하지 않은 정보는 Debug 이하로 설정하고 로깅을 하지 않는 편이 좋다.

(Error와 Warn레벨의 경우 실 사용 경우가 떠오르지 않아 gpt4에 검색을 해 보았다)

ERROR : ERROR 레벨은 시스템의 정상적인 동작에 심각한 문제가 발생했을 때 사용됩니다. 이는 예상치 못한 예외, 데이터 손실, 또는 시스템이 요청을 처리할 수 없을 때 기록됩니다. 예를 들어, 데이터베이스 연결 실패, 필수 서비스의 중단, 또는 예상치 못한 예외가 발생한 경우에 ERROR 로그를 남깁니다.

try {
    // ... 코드 로직 ...
} catch (DatabaseConnectionException e) {
    log.error("데이터베이스 연결 실패: {}", e.getMessage());
}

WARN: WARN 레벨은 잠재적인 문제를 알리기 위해 사용됩니다. 시스템의 동작에는 즉각적인 영향을 주지 않지만, 주의가 필요한 상황에서 사용됩니다. 예를 들어, 구식 API의 사용, 권장되지 않는 메소드의 사용, 또는 예상 가능한 문제의 가능성이 있을 때 WARN 로그를 남깁니다.

if (diskSpaceLow()) {
    log.warn("디스크 공간이 부족합니다. 현재 사용 가능한 공간: {}GB", availableDiskSpaceInGB);
}

if (apiRequestLimitApproaching()) {
    log.warn("API 요청 한도에 근접했습니다. 현재 사용량: {}/{}", currentUsage, usageLimit);
}

로그를 그럼 어떻게 남기고 어느 곳에다 남겨야 할까?

  • 로그를 어떤 기준으로 어떻게?
    기준은 위 로그 레벨에 맞춰서 작성한다

    의도하지 않은 예외라는 건 도대체 무엇일까?
    이 기준을 아직 나는 외부 api요청을 사용해 본 적이 없어 처음에는 이해하지 못했는데 api요청 장애가 있는 경우 의도하지 않은 에러가 발생할 수 있다

  • 로그를 어디다?

    운영 목적을 위한 로그를 정확히 어디다 남겨야 하는지는 모르겠다.
    지금의 나는 디버깅 목적을 위해 로그를 사용하는 입장인데, 이 경우에는 멘토께서 보고싶은 혹은 확인해야 하는 데이터 모든 곳에 로그를 남

SLF4j, Logback

Slf4j와 Logback은 로깅을 도와주는 로깅 프레임워크이다. Slf4j는 다양한 로깅 프레임워크에 대한 추상화역할(인터페이스) Logback은 실 구현체이다. 따라서 Slf4j는 단독으로 사용불가능하다.

왜 같이 사용하는지

SLF4J없이 Logback만 쓰다가 로깅 프레임워크를 변경해야 하는 상황이 온다면 일일이 코드를 한 줄씩 변경해야 할 수도 있다.

SLF4j 동작과정


SLF4J Binding은 SLF4J 인터페이스( = SLF4J API )를 로깅 구현체와 연결하는 어댑터 역할의 라이브러리다.
Slf4j Binding과 그 구현체는 반드시 classpath에 동시에 있어야 하며,
SLF4J Binding 라이브러리는 반드시 하나만 classpath에 존재해야 한다.

로깅을 하는 구현체를 하나로 통일하려고 쓰는 Slf4j인데, 구현체를 연결해주는 Binding을 여러개 둔다는 건 말이 안된다.

SLF4j, Logback 사용

SLF4J는 클래스 별로 Logger를 사용한다.
클래스 이름을 통해 어디에서 오류가 났는지 추적이 가능해진다.
(예시)

package org.example;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger("org.example.Main");
        logger.debug("Hello world.");
    }

(추가)
예시는 이렇게 썼었는데, Lombok을 이미 쓰고 있었다면
@Slf4j 어노테이션을 추가해서 간단하게 사용할 수 있다

에서

Logback 설정으로, 필요한 로그만 볼 수 있도록 해보자

(+ Log 를 알기 쉽게 출력 하도록 설정고치기)

resourec 패키지 안에 xml 파일을 만들자 (logback.xml) >> 의존성 추가해도 기본적으로 생기지 않았다 직접 만들어야 함.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %5level %logger - %msg%n</pattern>
        </encoder>
    </appender>
    <root level="INFO">
        <appender-ref ref="CONSOLE" />
    </root>
</configuration>

일단 제일 기본적인 콘솔에 찍히는 로그를 기록하고 "INFO" 레벨 이상의 로그들만 보이게 한 예제이다
설정들을 건들여 단순 파일저장, 파일저장 및 관리, 로그레벨 설정 등등을 컨트롤 할 수 있다

(추가)
Log level에 따라 색깔도 지정할 수 있고 설정을 세팅하면 어느 부분은 안 보이게도 할 수 있었다

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %5level %logger - %msg%n</pattern>
            <pattern>%highlight(%-5level) - %msg%n</pattern>
        </encoder>
    </appender>
    <root level="INFO">
        <appender-ref ref="CONSOLE" />
    </root>
    <logger name="packetlist.beforeregister.PacketForUserResister" level="OFF"/>
</configuration>

추가한 내용은 이렇다

  <pattern>%highlight(%-5level) - %msg%n</pattern> 으로 로그레벨별 색깔 지정
<logger name="packetlist.beforeregister.PacketForUserResister" level="OFF"/> 이 클래스의 로그 출력 안 되게끔 설정
profile
더 나은 사람이 되고 싶습니다

0개의 댓글