본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 22. · 3 Views
EFK 스택 로깅 완벽 가이드
마이크로서비스 환경에서 로그를 효과적으로 수집하고 분석하는 EFK 스택(Elasticsearch, Fluentd, Kibana)의 핵심 개념과 실전 활용법을 초급 개발자도 쉽게 이해할 수 있도록 정리한 가이드입니다.
목차
1. EFK 스택 소개
신입 개발자 이준호 씨는 회사에서 처음으로 마이크로서비스 프로젝트에 투입되었습니다. 서버가 10개나 되는 환경에서 버그를 찾으려니 각 서버의 로그를 일일이 확인해야 했습니다.
"도대체 어느 서버에서 에러가 난 거지?" 막막해하는 준호 씨에게 선배 강민수 씨가 다가왔습니다.
EFK 스택은 Elasticsearch, Fluentd, Kibana의 앞글자를 딴 로깅 시스템입니다. 마치 도서관에 사서가 있어 수많은 책을 체계적으로 정리하고 찾아주는 것처럼, EFK는 여러 서버에서 발생하는 로그를 한곳에 모아 쉽게 검색하고 분석할 수 있게 해줍니다.
분산 환경에서 로그 관리의 핵심 도구입니다.
다음 코드를 살펴봅시다.
version: '3.8'
services:
# Elasticsearch: 로그 저장소
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.5.0
environment:
- discovery.type=single-node
- xpack.security.enabled=false
ports:
- "9200:9200"
# Fluentd: 로그 수집기
fluentd:
image: fluent/fluentd:v1.15
ports:
- "24224:24224"
depends_on:
- elasticsearch
# Kibana: 시각화 도구
kibana:
image: docker.elastic.co/kibana/kibana:8.5.0
ports:
- "5601:5601"
depends_on:
- elasticsearch
이준호 씨는 입사 2개월 차 신입 개발자입니다. 오늘도 열심히 코드를 배포했는데, 갑자기 사용자로부터 에러 신고가 들어왔습니다.
문제는 회사의 서비스가 10개의 마이크로서비스로 나뉘어 있다는 점이었습니다. 준호 씨는 각 서버에 SSH로 접속해서 로그 파일을 하나씩 확인하기 시작했습니다.
첫 번째 서버, 두 번째 서버, 세 번째 서버... 5번째 서버를 확인할 때쯤 이미 한 시간이 지나있었습니다.
"이렇게 하면 하루가 다 가겠는데?" 좌절하던 준호 씨에게 선배 강민수 씨가 다가왔습니다. "준호 씨, EFK 스택 쓰면 10초면 찾을 수 있어요." 그렇다면 EFK 스택이란 정확히 무엇일까요?
쉽게 비유하자면, EFK는 마치 대형 쇼핑몰의 통합 고객센터와 같습니다. 쇼핑몰에 매장이 100개 있다고 상상해보세요.
손님이 불만을 제기하면 각 매장을 일일이 찾아다니는 대신, 통합 고객센터에서 모든 매장의 기록을 한 번에 조회할 수 있습니다. EFK도 이처럼 여러 서버의 로그를 한곳에 모아 통합 관리합니다.
EFK는 세 가지 도구의 조합입니다. Elasticsearch는 로그를 저장하는 데이터베이스입니다.
Fluentd는 각 서버에서 로그를 수집하는 수집기입니다. Kibana는 저장된 로그를 그래프와 차트로 보여주는 시각화 도구입니다.
EFK가 없던 시절에는 어땠을까요? 개발자들은 각 서버에 직접 접속해서 로그 파일을 확인해야 했습니다.
서버가 3개만 되어도 번거로운데, 10개, 20개가 되면 거의 불가능에 가까웠습니다. 더 큰 문제는 로그가 각 서버에 흩어져 있어서 전체적인 흐름을 파악하기 어렵다는 점이었습니다.
예를 들어 사용자 주문이 실패했다고 가정해봅시다. 주문 서비스, 결제 서비스, 재고 서비스를 거치는데, 어느 단계에서 문제가 생긴 건지 알려면 세 서버의 로그를 시간 순서대로 맞춰봐야 했습니다.
마치 퍼즐 조각을 맞추는 것처럼 시간이 오래 걸렸습니다. 바로 이런 문제를 해결하기 위해 EFK 스택이 등장했습니다.
EFK를 사용하면 모든 서버의 로그가 자동으로 Elasticsearch에 모입니다. 개발자는 Kibana 웹 페이지에 접속해서 검색어만 입력하면 됩니다.
"에러가 언제 발생했지?" "이 사용자 ID의 로그만 보고 싶어" 같은 질문에 즉시 답을 얻을 수 있습니다. 위의 Docker Compose 코드를 살펴보겠습니다.
먼저 Elasticsearch 서비스를 정의합니다. 포트 9200으로 접속할 수 있으며, single-node 모드로 간단하게 실행합니다.
보안 설정은 테스트 환경이므로 비활성화했습니다. 다음으로 Fluentd 서비스입니다.
포트 24224로 로그를 받아서 Elasticsearch로 전송합니다. depends_on 설정으로 Elasticsearch가 먼저 실행되도록 보장합니다.
마지막으로 Kibana 서비스입니다. 포트 5601로 웹 인터페이스를 제공하며, Elasticsearch의 데이터를 시각화합니다.
실제 현업에서는 어떻게 활용할까요? 대부분의 스타트업과 중견기업에서 마이크로서비스를 운영할 때 EFK를 표준으로 사용합니다.
예를 들어 전자상거래 회사에서 블랙프라이데이 같은 대규모 이벤트를 진행한다고 가정해봅시다. 평소보다 10배 많은 트래픽이 발생하면 어느 서비스에서 병목이 생기는지 실시간으로 파악해야 합니다.
Kibana 대시보드에서 각 서비스의 응답 시간, 에러율을 한눈에 모니터링할 수 있습니다. 준호 씨는 강민수 씨의 설명을 듣고 바로 EFK를 설치했습니다.
Docker Compose 파일 하나로 3분 만에 환경이 구축되었습니다. 이제 10개 서버의 로그를 Kibana 하나에서 검색할 수 있게 되었습니다.
하지만 주의할 점도 있습니다. EFK는 강력하지만 초기 학습 곡선이 있습니다.
Elasticsearch의 쿼리 문법, Fluentd의 설정 방법, Kibana의 대시보드 구성 등 배워야 할 것들이 있습니다. 하지만 한 번 익혀두면 평생 써먹을 수 있는 기술입니다.
강민수 씨는 마지막으로 조언했습니다. "처음에는 어려워 보이지만, 하나씩 배우다 보면 금방 익숙해져요.
오늘은 기본 설치만 해봅시다." EFK 스택을 제대로 이해하면 분산 시스템의 로그 관리가 훨씬 쉬워집니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
💡 - Docker Compose로 로컬 환경에 EFK를 쉽게 구축할 수 있습니다
- 작은 프로젝트부터 시작해서 점진적으로 확장하세요
- Elasticsearch는 메모리를 많이 사용하므로 최소 4GB RAM 권장합니다
2. Elasticsearch 역할
준호 씨는 EFK 스택을 설치하고 나서 궁금해졌습니다. "Elasticsearch가 로그를 저장한다는데, MySQL이나 MongoDB와 뭐가 다른 거지?" 강민수 씨는 웃으면서 대답했습니다.
"일반 데이터베이스로는 로그 검색이 너무 느려요. Elasticsearch는 검색에 특화된 데이터베이스거든요."
Elasticsearch는 검색과 분석에 최적화된 NoSQL 데이터베이스입니다. 마치 도서관의 색인 카드처럼, 모든 단어를 미리 색인해두어서 수백만 건의 로그에서도 1초 안에 원하는 내용을 찾아냅니다.
실시간 검색이 필요한 로그 시스템의 핵심 저장소입니다.
다음 코드를 살펴봅시다.
// Node.js에서 Elasticsearch 클라이언트 사용 예제
const { Client } = require('@elastic/elasticsearch');
// Elasticsearch 연결
const client = new Client({ node: 'http://localhost:9200' });
async function searchLogs() {
// 최근 1시간 동안의 에러 로그 검색
const result = await client.search({
index: 'application-logs',
body: {
query: {
bool: {
must: [
{ match: { level: 'ERROR' } }, // 에러 레벨만
{ range: { timestamp: { gte: 'now-1h' } } } // 1시간 이내
]
}
},
sort: [{ timestamp: 'desc' }], // 최신순 정렬
size: 100 // 최대 100개
}
});
console.log(`총 ${result.hits.total.value}개의 에러 발견`);
return result.hits.hits;
}
준호 씨는 이전 프로젝트에서 MySQL에 로그를 저장해본 경험이 있었습니다. 로그가 10만 건만 넘어가도 검색이 느려져서 결국 오래된 로그는 삭제해야 했습니다.
"Elasticsearch는 뭐가 다른데요?" 강민수 씨가 화면을 켜서 보여줍니다. Kibana에서 "ERROR"를 검색하자 1000만 건의 로그 중에서 0.5초 만에 결과가 나왔습니다.
준호 씨는 놀라서 눈이 휘둥그레졌습니다. 그렇다면 Elasticsearch는 어떻게 이렇게 빠를까요?
쉽게 비유하자면, Elasticsearch는 책의 색인(Index)과 같습니다. 두꺼운 백과사전에서 "호랑이"라는 단어를 찾는다고 상상해보세요.
첫 페이지부터 끝까지 읽으면 몇 시간이 걸리지만, 뒤쪽의 색인을 보면 "호랑이 - 235페이지"라고 바로 나옵니다. Elasticsearch도 모든 단어의 위치를 미리 색인해두어서 검색이 빠릅니다.
이런 방식을 **역색인(Inverted Index)**이라고 부릅니다. 일반 데이터베이스는 "문서 1번에 어떤 단어가 있지?"를 저장하지만, Elasticsearch는 "ERROR라는 단어가 어느 문서에 있지?"를 저장합니다.
검색 관점에서는 후자가 훨씬 효율적입니다. MySQL 같은 관계형 데이터베이스로 로그를 관리하면 어떤 문제가 생길까요?
먼저 검색 속도가 느립니다. LIKE 쿼리로 "에러 메시지에 'timeout'이 포함된 로그"를 찾으려면 모든 행을 스캔해야 합니다.
로그가 100만 건이면 100만 번 확인하는 셈입니다. 인덱스를 걸어도 부분 검색은 여전히 느립니다.
두 번째 문제는 스키마의 경직성입니다. 로그는 서비스마다 형식이 다릅니다.
결제 서비스는 amount 필드가 있지만, 인증 서비스에는 없습니다. MySQL에서는 미리 테이블 구조를 정의해야 하는데, 로그처럼 유동적인 데이터에는 불편합니다.
세 번째는 확장성입니다. 로그는 매일 수백만 건씩 쌓입니다.
MySQL은 단일 서버의 한계가 명확하지만, Elasticsearch는 여러 서버로 쉽게 확장할 수 있습니다. 바로 이런 이유로 Elasticsearch가 로그 저장소로 선택됩니다.
Elasticsearch의 가장 큰 장점은 전문 검색(Full-text Search) 기능입니다. 단순히 정확히 일치하는 것만 찾는 게 아니라, 유사한 단어, 오타, 동의어까지 찾아줍니다.
"payment failed"를 검색하면 "payment failure"도 함께 나옵니다. 위의 코드를 자세히 살펴보겠습니다.
먼저 Elasticsearch 클라이언트를 생성합니다. localhost:9200으로 연결하는데, 이는 기본 포트입니다.
프로덕션 환경에서는 보안 설정과 함께 클러스터 주소를 사용합니다. 검색 쿼리는 JSON 형태로 작성합니다.
bool 쿼리로 여러 조건을 조합할 수 있습니다. must 배열에는 반드시 만족해야 할 조건을 넣습니다.
여기서는 "level이 ERROR"이고 "시간이 최근 1시간 이내"인 로그를 찾습니다. range 쿼리는 시간 범위 검색에 유용합니다.
gte는 "greater than or equal"의 약자로, 지정한 시간 이후를 의미합니다. now-1h는 현재 시각에서 1시간을 뺀 시점입니다.
결과는 timestamp 기준으로 내림차순 정렬됩니다. 최신 로그부터 보여주는 것이죠.
size는 최대 결과 개수를 제한합니다. 실제 현업에서는 어떻게 활용할까요?
대규모 서비스에서는 하루에 수십 GB의 로그가 발생합니다. 네이버나 카카오 같은 회사는 하루 로그가 TB 단위입니다.
이런 환경에서 "특정 사용자가 언제 로그인했는지" 찾으려면 Elasticsearch가 필수입니다. 예를 들어 고객센터에 "결제가 안 돼요"라는 문의가 들어왔다고 가정해봅시다.
고객의 주문 번호로 Elasticsearch를 검색하면, 주문 생성부터 결제 실패까지 전체 과정의 로그를 시간 순서대로 볼 수 있습니다. 문제가 결제 게이트웨이 타임아웃이었다는 것을 30초 만에 파악할 수 있습니다.
또 다른 활용 사례는 장애 대응입니다. 새벽 3시에 서버 장애 알림이 왔다고 가정해봅시다.
개발자는 침대에서 노트북을 켜고 Kibana에 접속합니다. "error AND database"로 검색하면 데이터베이스 관련 에러만 필터링됩니다.
원인을 빠르게 파악하고 조치할 수 있습니다. 하지만 주의할 점도 있습니다.
Elasticsearch는 메모리를 많이 사용합니다. 색인을 메모리에 캐싱하기 때문에 성능이 좋은 대신, 서버 사양이 받쳐줘야 합니다.
최소 4GB, 권장 8GB 이상의 RAM이 필요합니다. 또한 Elasticsearch는 분산 시스템입니다.
클러스터 구성, 샤드 분배, 레플리카 설정 등 배워야 할 개념이 많습니다. 하지만 기본 사용법은 어렵지 않으니, 먼저 단일 노드로 시작해서 점진적으로 배우면 됩니다.
준호 씨는 처음으로 Elasticsearch에 로그를 저장하고 검색해봤습니다. "와, 진짜 빠르네요!" 검색 결과가 0.3초 만에 나오는 것을 보며 감탄했습니다.
Elasticsearch를 제대로 이해하면 대규모 로그 검색도 쉬워집니다. 여러분도 직접 설치해서 실습해 보세요.
실전 팁
💡 - Elasticsearch는 JSON 기반이라 REST API로 쉽게 사용할 수 있습니다
- 색인(Index)은 MySQL의 테이블과 비슷한 개념입니다
- 프로덕션에서는 반드시 보안 설정(인증, 암호화)을 활성화하세요
3. Fluentd 로그 수집
EFK 스택의 구조를 이해한 준호 씨는 다음 질문을 던졌습니다. "그런데 각 서버의 로그가 어떻게 자동으로 Elasticsearch에 들어가는 거예요?" 강민수 씨는 Fluentd 설정 파일을 열어 보여주며 설명하기 시작했습니다.
"Fluentd가 각 서버에서 로그를 수집해서 Elasticsearch로 보내는 역할을 해요."
Fluentd는 다양한 소스에서 로그를 수집하고, 가공하고, 목적지로 전송하는 데이터 수집기입니다. 마치 우편 집배원이 여러 집에서 편지를 수집해 우체국으로 배달하는 것처럼, Fluentd는 여러 서버와 애플리케이션의 로그를 모아 Elasticsearch로 전달합니다.
로그 파이프라인의 핵심 요소입니다.
다음 코드를 살펴봅시다.
# fluentd.conf - Fluentd 설정 파일
<source>
@type forward
port 24224
bind 0.0.0.0
</source>
# Docker 컨테이너 로그 수집
<source>
@type tail
path /var/lib/docker/containers/*/*.log
pos_file /var/log/fluentd-docker.pos
tag docker.*
<parse>
@type json
time_key time
time_format %Y-%m-%dT%H:%M:%S.%NZ
</parse>
</source>
# 에러 로그만 필터링
<filter docker.**>
@type grep
<regexp>
key log
pattern /ERROR|FATAL/
</regexp>
</filter>
# Elasticsearch로 전송
<match docker.**>
@type elasticsearch
host elasticsearch
port 9200
index_name application-logs
<buffer>
flush_interval 10s
</buffer>
</match>
준호 씨는 애플리케이션에서 console.log로 로그를 출력하고 있었습니다. "이게 어떻게 Elasticsearch에 들어가는 거죠?" 로그가 화면에 출력되는 것과 데이터베이스에 저장되는 것은 다른 문제처럼 보였습니다.
강민수 씨가 설명을 시작했습니다. "Docker를 쓰고 있죠?
Docker는 컨테이너의 표준 출력을 로그 파일로 저장해요. Fluentd는 그 파일을 읽어서 Elasticsearch로 보냅니다." 그렇다면 Fluentd는 정확히 무엇을 하는 걸까요?
쉽게 비유하자면, Fluentd는 물류 센터의 컨베이어벨트와 같습니다. 택배 상자(로그)가 여러 경로로 들어오면, 컨베이어벨트가 이를 분류하고 포장하고 목적지별로 보냅니다.
어떤 상자는 필터링되고, 어떤 상자는 라벨이 추가되고, 최종적으로 올바른 트럭(Elasticsearch)에 실립니다. Fluentd는 입력(Input) - 필터(Filter) - 출력(Output) 파이프라인으로 동작합니다.
이 구조 덕분에 매우 유연하게 로그를 처리할 수 있습니다. Fluentd 없이 로그를 관리하면 어떤 문제가 생길까요?
각 애플리케이션에서 직접 Elasticsearch에 로그를 전송해야 합니다. 애플리케이션 코드에 Elasticsearch 클라이언트 라이브러리를 추가하고, 연결 설정을 하고, 에러 처리를 해야 합니다.
서비스가 10개면 10번 반복입니다. 더 큰 문제는 결합도입니다.
나중에 Elasticsearch 대신 다른 저장소를 쓰고 싶다면? 10개 서비스를 모두 수정하고 재배포해야 합니다.
Fluentd를 쓰면 설정 파일만 바꾸면 되니 훨씬 유연합니다. 세 번째 문제는 로그 손실입니다.
Elasticsearch가 일시적으로 다운되면 어떻게 될까요? 애플리케이션에서 직접 전송하면 그 시간의 로그는 사라집니다.
Fluentd는 버퍼링 기능이 있어서 일시적인 장애에도 로그를 보존합니다. 바로 이런 이유로 Fluentd를 중간에 두는 것입니다.
Fluentd의 가장 큰 장점은 플러그인 생태계입니다. 500개 이상의 플러그인이 있어서 거의 모든 소스와 목적지를 지원합니다.
파일, Syslog, HTTP, Kafka, AWS S3, MongoDB 등 무엇이든 연결할 수 있습니다. 위의 설정 파일을 단계별로 살펴보겠습니다.
첫 번째 source 블록은 forward 입력을 정의합니다. 다른 Fluentd나 애플리케이션에서 포트 24224로 로그를 전송할 수 있습니다.
bind 0.0.0.0은 모든 네트워크 인터페이스에서 수신한다는 의미입니다. 두 번째 source는 파일을 읽습니다.
tail 타입은 리눅스의 tail -f 명령처럼 파일의 끝을 계속 추적합니다. Docker 컨테이너 로그 파일 경로를 지정했습니다.
pos_file은 어디까지 읽었는지 기록하는 파일로, Fluentd 재시작 시 중복을 방지합니다. parse 블록에서 로그 형식을 파싱합니다.
Docker 로그는 JSON 형식이므로 @type json을 사용합니다. time_key로 시간 필드를 지정하고, time_format으로 형식을 정의합니다.
filter 블록은 데이터를 가공합니다. grep 필터로 ERROR나 FATAL이 포함된 로그만 통과시킵니다.
불필요한 INFO 로그는 필터링되어 Elasticsearch 용량을 절약합니다. 마지막 match 블록이 출력입니다.
docker.**는 docker로 시작하는 모든 태그를 의미합니다. elasticsearch 타입으로 Elasticsearch 호스트와 포트를 지정합니다.
buffer 설정으로 10초마다 한 번씩 일괄 전송해서 효율을 높입니다. 실제 현업에서는 어떻게 활용할까요?
대부분의 회사는 여러 종류의 로그를 수집합니다. 애플리케이션 로그, 웹 서버 접근 로그, 시스템 로그, 데이터베이스 로그 등입니다.
Fluentd는 이 모든 것을 하나의 파이프라인으로 통합합니다. 예를 들어 e커머스 회사를 생각해봅시다.
주문 서비스, 결제 서비스, 배송 서비스가 각각 로그를 출력합니다. Fluentd는 이들을 모두 수집해서 Elasticsearch에 저장합니다.
추가로 민감한 정보(신용카드 번호, 주민번호)는 필터로 마스킹 처리합니다. 또 다른 사례는 멀티 테넌시입니다.
SaaS 서비스에서 고객사별로 로그를 분리해야 한다고 가정해봅시다. Fluentd는 로그에 customer_id 태그를 추가하고, match 패턴으로 고객사별 인덱스에 저장할 수 있습니다.
하지만 주의할 점도 있습니다. Fluentd 설정은 강력하지만 복잡할 수 있습니다.
source, filter, match의 순서와 태그 매칭 규칙을 정확히 이해해야 합니다. 잘못 설정하면 로그가 목적지에 도달하지 않거나, 무한 루프에 빠질 수 있습니다.
또한 Fluentd 자체의 리소스 사용량도 고려해야 합니다. 대량의 로그를 처리하려면 CPU와 메모리가 필요합니다.
버퍼 크기를 너무 크게 설정하면 메모리 부족이 발생할 수 있습니다. 준호 씨는 Fluentd 설정 파일을 작성하고 Docker 컨테이너를 재시작했습니다.
Kibana에서 확인하니 애플리케이션 로그가 실시간으로 들어오고 있었습니다. "오, 자동으로 수집되네요!" 강민수 씨가 웃으며 말했습니다.
"이제 매번 서버에 SSH 접속할 필요가 없어요. Kibana만 열면 되니까요." Fluentd를 제대로 설정하면 로그 수집이 완전히 자동화됩니다.
여러분도 간단한 설정부터 시작해서 점진적으로 확장해 보세요.
실전 팁
💡 - pos_file 설정으로 Fluentd 재시작 시 로그 중복을 방지하세요
- 민감한 정보는 record_transformer 필터로 마스킹 처리하세요
- 대량 로그 처리 시 buffer 설정을 튜닝해서 성능을 최적화하세요
4. Kibana 시각화
로그가 Elasticsearch에 잘 저장되는 것을 확인한 준호 씨는 다음 단계가 궁금해졌습니다. "그런데 로그를 어떻게 보는 건가요?
curl 명령으로 Elasticsearch API를 호출하나요?" 강민수 씨는 브라우저를 열어 Kibana 페이지를 보여줬습니다. "Kibana를 쓰면 클릭 몇 번으로 그래프와 차트를 만들 수 있어요."
Kibana는 Elasticsearch 데이터를 시각화하는 웹 기반 도구입니다. 마치 엑셀의 피벗 테이블과 차트 기능처럼, 복잡한 쿼리 없이도 클릭만으로 로그를 분석하고 대시보드를 만들 수 있습니다.
로그 데이터를 의미 있는 인사이트로 변환하는 시각화 도구입니다.
다음 코드를 살펴봅시다.
// Kibana에서 사용할 시각화를 프로그래밍 방식으로 생성
const axios = require('axios');
async function createVisualization() {
const kibanaUrl = 'http://localhost:5601';
// 시간별 에러 발생 빈도 시각화 생성
const visualization = {
title: '시간별 에러 발생 추이',
type: 'line',
params: {
type: 'line',
grid: { categoryLines: true },
valueAxes: [{
id: 'ValueAxis-1',
name: 'LeftAxis-1',
type: 'value',
position: 'left'
}],
seriesParams: [{
data: { id: '1' },
type: 'line',
mode: 'normal'
}]
},
aggs: [
{
id: '1',
enabled: true,
type: 'count',
schema: 'metric'
},
{
id: '2',
enabled: true,
type: 'date_histogram',
schema: 'segment',
params: {
field: 'timestamp',
interval: '5m' // 5분 간격
}
}
]
};
await axios.post(`${kibanaUrl}/api/saved_objects/visualization`, {
attributes: visualization
});
console.log('시각화 생성 완료!');
}
준호 씨는 지금까지 로그를 터미널에서 텍스트로만 봤습니다. 에러가 몇 개인지 세려면 grep과 wc 명령을 조합해야 했고, 시간대별 추이를 파악하는 것은 거의 불가능했습니다.
"로그가 천 개만 넘어가도 패턴을 파악하기 어려운데..." 강민수 씨가 Kibana 화면을 보여줬습니다. 막대 그래프가 시간대별 에러 개수를 한눈에 보여주고, 파이 차트는 에러 유형별 비율을 표시했습니다.
"와, 이렇게 보니까 새벽 2시에 에러가 급증했네요!" 그렇다면 Kibana는 어떤 기능을 제공할까요? 쉽게 비유하자면, Kibana는 데이터 분석가의 대시보드와 같습니다.
주식 트레이더는 여러 차트와 지표를 한 화면에 띄워놓고 실시간으로 모니터링합니다. Kibana도 마찬가지로 로그 데이터를 다양한 시각화로 보여주어, 시스템 상태를 한눈에 파악하게 해줍니다.
Kibana의 핵심 기능은 크게 세 가지입니다. Discover는 로그를 검색하고 탐색하는 기능입니다.
Visualize는 그래프와 차트를 만드는 기능입니다. Dashboard는 여러 시각화를 하나의 화면에 모아놓는 기능입니다.
Kibana 없이 Elasticsearch를 직접 사용하면 어떤 불편함이 있을까요? 먼저 쿼리를 JSON으로 작성해야 합니다.
앞에서 본 Elasticsearch 쿼리를 기억하시나요? bool, must, range 같은 키워드를 정확히 알아야 합니다.
초보자에게는 진입 장벽이 높습니다. 두 번째는 시각화입니다.
Elasticsearch는 데이터만 반환할 뿐, 그래프를 그려주지 않습니다. curl로 API를 호출하면 JSON 텍스트만 나옵니다.
이를 엑셀로 옮겨서 차트를 만들어야 하는데, 매번 하기엔 너무 번거롭습니다. 세 번째는 협업입니다.
개발자가 아닌 사람은 JSON 쿼리를 작성할 수 없습니다. 운영팀이나 기획팀이 로그를 직접 확인하려면 개발자에게 매번 요청해야 합니다.
Kibana를 쓰면 비개발자도 셀프 서비스로 데이터를 탐색할 수 있습니다. 바로 이런 이유로 Kibana가 필수입니다.
Kibana의 가장 강력한 기능은 실시간 대시보드입니다. 한 번 만들어두면 자동으로 최신 데이터를 반영합니다.
모니터에 띄워놓고 시스템 상태를 실시간으로 확인할 수 있습니다. 위의 코드는 Kibana API를 사용한 예제입니다.
Kibana는 UI로도 시각화를 만들 수 있지만, 프로그래밍 방식으로도 가능합니다. 이는 자동화에 유용합니다.
예를 들어 신규 서비스를 배포할 때마다 표준 대시보드를 자동으로 생성하는 스크립트를 만들 수 있습니다. visualization 객체는 시각화 설정을 담습니다.
type을 line으로 설정해서 선 그래프를 만듭니다. aggs는 aggregation의 약자로, 데이터를 집계하는 방법을 정의합니다.
첫 번째 aggregation은 count입니다. 문서 개수를 세는 것이죠.
두 번째는 date_histogram으로, 시간을 기준으로 그룹핑합니다. interval을 5m으로 설정해서 5분 단위로 데이터를 묶습니다.
결과적으로 "5분마다 몇 개의 로그가 발생했는지"를 보여주는 시계열 그래프가 생성됩니다. 시간에 따른 추이를 한눈에 파악할 수 있습니다.
실제 현업에서는 어떻게 활용할까요? 대부분의 DevOps 팀은 Kibana 대시보드를 사무실 모니터에 상시 띄워놓습니다.
에러율, 응답 시간, 요청 수 같은 핵심 지표를 실시간으로 모니터링합니다. 지표가 임계치를 넘으면 알림을 받아 즉시 대응합니다.
예를 들어 쇼핑몰 서비스를 운영한다고 가정해봅시다. 대시보드에 "시간별 주문 수", "결제 성공률", "페이지 로딩 시간" 같은 차트를 배치합니다.
평소에는 안정적인 패턴을 보이다가, 갑자기 결제 성공률이 떨어지면 즉시 알아챌 수 있습니다. 또 다른 사례는 A/B 테스트 분석입니다.
신규 기능을 일부 사용자에게만 배포했을 때, Kibana로 두 그룹의 행동 차이를 비교할 수 있습니다. 필터로 그룹을 나누고, 전환율이나 체류 시간 같은 지표를 시각화합니다.
하지만 주의할 점도 있습니다. 너무 많은 시각화를 한 대시보드에 넣으면 오히려 혼란스럽습니다.
핵심 지표에 집중하고, 관련 있는 차트끼리 묶어서 여러 대시보드로 나누는 것이 좋습니다. 또한 Kibana는 실시간 쿼리를 실행하므로 무거운 집계는 성능에 영향을 줄 수 있습니다.
수억 건의 로그를 복잡하게 집계하는 대시보드는 로딩이 느릴 수 있습니다. 이럴 때는 시간 범위를 제한하거나, 미리 집계된 데이터를 사용하는 것이 좋습니다.
준호 씨는 Kibana에서 첫 대시보드를 만들어봤습니다. 시간별 에러 그래프, 에러 타입별 파이 차트, 최근 에러 로그 테이블을 배치했습니다.
"이제 한 화면에서 모든 걸 볼 수 있네요!" 강민수 씨가 조언했습니다. "대시보드를 팀원들과 공유해보세요.
모두가 같은 화면을 보면 커뮤니케이션이 훨씬 쉬워져요." Kibana를 활용하면 로그 데이터에서 의미 있는 인사이트를 발견할 수 있습니다. 여러분도 자신만의 대시보드를 만들어 보세요.
실전 팁
💡 - Discover 화면에서 자주 쓰는 검색은 Saved Search로 저장하세요
- 대시보드는 URL로 공유할 수 있어서 팀 협업에 유용합니다
- Time Picker로 시간 범위를 조정하면 과거 데이터도 쉽게 분석할 수 있습니다
5. 로그 포맷 설정
한동안 EFK 스택을 사용하던 준호 씨는 불편한 점을 발견했습니다. 로그가 그냥 문자열로 저장되어 있어서 검색이 어려웠습니다.
"level이 ERROR인 로그만 보고 싶은데, 'ERROR'라는 단어가 메시지에도 나와서 구분이 안 돼요." 강민수 씨가 고개를 끄덕였습니다. "로그를 구조화해야 할 시점이네요."
로그 포맷 설정은 로그를 일관된 구조로 출력하도록 지정하는 것입니다. 마치 주소를 "시-구-동-번지" 순서로 적는 규칙처럼, 로그도 "시간-레벨-메시지-메타데이터" 같은 구조를 가지면 검색과 분석이 훨씬 쉬워집니다.
효과적인 로깅의 첫걸음입니다.
다음 코드를 살펴봅시다.
// Winston 로거로 구조화된 JSON 로그 출력
const winston = require('winston');
// 로그 포맷 정의
const logFormat = winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.errors({ stack: true }),
winston.format.metadata(),
winston.format.json()
);
// 로거 생성
const logger = winston.createLogger({
level: 'info',
format: logFormat,
defaultMeta: { service: 'payment-service' },
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
// 사용 예시
logger.info('사용자 로그인 성공', {
userId: 12345,
ip: '192.168.1.1'
});
logger.error('결제 처리 실패', {
orderId: 'ORD-789',
amount: 50000,
error: '카드 승인 거부'
});
// 출력 예시 (JSON):
// {"timestamp":"2025-12-22 10:30:15","level":"info","message":"사용자 로그인 성공","service":"payment-service","userId":12345,"ip":"192.168.1.1"}
// {"timestamp":"2025-12-22 10:31:20","level":"error","message":"결제 처리 실패","service":"payment-service","orderId":"ORD-789","amount":50000,"error":"카드 승인 거부"}
준호 씨는 처음에 console.log로 로그를 출력했습니다. "결제 완료: 주문번호 12345, 금액 50000원" 같은 자유 형식 문자열이었습니다.
나중에 Kibana에서 "금액이 50000원 이상인 로그"를 찾으려고 했지만, 금액이 문자열 중간에 섞여 있어서 불가능했습니다. 강민수 씨가 설명했습니다.
"로그를 단순 문자열이 아니라 구조화된 데이터로 출력해야 해요. JSON 형식이 가장 일반적이죠." 그렇다면 구조화된 로그란 무엇일까요?
쉽게 비유하자면, 구조화된 로그는 엑셀 시트의 행과 같습니다. "이름, 나이, 주소"처럼 각 컬럼이 정해져 있으면 정렬하고 필터링하기 쉽습니다.
반대로 "홍길동 30세 서울 거주"처럼 자유 형식 문장으로 적으면 분석이 어렵습니다. 로그도 마찬가지입니다.
JSON 형식이 로그에 가장 많이 쓰이는 이유는 키-값 구조 덕분입니다. level, timestamp, message, userId 같은 필드로 나뉘어 있어서, Elasticsearch가 각 필드를 인덱싱할 수 있습니다.
구조화되지 않은 로그를 사용하면 어떤 문제가 생길까요? 첫째, 검색이 부정확합니다.
"ERROR"라는 단어로 검색하면, 로그 레벨이 ERROR인 것뿐만 아니라 메시지에 "ERROR"가 포함된 INFO 로그도 함께 나옵니다. 원하는 결과만 정확히 찾기 어렵습니다.
둘째, 집계가 불가능합니다. "서비스별 에러 개수"를 세고 싶어도, 서비스 이름이 문자열 어딘가에 숨어 있으면 추출할 수 없습니다.
Kibana에서 파이 차트를 만들 수 없습니다. 셋째, 파싱 오버헤드입니다.
나중에라도 분석하려면 정규식이나 파서로 문자열을 쪼개야 합니다. 이는 느리고 에러가 발생하기 쉽습니다.
처음부터 구조화해서 출력하면 이런 수고를 덜 수 있습니다. 바로 이런 이유로 로그 포맷을 표준화해야 합니다.
가장 널리 쓰이는 포맷은 JSON입니다. 거의 모든 언어와 도구가 JSON을 지원하며, Elasticsearch와도 궁합이 완벽합니다.
다른 옵션으로는 Logfmt(키=값 형식)나 구조화된 텍스트(Apache 로그 형식) 등이 있습니다. 위의 Winston 코드를 자세히 살펴보겠습니다.
Winston은 Node.js에서 가장 인기 있는 로깅 라이브러리입니다. Python의 logging, Java의 Logback처럼 대부분의 언어에 비슷한 라이브러리가 있습니다.
winston.format.combine으로 여러 포맷을 조합합니다. 첫째는 timestamp로, 모든 로그에 현재 시각을 추가합니다.
YYYY-MM-DD HH:mm:ss 형식으로 읽기 쉽게 출력합니다. errors 포맷은 에러 객체를 처리합니다.
일반적으로 Error 객체는 JSON.stringify하면 빈 객체가 되는데, 이 포맷을 쓰면 스택 트레이스도 포함됩니다. 마지막 json 포맷이 핵심입니다.
모든 정보를 JSON으로 직렬화합니다. Fluentd가 이를 파싱 없이 바로 Elasticsearch에 보낼 수 있습니다.
defaultMeta는 모든 로그에 자동으로 추가될 메타데이터입니다. 여기서는 service 필드에 "payment-service"를 넣었습니다.
나중에 Kibana에서 서비스별로 필터링할 수 있습니다. transports는 로그를 어디에 출력할지 정의합니다.
Console은 표준 출력, File은 파일 저장입니다. error.log에는 ERROR 레벨만, combined.log에는 모든 레벨이 저장됩니다.
실제 사용 예시를 보면, logger.info와 logger.error에 두 번째 인자로 객체를 전달합니다. 이 객체의 모든 필드가 로그에 포함됩니다.
userId, orderId, amount 같은 정보를 구조화된 방식으로 기록할 수 있습니다. 실제 현업에서는 어떻게 활용할까요?
대부분의 회사는 로그 표준을 정의합니다. "반드시 포함해야 할 필드: timestamp, level, service, message, traceId" 같은 규칙을 만듭니다.
모든 서비스가 이 규칙을 따르면, 로그를 통합 분석하기 쉬워집니다. 예를 들어 마이크로서비스 환경에서 한 요청이 여러 서비스를 거친다고 가정해봅시다.
API Gateway → 주문 서비스 → 결제 서비스 → 배송 서비스. 각 서비스가 traceId를 로그에 포함하면, Kibana에서 traceId로 검색해서 전체 흐름을 추적할 수 있습니다.
또 다른 사례는 성능 분석입니다. 로그에 duration 필드를 추가해서 각 작업의 소요 시간을 기록합니다.
Kibana에서 평균, 최대, 최소값을 집계하면 병목 구간을 찾을 수 있습니다. 하지만 주의할 점도 있습니다.
너무 많은 정보를 로그에 넣으면 용량이 커집니다. 필수 정보만 포함하고, 디버그용 상세 정보는 DEBUG 레벨로 분리하세요.
프로덕션에서는 INFO 이상만 출력하면 됩니다. 또한 민감한 정보(비밀번호, 신용카드 번호)는 절대 로그에 넣으면 안 됩니다.
실수로 포함될 수 있으니, 로깅 전에 마스킹하는 미들웨어를 추가하는 것이 안전합니다. 준호 씨는 기존 console.log를 모두 Winston으로 교체했습니다.
로그가 JSON 형식으로 출력되자, Kibana에서 필드별 검색이 가능해졌습니다. "와, userId로 필터링하니까 해당 사용자의 모든 행동이 보여요!" 강민수 씨가 칭찬했습니다.
"잘했어요. 이제 제대로 된 로깅 시스템이 갖춰졌네요." 로그 포맷을 표준화하면 검색, 분석, 디버깅이 모두 쉬워집니다.
여러분도 프로젝트에 구조화된 로깅을 도입해 보세요.
실전 팁
💡 - Winston, Bunyan(Node.js), Logback(Java), structlog(Python) 같은 검증된 라이브러리를 사용하세요
- 모든 로그에 timestamp와 level은 필수로 포함하세요
- 환경변수로 로그 레벨을 조정해서 프로덕션과 개발 환경을 다르게 운영하세요
6. 구조화된 로깅
구조화된 포맷으로 로그를 출력하게 된 준호 씨는 한 단계 더 나아가고 싶어졌습니다. "로그에 어떤 정보를 넣어야 나중에 분석하기 좋을까요?" 강민수 씨는 실제 장애 대응 사례를 들려주며 설명했습니다.
"지난주에 결제 오류가 났을 때, userId와 orderId가 로그에 있어서 10분 만에 원인을 찾았어요."
구조화된 로깅은 로그에 맥락 정보를 체계적으로 포함하는 방법론입니다. 마치 범죄 현장에서 단서를 수집하듯이, 사용자 ID, 요청 ID, 작업 시간 같은 메타데이터를 로그에 담아 문제 해결의 실마리를 제공합니다.
효과적인 디버깅과 모니터링의 핵심입니다.
다음 코드를 살펴봅시다.
// Express 미들웨어로 구조화된 로깅 구현
const express = require('express');
const winston = require('winston');
const { v4: uuidv4 } = require('uuid');
const app = express();
// 로거 설정
const logger = winston.createLogger({
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [new winston.transports.Console()]
});
// 요청마다 traceId 생성 및 로깅 미들웨어
app.use((req, res, next) => {
// 고유한 추적 ID 생성
req.traceId = uuidv4();
req.startTime = Date.now();
// 요청 로그
logger.info('요청 시작', {
traceId: req.traceId,
method: req.method,
path: req.path,
ip: req.ip,
userAgent: req.get('user-agent')
});
// 응답 완료 시 로그
res.on('finish', () => {
const duration = Date.now() - req.startTime;
logger.info('요청 완료', {
traceId: req.traceId,
method: req.method,
path: req.path,
statusCode: res.statusCode,
duration: `${duration}ms`
});
});
next();
});
// 비즈니스 로직에서 구조화된 로깅
app.post('/orders', async (req, res) => {
const { userId, items, amount } = req.body;
logger.info('주문 생성 시작', {
traceId: req.traceId,
userId,
itemCount: items.length,
amount
});
try {
// 주문 처리 로직
const orderId = await processOrder({ userId, items, amount });
logger.info('주문 생성 성공', {
traceId: req.traceId,
userId,
orderId,
amount
});
res.json({ success: true, orderId });
} catch (error) {
logger.error('주문 생성 실패', {
traceId: req.traceId,
userId,
amount,
error: error.message,
stack: error.stack
});
res.status(500).json({ success: false, error: error.message });
}
});
준호 씨는 어느 날 사용자로부터 "주문이 안 돼요"라는 문의를 받았습니다. 로그를 확인해보니 "주문 생성 실패"라는 에러만 있었습니다.
어느 사용자인지, 어떤 상품을 주문하려 했는지, 어느 단계에서 실패했는지 알 수 없었습니다. "정보가 너무 부족해서 원인을 못 찾겠어요." 강민수 씨가 조언했습니다.
"로그에 맥락 정보를 충분히 담아야 해요. 누가, 무엇을, 언제, 어디서, 왜, 어떻게 했는지 알 수 있어야 합니다." 그렇다면 구조화된 로깅의 핵심 원칙은 무엇일까요?
쉽게 비유하자면, 구조화된 로깅은 형사의 수사 보고서와 같습니다. "사건 발생"이라고만 적으면 소용없습니다.
"2025년 12월 22일 10시 30분, 홍길동(ID: 12345)이 강남구 A상점에서 신용카드 결제 시도, 카드사 승인 거부로 실패" 같은 상세 정보가 있어야 사건을 재구성할 수 있습니다. 구조화된 로깅의 5W1H를 정리하면 이렇습니다.
Who(누가): userId, sessionId. What(무엇을): 수행한 작업, orderId, productId.
When(언제): timestamp. Where(어디서): service, component, function.
Why(왜): 작업의 목적이나 결과. How(어떻게): method, duration, statusCode.
구조화된 로깅 없이 개발하면 어떤 문제가 생길까요? 첫째, 장애 대응 시간이 길어집니다.
에러가 발생했을 때 "누구에게 일어난 일인지" 모르면 재현할 수 없습니다. 준호 씨처럼 사용자를 다시 찾아가 "언제 주문하셨어요?
어떤 상품이었어요?"라고 물어봐야 합니다. 둘째, 분산 시스템에서 요청 추적이 불가능합니다.
한 요청이 API Gateway → 주문 서비스 → 결제 서비스를 거칠 때, 각 서비스의 로그를 연결할 방법이 없습니다. traceId 같은 추적 ID가 없으면 퍼즐을 맞출 수 없습니다.
셋째, 성능 분석이 어렵습니다. "이 API가 왜 느린지" 알려면 각 단계의 소요 시간을 알아야 합니다.
duration 같은 정보가 로그에 없으면 어디가 병목인지 짐작만 할 뿐입니다. 바로 이런 이유로 구조화된 로깅이 필수입니다.
가장 중요한 개념은 **traceId(추적 ID)**입니다. 각 요청마다 고유한 ID를 생성해서 모든 로그에 포함시킵니다.
그러면 여러 서비스와 로그를 가로질러 한 요청의 전체 여정을 추적할 수 있습니다. 위의 Express 코드를 단계별로 분석해보겠습니다.
먼저 미들웨어에서 모든 요청에 traceId를 부여합니다. uuid 라이브러리로 충돌 없는 고유 ID를 생성합니다.
이 ID는 req 객체에 저장되어 모든 핸들러에서 접근 가능합니다. 요청 시작 시점에 첫 번째 로그를 남깁니다.
method(GET, POST), path(/orders), ip, userAgent 같은 기본 정보를 기록합니다. 나중에 "어느 클라이언트에서 보낸 요청인지" 알 수 있습니다.
res.on('finish')는 응답이 완료될 때 실행됩니다. 시작 시각과 종료 시각의 차이로 duration을 계산합니다.
statusCode로 성공/실패를 판단할 수 있습니다. 비즈니스 로직에서도 주요 이벤트마다 로그를 남깁니다.
"주문 생성 시작", "주문 생성 성공", "주문 생성 실패" 같은 지점에서 userId, orderId, amount를 기록합니다. 에러 로깅에서는 error.message와 error.stack을 모두 포함합니다.
message는 짧은 설명이고, stack은 에러가 발생한 코드 위치를 보여줍니다. 디버깅에 필수적인 정보입니다.
모든 로그에 traceId가 포함되어 있다는 점이 핵심입니다. Kibana에서 traceId로 검색하면, 해당 요청의 모든 로그가 시간 순서대로 나옵니다.
마치 동영상을 재생하듯이 요청의 전체 과정을 볼 수 있습니다. 실제 현업에서는 어떻게 활용할까요?
Netflix, Uber 같은 대규모 마이크로서비스 회사는 **분산 추적(Distributed Tracing)**을 사용합니다. Zipkin, Jaeger 같은 도구로 traceId를 자동으로 전파하고, 각 서비스의 호출 관계를 시각화합니다.
EFK와 함께 사용하면 강력한 모니터링 시스템이 됩니다. 예를 들어 사용자가 "상품 검색이 느려요"라고 신고했다고 가정해봅시다.
개발자는 해당 사용자의 userId로 Kibana를 검색합니다. traceId를 따라가 보니 "추천 엔진 호출"에서 5초가 걸렸다는 것을 발견합니다.
원인을 정확히 파악하고 추천 엔진 팀에 이슈를 전달합니다. 또 다른 사례는 비즈니스 분석입니다.
로그에 사용자 행동 정보(검색어, 클릭한 상품, 결제 금액)를 포함하면, 나중에 데이터 분석가가 Elasticsearch를 쿼리해서 인사이트를 얻을 수 있습니다. "가장 많이 검색된 키워드", "시간대별 구매 패턴" 같은 정보를 추출합니다.
하지만 주의할 점도 있습니다. 로그에 너무 많은 정보를 넣으면 성능과 비용 문제가 생깁니다.
대용량 payload를 통째로 로그에 넣지 마세요. 필요한 필드만 선택적으로 기록하세요.
또한 개인정보 보호를 고려해야 합니다. GDPR 같은 규정에서는 로그의 개인정보도 삭제 대상입니다.
userId 대신 해시값을 사용하거나, 개인정보는 별도 테이블에 저장하는 것이 안전합니다. 마지막으로 로그 레벨을 적절히 사용하세요.
DEBUG는 개발 중에만, INFO는 주요 이벤트, WARN은 잠재적 문제, ERROR는 실제 장애에만 사용합니다. 모든 것을 ERROR로 찍으면 중요한 알림이 묻힙니다.
준호 씨는 traceId를 도입한 후 디버깅 시간이 절반으로 줄었습니다. "이제 사용자 문의가 와도 traceId로 바로 원인을 찾을 수 있어요!" 강민수 씨가 마지막 조언을 했습니다.
"구조화된 로깅은 처음에 번거로워 보이지만, 나중에 엄청난 시간을 절약해줘요. 습관을 들이세요." 구조화된 로깅을 제대로 적용하면 시스템 가시성이 크게 향상됩니다.
여러분도 오늘부터 로그에 맥락 정보를 담아보세요.
실전 팁
💡 - traceId는 HTTP 헤더로도 전달해서 외부 API 호출 시에도 추적을 이어가세요
- 로그에 환경 정보(env: production/staging)를 포함하면 환경별 필터링이 쉽습니다
- 주기적으로 로그를 검토해서 불필요한 정보는 제거하고 누락된 정보는 추가하세요
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
서비스 메시 완벽 가이드
마이크로서비스 간 통신을 안전하고 효율적으로 관리하는 서비스 메시의 핵심 개념부터 실전 도입까지, 초급 개발자를 위한 완벽한 입문서입니다. Istio와 Linkerd 비교, 사이드카 패턴, 실무 적용 노하우를 담았습니다.
관찰 가능한 마이크로서비스 완벽 가이드
마이크로서비스 환경에서 시스템의 상태를 실시간으로 관찰하고 모니터링하는 방법을 배웁니다. Resilience4j, Zipkin, Prometheus, Grafana, EFK 스택을 활용하여 안정적이고 관찰 가능한 시스템을 구축하는 실전 가이드입니다.
Grafana 대시보드 완벽 가이드
실시간 모니터링의 핵심, Grafana 대시보드를 처음부터 끝까지 배워봅니다. Prometheus 연동부터 알람 설정까지, 초급 개발자도 쉽게 따라할 수 있는 실전 가이드입니다.
Prometheus 메트릭 수집 완벽 가이드
Spring Boot 애플리케이션의 메트릭을 Prometheus로 수집하고 모니터링하는 방법을 배웁니다. Actuator 설정부터 PromQL 쿼리까지 실무에 필요한 모든 내용을 다룹니다.
스프링 관찰 가능성 완벽 가이드
Spring Boot 3.x의 Observation API를 활용한 애플리케이션 모니터링과 추적 방법을 초급 개발자 눈높이에서 쉽게 설명합니다. 실무에서 바로 적용할 수 있는 메트릭 수집과 분산 추적 기법을 다룹니다.