본문 바로가기

Java

Logger 딱 대

+ 피곤해서 막 썼는데 나중에 수정하겠습니다. 그냥 대충 이해정도로만 봐주세요.

 

하하.. System.out.println()만 썼던 사람? 저요...

 

요점? 개발자들은 Logger 쓴단다...

 

Logger? Logging? 문제 발생 시 원인 분석을 위한 정보 확인용으로 많이 쓰죠?

<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-api</artifactId>
	<version>${org.slf4j-version}</version>
</dependency>

<dependency>
	<groupId>ch.qos.logback</groupId>
	<artifactId>logback-classic</artifactId>
	<version>1.2.3</version>
</dependency>

 

저는 일단 의존성이 2개 추가돼있는데 하나하나 알아봅시다. 아지삭들이 뭔지부터 알아야겠어.

 

1. SLF4J

  • Simple Logging Facade for Java 
  • 로깅 프레임워크에 대한 일관된 추상화 계층을 제공하는 라이브러리
  • 런타임 시에 다양한 로깅 프레임워크 선택하여 사용

역시 오늘도 바보는 하나하나 풀어서 이해해야 합니다..

 

F의 약자가 Facade에요. 오.. 디자인 패턴에서 들어봤는데.. 기억 날리가...

 

퍼사드 패턴이란 복잡한 서브시스템이나 클래스들을 단순화하여 사용하기 쉽게 만드는 방법입니다. 이게 무슨 말이냐면 내부 구조가 복잡한데 우리는 알 필요가 없고 단순한 인터페이스를 통해 서브시스템 기능을 사용할 수 있다는 거에요. 

class CPU {
    public void freeze() {
        System.out.println("CPU freezing...");
    }

    public void jump(long position) {
        System.out.println("CPU jumping to position " + position);
    }

    public void execute() {
        System.out.println("CPU executing...");
    }
}

class Memory {
    public void load(long position, byte[] data) {
        System.out.println("Loading data into memory at position " + position);
    }
}

class HardDrive {
    public byte[] read(long lba, int size) {
        System.out.println("Reading data from hard drive at LBA " + lba);
        return new byte[size];
    }
}

 

위와 같은 Cpu, Memory, HardDrive 클래스가 있다고 가정할게요. 

class Computer {
    private CPU cpu;
    private Memory memory;
    private HardDrive hardDrive;

    public Computer() {
        this.cpu = new CPU();
        this.memory = new Memory();
        this.hardDrive = new HardDrive();
    }

    public void start() {
        cpu.freeze();
        memory.load(0x1000, hardDrive.read(0x200, 1024));
        cpu.jump(0x1000);
        cpu.execute();
    }
}

 

컴퓨터 클래스에서 컴퓨터를 키려면 cpu, memory, harddrive클래스의 상호작용들을 통해서 컴퓨터를 시작하는 기능을 제공할 수 있죠. 근데 뭐다? 우리는 키는 과정이 어떻든 몰라도 돼요. 왜냐?

public class Main {
    public static void main(String[] args) {
        Computer computer = new Computer();
        computer.start();
    }
}

 

우리는 그냥 컴퓨터에서 start() 메서드만 호출하면 되니까. start() 메서드에서 어떤 상호작용이 일어나는지는 알 필요 없다는 거지. 이게 퍼사드 패턴입니다. 컴퓨터가 퍼사드 클래스가 되는거에요.

 

잠깐 돌아가서 우리가 로그를 기록할 때 아래와 같이 사용할텐데 Logger 인터페이스를 이용하면 의존성에 추가된 SLF4J의 구현체 중 하나인 logback으로 바인딩 됩니다. 

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

public class SLF4JExample {
    private static final Logger logger = LoggerFactory.getLogger(SLF4JExample.class);

    public static void main(String[] args) {
        logger.info("This is an info message");
        logger.debug("This is a debug message");
        logger.error("This is an error message");
    }
}

 

 

다시 돌아와서 퍼사드 패턴이 왜 나왔냐. SLF4J가 퍼사드 패턴이기 때문입니다.

 

일단 로깅 프레임워크는 하나가 아니에요.

  • logback - 기존 Log4j를 더 발전시킨 것
  • java.util.logging - JDK 1.4부터 포함된 로깅 API
  • log4j2 - 아파치 재단에서 제공하는 로깅 API
  • Apache Commons logging

다양한 프레임워크들이 있는데 우리는 Logger 인터페이스를 사용해서 로그를 기록하죠. 이게 핵심이에요. 다양한 프레임워크들이 있지만 우리는 Logger인터페이스만 사용하면 SLF4J가 다양한 로깅 프레임워크와 통합할 수 있다는 거죠.

 

예를들어 저는 SLF4J를 써서 Logback으로 구현했지만 다른 사람은 Log4j 프레임워크를 사용했다고 칠게요.

import org.apache.log4j.Logger;

public class BOldApp {
    private static final Logger logger = Logger.getLogger(BOldApp.class);

    public static void main(String[] args) {
        logger.info("This is an info message from B");
        logger.debug("This is a debug message from B");
        logger.error("This is an error message from B");
    }
}

 

그럼 이렇게 코드가 짜여있을 거에요. 근데 코드를 통합하려고 보니까 서로 다른 프레임워크를 써서 충돌이 일어나. 그럼 코드의 수정이 필요하단 말이에요. 근데 브릿지 모듈을 사용해서 SLF4J로 라우팅하면?

    <!-- Log4j over SLF4J (Log4j API 호출을 SLF4J로 라우팅) -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>log4j-over-slf4j</artifactId>
        <version>1.7.30</version>
    </dependency>
    
    <!-- Log4j 1.2 SLF4J Binding -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.30</version>
    </dependency>

 

 

이렇게 의존성만 추가하면 코드 수정이 필요없다 이거에요.  SLF4J로 라우팅해서 인터페이스를 사용하고, 다시 Log4J로 바인딩해서 구현한다. 

 

이래서 SLF4J가 퍼사드 패턴이라고 합니다. 

 

ㅋㅋㅋㅋㅋㅋㅋ 그냥 일단 이해만 하려고 너무 대충 적어놨나.. 틀렸을것도 같은데 틀렸으면 말해주세요.. 일단 제가 이해한건 이렇습니다..

 

검색하면 더 자세하게 나와있어요. 사실 바인딩 모듈이니 브릿지 모듈이니 더 깊게 들어가야되는데.. 

 

아 그리고 logback이 구현체라 했잖아요. 뭐 어떻게 출력되고 어떨때만 출력할지 정할 수 있는데 logback.xml을 만들면 됩니다. 

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <!-- 콘솔에 로그를 출력하는 appender 정의 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 루트 로거 설정 -->
    <root level="debug">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>

 

이게 기본이고 특정 패키지에 다른 레벨을 적용할 수도 있습니다.

<!-- 특정 패키지의 로거 설정 -->
<logger name="com.example" level="debug"/>

 

이거 추가하면 돼요.

 

* 참고로 로그 레벨은 TRACE -> DEBUG -> INFO -> WARN -> ERROR 순서로 우선순위를 가집니다.

 

* 2024-06-17 12:34:56 INFO com.example.ExampleClass - This is an info message from B 참고로 이런 식으로 출력됩니다. 위의 패턴대로라면. 

'Java' 카테고리의 다른 글

제네릭 특징  (0) 2024.07.01
(spring) Exception 이녀석........  (0) 2024.06.18
Set / Map / Iterator  (0) 2024.06.14
익명 클래스 / 람다식 / 메소드 참조  (1) 2024.06.08
형 변환 / valueOf() / stream  (0) 2024.06.07