SpringBoot 를 배워보자

Log4j 설정을 재시작 없이 변경하며 SQL 쿼리 소스 생성 여부를 조절하는 방법: 심층 분석 및 실제 적용 가이드

_Blue_Sky_ 2024. 11. 17. 00:22
728x90
728x90

Log4j는 자바 애플리케이션에서 로깅 기능을 구현하는 데 널리 사용되는 강력한 도구입니다. 하지만 기존 Log4j 설정 방식은 애플리케이션을 재시작해야 설정 변경 사항이 반영된다는 단점이 있었습니다. 이는 개발 및 운영 환경에서 유연성을 저해하고, 실시간으로 로그 설정을 조절해야 하는 경우에 어려움을 야기했습니다.

본 글에서는 Log4j 설정을 재시작 없이 동적으로 변경하는 방법과 함께, SQL 쿼리 소스 생성 여부를 설정을 통해 조절하는 방법에 대해 자세히 알아보고, 실제 개발 환경에 적용할 수 있는 구체적인 가이드를 제공합니다.

Log4j 동적 설정 변경의 필요성

  • 개발 환경: 디버깅 시 로그 레벨을 빠르게 변경하여 문제를 파악하고, 특정 로그만 출력하여 분석 효율을 높일 수 있습니다.
  • 운영 환경: 시스템 부하가 높을 때 로그 레벨을 낮춰 성능 저하를 방지하거나, 특정 오류 발생 시 로그 레벨을 높여 상세한 정보를 수집할 수 있습니다.
  • 유연한 설정: 애플리케이션 실행 중에 사용자 입력이나 시스템 상태에 따라 로그 설정을 동적으로 변경하여 다양한 상황에 대응할 수 있습니다.

Log4j 동적 설정 변경 방법

Log4j 설정을 재시작 없이 변경하기 위해서는 다음과 같은 방법들을 활용할 수 있습니다.

1. DOMConfigurator 클래스 사용:

  • Log4j 설정 파일을 DOM(Document Object Model)으로 파싱하여 변경하고, DOMConfigurator.configure() 메서드를 호출하여 설정을 다시 로드합니다.
  • 장점: 간단하고 직관적인 방법입니다.
  • 단점: 설정 파일의 구조를 잘 알아야 하고, 파일 파싱 과정에서 오류가 발생할 수 있습니다.

2. PropertyConfigurator 클래스 사용:

  • PropertyConfigurator.configure() 메서드를 이용하여 Properties 객체를 통해 설정을 변경합니다.
  • 장점: Properties 객체를 사용하여 설정을 쉽게 관리할 수 있습니다.
  • 단점: 설정 파일의 구조가 단순해야 합니다.

3. Log4j2 사용:

  • Log4j2는 Log4j의 후속 버전으로, 더욱 강력하고 유연한 기능을 제공합니다.
  • 장점: ConfigurationBuilder를 이용하여 설정을 프로그램적으로 구성하고, 변경할 수 있습니다. 또한, 재정의 가능한 로거(Reconfigurable Logger)를 사용하여 실행 중에 로거의 설정을 변경할 수 있습니다.
  • 단점: Log4j에 비해 학습 곡선이 조금 더 가파를 수 있습니다.

SQL 쿼리 소스 생성 여부 설정

SQL 쿼리 소스를 생성하는 것은 디버깅이나 문제 분석에 유용하지만, 성능 저하를 야기할 수 있습니다. 따라서 필요에 따라 SQL 쿼리 소스 생성 여부를 설정할 수 있어야 합니다.

  • Log4j 설정 파일에 추가: log4j.appender.SQLAppender.sql 속성을 통해 SQL 쿼리를 로그에 포함할지 여부를 설정합니다.
  • 프로그램 코드에서 동적으로 변경: Appender 객체를 직접 참조하여 sql 속성을 변경합니다.

