[Spring Boot] Event Tracking

Spring Boot

Language :

Event

클릭, 화면 스크롤 등 콘텐츠와 사용자 간에 발생하는 상호작용.

Event Tracking

사용자가 어떤 동작을 하고 있는지 추적하고 기록하는 과정.

Logging

개발 또는 운영 중에 발생하는 오류를 디버깅(debugging)하거나, 프로그램 상태를 모니터링하기 위해 필요한 정보(로그)를 기록하는 행위.

로그를 분석해 통계를 낼 수 있다.

로그 기준을 적절하게 잡지 못하면 로그 파일이 비대해지거나 의미 없는 로그만 쌓게 될 수가 있다.

Spring Framework는 기본적으로 Commons Logging을 구현한 Spring-JCL 모듈을 사용하고 있으며(링크), 컴파일 시점에 의존성에 근거하여 SLF4J(Logback) 또는 Log4j로 변경되어 동작한다.

spring_dependency_complie.png

spring_dependency_complie.png

@RestController
@RequestMapping("/tracker/api")
class TrackerController {

    companion object {
        private val logger = LoggerFactory.getLogger(this::class.java)
    }

    @GetMapping
    fun tracking(request: HttpServletRequest): ResponseEntity<Any> {
        logger.debug("tracker message")
        return ResponseEntity(HttpStatus.OK)
    }

}
[2023-08-22 14:36:44:28859] DEBUG 39592 --- [nio-8080-exec-1] c.e.t.c.TrackerController$Companion      : tracker message

JCL(Jakarta Commons Logging, Apache Commons Loggundefinedng)

SLF4J가 등장하기 전까지 주로 사용되었던 로그 인터페이스

런타임에 동작하며 현재는 거의 사용하지 않는 로깅 방법이다.

클래스 로더 문제와 메모리 누수 문제가 있다고 한다.

SLF4J(Simple Logging Facade for Java)

java.util.logging, logback 및 reload4j와 같은 다양한 로깅 구현체(Logging Framework)에 대한 추상화 역할(Interface, Facade Pattern)을 하는 라이브러리

런타임이 아닌 컴파일 시점에 로깅 구현체를 선택하여 사용한다.

제공 모듈

1. SLF4J API

로깅 구현체에 대해 SLF4J를 사용하기 위한 Interface를 제공한다. (SLF4J Interface)

2. SLF4J Binding (공급자)

SLF4J API와 로깅 구현체를 연결하는 라이브러리 (어댑터 역할)

사용하고자 하는 로깅 구현체에 대한 SLF4J binding을 단 하나만 사용해야 한다. (one and only one)

여러 바인딩을 사용할 경우 어떤 바인딩이 사용될지 예상할 수 없다.

3. SLF4J Bridging Modules

SLF4J가 아닌 다른 로깅 API로 Logger를 호출할 때 SLF4J Interface를 연결(Redirect)하여 SLF4J API가 대신 처리할 수 있도록 하는 라이브러리

SLF4J가 바인딩하는 로깅 구현체를 사용하게 된다.

Binding과 Bridge를 동일한 로깅 구현체와 관련된 jar로 사용할 수 없다. (무한 루프 발생)

동작 과정

다른 로깅 API(log4j, jul, jcl) > SLF4J Bridge > SLF4J API > SLF4J Binding > Logging Framework

Logback

Log4J 개발자가 만든 Logging Framework

SLF4J에서 제공하는 3가지 모듈이 연결되어 있다.

로그 레벨

로그 레벨 심각도 수준: Trace < Debug < Info < Warn < Error

콘솔 로그 수준 설정에 따라 상위 레벨은 출력되지 않는다.

  • Trace: 모든 레벨을 기록하여 경로 추적에 사용한다.
  • Debug: 개발 단계에서 사용하며 쿼리 상세 정보를 기록한다.
  • Info: 운영에 참고할만한 사항, 정보성 메시지를 기록한다.
  • Warn: 당장 서비스에 영향은 없지만 주의가 필요한 부분을 기록한다.
  • Error: 즉시 조치가 필요한 심각한 문제가 발생 시 기록한다.

