본문 바로가기
Server

Nest.js 로그 서버를 구축해보자

by 욧닭 2025. 2. 27.
반응형

🛫 들어가며

기존 AWS를 이용해 서버를 구성하면 option 으로 CloudWatch 선택해 log 와 모니터링을 자동으로 관리해주었어요.

그러나 지금 환경은 클라우드 서비스(Saas) 를 사용 할 수 없는 환경으로 window 에서 웹 어플리케이션(Nest.js) 의 로그 관리를 해야 하기 때문에 여러가지 플랫폼을 사용 했는데요

거기에 대해서 정리를 해볼까해요

🧰 사용 플랫폼 (기술 스택)

✔️ Docker

Docker 는 컨테이너 기반 VM 으로 로컬 환경과 별개로 VM 을 사용해서 서버를 띄울 수 있는 기술이예요.

그래서 OS에 의존성이 걸릴 수 있는 플랫폼들을 좀 더 유연하게 사용할 수 있도록 도와주는 기술 입니다

✔️ Nest.js

로그 서버에 로그를 적제 하기 위해서 웹 어플리케이션으로 Nest.js 를 사용했어요.
개인 취양이지만 Node.js 같은 경량화 프레임워크는 코드 자율성이 높기 때문에 그만큼 불안한게 있더라구요

그래서 나름 규격화 되어 있는 프레임워크를 사용해서 코드 구성을 좀 더 탄탄하게 했어요

✔️ Promtail

웹 어플리케이션에서 log 파일을 생성했다면 그 log 파일을 수집해 Loki 로 전달해주는 역할을 하는 플랫폼 입니다.

✔️ Loki

전달 받은 log 들을 저장하고 쿼리를 통해 원하는 log 정보를 얻을 수 있도록 도와주는 플랫폼입니다.

✔️ Grafana

대표적인 모니터링 플랫폼으로 GUI가 깔끔하게 구성되어 있어서 선택 했어요 GrafanaLoki 와 연동을 할 수 있어 로그 쿼리를 보다 깔끔하게 요청 할 수 있고, 실시간 로그현황도 볼 수 있는 플랫폼입니다.

🗺️ 구성도

Nest.js

처음으로 로그를 쌓을 웹 어플리케이션이 필요해요

로그를 파일로 쌓기 위해선 winston 이라는 라이브러리가 필요해요 그리고 실질적으로 winston-daily-rotate-file 에서 winston 의 로그를 추출해서 log 파일을 만들게 되요

📜 Logger.class

import winston from 'winston';
import DailyRotateFile from 'winston-daily-rotate-file';

const logger = winston.createLogger({
  level: process.env.NODE_ENV === 'prod' ? 'info' : 'debug',
  format: winston.format.combine(
    winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
    winston.format.printf(({ timestamp, level, ...meta }) => {
      const traceId = this.getTraceId();
      return `${timestamp} [${traceId}] ${level.toUpperCase()} ${
        Object.keys(meta).length ? JSON.stringify(meta) : ''
      }`;
    }),
  ),
  transports: [
    new winston.transports.Console(),
    new DailyRotateFile({
      dirname: logDir, // 로그 파일이 생성될 디렉토리
      filename: '%DATE%.log', // 파일 이름 형식
      datePattern: 'YYYY-MM-DD', // 날짜 형식
      zippedArchive: false, // 오래된 로그를 압축
      maxFiles: '14d', // 최대 보관 기간
    }),
  ],
});

크게 총 3개의 key 가 존재해요

  • level
    • 로그 레벨은 prod(운영 서버) 에서만 info 레벨 까지 수집하고 나머지 개발 서버, QA 서버 등 에선 debug 레벨까지 사용한다는 뜻이예요
  • format
    • winston 를 사용해서 로그를 찍는 다면 다음과 같은 포멧으로 찍어라 라는 뜻이예요.
  • transports
    • 서버에 찍힌 로그들을 log 파일로 만들기 위한 초기 값이예요

이 세가지 중에서 로그를 찍기 위한 key 는 transports 예요

transports

new winston.transports.Console() 란 format으로 설정한 로그를 console 즉 터미널로 출력하는 거예요

new DailyRotateFilewinston-daily-rotate-file 라이브러리에서 사용할 수 있는 class 예요

해당 설정을 해줘야 log 파일을 생성하게 돼요.

만약 서버의 저장용량이 적을시엔 maxSize 를 설정 할 수 있는데 그렇게 되면 promtail 에서 maxSize 이후에 생성되는 로그파일을 인식하지 못해서 로그가 누락되는 경우가 있어 주의 해야 해요.

이러한 설정을 class 에 생성자에 해도 좋고 따로 메서드로 빼서 호출해서 사용해도 좋을 것 같아요

Docker

Docker 를 구성하기 위해 docker-compose.yml 을 사용했어요
docker cli 를 사용해도 되지만 저는 스크립트로 관리해 재사용하는 것을 좋아하기 때문에 파일로 관리하게 되었어요

📜 docker-compose.yml

version: '3.8'

