4-4. 로그와 지표 확인 도구¶
4-4 Demo는 4-3에서 설계한 로그 필드(field)와 메트릭(metric)이 실제 조회 도구에서 어떻게 쓰이는지 확인하는 과정입니다. 수업 기본 경로에서는 Grafana Cloud 계정이나 토큰(token)을 요구하지 않고, Grafana Cloud에 보낼 수 있는 형태의 페이로드 미리보기(payload preview), Prometheus 형식 메트릭, 대시보드 JSON(dashboard JSON)을 생성해 확인합니다. 개인 계정과 토큰이 있는 경우에는 같은 산출물을 Grafana Cloud에 import하거나 push해서 Cloud Explore와 Dashboard에서 조회할 수 있습니다.
이 문서를 읽을 때는 다음 기준을 중심으로 확인합니다.
- Loki 조회:
request_id,trace_id,validation_failure로 로그를 찾을 수 있는지 확인 - Prometheus 메트릭: 요청 수, 오류 수, 지연 시간, 점수 평균,
high_risk비율 확인 - Tempo tracing:
course_trace_id로 요청 처리 span을 찾을 수 있는지 확인 - 산출물 연결: 생성된 로그, 메트릭, 대시보드 JSON(dashboard JSON)이 어떤 판단 기준에 답하는지 확인
- 실패 포인트: 로그가 있어도 필드와 라벨(label)이 없으면 찾기 어렵다는 점 확인
Grafana Cloud 문서는 로그(logs), 메트릭(metrics), 트레이스(traces)를 한곳에서 보고 분석하는 운영 관측 환경을 설명합니다. 수업에서는 실제 전송보다 페이로드(payload)와 대시보드(dashboard) 구조를 확인합니다. 중요한 것은 도구 화면을 만드는 것이 아니라, 어떤 로그/메트릭 라벨과 필드가 있어야 QA 판단에 필요한 근거를 찾을 수 있는지 이해하는 것입니다. 여기서 라벨은 high_risk 같은 정답 라벨이 아니라, service, environment처럼 조회 범위를 좁히는 운영 메타데이터입니다.
아래 파일들은 운영 관측 도구가 받게 될 데이터를 미리 확인하기 위한 산출물입니다.
| 구분 | 파일 | 확인할 내용 |
|---|---|---|
| 구조화 로그 | artifacts/logs/chapter_04_normal_events.jsonl, artifacts/logs/chapter_04_anomaly_events.jsonl |
요청별 request_id, trace_id, score, prediction |
| Prometheus 형식 메트릭 | artifacts/metrics/chapter_04_anomaly.prom |
요청 수, 오류 수, 평균 지연 시간, 평균 점수(score), high_risk 비율, score bucket, 예측 class count, drift 후보와 입력 분포 비교 |
| Grafana 대시보드 | artifacts/grafana/ai_quality_overview_dashboard.json, artifacts/grafana/ai_quality_details_dashboard.json |
1차 운영 판단 화면과 로그/trace 확인 화면 |
| Grafana Cloud 페이로드 미리보기(payload preview) | artifacts/grafana/grafana_cloud_payload_preview.json |
전송될 로그 라벨, 로그 필드, 메트릭 |
| Tempo trace preview | artifacts/traces/chapter_04_tempo_payload.json |
요청 처리 span과 course_trace_id attribute |
| 검증 실패 예시 | artifacts/reports/chapter_04_validation_failure_examples.md |
실패 요청의 필드, client/source, owner, next action |
| Grafana Cloud Demo 자료 | demos/ch04_grafana_cloud |
조회 예시와 대시보드 생성 코드 |
이 파일들은 저장소에 고정으로 두기보다 Lab과 Demo를 실행하면서 생성합니다. 수강생은 생성된 파일을 보고 어떤 필드, 라벨, 패널이 품질 판단에 쓰이는지 해석합니다. Cloud 연결을 진행하는 경우에도 먼저 이 파일이 생성되는지 확인해야 전송 실패와 데이터 생성 실패를 구분할 수 있습니다.
4-4-1. Loki에서 구조화 로그 조회¶
Loki는 로그를 조회하기 위한 데이터소스(datasource)로 사용할 수 있습니다. 4-4에서는 Loki를 설치하거나 운영하지 않고, 구조화 로그가 있을 때 어떤 방식으로 조회할 수 있는지만 확인합니다. Demo 쿼리(query) 예시는 demos/ch04_grafana_cloud/queries/loki_queries.md에 있습니다.
{service="ai-quality-serving"} | json | request_id="anomaly-0004"
{service="ai-quality-serving"} | json | validation_failure="true"
{service="ai-quality-serving"} | json | trace_id="current-trace-0000"
Grafana Loki Log queries는 로그 스트림(stream)을 선택한 뒤 파이프라인에서 | json 같은 파서를 사용해 로그 내용을 필드로 추출하는 흐름을 설명합니다. 여기서 중요한 점은 쿼리 문법 자체가 아니라, service 같은 라벨과 request_id 같은 JSON 필드가 있어야 원하는 로그를 찾을 수 있다는 점입니다.
첫 번째 쿼리는 특정 요청을 찾고, 두 번째 쿼리는 입력 검증 실패를 찾고, 세 번째 쿼리는 하나의 흐름에 묶인 로그를 찾습니다. Demo 페이로드에는 label_service="ai-quality-serving"와 label_environment="training"이 포함되므로, 실제 도구에 전송된다면 이 값들이 조회 범위를 좁히는 기준이 됩니다.
QA가 LogQL 자체를 깊게 배울 필요는 없습니다. 중요한 것은 어떤 라벨과 필드가 있어야 원하는 근거를 찾을 수 있는지입니다.
| 확인할 것 | 필요한 필드 |
|---|---|
| 특정 요청 조회 | request_id |
| 이상 흐름 전체 조회 | trace_id |
| 입력 오류 필터링 | validation_failure |
| 특정 모델 버전 필터링 | model_version |
4-4-2. Prometheus 메트릭 확인¶
다음 명령은 이상 상태(anomaly) 로그를 읽어 Prometheus text 형식의 메트릭을 생성합니다.
uv run --group lab python labs/ch04_observability/build_observability_artifacts.py
Prometheus Exposition formats는 Prometheus가 읽을 수 있는 text 형식과 메트릭 타입(metric type)을 설명합니다. 여기서는 counter를 누적 개수, gauge를 현재 상태나 계산된 값으로 이해하면 충분합니다.
생성되는 메트릭의 핵심 값은 다음과 같습니다.
ai_quality_request_total 120
ai_quality_error_total 8
ai_quality_validation_failure_total 8
ai_quality_latency_average_ms 223.750
ai_quality_score_average 0.640249
ai_quality_high_risk_rate 0.458333
ai_quality_valid_request_total 112
ai_quality_valid_score_average 0.643703
ai_quality_valid_high_risk_rate 0.464286
ai_quality_prediction_count{prediction="high_risk",scope="all"} 55
ai_quality_prediction_count{prediction="low_risk",scope="all"} 65
ai_quality_score_bucket_count{bucket="0.2-0.4",scope="all"} 65
ai_quality_score_bucket_count{bucket="0.8-1.0",scope="all"} 55
ai_quality_input_mean_delta{feature="heart_rate"} 10.491667
ai_quality_input_mean_delta{feature="oxygen_saturation"} -1.469766
ai_quality_input_histogram_count{feature="heart_rate",bucket="...",scope="baseline"} ...
ai_quality_input_histogram_count{feature="heart_rate",bucket="...",scope="current"} ...
ai_quality_score_average_delta 0.138247
ai_quality_high_risk_rate_delta 0.241667
이 값은 120개의 요청 중 8개의 오류가 있었고, high_risk 비율과 평균 점수가 높은 상태를 나타냅니다. ai_quality_valid_* 지표는 검증 실패 요청을 제외한 정상 처리 요청만 따로 집계한 값입니다. QA는 이 값만으로 원인을 확정하지 않고 정상 기준선(baseline)과 비교합니다. 예를 들어 ai_quality_high_risk_rate 0.458333은 전체 요청 120건 중 55건이 high_risk로 예측되었다는 뜻이고, ai_quality_valid_high_risk_rate 0.464286은 유효 요청 112건 중 52건이 high_risk로 예측되어 검증 실패를 제외해도 예측 분포 이동 후보가 유지된다는 뜻입니다. ai_quality_score_bucket_count는 평균만으로 보이지 않는 score bucket 분포를 확인하기 위한 교육용 gauge입니다.
전체 이벤트 지표와 유효 요청 지표는 보고서에서 다르게 해석합니다. ai_quality_request_total, ai_quality_error_total, ai_quality_latency_average_ms는 서비스 영향 범위를 설명합니다. 반면 ai_quality_valid_score_average와 ai_quality_valid_high_risk_rate는 검증 실패를 제외한 요청에서 모델 응답 경향이 어떻게 움직였는지 설명합니다.
메트릭을 읽을 때는 절대값과 변화량을 함께 봐야 합니다. 오류 수(error count)가 8이라는 숫자만 보면 크고 작은지 알 수 없습니다. 전체 요청 120개 중 8개인지, 10,000개 중 8개인지에 따라 의미가 다릅니다.
| 메트릭 | 함께 볼 것 |
|---|---|
ai_quality_request_total |
시간대별 트래픽(traffic) 변화 |
ai_quality_error_total |
전체 요청 대비 오류율(error rate) |
ai_quality_validation_failure_total |
실패 필드와 클라이언트(client) 변경 여부 |
ai_quality_latency_average_ms |
p95, p99 같은 지연 시간(latency) 분포 후보 |
ai_quality_score_average |
점수 분포(score distribution) 히스토그램(histogram) |
ai_quality_high_risk_rate |
임계값(threshold)과 모델 버전(model_version) |
ai_quality_prediction_count |
예측 class별 건수 |
ai_quality_score_bucket_count |
score bucket별 건수 |
ai_quality_valid_high_risk_rate |
검증 실패 제외 후에도 예측 분포가 이동했는지 |
ai_quality_valid_score_average |
검증 실패를 제외한 점수 분포 |
ai_quality_input_mean_delta |
current batch 입력 구성 변화 후보 |
ai_quality_input_histogram_count |
기준선과 현재 입력 bucket별 건수 |
ai_quality_score_average_delta |
기준선 대비 점수 분포 이동 |
ai_quality_high_risk_rate_delta |
기준선 대비 예측 분포 이동 |
4-4-3. request_id로 추론 로그 추적¶
대시보드에서 이상 신호를 발견하면 특정 시간대의 로그를 조회합니다. 그 다음 request_id로 개별 요청을 확인하고, 필요하면 trace_id로 관련 처리 흐름을 넓혀 봅니다.
| 단계 | 확인 항목 |
|---|---|
| 1 | 대시보드에서 오류(error), 지연 시간, 점수, 예측(prediction) 분포 변화를 찾기 |
| 2 | 메트릭으로 변화 규모와 시간대를 확인 |
| 3 | request_id로 개별 실패 요청을 확인 |
| 4 | trace_id 기반 관련 로그 흐름 묶음 |
| 5 | 입력 문제, API 문제, 모델/설정 문제 중 후보를 정리 |
이 흐름에서 중요한 것은 원인을 하나로 단정하지 않는 것입니다. 예를 들어 high_risk 비율이 증가했을 때 입력 분포 변화, 모델 버전 변경, 임계값 변경, 검증 실패(validation failure) 증가가 모두 후보가 될 수 있습니다.
QA는 로그 추적 결과를 다음 형식으로 남기면 좋습니다.
| 항목 | 예시 |
|---|---|
| 이상 신호 | high_risk 비율 증가 |
| 발생 시점 | 대시보드에서 증가가 시작된 시간대 |
| 영향 범위 | 120건 중 55건 high_risk |
| 관련 로그 | trace_id=current-trace-0000 |
| 원인 후보 | 점수 평균 증가, 임계값 동일, 검증 실패 8건 |
4-4-4. Grafana Cloud 실제 연결 경로¶
Grafana Cloud 실제 연결은 credential-gated Demo입니다. 계정, stack, telemetry token, Logs/Metrics/Traces endpoint가 있는 수강생만 진행하고, 없는 경우에는 로컬 artifact 확인으로 대체합니다. 이 구분을 보고서에 남겨야 “Cloud 전송 미실행”과 “운영 증거 부재”를 혼동하지 않습니다.
실행 전에는 저장소 루트의 .env.example을 참고해 저장소 루트에 .env를 만듭니다. Grafana demo 스크립트는 루트 .env를 자동으로 읽습니다. 수강생 경로에서는 Logs, Metrics, Traces 각각의 detail 화면에서 URL과 User를 그대로 복사하고, Logs/Metrics/Traces write scope를 가진 access policy token 하나를 GRAFANA_TELEMETRY_TOKEN에 넣습니다. GRAFANA_DASHBOARD_TOKEN은 Dashboard import API용으로 따로 발급합니다.
region과 instance ID는 Cloud Portal에서 직접 확인합니다. region은 logs-prod-..., prometheus-prod-..., otlp-gateway-prod-... 같은 endpoint URL에 이미 포함되어 있으므로 문서나 기억으로 추정하지 않습니다. Metrics의 remote_write URL과 username은 Cloud Portal의 Prometheus 또는 Metrics card에서 Details를 열어 확인하고, Logs와 Traces도 각각 Loki/Logs, Tempo/Traces 상세 정보의 endpoint와 user/username/instance ID를 그대로 사용합니다. GRAFANA_TELEMETRY_TOKEN은 Cloud access policies에서 생성하며, 생성된 token은 한 번만 표시됩니다.
Cloud Portal 화면에서 가장 자주 생기는 실수는 stack의 Instance ID와 각 데이터소스의 User를 섞는 것입니다. Dashboard import에는 stack의 Url을 사용하지만, Logs/Metrics/Traces 전송 인증에는 각 서비스 안내 박스에 표시되는 User를 사용합니다.
| 화면 | 복사할 항목 | .env 키 |
확인 기준 |
|---|---|---|---|
Stack Instance Details |
Url |
GRAFANA_CLOUD_URL |
https://<stack>.grafana.net |
Stack Instance Details |
Instance ID |
직접 사용하지 않음 | 전송 username으로 쓰지 않음 |
| Logs/Metrics/Traces 안내 | logs:write, metrics:write, traces:write scope로 생성한 access policy token |
GRAFANA_TELEMETRY_TOKEN |
Alloy가 세 전송 경로에 공통 사용 |
| Grafana Service Account | service account token | GRAFANA_DASHBOARD_TOKEN |
Dashboard API import 인증 |
| Grafana folder | import 대상 folder uid/title | GRAFANA_FOLDER_UID, GRAFANA_FOLDER_TITLE |
기본값 ai-quality, AI Quality |
| Logs 안내 박스 | URL |
GRAFANA_LOKI_URL |
https://logs-prod-...grafana.net |
| Logs Alloy 예시 | /loki/api/v1/push 포함 URL |
GRAFANA_LOKI_PUSH_URL |
Alloy loki.write에서 사용 |
| Logs 안내 박스 | User |
GRAFANA_LOKI_USER |
Logs 전송 username |
| Metrics 안내 박스 | URL |
GRAFANA_PROM_REMOTE_WRITE_URL |
/api/prom/push가 포함된 remote_write URL |
| Metrics 안내 박스 | User |
GRAFANA_PROM_USER |
Metrics 전송 username |
| Tempo 안내 박스 | User |
GRAFANA_TEMPO_USER |
Traces 전송 username |
| Tempo 또는 Traces ingest 안내 | OTLP traces endpoint | GRAFANA_OTLP_TRACES_ENDPOINT |
직접 전송 fallback이 필요할 때만 참고 |
| Tempo Alloy 예시 | tempo-prod-...grafana.net:443 endpoint |
GRAFANA_TEMPO_OTLP_GRPC_ENDPOINT |
Alloy otelcol.exporter.otlp에서 사용 |
Dashboard token은 Grafana HTTP API 자동화를 위한 Service Account token입니다. Grafana Service accounts 문서는 service account를 dashboard provisioning, configuration, report generation 같은 자동 작업에 사용하는 계정으로 설명하고, service account token은 Grafana HTTP API 인증에 쓰는 password 대체값으로 설명합니다. 이 Demo에서는 dashboard JSON을 import하는 자동 작업에만 GRAFANA_DASHBOARD_TOKEN을 사용합니다.
| 순서 | Grafana 화면에서 할 일 | 확인할 값 |
|---|---|---|
| 1 | Cloud Portal에서 대상 stack의 Launch 클릭 |
Grafana instance로 이동 |
| 2 | 왼쪽 메뉴에서 Administration 클릭 |
관리 메뉴 확인 |
| 3 | Users and access > Service accounts 클릭 |
service account 목록 확인 |
| 4 | Add service account 클릭 후 이름 입력 |
예: ai-quality-dashboard-import |
| 5 | role을 Editor로 지정 |
dashboard 생성/갱신 권한 확보 |
| 6 | 생성한 service account를 열고 Add service account token 클릭 |
token 생성 화면 진입 |
| 7 | token 이름과 필요한 만료일을 입력하고 Generate token 클릭 |
표시된 token을 GRAFANA_DASHBOARD_TOKEN에 입력 |
Service account token은 생성 직후 한 번만 안전하게 복사한다고 가정합니다. 수강생은 token 값을 문서, 코드, commit, 화면 캡처에 남기지 않고 개인 .env에만 넣습니다. Viewer 권한은 dashboard import에 부족할 수 있으므로 실습에서는 Editor role을 기준으로 안내하고, 실무에서는 조직의 권한 정책에 맞춰 더 좁은 RBAC 권한을 요청합니다.
Tempo 화면에는 Grafana datasource URL이 https://tempo-prod-.../tempo처럼 보일 수 있습니다. 이 URL은 Grafana에서 Tempo를 조회하는 datasource 설정입니다. 이 Demo의 기본 경로는 Alloy container이므로 otelcol.exporter.otlp 예시에 있는 tempo-prod-...grafana.net:443 형태의 gRPC endpoint를 사용합니다. GRAFANA_OTLP_TRACES_ENDPOINT는 OpenTelemetry HTTP endpoint를 확인할 때 참고할 수 있지만, 수업 기본 실행에서는 Python이 Grafana Cloud로 직접 trace를 보내지 않습니다.
Telemetry token은 access policy 자체가 아니라 access policy 아래에서 생성한 token 값입니다. Grafana Cloud Access Policies 문서는 token이 실제 인증 secret이고, policy는 token의 권한을 제어한다고 설명합니다. 따라서 화면에 보이는 policy ID, policy name, realm, scope를 .env에 넣으면 안 됩니다. 실습에서는 logs:write, metrics:write, traces:write scope를 가진 access policy token 하나를 GRAFANA_TELEMETRY_TOKEN에 넣고, Alloy가 이 값을 Logs/Metrics/Traces exporter의 password로 공통 사용합니다.
OpenTelemetry trace endpoint는 Tempo datasource 화면보다 OpenTelemetry connection tile을 우선합니다. Grafana Cloud OTLP endpoint 문서는 OpenTelemetry tile에서 OTEL_EXPORTER_OTLP_ENDPOINT와 OTEL_EXPORTER_OTLP_HEADERS를 생성해 쓰는 흐름을 안내합니다. Tempo datasource의 User와 URL은 Grafana가 Tempo를 조회하는 설정으로 보일 수 있으므로, trace ingest가 invalid token이나 404로 실패하면 OpenTelemetry tile의 endpoint와 header를 다시 확인합니다. OTEL_EXPORTER_OTLP_HEADERS가 Authorization=Basic ... 형태라면 header 안의 Basic 인증값이 user와 token을 함께 인코딩한 값이므로, 수동 .env 방식에서는 OpenTelemetry tile이 함께 제공하는 endpoint, user, token 안내를 기준으로 맞춥니다.
| 연결 대상 | 실행 또는 설정 | 확인할 값 |
|---|---|---|
| Dashboard | demos/ch04_grafana_cloud/import_dashboard.py |
Dashboard import 성공과 메트릭/로그/trace 패널 |
| Logs | Alloy loki.source.file와 loki.write |
validation_failure="true" 로그 조회 |
| Metrics | Alloy prometheus.scrape와 prometheus.remote_write |
ai_quality_high_risk_rate 조회 |
| Traces | send_traces_to_alloy.py와 Alloy otelcol.exporter.otlp |
course_trace_id 기반 Tempo span 조회 |
| Streaming | stream_observability_to_alloy.py |
로그, 메트릭, trace가 계속 들어오는 dashboard 확인 |
Dashboard import는 GRAFANA_DASHBOARD_TOKEN으로 Dashboard API를 호출합니다. Grafana Dashboard HTTP API는 dashboard 생성과 갱신 endpoint를 제공하고, Grafana Folder HTTP API는 folder 생성과 조회 endpoint를 제공합니다. 실제 실행 전에는 --dry-run으로 endpoint, folder uid, dashboard uid를 확인합니다. 스크립트는 AI Quality folder를 만들거나 재사용한 뒤 두 dashboard를 그 아래에 넣습니다. Cloud stack에서 namespace 오류가 발생하면 스크립트가 기존 /api/folders, /api/dashboards/db API로 자동 전환하므로, 수강생은 실패 메시지에 dashboard_import_result=ok가 이어지는지 확인합니다. Invalid API key로 실패하면 Grafana Service Account token을 다시 발급해 GRAFANA_DASHBOARD_TOKEN에 넣어야 합니다.
uv run python demos/ch04_grafana_cloud/import_dashboard.py --dry-run
uv run python demos/ch04_grafana_cloud/import_dashboard.py
Import된 dashboard는 Grafana의 Dashboards 메뉴에서 AI Quality folder 아래에 보입니다. folder 안에는 AI Quality Overview와 AI Quality Details 두 개가 있습니다. AI Quality Overview는 요청 수, 오류 수, 지연 시간, high_risk 비율, 점수/예측 추세, prediction 분포, score bucket, 입력 평균 변화 후보, 기준선 대비 현재 입력 분포를 보여 줍니다. AI Quality Details는 course_trace_id 변수를 기준으로 검증 실패 로그, Tempo trace 검색, 같은 trace_id의 Loki 로그, 대표 요청 trace waterfall, service graph metric을 연결합니다. Grafana Cloud에서 datasource 이름이 다르게 보이면 dashboard 상단의 metrics_datasource, logs_datasource, traces_datasource 변수를 실제 Cloud datasource로 바꿉니다. Grafana Traces visualization 문서는 요청이 지나간 처리 단계를 trace로 확인하는 화면을 설명합니다. 수강생은 Overview에서 이상을 발견한 뒤 Details에서 “어떤 요청 흐름에서 어떤 검증 실패와 모델 응답이 남았는가”를 확인합니다.
Grafana Time series 문서는 시간 흐름에 따른 값 변화를 보기 위한 시각화를 설명합니다. Grafana Histogram 문서는 bucket별 count로 분포를 읽는 방식을 설명하고, Grafana Bar chart 문서는 범주별 값을 비교할 때 bar chart를 사용할 수 있음을 설명합니다. 이 Demo의 prediction, score bucket, 입력 분포 metric은 이미 prediction, bucket, scope 같은 label이 붙은 count로 만들어져 있으므로, Overview에서는 bar chart로 전체/유효 요청과 기준선/현재 입력을 나란히 비교합니다.
이 Demo의 분포 패널은 KDE(density) plot이 아니라 Prometheus metric으로 만든 bucket count 비교입니다. Grafana Cloud dashboard에서는 운영 중 값이 계속 갱신되는 것이 중요하므로 bucket별 count를 bar chart로 보여 줍니다. Evidently report처럼 부드러운 density overlay가 필요하면 Grafana에서 자동 계산하기보다 raw data로 별도 분석 artifact를 생성해 5장 drift_report.md에 연결하는 방식이 더 적절합니다.
Topology map은 trace가 있다는 이유만으로 자동 표시되지 않습니다. Grafana Tempo service graph 문서는 service graph가 trace에서 만들어진 traces_service_graph_* metric을 사용한다고 설명합니다. Grafana Node graph 문서도 node와 edge 데이터가 필요하다고 설명합니다. 따라서 이 Demo는 Alloy otelcol.connector.servicegraph로 trace를 service graph metric으로 변환하고, Details dashboard의 Service Topology Map에서 client/server 관계를 node graph로 보여 줍니다. Service Graph Connectivity는 같은 metric이 실제로 edge 데이터로 쌓였는지 확인하는 보조 표이고, Service Graph Latency p90는 edge별 지연 시간 추세를 확인하는 보조 패널입니다.
Grafana Cloud로 Logs, Metrics, Traces를 보내는 기본 선택 경로는 Alloy container입니다. Grafana Alloy Docker 문서는 config file을 container에 mount하고 grafana/alloy image를 실행하는 방식을 안내합니다. Alloy 공식 component 문서는 loki.write, prometheus.remote_write, otelcol.exporter.otlp가 각각 Grafana Cloud 같은 managed service로 전송할 때 Basic Auth를 사용할 수 있음을 보여 줍니다.
uv run python demos/ch04_grafana_cloud/serve_metrics.py
docker compose -f demos/ch04_grafana_cloud/docker-compose.alloy.yml up
serve_metrics.py는 한 터미널에서 계속 실행합니다. 다른 터미널에서 Alloy container를 띄우면 Alloy가 artifacts/logs/chapter_04_anomaly_events.jsonl을 Loki로 보내고, http://host.docker.internal:9108/metrics를 scrape해 Metrics로 보냅니다. Alloy UI는 http://localhost:12345에서 열어 component health와 전송 오류를 확인할 수 있습니다.
Trace sample은 Alloy의 local OTLP receiver로 보냅니다. 이 명령은 Grafana Cloud로 직접 인증하지 않고 http://127.0.0.1:4318/v1/traces에 payload를 넣습니다. Cloud 인증과 전송은 Alloy가 담당합니다. 로그와 메트릭은 별도 push script를 쓰지 않습니다. 로그는 Alloy가 JSONL 파일을 읽고, 메트릭은 Alloy가 serve_metrics.py의 /metrics endpoint를 scrape합니다. Trace payload는 qa-client -> ai-quality-serving -> input-validator/model-runtime/observability-pipeline 구조의 client/server span 쌍을 포함하므로, Alloy service graph connector가 service 간 edge metric을 만들 수 있습니다.
uv run python demos/ch04_grafana_cloud/send_traces_to_alloy.py
기본 trace sample은 30개 요청입니다. 요청 하나는 client/server span 쌍 4개, 총 8개 span으로 구성되므로 기본 실행에서 240개 span을 보냅니다. 실행 결과에 표시되는 course_trace_id, tempo_trace_id, TraceQL은 Details dashboard 변수에 그대로 넣어 확인합니다.
실제 운영처럼 dashboard 값이 계속 쌓이는 장면을 보려면 streaming sender를 실행합니다. 이 명령은 실행 중 매 batch마다 data/operational_current_stream_events.jsonl을 다시 읽고, source event를 현재 시각의 새 요청으로 바꿔 JSONL 로그 파일에 append합니다. 동시에 Prometheus text metric 파일을 갱신하고, 같은 batch의 trace를 Alloy OTLP receiver로 보냅니다. Alloy와 serve_metrics.py는 계속 실행 중이어야 합니다.
uv run python demos/ch04_grafana_cloud/stream_observability_to_alloy.py --max-batches 10
기본 source event 파일은 2,000건입니다. data/operational_current_events.jsonl 120건은 문서와 보고서 예시의 수치를 고정하기 위한 current incident sample이고, data/operational_current_stream_events.jsonl은 Grafana dashboard를 운영처럼 보이게 하기 위한 streaming source입니다. Streaming sender는 이 source를 template처럼 사용해 새로운 request_id, trace_id, timestamp를 계속 만들기 때문에 dashboard에서는 요청 수와 검증 실패 수가 계속 증가합니다. 더 큰 JSONL source가 있으면 --source-event-file로 지정합니다.
Streaming 실행 결과에는 batch마다 대표 course_trace_id와 tempo_trace_id가 출력됩니다. Details dashboard의 변수 값을 그 출력값으로 바꾸면 새로 들어온 요청의 Loki 로그와 Tempo trace를 같은 기준으로 확인할 수 있습니다.
Cloud Explore에서는 다음 LogQL, PromQL, TraceQL을 확인합니다.
{service="ai-quality-serving", environment="training"} | json | validation_failure="true"
ai_quality_high_risk_rate{service="ai-quality-serving", environment="training"}
sum(traces_service_graph_request_server_seconds_count{}) by (client, server)
{ .course_trace_id = "current-trace-0000" }
AI Quality Details에는 course_trace_id 변수 기본값이 current-trace-0000으로 들어 있습니다. Trace Search by course_trace_id 패널은 이 값을 TraceQL attribute로 검색하고, Representative Request Trace 패널은 같은 샘플의 실제 Tempo trace id로 waterfall을 표시합니다. Service Topology Map이나 Service Graph Connectivity가 비어 있으면 먼저 Alloy UI에서 otelcol.connector.servicegraph와 prometheus.remote_write 상태를 확인합니다. trace가 비어 있으면 send_traces_to_alloy.py 실행 시각과 Grafana 시간 범위를 맞추고, 그 다음 Alloy UI에서 otelcol.exporter.otlp 상태를 확인합니다.
Drift 후보는 Cloud dashboard와 Prometheus metric에서도 확인합니다. ai_quality_input_mean_delta{feature="heart_rate"}, ai_quality_input_mean_delta{feature="oxygen_saturation"}는 평균 변화 방향을 보여 주고, Heart Rate Distribution: Reference vs Current와 Oxygen Saturation Distribution: Reference vs Current는 기준선과 현재 입력이 어떤 bucket에서 달라졌는지 보여 줍니다. ai_quality_score_average_delta, ai_quality_high_risk_rate_delta는 입력 구성 변화와 예측 변화가 같은 방향으로 움직였는지 확인하는 연결 지표입니다. 이 값들은 5장 drift_report.md로 넘어가기 전의 운영 관측 신호입니다.
trace payload만 확인하고 싶을 때는 --dry-run을 사용합니다. 이 명령은 payload preview를 만들고 span 수와 TraceQL을 출력하지만, Alloy나 Grafana Cloud로 전송하지 않습니다.
uv run python demos/ch04_grafana_cloud/send_traces_to_alloy.py --dry-run
이 Demo의 보고서 문장은 전송 여부와 조회 여부를 분리해 씁니다. 예를 들어 “Dashboard import와 Alloy collector는 실행했지만, otelcol.exporter.otlp가 인증 오류로 unhealthy였으므로 Traces Cloud 조회는 미확인입니다”처럼 남기면 증거 범위가 명확합니다.
4-4-5. 조회 실패 시 확인 포인트¶
운영 관측에서 흔한 문제는 “로그나 메트릭이 없다”가 아니라 “있지만 원하는 방식으로 찾을 수 없다”입니다. 라벨이 부족하거나, 필드가 JSON으로 파싱(parsing)되지 않거나, 시간 범위(time range)가 맞지 않으면 대시보드가 비어 보일 수 있습니다.
| 문제 | 확인 항목 |
|---|---|
| 로그가 보이지 않음 | 데이터소스(datasource), 라벨, 시간 범위(time range), Loki push 응답 |
request_id 검색 실패 |
필드 이름, JSON 파싱(parsing) 여부 |
trace_id가 없음 |
API에서 trace 필드를 남겼는지 확인 |
Dashboard import가 Invalid API key로 실패 |
Grafana Service Account token을 GRAFANA_DASHBOARD_TOKEN에 넣었는지 확인 |
| Dashboard가 root에 보이거나 folder 생성이 실패함 | GRAFANA_FOLDER_UID, GRAFANA_FOLDER_TITLE, service account 권한 확인 |
Logs/Metrics/Traces가 모두 invalid token으로 실패 |
access policy의 ID/name이 아니라 실제 token 값을 GRAFANA_TELEMETRY_TOKEN에 넣었는지 확인 |
| Alloy UI에서 component가 unhealthy | loki.write, prometheus.remote_write, otelcol.exporter.otlp 중 어느 component가 실패했는지 확인 |
Tempo trace 전송이 invalid token으로 실패 |
Alloy otelcol.exporter.otlp 인증 정보와 GRAFANA_TELEMETRY_TOKEN scope 확인 |
| local Alloy trace 전송이 실패 | http://127.0.0.1:4318/v1/traces receiver와 container port 확인 |
| Tempo trace가 보이지 않음 | OTLP traces endpoint, 인증, 시간 범위, course_trace_id attribute |
| 메트릭이 보이지 않음 | 메트릭 이름(metric name), 수집(scrape), remote_write endpoint와 인증, Prometheus 실행 여부 |
| 대시보드 패널(dashboard panel)이 비어 있음 | 쿼리, 데이터소스(datasource), 라벨 조건, Cloud 전송 여부 |
수업 기본 경로에서는 실제 Grafana Cloud 전송 대신 페이로드 미리보기(payload preview)를 사용합니다. 따라서 “전송 성공”보다 “전송된다면 어떤 라벨과 필드가 필요한가”를 확인합니다. Cloud 연결을 진행한 경우에는 import, push, remote_write 중 어느 단계까지 확인했는지를 보고서에 구분해 남깁니다.
QA는 조회 실패를 도구 문제로만 보지 않아야 합니다. 조회 실패는 로그 설계 문제일 수 있습니다. 처음부터 필요한 필드를 남기지 않으면 나중에 어떤 도구를 써도 원인 추적이 어렵습니다.