728x90
반응형
Spring Boot 프로젝트에서는 일반적으로 로깅 설정을 위해 logback-spring.xml 파일을 사용합니다. (XML 기반 설정)
Java 기반 설정의 장점(유연성과 정적 타입 검사)을 고려하여 로깅 설정을 Java Configuration으로 변환하겠습니다.
기존 XML 기반 Logback 설정
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds">
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss} - %msg%n"/>
<property name="LOG_FILE" value="/var/logs/myapp.log"/>
<!-- 콘솔 Appender 설정 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- 파일 Appender 설정 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_FILE}</file>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE}-%d{yyyy-MM-dd}.zip</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
</appender>
<!-- 로깅 레벨 설정 -->
<logger name="com.example" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</logger>
<root level="INFO">
<appender-ref ref="CONSOLE" />
</root>
</configuration>
Java Configuration으로 변환
Java 기반 Appender 구성
Appender 설정을 XML 대신 Java Configuration으로 전환합니다.
ConsoleAppender, RollingFileAppender를 Java 코드로 설정할 수 있게 하고, 여러 프로파일에 따른 Appender를 동적으로 설정하는 데도 유리합니다.
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import org.slf4j.LoggerFactory;
public abstract class BaseLogger {
private final LoggerContext logCtx = (LoggerContext) LoggerFactory.getILoggerFactory();
private Appender<ILoggingEvent> appender;
protected BaseLogger() {
}
protected void initializeAppender(Appender<ILoggingEvent> appender) {
if (this.appender == null) {
this.appender = appender;
}
}
protected LoggerContext getContext() {
return logCtx;
}
public Appender<ILoggingEvent> retrieveAppender() {
if (appender == null) {
throw new IllegalStateException("Appender has not been initialized.");
}
return appender;
}
}
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.ConsoleAppender;
import com.biz.config.logback.properties.LogProperties;
public class ConsoleLogger extends BaseLogger {
private ConsoleLogger(LogProperties logProperties) {
getContext().reset();
initializeAppender(getLogAppender(logProperties));
}
public static Appender<ILoggingEvent> getAppender(LogProperties logProperties) {
ConsoleLogger config = new ConsoleLogger(logProperties);
return config.retrieveAppender();
}
private ConsoleAppender<ILoggingEvent> getLogAppender(LogProperties logProperties) {
PatternLayoutEncoder consoleLogEncoder = createLogEncoder(logProperties.getPattern());
ConsoleAppender<ILoggingEvent> logConsoleAppender = createLogAppender(consoleLogEncoder);
logConsoleAppender.start();
return logConsoleAppender;
}
private PatternLayoutEncoder createLogEncoder(String pattern) {
PatternLayoutEncoder encoder = new PatternLayoutEncoder();
encoder.setContext(getContext());
encoder.setPattern(pattern);
encoder.start();
return encoder;
}
private ConsoleAppender<ILoggingEvent> createLogAppender(PatternLayoutEncoder consoleLogEncoder) {
ConsoleAppender<ILoggingEvent> logConsoleAppender = new ConsoleAppender<>();
logConsoleAppender.setName("STDOUT");
logConsoleAppender.setContext(getContext());
logConsoleAppender.setEncoder(consoleLogEncoder);
return logConsoleAppender;
}
}
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.rolling.RollingPolicy;
import ch.qos.logback.core.rolling.TimeBasedRollingPolicy;
import com.biz.config.logback.properties.LogProperties;
public class RollingFileLogger extends BaseLogger {
private RollingFileLogger(LogProperties logProperties) {
initializeAppender(getLogAppender(logProperties));
}
public static Appender<ILoggingEvent> getAppender(LogProperties logProperties) {
RollingFileLogger config = new RollingFileLogger(logProperties);
return config.retrieveAppender();
}
private RollingFileAppender<ILoggingEvent> getLogAppender(LogProperties logProperties) {
PatternLayoutEncoder rollingLogEncoder = createLogEncoder(logProperties.getPattern());
RollingFileAppender<ILoggingEvent> rollingFileAppender = createLogAppender(logProperties.getFile(), rollingLogEncoder);
TimeBasedRollingPolicy<RollingPolicy> rollingPolicy = createLogRollingPolicy(logProperties.getFile(), rollingFileAppender);
rollingFileAppender.setRollingPolicy(rollingPolicy);
rollingFileAppender.start();
return rollingFileAppender;
}
private TimeBasedRollingPolicy<RollingPolicy> createLogRollingPolicy(String logFilePath, RollingFileAppender<ILoggingEvent> fileAppender) {
TimeBasedRollingPolicy<RollingPolicy> policy = new TimeBasedRollingPolicy<>();
policy.setContext(getContext());
policy.setParent(fileAppender);
policy.setFileNamePattern(logFilePath + "_%d{yyyy-MM-dd}.zip");
policy.setMaxHistory(5);
policy.start();
return policy;
}
private PatternLayoutEncoder createLogEncoder(String pattern) {
PatternLayoutEncoder encoder = new PatternLayoutEncoder();
encoder.setContext(getContext());
encoder.setPattern(pattern);
encoder.start();
return encoder;
}
private RollingFileAppender<ILoggingEvent> createLogAppender(String logFilePath, PatternLayoutEncoder rollingLogEncoder) {
RollingFileAppender<ILoggingEvent> logRollingAppender = new RollingFileAppender<>();
logRollingAppender.setContext(getContext());
logRollingAppender.setFile(logFilePath);
logRollingAppender.setEncoder(rollingLogEncoder);
return logRollingAppender;
}
}
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "log")
public class LogProperties {
private String pattern;
private String file;
}
LogChain 및 Log 클래스 구현
Java Configuration에서 체이닝을 통해 여러 로거와 Appender 설정을 손쉽게 할 수 있도록 Log 클래스와 LogChain 인터페이스를 구현했습니다.
이 Log 클래스를 사용하여 각 로깅 레벨별로 Appender와 Logger를 관리할 수 있습니다.
public interface LogChain {
}
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public class Log implements LogChain {
private final LoggerContext logCtx;
private final List<LoggerConfig> loggerConfigs = new ArrayList<>();
private final List<String> offLoggerNames = new ArrayList<>();
public Log() {
this.logCtx = (LoggerContext) LoggerFactory.getILoggerFactory();
}
public Log off(Consumer<OffBuilder> offConfig) {
OffBuilder builder = new OffBuilder();
offConfig.accept(builder);
offLoggerNames.addAll(builder.loggerNames);
return this;
}
public Log debug(Consumer<LogLevelBuilder> levelConfig) {
LogLevelBuilder builder = new LogLevelBuilder(Level.DEBUG);
levelConfig.accept(builder);
loggerConfigs.addAll(builder.configs);
return this;
}
public Log info(Consumer<LogLevelBuilder> levelConfig) {
LogLevelBuilder builder = new LogLevelBuilder(Level.INFO);
levelConfig.accept(builder);
loggerConfigs.addAll(builder.configs);
return this;
}
public Log warn(Consumer<LogLevelBuilder> levelConfig) {
LogLevelBuilder builder = new LogLevelBuilder(Level.WARN);
levelConfig.accept(builder);
loggerConfigs.addAll(builder.configs);
return this;
}
public Log error(Consumer<LogLevelBuilder> levelConfig) {
LogLevelBuilder builder = new LogLevelBuilder(Level.ERROR);
levelConfig.accept(builder);
loggerConfigs.addAll(builder.configs);
return this;
}
public LogChain build() {
offLoggerNames.forEach(this::offLogger);
loggerConfigs.forEach(config -> {
Logger logger = logCtx.getLogger(config.name);
logger.setAdditive(config.additive);
logger.setLevel(config.level);
for (Appender<ILoggingEvent> appender : config.appenders) {
logger.addAppender(appender);
}
});
return this;
}
private void offLogger(String loggerName) {
Logger logger = logCtx.getLogger(loggerName);
logger.setLevel(Level.OFF);
}
private static class LoggerConfig {
private final String name;
private final Level level;
private final boolean additive;
private final Appender<ILoggingEvent>[] appenders;
@SafeVarargs
public LoggerConfig(String name, Level level, boolean additive, Appender<ILoggingEvent>... appenders) {
this.name = name;
this.level = level;
this.additive = additive;
this.appenders = appenders;
}
}
public static class OffBuilder {
private final List<String> loggerNames = new ArrayList<>();
public OffBuilder add(String loggerName) {
loggerNames.add(loggerName);
return this;
}
}
public static class LogLevelBuilder {
private final List<LoggerConfig> configs = new ArrayList<>();
private final Level level;
public LogLevelBuilder(Level level) {
this.level = level;
}
@SafeVarargs
public final LogLevelBuilder add(String loggerName, boolean additive, Appender<ILoggingEvent>... appender) {
configs.add(new LoggerConfig(loggerName, level, additive, appender));
return this;
}
@SafeVarargs
public final LogLevelBuilder add(String loggerName, Appender<ILoggingEvent>... appender) {
return add(loggerName, true, appender);
}
}
}
- log.off(), log.debug(), log.info() 메서드 등을 통해 로깅 레벨과 각 로거 설정을 체이닝 방식으로 적용할 수 있습니다.
- Appender를 여러개 지정할 수 있고, 각 로거는 개별적으로 additive 설정을 가질 수 있습니다.
- 특정 패키지나 클래스의 로깅을 끄기 위해 OffBuilder를 구현하였고, 로깅 레벨별 설정을 제공하기 위해 LogLevelBuilter를 구현하였습니다.
변환코드 예시)
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Profile("local")
@Configuration
public class LogConfig {
@Bean
public LogChain logger(LogProperties logProperties) {
Log log = new Log();
// Off 로그 설정 - 특정 패키지나 클래스를 로깅에서 제외합니다.
log.off(off -> off
.add("io.lettuce.core")
.add("com.zaxxer.hikari")
.add("jdbc")
.add("jdbc.connection")
.add("jdbc.audit")
);
// Appender 구성 - ConsoleAppender를 예제로 추가합니다.
Appender<ILoggingEvent> consoleAppender = ConsoleAppender.getAppender(logProperties);
// Debug 로그 설정 - 다양한 로그 레벨을 체이닝 방식으로 설정할 수 있습니다.
log.debug(debug -> debug
.add("com.example", false, consoleAppender)
.add("jdbc.sqltiming", false, consoleAppender)
.add("jdbc.resultsettable", false, consoleAppender)
.add("root", consoleAppender)
);
// Info 로그 설정
log.info(info -> info
.add("org", false, consoleAppender)
);
// log.build()를 통해 설정을 마치고 반환합니다.
return log.build();
}
}
Java Configuration은 프로파일 기반 로깅 설정에 매우 유연합니다. @Profile을 활용하여 특정 프로파일에 따른 Logger와 Appender 구성을 쉽게 적용할 수 있습니다.
728x90
반응형
'spring' 카테고리의 다른 글
[SpringBoot] Spring Profile과 환경별 resource 설정 (0) | 2024.11.04 |
---|---|
Spring MVC의 PathPattern (AntPathMatcher, PathPatternParser) (0) | 2024.05.08 |
[SpringBoot3] RestTemplate (httpclient 4 → 5) 마이그레이션 (0) | 2024.05.07 |
ehcache2(net.sf.ehcache)에서 ehcache3(org.ehcache)로 변경 (0) | 2024.04.30 |
[Spring] RestTemplate Deprecated의 진실 (0) | 2023.07.13 |
댓글