실제 적용 가이드

  1. 프로젝트에 Log4j 또는 Log4j2 라이브러리 추가:
    • Maven 또는 Gradle을 사용하여 의존성을 관리합니다.
  2. 설정 파일 생성:
    • log4j.properties 또는 log4j2.xml 파일을 생성하고 기본적인 설정을 구성합니다.
  3. 동적 설정 변경 코드 구현:
    • 위에서 설명한 방법 중 하나를 선택하여 코드를 작성합니다.
    • 예를 들어, 버튼 클릭 이벤트에 설정 변경 로직을 추가하여 GUI 환경에서 쉽게 설정을 변경할 수 있도록 할 수 있습니다.
  4. SQL 쿼리 소스 생성 여부 설정:
    • 설정 파일에 sql 속성을 추가하거나, 프로그램 코드에서 Appender 객체의 sql 속성을 변경합니다.

Log4j 설정을 재시작 없이 변경하는 것은 개발 및 운영 환경에서 유연성을 높이고, 문제 해결 시간을 단축하는 데 매우 중요합니다. 본 글에서 소개한 방법들을 활용하여 Log4j 설정을 효과적으로 관리하고, SQL 쿼리 소스 생성 여부를 유연하게 조절하여 개발 생산성을 향상시킬 수 있습니다.

 


 

728x90

 

Log4j 또는 Log4j2 라이브러리 추가하기: 자바 프로젝트 로깅 설정 가이드

왜 Log4j(2)를 사용해야 할까요?

Log4j 또는 Log4j2는 자바 애플리케이션에서 로깅 기능을 구현하기 위한 강력한 도구입니다. 이 라이브러리를 사용하면 애플리케이션 실행 중 발생하는 다양한 이벤트를 기록하고 분석할 수 있습니다. 주요 장점은 다음과 같습니다.

  • 유연한 설정: 다양한 형식의 로그 파일 생성, 콘솔 출력, 원격 전송 등 다양한 설정이 가능합니다.
  • 성능: 고성능 로깅을 제공하여 시스템 자원 낭비를 최소화합니다.
  • 확장성: 플러그인 시스템을 통해 기능을 확장할 수 있습니다.
  • 커뮤니티: 활발한 커뮤니티를 통해 많은 정보와 지원을 얻을 수 있습니다.

라이브러리 추가하기

1. 빌드 도구 설정:

  • Maven: pom.xml 파일에 다음과 같이 의존성을 추가합니다.
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.17.2</version>
</dependency>
 
  • Gradle: build.gradle 파일에 다음과 같이 의존성을 추가합니다.
implementation 'org.apache.logging.log4j:log4j-core:2.17.2'
 
 

2. 설정 파일 생성:

  • log4j2.xml: Log4j2의 기본 설정 파일입니다. resources 폴더 아래에 생성합니다.
  • log4j.properties: Log4j의 설정 파일입니다.

설정 파일 예시 (log4j2.xml)

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <Console name="Console">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
        <File name="App" fileName="logs/app.log">
            <PatternLayout pattern="%d{ISO8601} %-5level %logger{36} - %msg%n"/>
        </File>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="App"/>
        </Root>
    </Loggers>
</Configuration>
 
  • 설명:
    • status: 로그 출력 레벨 설정
    • Appenders: 로그를 출력할 대상 (Console, File 등)
    • PatternLayout: 로그 출력 형식
    • Loggers: 로거 설정 (Root 로거는 모든 로거의 부모)

자바 코드에서 사용하기

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

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

    public void myMethod() {
        logger.info("This is an info message");
        logger.error("This is an error message");
    }
}
 

주요 설정 옵션

  • 레벨: TRACE, DEBUG, INFO, WARN, ERROR, FATAL
  • Appender: Console, File, JDBC, SMTP 등
  • Layout: 로그 출력 형식 (PatternLayout, HTMLLayout 등)
  • Filter: 로그 필터링

 

Log4j 또는 Log4j2를 이용한 SQL 쿼리 로깅 방법

