HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
🤩
개발
/
Spring
Spring
/
🎧
Spring Framework 핵심개념(Core)
/
🔈
로깅
🔈

로깅

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

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 설정파일 찾는 순서
  1. logback-test.xml 찾고, 없으면 (이 파일은 test - resource 폴더 아래에)
  1. logback.groovy 찾고, 없으면
  1. logback.xml 찾고
  1. 모두 없다면 기본 설정 전략을 따름 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 an OutputStream
    • <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 — application.yml에 정의된 값을 logback 설정으로 가져올 수 있음
      • <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
[참고] — https://logback.qos.ch/manual/layouts.html#conversionWord
 
 

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 남길 때

https://logback.qos.ch/manual/appenders.html
  • 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)

Logback 도큐멘트
<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
    • How to set jvm timezone

Log level 테이블

notion image
  • 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

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는 애플리케이션의 컨텍스트 정보를 로그 메시지에 추가하는 데 유용합니다. 예를 들어, 다음과 같은 경우에 활용할 수 있습니다:

Slack 로깅 설정

Slack 로깅 설정
Slack 로깅 설정
EC2 로그 시간대 맞지 않는 문제