모잇지 프로젝트를 진행하며 개발 환경과 운영 환경에서 서로 다른 로깅 전략이 필요했다. 그 이유는 개발 환경에서 직접 보는 것이 효과적인 로그들이 있었고, 운영 환경에서는 모니터링을 목적으로 남기는 로그들이 필요했다. 이번에 로그를 담당하며 이를 효과적으로 관리하는 방법을 알아보았다.
로깅 설정에서 사용할 변수들을 중앙에서 관리한다다.
# 로그 파일 위치
# 로그 파일 경로 backend/logs/info 또는 backend/logs/error
LOG_PATH=./logs
DEBUG_PATH=debug
WARN_PATH=warn
INFO_PATH=info
ERROR_PATH=error
# 로그 출력 포맷
# CONSOLE_PATTERN : 개발 환경(콘솔에 출력)
# ROLLING_PATTERN : 운영 환경(파일로 저장)
CONSOLE_PATTERN=%d{yyyy-MM-dd HH:mm:ss.SSS} %magenta([%thread]) %highlight([%-3level]) %logger{36} - %replace(%msg){'\n', ' '} %n
ROLLING_PATTERN=%d{yyyy-MM-dd HH:mm:ss.SSS} %logger{5} - %msg %n
# 로그 파일 정책
MAX_FILE_SIZE=10MB
TOTAL_SIZE=100MB
MAX_HISTORY=10
# application-dev.properties
# 개발 환경에서는 주로 콘솔 로깅 사용
spring.profiles.active=dev
# application-prod.properties
# 운영 환경에서는 파일 로깅 사용
spring.profiles.active=prod
logging.file.path=./logs
항목 | 의미 | 예시 출력 |
---|---|---|
%d{yyyy-MM-dd HH:mm:ss.SSS} | 로그 발생 시각 (형식 지정 가능) | 2025-08-04 10:15:31.420 |
%thread | 로그를 발생시킨 스레드명 | main , http-nio-8080-exec-1 |
%magenta(...) | 해당 항목을 콘솔에서 자홍색으로 출력 (색상 강조) | [main] |
%highlight(...) | 로그 레벨별 색상 지정 (INFO , ERROR 등) | [INF] , [ERR] |
%-3level | 로그 레벨 (좌측 정렬, 최소 너비 3칸) | INF , WRN , DBG |
%logger{36} | Logger 이름 (FQCN 기준 최대 36자) | com.f12.moitz.LoggingTest |
%logger{5} | Logger 이름 (마지막 5단어 또는 문자 기준으로 축약) | moitz.LoggingTest |
%replace(%msg){'\n', ' '} | 메시지에서 줄바꿈(\n )을 공백으로 치환 (단일라인 유지용) | "line1\nline2" → "line1 line2" |
%msg | 로그 메시지 본문 | "사용자 정보 조회 완료" |
2025-08-04 10:15:31.420 [main] [DEBUG] com.f12.moitz.service.UserService - 사용자 정보 조회 시작
2025-08-04 10:15:31.425 [http-nio-8080-exec-1] [INFO] com.f12.moitz.controller.UserController - GET /api/users 요청 수신
운영 환경에서는 단순히 로그를 파일에 저장하는 것만으로는 부족하다.
ElasticSearch + Logstash 또는 CloudWatch, OpenSearch 같은 로그 수집 시스템으로 로그를 전달하고, 이를 분석할 수 있어야 한다.
이를 위해 logback-spring.xml에서는 LogstashEncoder를 사용해 로그를 JSON 포맷으로 변환했다.
이를 위한 설정은 아래와 같다.
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 외부 프로퍼티 로딩 -->
<property resource="logback-variable.properties" />
<!-- Spring 환경 변수 (application.properties 에서 주입됨) -->
<springProperty scope="context" name="DEBUG_LOG_PATH" source="logging.file.debug.path"/>
<springProperty scope="context" name="INFO_LOG_PATH" source="logging.file.info.path"/>
<springProperty scope="context" name="WARN_LOG_PATH" source="logging.file.warn.path"/>
<springProperty scope="context" name="ERROR_LOG_PATH" source="logging.file.error.path"/>
<!-- 개발 환경용 설정 -->
<springProfile name="dev">
<!-- 콘솔 전용 Appender -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_PATTERN}</pattern>
</encoder>
</appender>
<!-- DEBUG 전용 Appender -->
<appender name="FILE_DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
<file>${LOG_PATH}/${DEBUG_PATH}/application.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${DEBUG_LOG_PATH}/application-info-%d{yyyy-MM-dd}.%i.log.zip</fileNamePattern>
<maxHistory>${MAX_HISTORY}</maxHistory>
<maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
<totalSizeCap>${TOTAL_SIZE}</totalSizeCap>
</rollingPolicy>
</appender>
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE_DEBUG"/>
</root>
<!-- 프레임워크 기본 로거 수준 설정 -->
<logger name="org.springframework" level="INFO"/>
<logger name="org.springframework.boot" level="INFO"/>
<logger name="org.hibernate" level="WARN"/>
<logger name="org.apache.catalina" level="INFO"/>
<logger name="com.zaxxer.hikari" level="WARN"/>
<!--mongoDB-->
<logger name="data.mongodb" level="WARN"/>
<logger name="org.mongodb.driver" level="WARN"/>
<!--Web Flux-->
<logger name="io.netty" level="WARN"/>
<logger name="reactor.netty" level="WARN"/>
<!-- CloudWatch 메트릭 관련 로거 비활성화 -->
<logger name="io.micrometer.cloudwatch2" level="OFF"/>
<logger name="software.amazon.awssdk" level="OFF"/>
<logger name="io.micrometer.core.instrument.push.PushMeterRegistry" level="OFF"/>
<!-- AWS SDK 관련 로거 비활성화 -->
<logger name="software.amazon.awssdk.auth.credentials" level="OFF"/>
<logger name="software.amazon.awssdk.core.interceptor" level="OFF"/>
<logger name="software.amazon.awssdk.services.cloudwatch" level="OFF"/>
<logger name="software.amazon.awssdk.utils.cache" level="OFF"/>
<logger name="software.amazon.awssdk.core.internal" level="OFF"/>
</springProfile>
<!-- ========================================================= -->
<!-- 운영 환경용 설정 -->
<springProfile name="prod">
<!-- INFO 전용 Appender -->
<appender name="FILE_INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
<file>${LOG_PATH}/${INFO_PATH}/application-info.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${INFO_LOG_PATH}/application-info-%d{yyyy-MM-dd}.%i.log.zip</fileNamePattern>
<maxHistory>${MAX_HISTORY}</maxHistory>
<maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
<totalSizeCap>${TOTAL_SIZE}</totalSizeCap>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- WARN 전용 Appender -->
<appender name="FILE_WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
<file>${LOG_PATH}/${WARN_PATH}/application-warn.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${WARN_LOG_PATH}/application-warn-%d{yyyy-MM-dd}.%i.log.zip</fileNamePattern>
<maxHistory>${MAX_HISTORY}</maxHistory>
<maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
<totalSizeCap>${TOTAL_SIZE}</totalSizeCap>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- ERROR 전용 Appender -->
<appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
<file>${LOG_PATH}/${ERROR_PATH}/application-error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${ERROR_LOG_PATH}/application-error-%d{yyyy-MM-dd}.%i.log.zip</fileNamePattern>
<maxHistory>${MAX_HISTORY}</maxHistory>
<maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
<totalSizeCap>${TOTAL_SIZE}</totalSizeCap>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<root level="INFO">
<appender-ref ref="FILE_INFO"/>
<appender-ref ref="FILE_WARN"/>
<appender-ref ref="FILE_ERROR"/>
</root>
</springProfile>
</configuration>
<property resource="logback-variable.properties" />
logback-variable.properties에 정의된 전역 변수들을 logback 설정에서 사용할 수 있게 한다.
<springProperty scope="context" name="DEBUG_LOG_PATH" source="logging.file.debug.path" defaultValue="${LOG_PATH}/${DEBUG_PATH}" />
<springProperty scope="context" name="INFO_LOG_PATH" source="logging.file.info.path" defaultValue="${LOG_PATH}/${INFO_PATH}" />
<springProperty scope="context" name="WARN_LOG_PATH" source="logging.file.warn.path" defaultValue="${LOG_PATH}/${WARN_PATH}" />
<springProperty scope="context" name="ERROR_LOG_PATH" source="logging.file.error.path" defaultValue="${LOG_PATH}/${ERROR_PATH}" />
${INFO_LOG_PATH}
)<springProfile name="dev">
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_PATTERN}</pattern>
</encoder>
</appender>
<!-- 프레임워크 로거 레벨 설정 -->
<logger name="org.springframework" level="INFO"/>
<logger name="org.springframework.boot" level="INFO"/>
<logger name="org.hibernate" level="WARN"/>
<logger name="org.apache.catalina" level="INFO"/>
<logger name="com.zaxxer.hikari" level="WARN"/>
<!-- 그외에도 DB, CloudWatch, AWS 등의 로거 레벨 설정 가능-->
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
주요 구성 요소:
dev
일 때만 활성화<appender name="FILE_INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${INFO_PATH}/application-info.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${INFO_LOG_PATH}/application-info-%d{yyyy-MM-dd}.%i.log.zip</fileNamePattern>
<maxHistory>${MAX_HISTORY}</maxHistory>
<maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
<totalSizeCap>${TOTAL_SIZE}</totalSizeCap>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<pattern>${ROLLING_PATTERN}</pattern>
</encoder>
</appender>
<appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${ERROR_PATH}/application-error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${ERROR_LOG_PATH}/application-error-%d{yyyy-MM-dd}.%i.log.zip</fileNamePattern>
<maxHistory>${MAX_HISTORY}</maxHistory>
<maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
<totalSizeCap>${TOTAL_SIZE}</totalSizeCap>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<encoder>
<pattern>${ROLLING_PATTERN}</pattern>
</encoder>
</appender>
주요 구성 요소:
# IDE에서 실행 시
-Dspring.profiles.active=dev
# JAR 실행 시
java -jar -Dspring.profiles.active=dev your-application.jar
# JAR 실행 시
java -jar -Dspring.profiles.active=prod your-application.jar
운영 환경에서는 다음과 같은 폴더 구조가 생성된다.
logs/
├── info/
│ ├── application-info.log
│ ├── application-info-2025-08-04.0.log.zip
│ └── application-info-2025-08-04.1.log.zip
└── error/
├── application-error.log
├── application-error-2025-08-04.0.log.zip
└── application-error-2025-08-04.1.log.zip
└── warn/
├── application-warn.log
├── application-warn-2025-08-04.0.log.zip
└── application-warn-2025-08-04.1.log.zip
이 설정을 통해 개발과 운영 환경에서 각각 최적화된 로깅 전략을 구현할 수 있었다. 개발 시에는 상세한 디버그 정보를, 운영 시에는 효율적이고 관리하기 쉬운 로그를 얻을 수 있었다.