배포 환경에 따라 기록 형식 다르게 설정하기 (링크)

1. application-dev.yml

log:
  config:
    path: ./logs
    filename: dev
    max-history: 7
    total-size-cap: 1GB

2. application-prod.yml

log:
  config:
    path: ./logs
    filename: prod
    max-history: 7
    total-size-cap: 1GB

3. logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <springProfile name="dev">
        <property resource="application-dev.yml"/>
    </springProfile>
    <springProfile name="prod">
        <property resource="application-prod.yml"/>
    </springProfile>

    <springProperty name="LOG_DIR_PATH" source="log.config.path"/>
    <springProperty name="LOG_FILE_NAME" source="log.config.filename"/>
    <springProperty name="LOG_MAX_HISTORY" source="log.config.max-history"/>
    <springProperty name="LOG_TOTAL_SIZE_CAP" source="log.config.total-size-cap"/>

    <property name="CONSOLE_LOG_PATTERN"
              value="[%d{yyyy-MM-dd HH:mm:ss}:%-3relative] %highlight(%-5level) %magenta(${PID:-}) --- [%15.15thread] %cyan(%-40.40logger{36}) : %msg%n"/>
    <property name="FILE_LOG_PATTERN"
              value="[%d{yyyy-MM-dd HH:mm:ss.SSS,Asia/Tokyo}:%-3relative] %-5level ${PID:-} --- [%15.15thread] %-40.40logger{36} : %msg%n"/>
    <property name="FILE_LOG_PATTERN_CLASS_NUMBER"
              value="[%d{yyyy-MM-dd HH:mm:ss.SSS,Asia/Tokyo}:%-3relative] %-5level ${PID:-} --- [%15.15thread] %logger{36} [%file:%line] - %msg ##%n"/>

    <!-- 콘솔(STDOUT)에 로그 기록 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <!-- 파일(FILE)에 로그 기록 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- Application 동작 중에 쌓이는 로그 -->
        <file>${LOG_DIR_PATH}/${LOG_FILE_NAME}.log</file>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${FILE_LOG_PATTERN_CLASS_NUMBER}</pattern>
        </encoder>
        <!-- 로그 파일 보관 규칙 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 보관되는(rolled-over, archived) 로그 파일명 지정 -->
            <fileNamePattern>${LOG_DIR_PATH}/%d{yyyy-MM,aux}/${LOG_FILE_NAME}.%d.log</fileNamePattern>
            <!-- 최대 보관 개월수. 오래된 파일 순으로 삭제 -->
            <maxHistory>${LOG_MAX_HISTORY}</maxHistory>
            <!-- 총 보관 크기 제한. 기본 byte 단위 -->
            <totalSizeCap>${LOG_TOTAL_SIZE_CAP}</totalSizeCap>
        </rollingPolicy>
    </appender>

    <!-- 비동기 파일(FILE) -->
    <appender name="FILE-ASYNC" class="ch.qos.logback.classic.AsyncAppender">
        <appender-ref ref="FILE"/>
        <!-- 이벤트와 연관된 코드 정보(클래스:줄번호)를 기록할지 여부. 사용할 경우 성능 저하가 발생한다.-->
        <includeCallerData>false</includeCallerData>
        <!-- 큐 용량이 설정값% 남으면 ERROR, Warn 외 로그는 삭제. 0으로 설정하면 모든 로그를 유지 -->
        <discardingThreshold>0</discardingThreshold>
        <!-- 큐 크기 -->
        <queueSize>1024</queueSize>
        <!-- 기본 false. 큐에 남는 공간이 없으면 스레드가 블락킹(blocking) 상태가 되어 공간이 비길 기다린다. true 면 블락킹 상태에 빠지지 않는 대신 로그를 버린다 -->
        <neverBlock>true</neverBlock>
    </appender>

    <springProfile name="dev">
        <root level="DEBUG">
            <appender-ref ref="STDOUT"/>
            <appender-ref ref="FILE-ASYNC"/>
        </root>
    </springProfile>

    <springProfile name="prod">
        <root level="INFO">
            <appender-ref ref="STDOUT"/>
        </root>
        <logger name="my_logger" level="DEBUG">
            <appender-ref ref="FILE"/>
        </logger>
    </springProfile>
