Java Logging FrameworkSLF4JLoggerSentry for Spring Bootlogback 설정하기요소AppenderConsoleAppender구현 시, 로그 중복 해결 방법 → additivity=”false”SocketAppender, SSLSocketAppenderServerSocketAppenderPatternLayoutConversion Rule 추가하기File로 logging 남길 때에러 로그만 따로 관리하기 (LevelFilter)실습 config 파일하이버네이트 SQL 로그Timezone 설정Log level 테이블CharsetSpring Logback ExtensionMapped Diagnostic Context (MDC)MDC의 주요 개념왜 MDC를 사용하는가?Slack 로깅 설정
- System.out.println —> 너무 자주 찍어대면 성능 문제가 생길 수 있음. 안에서 synchronized 가 있기 때문에 자주 쓰면 오버헤드가 생길 수 있음 ⇒ 운영 상에서는 안하는 거다. 절대 쓰지 마라
Java Logging Framework
- Log4J
- Logback — SpringBoot에서기본적으로 제공. 현재 제일 많이 사용됨
- SLF4J가 위의 두개를 공통적으로 쓸 수 있도록 묶어 준것. Log4J를 쓰다가 Logback으로 쓰다가 바꾸려면 코드 다 바꿔야 하니까. 이것을 도입한 것
SLF4J
- 여러 로깅 프레잌워크를 추상화해 놓은 것
- Facade Pattern을 이용한 Logging Framework임

- 안에 어떤 클래스가 있는지를 모르고 바깥에서 제공해주는 API통해서 안의 클래스들을 활용
- 좋은 로깅 프레임워크가 나올 때마다 코드를 변경해야 하면 너무 힘들다.. slf4j 이용하고 binding만 바꿔주면 코드 변경없이 그대로 사용!
- SLF4J가 다양한 로깅 프레임워크를 지원하는데 이는 바인딩 모듈을 통해서 처리됨