1. JDBC 드라이버 설정

  • JDBC 드라이버 로깅 활성화: 대부분의 JDBC 드라이버는 로깅 기능을 제공합니다. 해당 드라이버의 설정을 통해 SQL 쿼리를 로깅하도록 설정할 수 있습니다.
  • 예시 (MySQL Connector/J):
    # log4j2.properties
    appender.sqlLogger.name=SQL
    appender.sqlLogger.type=Console
    appender.sqlLogger.layout.type=PatternLayout
    appender.sqlLogger.layout.pattern=%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n
    
    logger.com.mysql.jdbc.Connection.level=DEBUG
    

2. AOP를 이용한 로깅

  • AspectJ: AOP(Aspect Oriented Programming)를 이용하여 SQL 쿼리를 실행하기 전후에 로깅하는 Aspect를 작성할 수 있습니다.
  • Spring AOP: 스프링 프레임워크를 사용하는 경우, @Aspect 애노테이션을 이용하여 AOP를 간편하게 구현할 수 있습니다.
@Aspect
@Component
public class SqlLoggingAspect {

    @Around("execution(* javax.sql.DataSource.getConnection(..))")
    public Object logSql(ProceedingJoinPoint joinPoint) throws Throwable {
        // 시작 시간 기록
        long startTime = System.currentTimeMillis();

        // SQL 실행
        Object result = joinPoint.proceed();

        // 종료 시간 기록 및 로그 출력
        long endTime = System.currentTimeMillis();
        logger.info("SQL: {}, Execution time: {} ms", getSqlFromConnection(joinPoint), endTime - startTime);

        return result;
    }

    // Connection 객체에서 SQL 추출하는 메소드
    private String getSqlFromConnection(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result = joinPoint.proceed(); // Connection 객체 얻기

        if (result instanceof Connection) {
            Connection connection = (Connection) result;

            // PreparedStatement를 이용한 쿼리 추출
            try {
                Method method = connection.getClass().getMethod("prepareStatement", String.class);
                PreparedStatement preparedStatement = (PreparedStatement) method.invoke(connection, "/* Placeholder for SQL */");

                // 실제 SQL 추출 로직 (JDBC 드라이버마다 다를 수 있음)
                String sql = getSqlFromPreparedStatement(preparedStatement);

                return sql;
            } catch (Exception e) {
                // SQL 추출 실패 시 예외 처리
                logger.error("Failed to extract SQL from Connection", e);
                return "Failed to extract SQL";
            }
        } else {
            return "Result is not a Connection object";
        }
    }

    private String getSqlFromPreparedStatement(PreparedStatement preparedStatement) {
        // JDBC 드라이버 별로 SQL 추출 로직이 다름
        // 예시:
        // - HikariCP: preparedStatement.toString()
        // - 다른 드라이버: Reflection을 이용하여 내부 필드에 접근하여 추출
        // ...

        // 여기에 JDBC 드라이버에 맞는 SQL 추출 로직 구현
        return "/* Placeholder for actual SQL */";
    }
}
 
 

 

더보기

각 데이터베이스별 구현 예시

MySQL

private String getSqlFromPreparedStatement(PreparedStatement preparedStatement) {
    try {
        // MySQL Connector/J 8.0.28 기준
        Field field = preparedStatement.getClass().getDeclaredField("sql");
        field.setAccessible(true);
        return (String) field.get(preparedStatement);
    } catch (Exception e) {
        logger.error("Failed to extract SQL from PreparedStatement", e);
        return "Failed to extract SQL";
    }
}
  • 설명: MySQL Connector/J의 경우, sql 필드에 SQL 문장이 저장되어 있으므로 이 필드에 접근하여 값을 가져옵니다.

Oracle

