0. 진행 단계
- FastAPI 애플리케이션 설정
- Prometheus 설정:
- Prometheus는 메트릭 수집 도구로, FastAPI 애플리케이션에서 노출하는 메트릭을 수집합니다.
- FastAPI 애플리케이션에 prometheus_client 패키지를 사용하여 메트릭을 노출합니다.
- Prometheus 설정 파일을 구성하여 FastAPI 애플리케이션에서 메트릭을 스크랩합니다.
- Grafana 설정:
- Grafana는 시각화 도구로, Prometheus에서 수집한 메트릭을 시각화합니다.
- Grafana에 Prometheus 데이터를 소스로 추가하고, 대시보드를 구성합니다.
- Loki 설정:
- Loki는 로그 수집 및 저장 도구로, FastAPI 애플리케이션의 로그를 수집합니다.
- promtail을 사용하여 로그를 Loki로 보냅니다.
- Tempo 설정:
- Tempo는 분산 트레이싱 도구로, FastAPI 애플리케이션에서 트레이스를 수집합니다.
- OpenTelemetry를 사용하여 트레이스를 노출하고 Tempo로 보냅니다.
1. FastAPI 애플리케이션 설정
1) main.py 추가
import logging
import uvicorn
from fastapi import FastAPI
# 로깅 관련 코드 관리를 위해 따로 loggin_config.py를 생성하여 관리
from logging_config import ExceptionLoggingMiddleware, RequestResponseLoggingMiddleware
from utils import PrometheusMiddleware, metrics, setting_otlp
app = FastAPI()
# 환경 변수 설정
APP_NAME = os.environ.get("APP_NAME", "app")
EXPOSE_PORT = os.environ.get("EXPOSE_PORT", 58000)
OTLP_GRPC_ENDPOINT = os.environ.get("OTLP_GRPC_ENDPOINT", "http://tempo:4317")
# 로깅 미들웨어 추가
app.add_middleware(ExceptionLoggingMiddleware)
app.add_middleware(RequestResponseLoggingMiddleware)
# Prometheus 설정
app.add_middleware(PrometheusMiddleware, app_name=APP_NAME)
app.add_route("/metrics", metrics)
# OpenTelemetry exporter 설정
setting_otlp(app, APP_NAME, OTLP_GRPC_ENDPOINT)
# Uvicorn 엔드포인트 접근 로그 필터
class EndpointFilter(logging.Filter):
def filter(self, record: logging.LogRecord) -> bool:
return record.getMessage().find("GET /metrics") == -1
# Filter out /endpoint
logging.getLogger("uvicorn.access").addFilter(EndpointFilter())
@app.get("/")
async def read_root():
return {"Hello": "World"}
if __name__ == "__main__":
# update uvicorn access logger format
log_config = uvicorn.config.LOGGING_CONFIG
log_config["formatters"]["access"][
"fmt"
] = "%(asctime)s %(levelname)s [%(name)s] [%(filename)s:%(lineno)d] [trace_id=%(otelTraceID)s span_id=%(otelSpanID)s resource.service.name=%(otelServiceName)s] - %(message)s"
uvicorn.run(app, host="0.0.0.0", port=EXPOSE_PORT, log_config=log_config)
2) logging_config.py
import os
import logging
from logging.handlers import TimedRotatingFileHandler
from starlette.middleware.base import BaseHTTPMiddleware
from fastapi import Request
from fastapi.responses import JSONResponse
# 로그 디렉토리 생성 함수
def create_log_directory(path):
if not os.path.exists(path):
os.makedirs(path)
# 로그 디렉토리 생성
create_log_directory("logs/error")
create_log_directory("logs/request")
# 로깅 포맷 설정
log_formatter = logging.Formatter("%(asctime)s %(levelname)s [%(name)s] [%(filename)s:%(lineno)d] " \
"[trace_id=%(otelTraceID)s span_id=%(otelSpanID)s resource.service.name=%(otelServiceName)s] - %(message)s")
# 에러 로그 핸들러 설정
error_log_handler = TimedRotatingFileHandler("logs/error/error.log", when="midnight", interval=1, backupCount=90, encoding='utf-8')
error_log_handler.setFormatter(log_formatter)
error_log_handler.suffix = "%Y-%m-%d"
error_log_handler.setLevel(logging.ERROR)
# 요청/응답 로그 핸들러 설정
request_log_handler = TimedRotatingFileHandler("logs/request/request.log", when="midnight", interval=1, backupCount=90, encoding='utf-8')
request_log_handler.setFormatter(log_formatter)
request_log_handler.suffix = "%Y-%m-%d"
request_log_handler.setLevel(logging.INFO)
# 스트림 핸들러 설정
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(log_formatter)
# 로깅 기본 설정
logging.basicConfig(level=logging.INFO, handlers=[error_log_handler, request_log_handler, stream_handler])
logger = logging.getLogger(__name__)
# 로깅 미들웨어 설정
class ExceptionLoggingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
try:
response = await call_next(request)
return response
except Exception as exc:
logger.error(f"Exception: {exc}", exc_info=True)
return JSONResponse(status_code=500, content={"message": "Internal Server Error"})
class RequestResponseLoggingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
client_ip = request.client.host
pid = os.getpid()
logger.info(f"Request from {client_ip}: {request.method} {request.url} - PID:{pid}")
response = await call_next(request)
logger.info(f"Response to {client_ip}: {response.status_code} - PID:{pid}")
return response
3) Dockerfile
# 기본 이미지 설정
FROM python:3.10-slim
# apt init
ENV LANG=C.UTF-8
ENV TZ=Asia/Seoul
# 작업 디렉토리 설정
WORKDIR /app
# 의존성 파일 복사 및 설치
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 소스 코드 복사
COPY . /app
EXPOSE 58000
# 애플리케이션 실행
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "8"]
2. Prometheus 설정
1) Prometheus 설정 파일('promehteus.yml') 작성
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: [localhost:9090']
- job_name: 'fastapi'
static_configs:
- targets: [localhost:8000']
3. Loki 설정
1) Loki 설정 파일 작성(loki-config.yaml)- default 설정 파일 사용해도 됨! 로그 보관 기간 설정이 필요해서 구성함
auth_enabled: false
server:
http_listen_port: 3100
common:
instance_addr: 127.0.0.1
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
schema_config:
configs:
- from: 2020-10-24
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
ruler:
alertmanager_url: http://localhost:9093
storage_config:
boltdb_shipper:
active_index_directory: /loki/index
cache_location: /loki/boltdb-cache
cache_ttl: 24h
shared_store: filesystem
filesystem:
directory: /loki/chunks
table_manager:
retention_deletes_enabled: true
retention_period: 2160h # 90 days
limits_config:
enforce_metric_name: false
reject_old_samples: true
reject_old_samples_max_age: 2160h # 90 days
Docker Loki 로깅 플러그인 설치
# Docker Loki 플러그인 설치
docker plugin install grafana/loki-docker-driver:latest --alias loki --grant-all-permissions
# Docker 서비스를 재시작하여 변경사항 적용
sudo systemctl restart docker
# Docker Loki 플러그인 활성화
docker plugin enable loki
# Loki 플러그인이 정상 설치되었는지 확인: 상태가 enabled 로 표시됨
docker plugin ls
4. Grafana 설정
1) Grafana 웹 인터페이스 접속
URL: http://localhost:3000
2) 데이터 소스 추가
[Springboot] 그라파나(grafana)-프로메테우스 연동
그라파나는 프로메테우스를 통해서 데이터를 조회하고 보여주는 역할을 함 프로메테우스를 데이터 소스로 사용해서 데이터 읽어옴 1. 데이터 소스 추가 1) 그라파나 접속 후 admin으로 로그인 http
k-sky.tistory.com
Loki 데이터 소스 추가
데이터 소스를 저장해놔야 도커 재실행 시 데이터 소스가 유지되므로 dashboards.yaml 작성 필요
apiVersion: 1
providers:
- name: 'FastAPI Observability'
orgId: 1
folder: ''
type: 'file'
disableDeletion: true
editable: true
options:
path: '/etc/grafana/dashboards'
2) 대시보드 생성
Home > Dashboards > New dashboard > Save dashboard 아이콘 클릭
Title, Description 등 내용 입력 > Save 버튼 클릭
만들어진 Dashboard 확인
4) 메트릭 시각화 구성
4. Tempo 설정
1) OpenTelemetry 패키지 설치
pip install opentelemetry-api opentelemetry-sdk opentelemetry-instrumentation-fastapi opentelemetry-exporter-otlp
4) FastAPI 애플리케이션 코드에 트레이싱 추가
from fastapi import FastAPI
from opentelemetry import trace
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
app = FastAPI()
# OpenTelemetry 트레이서 설정
trace.set_tracer_provider(TracerProvider())
span_processor = BatchSpanProcessor(OTLPSpanExporter(endpoint="http://<Tempo 서버 주소>:3200"))
trace.get_tracer_provider().add_span_processor(span_processor)
# FastAPI 애플리케이션에 트레이서 적용
FastAPIInstrumentor.instrument_app(app)
@app.get("/")
def read_root():
return {"Hello": "World"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
docker-compose.yml 구성
Loki
https://grafana.com/docs/loki/latest/setup/install/docker/
Install Loki with Docker or Docker Compose | Grafana Loki documentation
Open source Install Loki with Docker or Docker Compose You can install Loki and Promtail with Docker or Docker Compose if you are evaluating, testing, or developing Loki. For production, Grafana recommends installing with Helm or Tanka. The configuration f
grafana.com