</configuration>

4. controller

@RestController
@RequestMapping("/tracker/api")
class TrackerController {

    companion object {
        private val logger = LoggerFactory.getLogger(this::class.java)
        private val myLogger = LoggerFactory.getLogger("my_logger")
    }

    @GetMapping
    fun tracking(request: HttpServletRequest): ResponseEntity<Any> {
        logger.debug("tracker message debug")
        logger.info("tracker message info")
        myLogger.debug("tracker message mylogger debug")
        myLogger.info("tracker message mylogger info")
        return ResponseEntity(HttpStatus.OK)
    }

}
1. profile dev로 실행한 경우 Console Log
[2023-08-22 15:23:49:13390] DEBUG 41788 --- [nio-8080-exec-1] c.e.t.c.TrackerController$Companion      : tracker message debug
[2023-08-22 15:23:49:13390] INFO  41788 --- [nio-8080-exec-1] c.e.t.c.TrackerController$Companion      : tracker message info
[2023-08-22 15:23:49:13390] DEBUG 41788 --- [nio-8080-exec-1] my_logger                                : tracker message mylogger debug
[2023-08-22 15:23:49:13390] INFO  41788 --- [nio-8080-exec-1] my_logger                                : tracker message mylogger info

2. profile prod로 실행한 경우 Console Log (my_logger debug 주석했을 때)
[2023-08-22 15:23:08:17824] INFO  24896 --- [nio-8080-exec-2] c.e.t.c.TrackerController$Companion      : tracker message info
[2023-08-22 15:23:08:17824] INFO  24896 --- [nio-8080-exec-2] my_logger                                : tracker message mylogger info

3. profile prod로 실행한 경우 Console Log (my_logger debug 주석 안했을 때)
[2023-08-22 15:27:10:11846] INFO  420 --- [nio-8080-exec-1] c.e.t.c.TrackerController$Companion      : tracker message info
[2023-08-22 15:27:10:11846] DEBUG 420 --- [nio-8080-exec-1] my_logger                                : tracker message mylogger debug
[2023-08-22 15:27:10:11847] INFO  420 --- [nio-8080-exec-1] my_logger                                : tracker message mylogger info

파일 생성은 좀 더 연구가 필요

Log4j

Apache의 Java 기반 Logging Framework로, 가장 오래된 프레임워크이다.

2021년에 치명적인 취약점이 발견되어 이슈화되었다.

버전을 2.17.1 이상으로 사용하거나 다른 로깅 모듈로 교체하는 것을 권장한다.

Reference

https://www.slf4j.org/manual.html

https://loosie.tistory.com/829

https://livenow14.tistory.com/63

https://gmlwjd9405.github.io/2019/01/04/logging-with-slf4j.html

https://velog.io/@mindfulness_22/slf4j-slf4j-logback-3

https://velog.io/@codemcd/Spring-Boot-and-Logging-1.-SLF4J

https://rlawls1991.tistory.com/entry/스프링-부트-기본-로거-설정

https://velog.io/@mindfulness_22/slf4j-facade-pattern-1

https://tecoble.techcourse.co.kr/post/2021-08-07-logback-tutorial

https://ckddn9496.tistory.com/82

https://velog.io/@cho876/Springboot-LogBack-설정하기

민갤

Back-End Developer

백엔드 개발자입니다.