ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 문제 해결을 위한 증거자료 수집하기 (feat.로그)
    운영 중인 서비스/Coconut. 2024. 2. 17. 17:27

     

    aws에서 제공하는 프리티어 EC2를 사용해서 개발 환경을 운영하고 있습니다.

    도커 컨테이너 위에 Spring 서버가 떠 있는데, 종종 서버가 종료되는 상황이 발생해서 로그를 남겨보려고 합니다.

     

    이전 글에서는 인프라 레벨에서 문제 확인을 위해 알림을 세팅했다면, 이번 글에서는 제 코드 레벨에서 문제가 있는지 확인을 위해서 로그를 남기는 방법을 찾아보겠습니다. 

    (이전글: https://iwsaitw.tistory.com/entry/Discord를-사용해서-간단한-EC2-알림-받기

     

     


     

     

    1. 로그 파일 생성

    spring-boot-start-web을 의존성으로 사용하면 별다른 설정 없이도 Logback을 사용해서 콘솔창에서 로그를 확인할 수 있습니다.

    @RequiredArgsConstructor
    @Service
    public class PostServiceImpl implements PostService {
        private static final Logger logger = LoggerFactory.getLogger(PostServiceImpl.class);
    
        ...생략
        
        public void logging() {
          logger.info("로그를 남깁니다.");
        }
        
    }

     

    저는 들어오는 모든 요청에 대해서 처음 요청이 들어올 때, 그리고 응답이 반환될 때 2차례 로그를 남기려고 합니다.

    여러가지 방법 중에 filter를 사용해서 사이클의 가장 앞단에서 로그를 남겨보겠습니다.

    @Slf4j
    @RequiredArgsConstructor
    @WebFilter(filterName = "LogFilter", urlPatterns = {"/api/v1/*"})
    public class LogFilter extends OncePerRequestFilter {
    
      @Override
      protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        long startTime = System.currentTimeMillis();
        log.info("[Filter] Request In: {} {}", request.getMethod(), request.getRequestURI());
    
        try {
          filterChain.doFilter(request, response);
        } catch (Exception e) {
          log.error("[Filter] Request: {} {} | Error: {}", request.getMethod(), request.getRequestURI(), e.getMessage(), e);
          throw e;
        } finally {
          long endTime = System.currentTimeMillis();
          log.info("[Filter] Request Out: {} {} | Status: {} | End Time: {} | Duration: {} ms",
                  request.getMethod(), request.getRequestURI(), response.getStatus(), endTime, (endTime - startTime));
    
          if (!response.isCommitted()) {
            log.warn("[Filter] Request: {} {} | Warning: Response not committed", request.getMethod(), request.getRequestURI());
          }
        }
      }
    }

     

    서버에 요청을 보내니 필터 구간에서 적절하게 로그가 찍히고 있습니다.

     

    평소 개발 환경이라면 콘솔에 찍히는 로그로 충분하겠지만, 배포를 해놓고 운영을 하는 경우에는 파일로 남기는 편이 찾아서 살펴보기에 더 수월할 것이라고 생각됩니다.

    Logback을 사용해서 파일에 로그를 남기려면 logback-spring.xml 파일을 생성해주어야 합니다.

    // src/resources/logback-spring.xml
    
    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
    
        <!-- 콘솔 애펜더 설정 -->
        <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss} - %msg%n</pattern>
            </encoder>
        </appender>
    
        <!-- 모든 로그를 기록하는 파일 애펜더 -->
        <appender name="FILE_APPENDER_ALL" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>logs/all-logs.log</file>
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
            </encoder>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>logs/all-logs-%d{yyyy-MM-dd}.log</fileNamePattern>
                <maxHistory>30</maxHistory>
            </rollingPolicy>
    
        </appender>
    
        <!-- 필터 로그를 기록하는 애펜더 설정 -->
        <appender name="FILE_APPENDER_FILTER" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>logs/filter-app.log</file>
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
            </encoder>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>logs/filter-app-%d{yyyy-MM-dd}.log</fileNamePattern>
                <maxHistory>30</maxHistory>
            </rollingPolicy>
        </appender>
    
        <!-- 모든 로그 비동기 애펜더 설정 -->
        <appender name="ASYNC_ALL" class="ch.qos.logback.classic.AsyncAppender">
            <appender-ref ref="FILE_APPENDER_ALL"/>
            <queueSize>512</queueSize>
            <discardingThreshold>0</discardingThreshold>
            <includeCallerData>true</includeCallerData>
        </appender>
    
        <!-- 필터 로그 비동기 애펜더 설정 -->
        <appender name="ASYNC_FILTER" class="ch.qos.logback.classic.AsyncAppender">
            <appender-ref ref="FILE_APPENDER_FILTER"/>
            <queueSize>512</queueSize>
            <discardingThreshold>0</discardingThreshold>
            <includeCallerData>true</includeCallerData>
        </appender>
    
    
        <!-- 루트 로거 설정 -->
        <root level="info">
            <appender-ref ref="CONSOLE"/>
            <appender-ref ref="ASYNC_ALL"/>
        </root>
    
        <!-- 특정 로거에 대해 비동기 파일 로깅 활성화 -->
        <logger name="com.jdh.community_spring.common.filter" level="info">
            <appender-ref ref="ASYNC_FILTER"/>
        </logger>
    </configuration>

     

    개발 중에는 콘솔에서 확인하는 것이 편하기 때문에 기존에 콘솔에서 찍히던 로그는 유지하도록 설정하였습니다.

        // 콘솔에 남기는 것을 유지
        <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss} - %msg%n</pattern>
            </encoder>
        </appender>
        
        ... 생략
        
        <root level="info">
            <appender-ref ref="CONSOLE"/>    // 콘솔에 남기는 것을 유지
            <appender-ref ref="ASYNC_ALL"/>
        </root>

     

    파일에 남길 때 2가지 파일로 분류해서 남기도록 설정하였습니다. all-log에는 모든 로그를 남기고, filter-log에는 필터 레벨에서 발생하는 로그만 따로 남기도록 적용하였습니다.

        ... 생략
        
        
        // 모든 로그 파일에 기록
        <appender name="FILE_APPENDER_ALL" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>logs/all-logs.log</file>
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
            </encoder>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>logs/all-logs-%d{yyyy-MM-dd}.log</fileNamePattern>
                <maxHistory>30</maxHistory>
            </rollingPolicy>
    
        </appender>
    
    	// 필터에서 발생하는 로그만 파일에 기록
        <appender name="FILE_APPENDER_FILTER" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>logs/filter-app.log</file>
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
            </encoder>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>logs/filter-app-%d{yyyy-MM-dd}.log</fileNamePattern>
                <maxHistory>30</maxHistory>
            </rollingPolicy>
        </appender>
        
        
        ... 생략

     

    그리고 파일에 남기는 작업은 동기적으로 실행될 경우에 서버 성능 저하를 초래할 수 있기 때문에 비동기로 작동하도록 설정해주었습니다.

        ... 생략
    
        // 모든 로그를 쓰는 작업을 비동기화
        <appender name="ASYNC_ALL" class="ch.qos.logback.classic.AsyncAppender">
            <appender-ref ref="FILE_APPENDER_ALL"/>
            <queueSize>512</queueSize>
            <discardingThreshold>0</discardingThreshold>
            <includeCallerData>true</includeCallerData>
        </appender>
    
        // 필터 로그를 쓰는 작업을 비동기화
        <appender name="ASYNC_FILTER" class="ch.qos.logback.classic.AsyncAppender">
            <appender-ref ref="FILE_APPENDER_FILTER"/>
            <queueSize>512</queueSize>
            <discardingThreshold>0</discardingThreshold>
            <includeCallerData>true</includeCallerData>
        </appender>
        
        ... 생략

     

    이제 서버에 요청을 보내면 위에서 설정한 경로에 로그파일이 생성됩니다.

     

     

     

    2. AWS EBS 볼륨 마운트

    현재 배포 환경에서는 도커 컨테이너에서 서버를 실행 중입니다. 기본적으로 컨테이너가 종료될 때 컨테이너 내부의 모든 데이터도 함께 정리 되기 때문에 로그 파일을 남겨두어도 소실될 가능성이 큽니다.

    영구적으로 보관하기 위해, 도커 컨테이너 레벨이 아닌 호스트 레벨에 남기거나, EC2 인스턴스의 데이터를 영구적으로 보관할 수 있는 EBS에 남기는 방법을 고려할 수 있을 것 같습니다.

     

    EC2를 terminate 해서 로그 자료를 몇번 날린 적이 있기 때문에, EC2 인스턴스와 독립적인 EBS 공간에 로그를 남겨보겠습니다.

    로그 생성을 위한 EBS를 생성해보겠습니다. 

     

    로그만 조금 저장할 예정이니 5GB면 충분할 것이라고 생각합니다. 프리티어는 30GB 까지 무료인 것으로 알고 있습니다.

     

    볼륨 생성 후, 적절한 EC2 인스턴스와 연결을 진행합니다.

     

    이름을 뭘로 만들어도 인스턴스에서는 /dev/xvdf로 사용이 된다고 하니 그냥 정해주는 이름을 사용하겠습니다.

     

    이제 ssh로 EC2에 붙어서 방금 만들어준 EBS가 잘 연결되었는지 확인해 보겠습니다.

    $ lsblk -f

     

    기존에 없던 xvdf가 새로 생긴 것을 확인할 수 있습니다.

     

     

    포맷이 안되어 있으니 ext4 포맷으로 설정을 해주겠습니다. 

    $ sudo mkfs -t ext4 /dev/xvdf

     

    포맷 확인완료!

     

     

    그럼 이제 EC2에 EBS 볼륨의 마운트 포인트로 사용될 디렉토리를 설정해줍니다.

    $ sudo mkdir /mnt/log-ebs-volume

     

    마지막으로 EBS를 해당 디렉토리에 마운트 해줍니다.

    $ sudo mount /dev/xvdf /mnt/log-ebs-volume

     

    이제 lsblk 명령어를 사용되면 정상적으로 마운트된 것을 확인할 수 있습니다.

     

     

    3. 도커 컨테이너에 볼륨 마운트

    이제 로그가 저장 되길 원하는 경로를 볼륨으로 마운트하면서 컨테이너를 실행해주면 됩니다.

    /mnt/log-ebs-volume 경로에 EBS 볼륨을 마운트 했고, 컨테이너에서는 /workspace/spring/logs 에 로그가 저장되고 있습니다.

    $ sudo docker run -v /mnt/log-ebs-volume:/workspace/spring/logs app

     

    요청을 보내준 후에 /mnt/log-ebs-volume 경로에 로그가 생성되는지 확인해 보겠습니다.

    cat으로 확인해보니 로그가 잘 남은 것을 확인할 수 있습니다.

     

    다시 인스턴스 세팅을 하려면 조금 귀찮긴 하지만 제대로 작동하는지 확인하는것이 좋으니, 인스턴스를 terminate 한 이후에도 그대로 로그를 확인할 수 있는지 보겠습니다.

     

    AWS 콘솔에서 EBS를 찾아서 새로운 EC2에 attatch를 진행합니다.

    이후에 ssh로 새로운 ec2에 접근하여 /mnt/log-ebs-volume 디렉토리를 만든 후 

    mount 명령어로 마운트 해주면 이전 로그를 다시 확인할 수 있습니다.

    $ sudo mkdir /mnt/log-ebs-volume
    $ sudo mount /dev/xvdf /mnt/log-ebs-volume

     

     

     

    이제 서버에 이슈가 생겨도 살펴볼 수 있는 로그를 남겨두었으니, 조금은 안심이 됩니다!

    감사합니다.

     

     

     

Designed by Tistory.