스프링 부트에서의 로컬 및 분산 로그 시스템 구성

최근 면접 과정에서 로그 관리에 대한 질문을 자주 접했고, 이에 대해 로컬 로그 출력과 전방위 분산 로깅 시스템 구성을 정리해본다.
목적은 프레임워크 내부에서 사용하는 로컬 로깅 시스템과 분산 트레이싱 기능을 소개하는 것이다. 로컬 로그는 스프링이 권장하는 Logback을 기반으로 하며, 기본적으로 최소한의 설정만으로 동작한다. 분산 로그 추적(전방위 로깅)은 Zipkin을 활용하며, 그 원리는 다음과 같다:
1. 각 모듈에서 Zipkin 수집 기능을 통합하고, 로그를 Kafka로 발행(생산자 역할);
2. Zipkin 서버가 Kafka를 소비하여 데이터를 수집하고, Elasticsearch에 저장;
3. Zipkin UI 또는 Kibana를 통해 로그를 시각화. 실제 프로젝트에서는 두 가지 모두 사용된 바 있다.

1. 로컬 로그 출력 설정

1-1. 로그 설정 파일 생성 (src/main/resources/logback-spring.xml)
반드시 해당 이름의 파일을 사용해야 하며, 운영팀과 사전 협의된 형식(파일 경로, 이름 규칙, 보관 주기 등)을 따르도록 한다. 자체 개발한 비표준 형식의 로그 파일은 운영 측에서 인식하지 못하므로 피해야 한다.
이 설정 파일에서 사용된 `${app.dir}` 변수는 미리 값을 지정해야 한다.
방법 1: 이미 제작된 공통 모듈을 의존성 추가. 현재 버전은 0.0.2.
방법 2: 직접 코드로 처리. 애플리케이션 시작 시점에 다음 코드를 포함한다.
log.info("System.setProperty(\"app.dir\") 초기화");
String userDir = System.getProperty("user.dir");
System.setProperty("app.dir", userDir.substring(userDir.lastIndexOf(File.separator)));
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <property name="CONSOLE_LOG_PATTERN"
              value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:[%L]){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
    <property name="FILE_LOG_PATTERN"
              value="${FILE_LOG_PATTERN:-%d{yyyy-MM-dd HH:mm:ss.SSS} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%t] %-40.40logger{39} :[%L] %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
    <property name="LOG_PATH" value="/log/web/${app.dir}"/>
    <property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/${app.dir}-info}"/>

    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <include resource="org/springframework/boot/logging/logback/console-appender.xml"/>

    <appender name="FILE"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_FILE}.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_FILE}-%d{yyyy-MM-dd-HH00}.%i.log</fileNamePattern>
            <maxFileSize>100MB</maxFileSize>
            <maxHistory>360</maxHistory>
            <totalSizeCap>20GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>${FILE_LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <logger name="org.springframework.security" level="DEBUG"/>
    <logger name="org.springframework.cloud.sleuth.instrument.web.client.feign.TraceFeignClient" level="DEBUG"/>
    <logger name="org.springframework.web.servlet.DispatcherServlet" level="DEBUG"/>
    <logger name="org.springframework.cloud.sleuth.instrument.web.TraceFilter" level="DEBUG"/>
    <logger name="com.jarvis.cache" level="DEBUG"/>
    <logger name="com.youli" level="DEBUG"/>

    <logger name="org.springframework.security.web.util.matcher" level="INFO"/>

    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
    </root>
</configuration>
1-2. 스프링 부트 실행 클래스 수정
아래 코드를 `main` 메서드 내에 추가하여 실행 디렉터리 정보를 환경 변수로 설정한다.
public static void main(String[] args) {
    String currentDir = System.getProperty("user.dir");
    System.setProperty("app.dir", currentDir.substring(currentDir.lastIndexOf(File.separator)));
    SpringApplication.run(AppGatewayApplication.class, args);
}
1-3. 로그 출력 코드 작성 (Lombok 활용)
Lombok은 `@Slf4j` 어노테이션을 통해 로거 객체를 자동 생성해주는 편리한 라이브러리이다. Maven 의존성 추가 후, 클래스에 `@Slf4j`를 붙이면 된다.
<!-- Lombok 의존성 -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
@RestController
@Slf4j
@RequestMapping("/demo/log")
public class LogController {

    @RequestMapping("/logMe")
    public String logMe(HttpServletRequest request) {
        String paramA = request.getParameter("a");
        String paramB = request.getParameter("b");

        // 문자열 연결 대신 포맷팅 자리 표시어 사용
        log.debug("입력 파라미터: a={}, b={}", paramA, paramB);

        return "ok";
    }
}
애플리케이션 실행 후 `D:\log\web\[프로젝트명]` 경로에서 로그 파일을 확인할 수 있다.

2. 로그 상황 예시: 404 에러 발생 시

특정 경로 요청 시 404 오류가 발생하면, 핸들러 매핑 단계에서 어떤 경로를 찾았는지 확인하기 위해 다음과 같이 설정한다.
<logger name="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping" level="DEBUG" />
이후 404 발생 시 다음과 같은 로그를 확인할 수 있다:
2017-12-14 11:35:17.691 DEBUG 4484 --- [nio-9050-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Looking up handler method for path /abcdsdfsdf
2017-12-14 11:35:17.694 DEBUG 4484 --- [nio-9050-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Did not find handler method for [/abcdsdfsdf]
2017-12-14 11:35:17.699 DEBUG 4484 --- [nio-9050-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Looking up handler method for path /error
2017-12-14 11:35:17.701 DEBUG 4484 --- [nio-9050-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Returning handler method [public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)]

3. 전방위 로그 추적 설정

3-1. 목표
간단한 두 단계의 설정으로 완료되며, 성공 시 로그에 전역 트레이스 ID가 포함된다. 예시: `[bootstrap,948cb6650eb020a6,948cb6650eb020a6,true]` — 이는 전체 요청의 트레이스 및 스팬 식별자이다.
2017-12-15 15:07:55.570  INFO [bootstrap,948cb6650eb020a6,948cb6650eb020a6,true] 5856 --- [nio-9001-exec-1] s.c.a.AnnotationConfigApplicationContext :[583] Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@40c35b01: startup date [Fri Dec 15 15:07:55 CST 2017]; parent: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@162be91c
3-2. 설정 절차
① pom.xml에 의존성 추가
<!-- 분산 로깅 추적: Zipkin + Kafka -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-sleuth-zipkin-stream</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-stream-kafka</artifactId>
</dependency>
② application.yml 설정
Kafka 및 ZooKeeper 노드 정보를 입력하고, 샘플링률을 1.0(100%)로 설정한다.
spring:
  cloud:
    stream:
      kafka:
        binder:
          brokers: 192.167.1.3:9092,192.168.1.4:9092,192.168.1.5:9092
          zkNodes: 192.168.1.3:2181
sleuth:
  sampler:
    percentage: 1.0
설정 완료. 매우 간단하다.

태그: Spring Boot Logback Zipkin kafka Sleuth

7월 17일 23:51에 게시됨