Log Drains × Grafana Loki — Vercel·Supabase 로그를 ELK 대비 90% 저렴하게 수집하는 옵저버빌리티 파이프라인
배포 직후 뭔가 이상한데 정확히 뭐가 문제인지 모르겠을 때, 결국 Slack에 "방금 에러 본 사람 있어요?"를 물어본 경험, 한 번쯤 있지 않으신가요? 저는 꽤 많이 했습니다. 로그가 어딘가에 분명히 있는데, 플랫폼 대시보드에서 찍히는 건 너무 단편적이고 제대로 된 검색도 안 되고, 결국 팀원 눈에 먼저 발견되길 기다리는 상황이 됐죠.
이 글에서는 Log Drain과 Grafana Loki를 연결해서 Vercel·Supabase에서 발생하는 로그를 실시간으로 수집하고, Grafana에서 LogQL로 바로 조회하는 파이프라인을 구성하는 방법을 다룹니다. ELK 스택보다 스토리지 비용이 최대 90% 저렴하고, 이미 Prometheus를 쓰고 있는 팀이라면 레이블 체계가 그대로 이어지니까 기존 메트릭 스택과도 자연스럽게 붙습니다. 이 글을 읽고 나면 ① 로컬에서 Loki + Alloy + Grafana 스택을 올리고 ② Supabase 또는 Vercel의 Log Drain을 연결하고 ③ 레이블 설계의 함정을 피하는 방법까지 직접 구성해볼 수 있습니다. Docker와 기본 터미널 사용에 익숙하다면 바로 따라올 수 있습니다.
Promtail 사용 중이라면: 2026년 3월 2일부로 Promtail이 공식 EOL(지원 종료)됐습니다. 보안 패치도 중단된 상태라 Grafana Alloy로의 이전을 권장합니다. 이 글에서 다루는 Alloy 설정이 마이그레이션 방향을 잡는 데 도움이 될 겁니다.
목차
- Log Drain — 로그를 '당기는' 게 아니라 '흘려보내는' 것
- Grafana Loki — 레이블만 인덱싱한다
- Grafana Alloy — Promtail의 후계자 (River 언어 기반)
- 예시 1 (기초): Docker Compose로 로컬 관측 스택 구성
- 예시 2 (중급): Supabase → Loki 직접 연동
- 예시 3 (심화): Vercel → Alloy → Loki — 레이블 정제 + 트레이스 연동
- 장단점 분석
- 마치며
핵심 개념
Log Drain — 로그를 '당기는' 게 아니라 '흘려보내는' 것
Log Drain은 플랫폼이 로그를 생성하는 즉시 미리 지정된 외부 엔드포인트로 자동 전송하는 스트리밍 파이프라인입니다. 우리가 대시보드에 들어가서 수동으로 로그를 조회하는 방향과 정반대예요. 로그가 흘러갈 목적지를 설정해두면, 플랫폼이 알아서 HTTP POST로 밀어 넣어줍니다.
Log Drain: 인프라·PaaS 플랫폼에서 발생하는 로그를 실시간으로 외부 목적지(Loki, Datadog, S3 등)로 자동 전달하는 지속적 스트리밍 파이프라인. Vercel, Supabase, Render 같은 플랫폼이 이 방식을 공식 지원합니다.
Vercel은 2025년 10월 "Vercel Drains"를 정식 출시하면서 로그뿐 아니라 분산 트레이스·성능 지표까지 단일 스트리밍 메커니즘으로 통합했고, Supabase는 2026년 3월부터 Pro 플랜에서 Log Drains를 확대 지원하기 시작했습니다. Loki를 목적지로 직접 지정할 수 있어서 중간 수집 레이어 없이도 파이프라인 구성이 가능해졌습니다.
Grafana Loki — 레이블만 인덱싱한다
Loki의 설계 철학을 한 줄로 요약하면 이렇습니다. "로그 내용은 인덱싱하지 않는다. 레이블만 인덱싱한다."
ELK 스택(Elasticsearch + Logstash + Kibana)은 로그의 모든 필드를 인덱싱해서 임의 필드 검색이 빠른 대신 인덱스 크기가 폭발적으로 커집니다. Loki는 반대예요. 각 로그 스트림에 붙인 레이블 집합만 인덱싱하고, 로그 본문은 압축된 청크로 오브젝트 스토리지(S3, GCS 등)에 저장합니다. 그 덕분에 스토리지 비용이 ELK 대비 최대 90% 절감되는데, 대신 임의 필드 기반 복잡한 집계는 상대적으로 느립니다.
Loki에서 로그를 조회하는 언어는 LogQL입니다. Prometheus의 PromQL에서 영감을 받은 쿼리 언어로, 레이블 필터링({app="api", env="production"})과 로그 본문 검색(|= "error"), 그리고 rate()나 sum by() 같은 메트릭 변환까지 지원합니다.
전체 파이프라인 구조는 이렇습니다.
[PaaS 플랫폼 / 애플리케이션]
↓ HTTP POST (Loki Push API 또는 OTLP)
[Loki HTTP 엔드포인트 / Grafana Alloy]
↓ 스트림 라벨링 + 청크 저장
[Loki (인덱스 + 오브젝트 스토리지)]
↓ LogQL 쿼리
[Grafana 대시보드]Grafana Alloy — Promtail의 후계자
앞서 말했지만 2026년 3월 2일부로 Promtail이 공식 지원 종료됐습니다. Grafana는 모든 수집 기능을 Grafana Alloy로 통합했는데요, Alloy는 OpenTelemetry Collector의 Grafana 배포판으로 메트릭·로그·트레이스·프로파일을 단일 에이전트로 처리합니다.
Alloy 설정 파일은 River 언어로 작성합니다. HCL(HashiCorp Configuration Language)과 유사하지만 Grafana가 직접 만든 DSL이에요. 컴포넌트명 "이름" { ... } 형태로 파이프라인 블록을 정의하고, forward_to로 블록 간 데이터 흐름을 연결하는 구조입니다. 처음 보면 낯설 수 있는데, 한 번 패턴을 익히면 선언형으로 파이프라인을 조립하는 느낌이 꽤 직관적입니다.
기존 Promtail YAML 설정은 아래 명령어로 Alloy 형식으로 변환할 수 있습니다.
alloy convert --source-format=promtail --output=config.river promtail-config.yamlGrafana Alloy: OpenTelemetry Collector 기반의 Grafana 공식 수집 에이전트.
loki.source.api컴포넌트를 통해 HTTP 드레인으로 들어오는 로그를 바로 수신할 수 있습니다.
실전 적용
예시 1: Docker Compose로 로컬 관측 스택 구성 (기초)
구성: 로컬 머신 → Alloy(포트 9999) → Loki → Grafana
팀에 Loki를 제안하려면 로컬에서 먼저 직접 돌려보는 게 제일 빠릅니다. 저도 처음엔 "설정이 복잡하지 않을까" 싶었는데, docker-compose 하나로 Loki + Alloy + Grafana를 올리는 건 생각보다 간단했어요.
# docker-compose.yml
services:
loki:
image: grafana/loki:3.0.0 # 3.x는 유효하지 않은 태그 형식 — Docker Hub에서 실제 버전 확인 권장
ports:
- "3100:3100"
command: -config.file=/etc/loki/local-config.yaml
volumes:
- ./local-loki-config.yaml:/etc/loki/local-config.yaml
alloy:
image: grafana/alloy:latest
ports:
- "12345:12345" # Alloy 관리 UI
- "9999:9999" # HTTP 드레인 수신 포트
volumes:
- ./alloy-config.river:/etc/alloy/config.river
command: run /etc/alloy/config.river
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin⚠️ 로컬 개발 전용 설정: 아래 Loki 설정의
auth_enabled: false는 인증을 비활성화합니다. 프로덕션 환경에서는 반드시 인증을 활성화하거나 Loki 엔드포인트를 내부 네트워크로 격리해야 합니다.
# local-loki-config.yaml — 로컬 개발용 최소 설정
auth_enabled: false # ⚠️ 로컬 전용 — 프로덕션에서는 반드시 비활성화 해제 필요
schema_config:
configs:
- from: 2020-10-15
# 참고: 새 클러스터는 과거 날짜 사용 가능.
# 기존 운영 클러스터에 새 스키마를 추가할 때는 미래 날짜로 설정해야 합니다.
store: tsdb # TSDB: Loki가 인덱스를 저장하는 Time Series Database Block 형식
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24hAlloy 설정 파일도 간단한 형태로 시작할 수 있습니다.
// alloy-config.river — HTTP 드레인 수신 후 Loki로 전달
// River 언어: 컴포넌트명 "이름" { ... } 형태로 파이프라인 블록을 정의하고
// forward_to로 블록 간 데이터 흐름을 연결합니다
loki.source.api "http_drain" {
http {
listen_address = "0.0.0.0"
listen_port = 9999
}
forward_to = [loki.write.default.receiver]
}
loki.write "default" {
endpoint {
url = "http://loki:3100/loki/api/v1/push"
}
}컨테이너를 올린 뒤 Grafana(http://localhost:3000)에서 Connections > Data Sources > Loki를 선택하고 URL에 http://loki:3100을 입력하면 바로 로그 탐색이 가능합니다. Grafana Explore에서 {job="alloy"} 같은 간단한 LogQL 쿼리로 로그가 잘 들어오는지 확인해볼 수 있습니다.
| 컴포넌트 | 역할 | 포트 |
|---|---|---|
loki.source.api |
HTTP로 들어오는 드레인 로그 수신 | 9999 |
loki.write |
Loki Push API로 전달 | 3100 |
| Grafana Explore | LogQL 쿼리 실행 및 시각화 | 3000 |
예시 2: Supabase → Loki 직접 연동 (중급)
구성: Supabase → Loki (Alloy 없이 직접 연결)
Supabase는 설정 UI에서 Loki를 목적지로 바로 지정할 수 있습니다. Project Settings > Log Drains에서 Loki URL을 입력하면, 최대 250개 이벤트 또는 1초 단위로 자동 플러시되며 gzip 압축으로 전송됩니다.
여기서 Structured Metadata를 반드시 활성화해야 합니다. Supabase 로그에는 요청 ID, 사용자 세션 ID 같은 고카디널리티 필드가 많이 담겨 있는데, 이걸 그냥 레이블로 받으면 Loki 인덱스가 폭발합니다. Structured Metadata는 이런 필드를 인덱스에 포함하지 않고 메타데이터 레이어에 별도 저장하는 방식입니다. 처음에 이 개념을 놓쳤다가 인덱스 크기가 예상의 10배 넘게 불어나는 걸 경험했는데, 정말 처음부터 설정해두기를 강력히 권장합니다.
단, Structured Metadata는 schema v13 이상에서만 동작합니다. 기존 클러스터를 운영 중이라면 스키마 버전을 먼저 확인하는 것이 좋습니다.
# loki-config.yaml — Supabase Log Drains 연동 시 권장 설정
auth_enabled: false # ⚠️ 프로덕션에서는 반드시 인증 활성화 또는 네트워크 격리 필요
limits_config:
allow_structured_metadata: true
max_structured_metadata_entries_count: 500
# 기본값은 128. Supabase 로그는 메타데이터 필드가 많으므로 상향을 권장합니다.
schema_config:
configs:
- from: 2020-10-15
store: tsdb
object_store: filesystem
schema: v13 # Structured Metadata 사용에 필수
index:
prefix: index_
period: 24h레이블 자동 매핑: Supabase가 자동으로 매핑하는 레이블은 로그 소스(
auth,storage,realtime등)와 제품명입니다. 이 값들은 카디널리티가 낮아서 Loki 레이블로 쓰기 적합합니다.
예시 3: Vercel → Alloy → Loki — 레이블 정제 + 트레이스 연동 (심화)
구성: Vercel → Alloy(포트 9998, 레이블 정제) → Loki → Grafana Tempo 드릴다운
Vercel Drains는 HTTP 엔드포인트로 런타임·빌드·엣지 함수 로그를 전송합니다. Alloy의 loki.source.api가 이 로그를 수신하고, 레이블을 정제한 뒤 Loki에 기록하는 구조예요.
흥미로운 점은 Vercel이 로그에 traceId와 spanId를 자동으로 포함한다는 겁니다. 이 필드를 Loki의 Structured Metadata로 저장해두면, Grafana Explore에서 로그 라인을 클릭해 Grafana Tempo의 분산 트레이스로 바로 드릴다운할 수 있습니다.
Alloy에서 정적 레이블(예: env = "production")을 추가할 때는 loki.process 컴포넌트의 stage.static_labels를 사용하는 것이 정확합니다. loki.relabel에서 __path__를 소스 레이블로 쓰는 방식은 로컬 파일 테일링에서만 유효한 내부 메타 레이블이라 HTTP 드레인 컨텍스트에서는 존재하지 않아요. 이 부분에서 __path__를 썼다가 레이블이 전혀 붙지 않아서 한참 헤맸습니다.
// alloy-config.river — Vercel 드레인 수신 + 정적 레이블 추가
// 예시 1(포트 9999)과 함께 운영할 경우 포트를 달리해야 합니다
loki.source.api "vercel_drain" {
http {
listen_address = "0.0.0.0"
listen_port = 9998
}
forward_to = [loki.process.add_env_label.receiver]
}
loki.process "add_env_label" {
forward_to = [loki.write.loki_backend.receiver]
stage.static_labels {
values = {
env = "production",
}
}
}
loki.write "loki_backend" {
endpoint {
url = "http://loki:3100/loki/api/v1/push"
}
}| 처리 단계 | 설명 |
|---|---|
loki.source.api "vercel_drain" |
Vercel HTTP 드레인 수신 (포트 9998) |
loki.process "add_env_label" |
stage.static_labels로 정적 레이블 추가 |
loki.write "loki_backend" |
Loki Push API로 최종 전달 |
장단점 분석
장점
| 항목 | 내용 |
|---|---|
| 저비용 | 로그 본문 미인덱싱으로 ELK 대비 스토리지 비용 최대 90% 절감 |
| 운영 단순성 | 단일 바이너리(모놀리식) 모드로 소규모 팀도 쉽게 운영 가능 |
| Prometheus 친화성 | 동일한 레이블 체계, PromQL 유사 LogQL, Grafana 통합이 자연스러움 |
| 수평 확장 | 재인덱싱 없이 컴포넌트별 독립 스케일 아웃 가능 |
| OpenTelemetry 통합 | OTLP 네이티브 지원으로 메트릭·로그·트레이스 통합 파이프라인 구성 용이 |
| PaaS 연동 | Vercel·Supabase 등 주요 플랫폼의 Log Drain 목적지로 공식 지원 |
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| 레이블 카디널리티 | 고카디널리티 레이블 사용 시 성능 급격히 저하 | user_id, trace_id는 Structured Metadata로 이동 |
| 전문 검색 부재 | 임의 필드 기반 복잡한 집계는 ELK 대비 느림 | 집계가 많은 경우 ELK 병행 또는 Grafana Mimir 조합 검토 |
| 대규모 셀프 호스팅 복잡도 | Loki + Mimir + Tempo 분산 구성의 운영 부담이 큼 | 소규모는 모놀리식 모드, 대규모는 Grafana Cloud 관리형 검토 |
| Schema 버전 호환성 | Structured Metadata는 schema v13 이상에서만 동작 | 기존 클러스터 마이그레이션 시 스키마 버전 선확인 |
| LogQL 학습 비용 | PromQL에 익숙하지 않은 팀은 학습 필요 | Grafana Explore의 쿼리 빌더 UI로 시작 가능 |
솔직히 이 중에서 레이블 카디널리티가 실무에서 가장 자주 터지는 문제입니다. 설정을 잘못 잡으면 Loki가 느려지는 게 아니라 아예 OOM으로 죽어버리는 경우도 있어요. 처음 구성할 때 레이블 목록 한 번만 꼼꼼히 검토해두면 나중에 클러스터를 통째로 재구성하는 수고를 아낄 수 있습니다.
카디널리티(Cardinality): 레이블 값이 가질 수 있는 고유한 경우의 수.
env레이블의 값이production,staging,dev세 가지라면 카디널리티는 3으로 낮습니다.user_id처럼 사용자마다 다른 값을 가지면 카디널리티가 매우 높아져 Loki 인덱스 크기가 폭발합니다.
실무에서 가장 흔한 실수
user_id,request_id,trace_id를 레이블로 등록하기 — 이건 놓치면 바로 장애로 이어집니다. 고카디널리티 필드를 레이블로 쓰는 것이 Loki 성능 저하의 가장 흔한 원인이에요. 이런 필드는 반드시 Structured Metadata로 저장하는 것을 권장합니다.
✅ 좋은 레이블: app, env, namespace, pod, level, host
❌ 나쁜 레이블: user_id, request_id, trace_id, timestamp, order_id-
Promtail 설정을 그대로 유지하기 — 2026년 3월부로 Promtail은 EOL입니다. 보안 패치도 중단된 상태이니,
alloy convert명령으로 마이그레이션하는 것을 권장합니다. -
Schema v13 설정 없이 Structured Metadata를 활성화하려다 실패하기 —
allow_structured_metadata: true만 설정하고 스키마 버전을 올리지 않으면 OTel 로그가 거부됩니다.schema: v13을 함께 설정해야 동작합니다.
마치며
로그가 어딘가에 있다는 것을 아는 것과, 필요할 때 바로 찾을 수 있다는 것은 완전히 다른 이야기입니다. Log Drains + Loki 파이프라인을 구성하고 나면 "Slack에 에러 본 사람 있어요?" 대신 Grafana Explore에서 LogQL 한 줄로 바로 확인할 수 있게 됩니다.
지금 바로 시작해볼 수 있는 3단계:
-
로컬 스택 먼저 올려보기 — 예시 1의
docker-compose.yml을 복사해docker compose up -d로 실행해보시면 Loki + Alloy + Grafana가 바로 올라옵니다. Grafana Explore에서{job="alloy"}같은 간단한 LogQL로 로그가 잘 들어오는지 확인해볼 수 있습니다. -
PaaS 플랫폼의 Log Drain 연결하기 — Supabase를 사용 중이라면
Project Settings > Log Drains에서, Vercel이라면 프로젝트 설정의Drains탭에서 Loki 엔드포인트를 추가해볼 수 있습니다.loki-config.yaml에allow_structured_metadata: true와schema: v13은 반드시 함께 설정해야 합니다. -
레이블 설계 리뷰하기 — 기존에 로그에 붙이던 필드 목록을 꺼내서 카디널리티 기준으로 분류해보시면 좋습니다. 값의 종류가 수십 가지 이하인 필드만 레이블로, 나머지는 Structured Metadata 또는 로그 본문으로 유지하는 방향이 Loki가 가장 잘 동작하는 패턴입니다.
막혔을 때는 Grafana 커뮤니티 포럼이나 grafana/loki GitHub Issues에서 도움을 받을 수 있습니다.
참고 자료
- Grafana Loki Overview | Grafana 공식 문서
- Grafana Loki HTTP API | Grafana 공식 문서
- Structured Metadata | Grafana Loki 공식 문서
- Label Best Practices | Grafana Loki 공식 문서
- Label Cardinality | Grafana Loki 공식 문서
- OpenTelemetry 수집 | Grafana Loki 공식 문서
- Send Logs to Loki | Grafana Alloy 공식 문서
- loki.source.api | Grafana Alloy 공식 문서
- Log Drains | Supabase 공식 문서
- Introducing Log Drains | Supabase Blog
- Introducing Vercel Drains | Vercel Blog
- Working with Drains | Vercel 공식 문서
- The Modern Logging Stack: Loki + Alloy (Why Not Promtail) | rommelporras.com
- Building a 1M/sec Log Ingestion Pipeline with Grafana Loki | Medium
- How to Optimize Loki Label Cardinality | OneUptime Blog
- How to Troubleshoot Loki Rejecting OTel Logs — Structured Metadata | OneUptime
- grafana/loki | GitHub