Logger
- 로거 이름은 클래스의 이름을 주는데, 패키지가 다 포함된 클래스명으로 이름 지음
- 클래스에 하나(객체마다 생기는 것이 아니라)만 생겨야 하기 때문에 static
- 다른사람이 변경하면 안되니까 private final로 선언함
import org.slf4j.Logger; import org.slf4j.LoggerFactory; //org.prgms.kdt // SET WARN 제일상위에 로그 레벨 설정하면 하위는 알아서 똑같이 적용됨 //org.prgms.kdt.A => WARN //org.prgms.kdt.voucher // SET INFO. 하위에서 다른 레벨 설정하려면 셋 해주어야 함 private static final Logger logger = LoggerFactory.getLogger(OrderTester.class); // this.getClass()로 할 수도 있고, org.prgms.kdt.OrderTester(패키지이름까지) 로 제공할수도 있고.
- Logger이름을 통해서 logging을 제어함
Sentry for Spring Boot
[ Sentry Docs ] Getting Started
logback 설정하기
logback 설정파일 찾는 순서
- logback-test.xml 찾고, 없으면 (이 파일은 test - resource 폴더 아래에)
- logback.groovy 찾고, 없으면
- logback.xml 찾고
- 모두 없다면 기본 설정 전략을 따름 BasicConfiguration
https://logback.qos.ch/manual/configuration.html — logback configuration 참조
요소
- appender : Logback delegates the task of
writing a logging event
to components called appenders - Layout : logback components responsible for transforming an
incoming event into a String
- Encoders : responsible for
transforming an event into a byte array
as well as writing out that byte array into anOutputStream
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <Pattern> %d{yyyy-MM-dd HH:mm:ss} [%-5level] %logger{36} - %msg%n </Pattern> <charset>UTF-8</charset> </encoder>
- property — 환경변수처럼 변수 정의해서 사용이 가능함
<property name="CONSOLE_LOG_PATTERN" value="%clr(%d{HH:mm:ss.SSS}){cyan} [%thread] %clr(%-5level) %logger{36} - %msg%n"/>
<springProperty scope="context" name="LOG_LEVEL" source="bamdule.logging.level"/>
- conversionRule
- filter : Logback filters are based on ternary logic
allowing them to be assembled or chained together to compose an arbitrarily complex filtering policy
. They are largely inspired by Linux's iptables.
Appender
ConsoleAppender구현 시, 로그 중복 해결 방법 → additivity=”false”
- 새 logger를 만들어서 appender-ref에 STDOUT을 추가하면 로그가 중복되어 출력됨. 이 때 이를 막으려면
<logger name="org.prgms" level="debug" additivity="false">
이런 식으로 attribute 할당 해주어야 함
<configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder by default --> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <logger name="org.prgms" level="debug"> <appender-ref ref="STDOUT" /> <!-- 새로운 logger를 만들어서 appender-ref stdout 추가하려면 additivity=false 옵션을 주어서 중복안되도록 하거나 아예 다른 appender를 할당하거나, 아님 아예 appender-ref를 안하거나 해야 함--> </logger> <root level="warn"> <!-- 전체 log level을 설정하는 부분--> <appender-ref ref="STDOUT" /> </root> </configuration>
SocketAppender, SSLSocketAppender
- SocketAppender : log를 원격 서버로 보내기 위해 쓰이는 Appender임
- SSLSocketAppender: logging events 가 secure channel을 통해 전송됨
ServerSocketAppender
- SocketAppender는 로깅 이벤트를 전달하기 위해서 커넥션을 어플리케이션이 시작을 해야 함. 이를 보완하기 위해 ServerSocketAppender는 직접 커넥션을 맺지 않고 TCP socket을 listen만 하고 있음
- 원격 클라이언트로부터 커넥션 연결이 들어오기까지
PatternLayout
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
이부분- logger.info 같은거 호출할 때 우리는 string을 전달하지만 내부적으로는 logging event라는 것이 만들어짐
- 그 logging event를 문자열로 바꾸어 주는 것이 Layout pattern
Conversion Rule 추가하기
logback documentaion Chap6 에서 Creating a custom conversion specifier 키워드로 검색
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/> <property name="CONSOLE_LOG_PATTERN" value="%clr(%d{HH:mm:ss.SSS}){cyan} [%thread] %clr(%-5level) %logger{36} - %msg%n"/>
- ColorConverter라는 class를 clr 가 쓰이면 적용하여서 해당하는 logging event를 문자열로 바꿀 때 ColorConverter의 로직이 적용되도록 함
PatternLayout
작성 시, 괄호는 conversion pattern들을 묶기 위해서 사용할 수 있음. 만약 괄호를 literal로 쓰려면 escaping처리를 해줘야함
- conversionWorld 다음 { } 를 이용하여 해당 ConversionWord에 파라미터를 넘길 수 있음
- 예 :
%clr(…){cyan}
File로 logging 남길 때
- prudent mode 라는 것이 있어서, 다른 jvm에서 똑같은 파일에 쓸때에도 충돌나지 않게 하는 옵션이 있음
- append 옵션
- timestamp 변수 설정
- RollingFileAppender
<appender name="ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>logs/access.log</file> <!-- rollingPolicy 와 triggeringPolicy 를 정의해주어야 함. triggering을 file size 특정 이상 되면 할수도 있고, 특정 시간이 되면 만들게 할 수도 있고--> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"/> <fileNamePattern>logs/access-%d{yyyy-MM-dd}.log</fileNamePattern> <encoder> <pattern>${LOG_PATTERN}</pattern> </encoder> </appender>
- <file>과 <fileNamePattern>이 같이 있으면, 오늘의 로그(예로 7.1)는 access.log에 기록이 되고 다음날(7.2)이 되면 어제 로그파일(access-2021-07-01.log)이 어제 날짜로 바뀌게 되고 오늘 파일은 access.log가 됨 ⇒ 항상 access.log는 오늘의 로그
- <file>이 없고 <fileNamePattern>만 있으면, 그냥 날짜별로 access-yyyy-MM-dd.log 형태로 만들어짐
에러 로그만 따로 관리하기 (LevelFilter)
<configuration> <appender name="error" class="ch.qos.logback.core.rolling.RollingFileAppender"> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>error</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> <file>./application_error_log/application.log</file> <encoder> <pattern>%d{yyyyMMdd HH:mm:ss.SSS} [%thread] %-5level [%logger{0}:%line] - %msg %n</pattern> </encoder> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>application_error.log.%d{yyyy-MM-dd}.gz</fileNamePattern> <maxHistory>30</maxHistory> <totalSizeCap>5GB</totalSizeCap> </rollingPolicy> </appender> <root level="info"> <appender-ref ref="console" /> <appender-ref ref="file" /> <appender-ref ref="error" /> </root> </configuration>
실습 config 파일
<configuration> <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/> <property name="CONSOLE_LOG_PATTERN" value="%clr(%d{HH:mm:ss.SSS}){cyan} [%thread] %clr(%-5level) %logger{36} - %msg%n"/> <property name="FILE_LOG_PATTERN" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/> <!--%d{yyyy-MM-dd HH:mm:ss.SSS} %clr(%-5level) --- [%thread] %clr(%logger{36}){cyan} : %msg %n. Spring Boot 로그 패턴 --> <timestamp key="bySecond" datePattern="yyyyMMdd'T'HHmmss"/> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>${CONSOLE_LOG_PATTERN}</pattern> </encoder> </appender> <appender name="FILE" class="ch.qos.logback.core.FileAppender"> <file>logs/kdt_${bySecond}.log</file> <append>false</append> <!-- file에 append하지 말고 새로 쓰기 --> <encoder> <pattern>${FILE_LOG_PATTERN}</pattern> </encoder> </appender> <appender name="ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>logs/access.log</file> <!-- rollingPolicy 와 triggeringPolicy 를 정의해주어야 함. triggering을 file size 특정 이상 되면 할수도 있고, 특정 시간이 되면 만들게 할 수도 있고--> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"/> <fileNamePattern>logs/access-$d{yyyy-MM-dd}.log</fileNamePattern> <encoder> <pattern>${LOG_PATTERN}</pattern> </encoder> </appender> <logger name="org.prgms" level="debug"> <appender-ref ref="FILE" /> <!-- 새로운 logger를 만들어서 appender-ref stdout 추가하려면 additivity=false 옵션을 주어서 중복안되도록 하거나 아예 다른 appender를 할당하거나, 아님 아예 appender-ref를 안하거나 해야 함--> </logger> <root level="warn"> <!-- 전체 log level을 설정하는 부분--> <appender-ref ref="STDOUT" /> </root> </configuration>
하이버네이트 SQL 로그
<logger name="org.hibernate.SQL" level="DEBUG">...</logger> <logger name="org.hibernate.type" level="TRACE">...</logger>
Timezone 설정
- logback은 기본적으로 jvm 레벨의 시간 설정을 가져오게 됨
- jvm 에서 timezone 설정 방법 —
java -Duser.timezone=Asia/Seoul Test
Log level 테이블