private String getSqlFromPreparedStatement(PreparedStatement preparedStatement) {
    try {
        // Oracle JDBC 드라이버 19.8.0.0 기준
        Method method = preparedStatement.getClass().getDeclaredMethod("getSQL");
        method.setAccessible(true);
        return (String) method.invoke(preparedStatement);
    } catch (Exception e) {
        logger.error("Failed to extract SQL from PreparedStatement", e);
        return "Failed to extract SQL";
    }
}
  • 설명: Oracle JDBC 드라이버는 getSQL() 메서드를 제공하여 SQL 문장을 가져올 수 있습니다.

MSSQL

private String getSqlFromPreparedStatement(PreparedStatement preparedStatement) {
    // MSSQL JDBC 드라이버는 직접적인 SQL 추출 메서드를 제공하지 않음
    // 따라서, 다음과 같은 방법을 시도해 볼 수 있습니다.

    // 1. vendor specific metadata 메타데이터를 이용한 방법
    // 2. Reflection을 이용하여 내부 필드에 접근하는 방법
    // 3. SQLServerDataSource 클래스를 사용하여 SQL을 추출하는 방법

    // 예시 (2번 방법):
    try {
        Field field = preparedStatement.getClass().getDeclaredField("sql");
        field.setAccessible(true);
        return (String) field.get(preparedStatement);
    } catch (Exception e) {
        logger.error("Failed to extract SQL from PreparedStatement", e);
        return "Failed to extract SQL";
    }
}
  • 설명: MSSQL JDBC 드라이버는 다른 드라이버에 비해 SQL 추출 방법이 명확하게 정의되어 있지 않습니다. 위 예시는 Reflection을 이용하여 SQL을 추출하는 방법을 보여줍니다. 하지만 이 방법은 드라이버 버전에 따라 변경될 수 있으므로 주의해야 합니다.

 

3. 프레임워크 제공 기능 활용

  • Spring Data JPA: Spring Data JPA는 @Query 애노테이션을 사용하여 SQL 쿼리를 작성할 때, 쿼리 문자열을 로그에 출력하는 기능을 제공합니다.
  • MyBatis: MyBatis는 Interceptor를 이용하여 SQL 쿼리를 가로채고 로깅할 수 있습니다.

주의 사항

  • 성능: SQL 쿼리 로깅은 성능에 영향을 줄 수 있으므로, 개발 환경이나 문제 발생 시에만 활성화하는 것이 좋습니다.
  • 보안: 민감한 정보가 포함된 SQL 쿼리를 로깅할 때는 주의해야 합니다.
  • 로그 레벨: 필요한 정보만 로깅하기 위해 적절한 로그 레벨을 설정해야 합니다.
  • 로그 포맷: 가독성이 좋은 로그 포맷을 사용하여 문제 분석을 용이하게 해야 합니다.

 


 

728x90

 

추가 기능

  • AsyncLogger: 비동기 로깅을 통해 성능 향상
  • Custom Appender: 필요에 따라 커스텀 Appender를 구현
  • Marker: 로그 메시지에 추가 정보를 붙여 필터링
  • ConfigurationWatchdog: 설정 파일 변경 감지

Log4j와 Log4j2의 차이점

  • 성능: Log4j2가 일반적으로 더 높은 성능을 제공합니다.
  • 기능: Log4j2는 더 많은 기능과 유연성을 제공합니다.
  • 설정: 설정 파일 형식이 다를 수 있습니다.

주의:

  • Log4j 1.x는 보안 취약점이 발견되어 사용을 자제하고 Log4j 2로 업그레이드하는 것이 좋습니다.
  • 설정 파일의 내용에 따라 로깅 동작이 크게 달라질 수 있으므로, 필요에 맞게 설정을 조정해야 합니다.
  • 동적 설정 변경은 시스템에 부하를 줄 수 있으므로, 너무 자주 변경하는 것은 피해야 합니다.
  • 설정 변경 시 오류가 발생할 수 있으므로, 변경 전에 충분히 테스트해야 합니다.
  • Log4j2는 더욱 강력하고 유연한 기능을 제공하므로, 새로운 프로젝트에서는 Log4j2를 사용하는 것이 좋습니다.

참고 자료:

 

728x90
728x90