services:
  loki:
    image: grafana/loki:latest
    container_name: loki
    ports:
      - "3100:3100"
    command: -config.file=/etc/loki/local-config.yaml
    networks:
      - app
    restart: always

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    ports:
      - "3111:3000"
    volumes:
      - ./grafana-data:/var/lib/grafana
    networks:
      - app
    restart: always

  promtail:
    image: grafana/promtail:latest
    container_name: promtail
    volumes:
      - ./promtail-config.yml:/etc/promtail/config.yml
      - C:/ProgramData/Jenkins/.jenkins/workspace:/var/app # please change path
    networks:
      - app
    depends_on:
      - loki
    command:
      - -config.file=/etc/promtail/config.yml
    restart: always

networks:
  app:
    external: true
    name: app

크게 보자면 미리 만들어둔 app 네트워크에서 컨테이너를 실행하게 되어 있어요
이렇게 하나의 네트워크로 묶에 두면 네트워크 내에 있는 컨테이너들 간의 데이터 통신이 훨신 편해지게 돼요.

예를 들면 promtail 에서 loki 로 로그 데이터를 넘겨야 한다면 http://loki:3100/loki/api/v1/push 다음과 같이 도메인을 컨테이너 네임으로 설정을 할 수 있어요

promtail -> http://loki:3100/loki/api/v1/push -> loki

✨ 도메인을 컨테이너 네임으로 사용 가능

앞서 설명드린 기술 스텍들을 docker-compose 를 통해 컨테이너를 구성하면 돼요

여기서 중요한것이 promtail 의 config 파일을 생성하여 Nest.js 에서 생성한 log 파일을 트레킹하고 loki 에세 전송하는 스크립트를 구성해야 해요

📜 promtail-config.yml

server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /tmp/positions.yaml

clients:
  - url: http://loki:3100/loki/api/v1/push

scrape_configs:
  - job_name: your_job_name
    static_configs:
      - targets:
          - localhost
        labels:
          job: your_job_name
          app: backend
          __path__: your_log_path

server 내에 작성된 내용들은 서버 포트를 설정하는 거예요
http_listen_portpromtail 서버의 HTTP 포트를 9080 으로 설정하겠다예요
그러나 docker 컨테이너를 만들 때 따로 외부 포트를 설정 해두지 않았기 때문에 사용하지 않아요

grpc_listen_port 는 grpc 포트를 설정하는 것이지만 0 을 통해서 설정 하지 않았어요

clients 는 로그 데이터를 전송하기 위해 loki API 를 사용해요

실직적으로 로그를 수집하기 위한 설정은 scrape_configs 여기서 하게 돼요
job_name 을 통해 수집한 로그를 그룹화 하고
targetslocalhost 로 하여 promtail 의 로컬에서 log 를 수집 하게 돼요

여기서 잠깐!!

❓ 어떻게 promtail 컨네이너 내에서 Nest.js 에서 생성된 로그를 수집하는거지?

-> docker-compose 에서 promtail 컨테이너를 만들 때 volume 경로를 통해 local 환경의 log 데이터를 promtail 컨테이너 내의 log 데이터와 매핑 시켜놓았기 때문에 가능합니다!

labels 에선 중요한건 __path__ 값 입니다. volume 으로 매핑 시켜놓은 로그 데이터를 지정해 놓아야 수집을 하기 때문이예요

이떄 __path__targetlocalhost 이기 때문에 로컬 환경이 아닌 컨테이너 환경의 log 파일 path 를 지정 해야 해요

그리고

docker-compose up -d --build

명령어를 통해 컨테이너를 실행 시켜요

😀 실행

컨테이너에 설정한 grafana 포트로 접속합니다

grafana login

초기 로그인 정보는 admin / admin 이예요

 

data souces 설정

Loki의 기능을 사용하기 위해서 Data source 를 연결 설정 해야해요

 

add new data source

해당 버튼을 눌러 주고

 

search loki

 

loki 를 찾아 줍니다.

 

url setting

docker network 가 app 으로 묶여 있기 때문에 garafana 역시 loki 컨테이너를 접근 할 때 컨테이너 네임을 도메인으로 사용해도 돼요

 

save & test

 

버튼을 누르면 loki 셋팅이 돼요

 

explore 접속

이제 수집한 로그들을 loki를 통해 확인 할 수 있어요 Explore를 들어가세요

 

loki explore

 

좌측상단에 Loki datasource 를 선택해요

Label filters 는 앞서 promtail.config.yml 에 설정한 값을 넣어줘야 해요

Line contains 에 찾고 싶은 문자열을 입력하고 검색 하게 되면 아래에 원하는 로그 데이터가 필터링 되서 보이게 되요

 

🛬 마치며

AWS 를 사용해서 여러가지 옵션을 통해 로그관리를 자동으로 해주는 서비스를 사용해보는 것도 좋지만 이렇게 직접 커스텀마이징 해서 로그 서버를 구성하니까 좋았고

 

어떻게 웹 어플리케이션의 log 가 어떤식으로 로그를 관리하는 서버로 넘어가는지 아는 의미 있는 시간이 되었어요

추후엔 Slack 과 logger 서버를 사용해서 에러가 났을때 slack 으로 알람이 가게 되고 Detail 버튼을 slack 에서 눌렀을때 만들어 놓은 log 서버로 이동하게 되는 구성을 포스팅 해볼까 해요

 

긴글 봐주셔서 감사해요~ 

반응형

'Server' 카테고리의 다른 글

window 에서 Nginx+Nodejs+SSL 설정하기  (0) 2024.12.26
window 에서 nginx 설정 하기  (0) 2024.12.26

댓글