모니터링 시스템 구축기 2편 - Log
이 글은 우아한테크코스 백엔드 6기 냥인, 러쉬, 명오에 의해 작성되었습니다.
로깅 모니터링 도구 선택 기준
로그 모니터링의 경우에는 로그 프레임워크는 Logback, 모니터링 도구로는 Loki와 Promtail, Grafana를 사용했습니다. 메트릭 모니터링 시스템을 구축할 때와 마찬가지로 아래와 같은 기준을 적용하였습니다.
▶︎ 요구사항에서 언급된 대로 빠르게 구축할 수 있어야 한다.
▶︎ 스프링 부트와 연동하기 쉬워야 한다.
Logback을 사용한 이유는 단순히 Spring boot에 기본으로 내장되어 있는 로깅 라이브러리를 사용하기 위함이었습니다. Loki의 경우는 이미 Grafana를 사용하여 메트릭 대시보드를 구축해놨기 때문에 자연스럽게 후보군에 올랐고, 위 기준에 부합하였기 때문에 도입하기로 결정하였습니다.
구조
- Promtail은 Spring boot와 같이 Public subnet에 위치한 WAS에 설치됩니다.
- Loki와 Grafana는 Private subnet에 위치한 모니터링 서버에 설치합니다.
모니터링 흐름
Spring Boot 애플리케이션은 Logback을 통해 로그 파일을 생성합니다. 그리고 Promtail이 이 로그 파일을 수집하여 Loki로 전송합니다. Grafana는 Loki를 데이터 소스로 사용하여 로그 데이터를 시각화합니다.
로그 모니터링 시스템 구축
Logback 설정 파일 작성
Spring Application 로깅을 위해 Logback 프레임워크를 사용했습니다. Logback은 Spring framework에서 기본 라이브러리로 채택하고 있어 따로 의존성을 추가하지 않아도 됩니다.
Logback을 이용하여 로그 메세지 커스터마이징, 로그를 파일로 저장하기 위해서는 resources 디렉토리 하위에 logback.xml 파일을 작성해야합니다.
logback.xml
이나 다른 설정 파일이 없을 경우 Logback은 기본 설정을 사용합니다. 기본적으로 로그는 콘솔에 출력되며, 로그 레벨은 DEBUG
로 설정됩니다.
다음은 팀 크루루가 작성한 logback.xml 파일입니다.
-
logback.xml
<?xml version="1.0" encoding="UTF-8" ?> <configuration> <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/> <!-- Property --> <property name="CONSOLE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %clr(%5level) %cyan(%logger) - %msg%n"/> <property name="INFO_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %5level %logger - %msg%n"/> <property name="WARN_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %5level %M %logger - %msg%n"/> <property name="ERROR_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %5level %M %logger - %msg%n"/> <!-- Console Appender --> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>${CONSOLE_LOG_PATTERN}</pattern> </encoder> </appender> <!-- INFO Appender --> <appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender"> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>INFO</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> <encoder> <pattern>${INFO_LOG_PATTERN}</pattern> </encoder> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <fileNamePattern>./log/info/%d{yyyy-MM-dd}.%i.log</fileNamePattern> <maxFileSize>100MB</maxFileSize> <maxHistory>120</maxHistory> </rollingPolicy> </appender> <!-- WARN Appender --> <appender name="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender"> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>WARN</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> <encoder> <pattern>${WARN_LOG_PATTERN}</pattern> </encoder> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <fileNamePattern>./log/warn/%d{yyyy-MM-dd}.%i.log</fileNamePattern> <maxFileSize>100MB</maxFileSize> <maxHistory>120</maxHistory> </rollingPolicy> </appender> <!-- ERROR Appender --> <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> <encoder> <pattern>${ERROR_LOG_PATTERN}</pattern> </encoder> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <fileNamePattern>./log/error/%d{yyyy-MM-dd}.%i.log</fileNamePattern> <maxFileSize>100MB</maxFileSize> <maxHistory>120</maxHistory> </rollingPolicy> </appender> <!-- Local Profile --> <springProfile name="local"> <root level="INFO"> <appender-ref ref="CONSOLE"/> </root> </springProfile> <!-- Dev Profile --> <springProfile name="dev"> <root level="INFO"> <appender-ref ref="CONSOLE"/> <appender-ref ref="INFO"/> <appender-ref ref="WARN"/> <appender-ref ref="ERROR"/> </root> </springProfile> </configuration>
설정에 대해 간단히 살펴봅시다.
**<property>**
** 태그: **로그 패턴을 정의하는 속성- 로그 레벨별로 다른 패턴을 정의하였습니다.
타임 스탬프 - 스레드 - 로그 메시지
형식으로 출력합니다.- %d: 타임 스탬프를 출력하며 어떤 형식으로 출력할지 설정합니다.
- %thread: 에러가 발생한 스레드를 출력합니다.
- %5level: 에러 레벨을 출력합니다.
- %M: 에러가 발생한 메서드명을 출력합니다.
- %logger: 에러 클래스를 출력합니다.
- %msg: 에러 메시지를 출력합니다.
**<appender>**
** 태그: **로그를 출력하는 방식과 위치- ConsoleAppender는 로그를 Console에 출력합니다.
- 로깅 레벨에서 RollingFileAppender를 사용했습니다.
- LevelFilter를 통해 특정 레벨에 대해서만 수행하도록 설정할 수 있습니다.
- pattern을 통해 로그를 출력하는 패턴을 설정하고, ${}로 위에서 명시한 패턴으로 설정할 수 있습니다.
- rollingPolicy를 통해 롤오버 정책을 정할 수 있습니다.
- fileNamePattern으로 파일이름의 규칙을 설정할 수 있습니다. %i는 자동으로 인덱스 번호를 부여합니다.
- 로그 파일이 100MB에 도달하면 롤오버가 발생합니다.
- 120개의 로그 파일을 보관하며, 그 이후에는 가장 오래된 파일이 삭제됩니다.
**<springProfile>**
** 태그: **Spring의 프로파일에 따라 다른 로그 구성 적용- local 프로파일: 콘솔 로깅을 지원합니다.
- dev 프로파일에: 콘솔 로깅과 함께 파일 로깅도 지원합니다.
- INFO, WARN, ERROR 레벨에 따라 각각 다른 파일에 기록됩니다.
모니터링 서버에 Promtail 설치 및 설정하기
-
**docker-compose.promtail.yml **
모니터링 서버에 도커 컴포즈로 Promtail을 설치하기 위해 도커 컴포즈 파일을 작성합니다.
version: '3.8' services: promtail: container_name: promtail image: grafana/promtail:latest volumes: - ./promtail-config.yml:/etc/promtail/config.yml - ./log:/mnt/log - /var/log/nginx:/mnt/log/nginx command: -config.file=/etc/promtail/config.yml
호스트의 로그 파일을 도커 컨테이너 내부에서 사용하려면, 볼륨을 마운트해야 합니다.
다음은 Promtail의 설정 파일입니다.
-
promtail-config.yml
server: http_listen_port: 9080 # Promtail이 HTTP 요청을 받을 포트 grpc_listen_port: 0 # gRPC 요청을 받을 포트(0으 positions: filename: /tmp/positions.yaml clients: - url: http://Promtail_ip_주소:3100/loki/api/v1/push scrape_configs: - job_name: error_logs static_configs: - targets: - localhost labels: job: error_logs __path__: /mnt/log/error/*.log - job_name: info_logs static_configs: - targets: - localhost labels: job: info_logs __path__: /mnt/log/info/*.log - job_name: warn_logs static_configs: - targets: - localhost labels: job: warn_logs __path__: /mnt/log/warn/*.log - job_name: http_logs static_configs: - targets: - localhost labels: job: http_logs __path__: /mnt/log/nginx/*.log pipeline_stages: - regex: expression: '^(?P<remote_addr>[^\s]+) - - \[(?P<timestamp>[^\]]+)\] "(?P<method>[A-Z]+) (?P<request>[^ ]+) HTTP/[^"]+" (?P<status>\d+) (?P<body_bytes_sent>\d+)'
-
server
http_listen_port
: Promtail이 HTTP 요청을 받을 포트를 설정합니다.grpc_listen_port
: gRPC 요청을 받을 포트를 설정합니다. 0으로 설정하여 gRPC 서버를 비활성화하였습니다.
-
positions
Promtail이 로그 파일을 읽고 난 후, 마지막으로 읽은 위치를 기록하는 파일을 지정합니다. 로그 수집이 중단된 후 재시작될 때 이전에 읽은 위치에서 로그 수집을 재개할 수 있게 하기 위함입니다.
-
clients
로그 데이터를 푸시할 Loki instance의 URL을 설정합니다. Loki는 3100 port로 리스닝하고 있으므로 해당 port로 지정합니다. 또한 프롬테일이 위치한 인스턴스와 로키가 위치한 인스턴스는 같은 가용 공간내에 위치하기 때문에 private ip를 통해 통신하도록 설정했습니다.
-
scrape_configs
job_name
: 로그 수집 작업의 이름입니다.static_configs
targets
: 로그를 수집할 대상(로그 파일이 있는 서버의 주소)을 설정합니다.labels
: 로그에 붙일 레이블을 설정합니다.__path``**__**
**: **Promtail이 수집할 로그 파일의 경로를 지정합니다.
-
pipeline_stages
-
로그를 수집한 동안 추가로 처리할 단계를 설정합니다.
-
regex
: 정규 표현식을 사용해 로그 메시지에서 특정 필드를 파싱합니다.여기서는 Nginx 로그 파일에서
remote_addr
,timestamp
,method
,request
,status
,body_bytes_sent
등의 필드를 추출합니다.
-
-
모니터링 서버에 Loki 설치 및 설정하기
Loki는 로그 데이터의 flow에서 Grafana 대시보드의 쿼리 요청을 처리하고 로그를 시계열 데이터 형식으로 수집 및 가공-저장하는 역할을 가지고 있습니다.
-
docker-compose-monitor.yml
모니터링 서버에 도커 컴포즈로 Loki를 설치하기 위해 도커 컴포즈 파일을 작성합니다. 다음은 팀 크루루가 작성한 파일의 일부입니다.
loki: container_name: 'loki' image: grafana/loki:latest ports: - "3100:3100" volumes: - ./loki-config.yml:/etc/loki/local-config.yml command: -config.file=/etc/loki/local-config.yaml
컨테이너의 3100 port를 Host의 3100 port와 연결했습니다. 따라서 Host의 3100 port로 들어오는 트래픽은 컨테이너의 3100 port로 전달됩니다.
다음은 Loki 설정 파일입니다.
-
loki-config.yml
auth_enabled: false server: http_listen_port: 3100 common: instance_addr: 127.0.0.1 path_prefix: /tmp/loki storage: filesystem: chunks_directory: /tmp/loki/chunks rules_directory: /tmp/loki/rules replication_factor: 1 ring: kvstore: store: inmemory query_range: split_queries_by_interval: 24h results_cache: cache: embedded_cache: enabled: true max_size_mb: 100 query_scheduler: max_outstanding_requests_per_tenant: 4096 frontend: max_outstanding_per_tenant: 4096 schema_config: configs: - from: 2024-07-31 store: tsdb object_store: filesystem schema: v13 index: prefix: index_ period: 24h limits_config: retation_period: 168h
설정 내용을 간단히 살펴보겠습니다.
-
**auth_enabled: false**
인증 기능 활성화 여부를 설정합니다.
false
로 설정하면 인증 없이 Loki에 접근할 수 있습니다.참고로 프로덕션 환경에서는
true
로 설정하여 인증을 활성화하는 것이 보안상 좋습니다. -
**split_queries_by_interval**
쿼리 범위를 24시간 단위로 나누어 처리합니다.
-
**retention_period: 168h**
로그 데이터를 보관할 최대 기간을
168h
(7일)으로 설정합니다. 이 기간이 지나면 로그 데이터는 삭제됩니다. -
**configs**
**store**
: 인덱스 저장소 유형을tsdb
로 설정합니다. 시계열 데이터베이스를 사용하여 인덱스를 저장함을 의미합니다.
-
Grafana 대시보드 구성
해당 내용은 모니터링 시스템 구축기 1편 - Metric에서 자세히 다뤘으므로, 간략하게 다루고자 합니다.
Name을 임의로 지정한 뒤, Connection에서 Loki의 서버 URL을 입력하고 [Save & test]를 하면 설정이 완료됩니다.
Loki 대시보드를 생성합니다.
대시보드를 통해 Spring 애플리케이션에서 발생하는 Error log와 서버에서 발생하는 Nginx log를 모니터링 할 수 있습니다. 또한 Promtail에서 설정한 레이블별 log를 확인할 수 있습니다.