본문으로 바로가기
반응형

swing 을 이용하여 윈도우 gui 프로그래밍은 만들었습니다.

하지만 이번에 무언가 기능을 추가하고 로깅을 원했는데, 버튼을 눌러 실행하면 메서드가 실행되고 완료될때까지 얼어있다가 마지막에 한번에 로깅을 하니 답답하기 그지없습니다.

그래서 수정된 기능. 실시간 로깅하기.

먼저 logback을 이용.

log4j도 이용할 수 있습니다.

pom.xml에 dependency 설정.

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

프로젝트 구조는 알아서 하면 되지만 참고용으로 아래와 같이 하면 됩니다.

더보기

MyLoggerExample

└───src
    │
    └───main
        │
        └───java
            │
            └───com
                │
                └───example
                    │
                    ├───A.java
                    ├───B.java
                    ├───GUI_Main.java
                    └───LoggerUtil.java

우선 LoggerUtil.java 를 생성해서, setupLoggerWithAppender 를 생성합니다.

import ch.qos.logback.classic.AsyncAppender;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.core.OutputStreamAppender;
import org.slf4j.LoggerFactory;

import javax.swing.*;
import java.io.OutputStream;
import java.io.PrintStream;

public class LoggerUtil {
    public static Logger setupLoggerWithAppender(Class<?> clazz, JTextArea textArea) {
        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
        PatternLayoutEncoder encoder = new PatternLayoutEncoder();
        encoder.setContext(loggerContext);
        encoder.setPattern("%-4relative [%thread] %-5level %logger{35} - %msg%n");
        encoder.start();

        OutputStreamAppender<ch.qos.logback.classic.spi.ILoggingEvent> appender = new OutputStreamAppender<>();
        appender.setContext(loggerContext);
        appender.setEncoder(encoder);

        OutputStream outputStream = new OutputStream() {
            @Override
            public void write(int b) {
                SwingUtilities.invokeLater(() -> textArea.append(String.valueOf((char) b)));
            }
        };

        appender.setOutputStream(new PrintStream(outputStream));
        appender.start();

        Logger logger = (Logger) LoggerFactory.getLogger(clazz);
        logger.detachAndStopAllAppenders();
        logger.addAppender(appender);
        logger.setAdditive(false);

        return logger;
    }
}

setupLoggerWithAppender 메서드는 Logger 인스턴스를 설정하고 JTextArea에 로그 메시지를 출력하기 위한 appender를 추가하는 기능을 수행합니다. 이 메서드는 다음과 같은 작업을 수행합니다.

전달받은 Class<?> 객체로부터 해당 클래스에 대한 Logger 인스턴스를 생성합니다.
로그 메시지의 출력 형식을 설정하기 위해 PatternLayoutEncoder 인스턴스를 생성하고 시작합니다.
OutputStreamAppender 인스턴스를 생성하고, 로그 메시지를 JTextArea에 출력할 수 있는 OutputStream을 설정합니다.
생성된 OutputStreamAppender에 PatternLayoutEncoder를 연결하고 시작합니다.
생성된 Logger 인스턴스에서 모든 기존의 appender를 제거하고 OutputStreamAppender를 추가합니다.
생성된 Logger 인스턴스의 setAdditive(false) 메서드를 호출하여 상위 로거로 메시지를 전달하지 않도록 설정합니다.
설정이 완료된 Logger 인스턴스를 반환합니다.
이 메서드를 사용하면, 특정 클래스의 로그 메시지를 JTextArea에 실시간으로 출력할 수 있습니다. 클래스의 로거를 설정하려면 해당 클래스와 출력할 JTextArea를 인수로 setupLoggerWithAppender 메서드를 호출하면 됩니다.

아래는 이를 사용하기 위한 예제 클래스들입니다.

import ch.qos.logback.classic.Logger;
import org.slf4j.LoggerFactory;

import javax.swing.*;

public class A {
    private static Logger logger;
    private JTextArea textArea;

    public A(JTextArea textArea) {
        if (textArea == null) {
            throw new IllegalArgumentException("JTextArea 인스턴스가 null입니다.");
        }
        this.textArea = textArea;
        logger = LoggerUtil.setupLoggerWithAppender(A.class, textArea);
    }

    public void run() {
        new Thread(() -> {
            logger.info("A 클래스의 run 메서드가 실행되었습니다.");

            B bInstance = new B(textArea); // B 클래스 인스턴스 생성
            bInstance.someMethod(); // B 클래스의 someMethod 메서드 호출

            aa(); // 모든 작업이 완료된 후 A 클래스의 aa 메서드 호출
        }).start();
    }

    public void aa() {
        logger.info("A 클래스의 aa 메서드가 실행되었습니다.");
        // aa 메서드의 구현
    }
}

아래는, A 메서드에서 또다른 B 메서드를 이용할 때, B 메서드에서 실행되는 로그 조차 가져오고자 할 때, 샘플 예제입니다. A메서드를 쓰레드로 구현하고 A run() 메서드에서 B의 인스턴스를 호출합니다.

import ch.qos.logback.classic.Logger;
import org.slf4j.LoggerFactory;

import javax.swing.*;

public class B {
    private static Logger logger;

    public B(JTextArea textArea) {
        if (textArea == null) {
            throw new IllegalArgumentException("JTextArea 인스턴스가 null입니다.");
        }
        logger = LoggerUtil.setupLoggerWithAppender(B.class, textArea);
    }

    public void someMethod() {
        logger.info("B 클래스의 someMethod 메서드가 실행되었습니다.");
        // someMethod 메서드의 구현
    }
}

마지막으로 GUI 클래스는 가장 단순하게 하나의 프레임에 하나의 버튼, 하나의 textarea를 생성하여 보여주는 샘플입니다.

 

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class GUI_Main {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame("Logger Example");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setSize(500, 300);

            JTextArea textArea = new JTextArea();
            JScrollPane scrollPane = new JScrollPane(textArea);
            frame.add(scrollPane, BorderLayout.CENTER);

            JPanel buttonPanel = new JPanel();
            JButton runButton = new JButton("Run");
            buttonPanel.add(runButton);
            frame.add(buttonPanel, BorderLayout.SOUTH);

            A aInstance = new A(textArea); // A 클래스 인스턴스 생성

            runButton.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    aInstance.run(); // A 클래스의 run() 메서드 호출
                }
            });

            frame.setVisible(true);
        });
    }
}

 

이렇게 하면 가장 먼저 B 인스턴스에서 생성되는 로그, 그다음 A 인스턴스에서 생성되는 로그가 textarea에 실시간으로 보여지게 됩니다.

오늘도 좋은하루 되십시오!

반응형