- log level을 application.yaml 에서 관리할 수도 있고 logback에서 관리할수도 있음
logging: level: com.uplus.virtualoffice: DEBUG com.uplus.virtualoffice.infra: INFO
<logger name="com.uplus.virtualoffice" level="DEBUG"> <appender-ref ref="LOGS_TO_FILE"/> <appender-ref ref="ERROR_LOGS_TO_FILE"/> </logger>
- 근데 이 둘다 설정 있으면 application.yaml의 설정을 더 우선시함
- 만약
application.yaml
에서com.uplus.virtualoffice
의 LEVEL을INFO
로 설정하고logback-spring.xml
의com.uplus.virtualoffice
LEVEL을DEBUG
로 설정해두고appender-ref
를 달아두면,application.yaml
의 LEVEL이 적용되어INFO
이상의 로그에 대해서만 appender가 적용됨
Charset
- docker container 내부에서 spring application 을 실행시켰을 때, 한글 로그가 아래와 같이 깨지는 현상이 발생했음
com.uplus.virtualoffice.common.exception.admin.AccountLoginException: ??? ?? ????? ???????]
ErrorResponse{code=1005, message='??? ?? ????? ???????'}
- 이 때, encoder 설정 안에 charset설정을 해주면 한글이 안 깨지고 잘 나오게 됨
<appender name="Console" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <charset>UTF-8</charset> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %clr(%-5level) --- [%thread][traceId:%X{traceId:-} spanId:%X{spanId:-}] %clr(%logger{36}){cyan} : %msg %n</pattern> </encoder> </appender>
Spring Logback Extension
<!-- active profile 일 때만 작동하는 tag 설정할 수 있음. Profile specific Config--> <springProfile name="prod"> <logger name="com.uplus.virtualoffice" level="DEBUG"> <appender-ref ref="ASYNC_SLACK"/> </logger> </springProfile> <springProperty name="SLACK_WEBHOOK_URI" source="logging.slack.webhook-uri"/>
Mapped Diagnostic Context (MDC)
특정 실행 스레드와 관련된 진단 정보를 저장하고 이를 로그 메시지와 함께 출력할 수 있도록 도와줍니다.
MDC의 주요 개념
- 맵(Map) 형태의 데이터 저장소:
- MDC는 키-값 쌍의 형태로 데이터를 저장합니다.
- 이 데이터는 특정 스레드에 국한되며, 해당 스레드의 작업이 완료되면 제거됩니다.
- 스레드 로컬(Thread-Local) 저장소:
- MDC는 스레드 로컬(ThreadLocal) 저장소를 사용하여 데이터를 저장합니다.
- 같은 애플리케이션 내에서 동시 실행 중인 여러 스레드 간에 데이터 충돌 없이 별도로 저장할 수 있습니다.
왜 MDC를 사용하는가?
MDC는 애플리케이션의 컨텍스트 정보를 로그 메시지에 추가하는 데 유용합니다. 예를 들어, 다음과 같은 경우에 활용할 수 있습니다: