
LLM 로깅 베스트 프랙티스: 우리가 프로덕션에서 따르는 9가지 규칙 [2026]
이 아홉 가지 LLM 로깅 베스트 프랙티스는 우리 프로덕션 스택이 실제로 따르는 규칙입니다. 우리는 월 1.2백만 개의 LLM 요청을 4개 서비스에서 로깅하며, 모든 요청은 Grafana Loki에 모델, 토큰, 레이턴시, cost_usd, trace_id를 담은 단일 JSON 라인으로 기록됩니다. structlog 25.4.0이 레코드를 작성하고, Presidio가 PII를 먼저 제거한 후 전체 파이프라인은 우리 옵저버빌리티 스택의 로깅 부분입니다.
핵심 요약
- 모든 LLM 요청을 14개 이상의 명명된 필드가 있는 구조화된 JSON으로 로깅하되, 자유 형식 텍스트는 사용하지 말 것.
- Presidio 또는 이에 상응하는 도구로 로그 작성 전에 PII를 제거할 것, 작성 후가 아니라.
- 모든 트레이스에 OpenTelemetry GenAI 의미 규약 속성을 첨부할 것.
- 일일 100만 요청 기준, 동일한 60GB 용량이 Datadog에서는 월 $108, Loki에서는 $30, ClickHouse에서는 $1.20의 비용이 소요됨.
LLM 로깅의 의미와 "모든 것을 로깅하면 되지 않나?" 왜 실패하는가
LLM 로깅은 모든 모델 요청과 응답의 구조화된 레코드를 캡처하는 것을 의미합니다: 프롬프트, 완료, 토큰 수, 레이턴시, 비용, 그리고 모든 것을 사용자 세션과 연결하는 트레이스. 이는 인프라 로깅이 아닙니다. CPU, 메모리, 파드 재시작은 메트릭 스택에 속하며, 이 글은 모델 동작을 디버깅하고, 비용을 추적하고, 감사할 수 있게 해주는 요청 수준의 레코드만 다룹니다.
"모든 것을 로깅하자"는 본능은 쉽게 사라지지 않으며, 비용이 많이 듭니다. 일일 100만 요청에서 전체 프롬프트와 완료를 로깅하면 월 약 60GB의 텍스트가 생성되며, 그 텍스트의 상당 부분은 무기한으로 저장하고 있는 고객 PII입니다. GDPR의 제5조 데이터 최소화 원칙은 개인 데이터가 "적절하고 관련성 있으며 필요한 범위로 제한"되어야 한다고 요구하며, 원본 프롬프트 덤프는 첫날부터 이 기준을 충족하지 못합니다. 모든 것을 로깅하는 것은 전략이 아닙니다. 월별 청구서가 붙은 책임입니다.
9가지 LLM 로깅 규칙은 무엇인가?
아홉 가지 규칙을 구현 순서대로 나열하면: 해시 식별자를 사용하여 전체 프롬프트와 응답 로깅, 구조화된 JSON 발신, 요청당 토큰과 비용 캡처, OpenTelemetry 트레이스 컨텍스트 첨부, 작성 전 PII 제거, 높은 볼륨에서 샘플링, 보존 계층 설정, 안전 이벤트 분리, 결과 쿼리 가능하게 만들기. 아래 각 규칙과 함께 이를 적용하는 코드 또는 테이블이 제시됩니다.
규칙 1: 전체 프롬프트와 응답 로깅 (원본 PII가 아닌 해시 사용)
모든 요청에 대해 완전한 프롬프트와 완전한 완료를 로깅하십시오. 부분 로그는 인시던트를 분석할 때 모델이 실제로 무엇을 봤는지 기록하지 못하는 경우입니다. 한 가지 예외는 신원입니다: 사용자 ID, 이메일, 이름을 원본 형태로 레코드에 작성하지 마십시오. 대신 사용자 ID의 SHA-256 해시를 저장하십시오. 해시는 여전히 오프라인 조회를 통해 특정 사용자의 전체 세션 기록을 재구성할 수 있게 해주면서, 로그 라인 자체는 읽어서는 안 되는 사람에게는 무용지물이 됩니다. 시스템 프롬프트도 동일한 논리를 적용하십시오: 해시하고, 해시를 로깅하고, 평문은 이미 버전 관리되고 있는 프롬프트 레지스트리에 보관하십시오.
규칙 2: 구조화된 JSON 사용: 모든 필드의 명명, 자유 형식 텍스트 없음
Python이나 다른 언어에서의 LLM 로깅 베스트 프랙티스는 구조화된 JSON 로깅이 필수적입니다: 모든 필드가 명명되고, 타입이 정해지고, 쿼리 가능하며, 형식화된 문자열로 덤프되지 않습니다. "INFO called gpt-4o, took 812ms"처럼 자유 형식 라인은 grep으로만 검색할 수 있습니다. JSON 레코드는 모델별로 집계되고, 비용별로 합산되고, 트레이스에 조인될 수 있습니다. OpenAI의 자체 프로덕션 베스트 프랙티스도 동일한 아이디어를 지지합니다: print 문이 아닌 SDK 계층에서 구조화된 메타데이터를 캡처하십시오.
모든 Techsy 서비스가 발신하는 스키마는 14개 필드입니다:
세 개의 필드가 주목할 만합니다. cost_usd는 요청 시간에 토큰 수와 모델의 공시 요율에서 계산되며, 야간 작업으로 역채우기(backfill) 되지 않습니다. 두 해시 필드는 규칙 1의 타협입니다: 오프라인에서는 연관 가능하지만 로그에서는 불투명합니다. 그리고 trace_id와 span_id는 W3C 트레이스 컨텍스트 값이며, 이는 정확히 규칙 4가 다루는 내용입니다.
프로바이더를 직접 호출하는 4개 서비스가 4개의 계측 지점처럼 들린다면, LiteLLM 프록시가 이를 집중화합니다: 모든 프로바이더 앞에 하나의 로깅 훅.
규칙 3: 요청당 토큰 수와 비용 캡처
토큰 사용 추적은 내일 실행되는 웨어하우스 작업이 아닌 로그 라인 자체에 속합니다. 모든 프로바이더는 응답에서 입력과 출력 토큰 수를 반환합니다. 모델의 정확한 시점의 토큰당 요율로 곱하고 cost_usd를 레코드에 작성하십시오. 요율은 변하고, 캐시된 입력 토큰과 신규 입력 토큰 간에 다르므로, 나중에 정적 가격 테이블로 비용을 계산하면 조용히 기록이 다시 작성됩니다. 모든 라인에 비용이 있으면 "어느 기능이 비싼가?"라는 질문이 일일 금융 프로젝트 대신 한 줄 쿼리가 되고, 이는 LLM API 지출을 줄이기 위한 작업으로 직접 이어집니다.
규칙 4: 트레이스 컨텍스트 첨부 (OpenTelemetry GenAI Semconv)
trace_id가 없는 로그 라인은 고아입니다: 읽을 수는 있지만 어느 재시도, 어느 RAG 스테프, 어느 사용자 턴이 이를 생성했는지 알 수 없습니다. 해결책은 OpenTelemetry의 GenAI 의미 규약입니다. 모델 호출을 계측하기 위한 표준 속성명입니다. 활성 스팬 내에서 로그를 발신하면 trace_id와 span_id가 자동으로 첨부되므로 Grafana에서 한 번 클릭하면 트레이스 워터폴에서 원본 레코드로 바로 이동합니다.
모든 gen_ai 스팬에서 설정할 가치가 있는 속성:
규칙 5: 로그 작성 전에 PII 제거
PII 제거는 레코드가 작성된 후가 아닌 작성 전에 일어나야 합니다. 이메일 주소가 일단 Loki에 있으면 객체 저장소 백업에도 있으며, "나중에 삭제했다"는 GDPR 답변이 아닙니다. 우리 설정에서는 Microsoft Presidio가 structlog 프로세서로 실행되어 이메일 주소와 전화번호의 94%를 Loki에 도착하기 전에 포착합니다. 누락된 부분은 거의 모두 이상한 형식이며, 발견하면서 커스텀 인식기에 패치합니다.
전체 훅은 15줄입니다:
제거는 입력 및 출력 필터와 동일한 파이프라인 계층에 위치하며, 동일한 방식으로 테스트되어야 합니다. 우리의 가드레일 파이프라인은 로그에 누출된 이메일을 옵스 각주가 아닌 실패한 평가로 취급합니다.
규칙 6: 높은 볼륨에서 지능형 샘플링
하루에 약 100,000 요청 이하일 때는 모든 것을 로깅하십시오. 그 이상이면 전체 볼륨 로깅은 결코 읽지 않을 데이터에 대한 저장소 세금이며, 샘플링은 중요한 레코드를 유지하는 방법입니다. 문제는: 무작위 샘플링은 LLM 트래픽의 최악의 선택입니다. 실패, 거부, 5달러 요청이 정의상 드물기 때문에 균일한 10% 비율은 정확히 디버깅하는 이벤트를 제거합니다. 동전 던지기가 아닌 결과로 샘플링하십시오.
일반적인 설정은 엣지(프로덕션 및 엔터프라이즈 테넌트: 항상 로깅)에서 규칙 기반이고 중간에서 테일 기반입니다. 로그 관점: guardrail_result와 cost_usd 필드가 샘플링 신호이며, 규칙 2와 8을 따랐다면 이미 존재합니다.
규칙 7: 필요하기 전에 보존 정책 설정
로그 보존 정책은 침착한 상태에서 내리는 결정입니다. 다른 선택지는 비용 검토 중에 2배의 볼륨으로 이 결정을 내리는 것이기 때문입니다. GDPR의 제5조 저장소 제한 원칙은 개인 데이터를 "필요한 것보다 오래" 유지하지 않아야 한다고 말하며, 실제로는 계층화된 보존을 의미합니다:
Hot은 "10분 전에 무엇이 일어났나?"에 빠르고 비싸게 답하고, Cold는 "3월에 이 고객에게 무엇을 말했나?"에 느리고 싸게 답합니다. 자동으로 일정에 따라 삭제하거나, 계층은 단지 다이어그램일 뿐입니다.
규칙 8: 가드레일과 안전 이벤트를 별도로 로깅
안전 이벤트(가드레일 블록, 거부, 정책 위반)는 텔레메트리가 아닙니다. 감사 레코드이며, 자체 스트림에 속합니다. 세 가지 이유가 있습니다. 알림: 차단된 프롬프트 인젝션의 급증은 누군가를 호출해야 하며, 100만 개의 루틴 라인에 대해 경보를 조정할 수 없습니다. 보존: 규정 준수는 안전 레코드가 디버그 로그보다 몇 년 더 오래 유지되도록 요구할 수 있습니다. 접근: 감사자는 전체 파이어호스가 아닌 안전 스트림을 얻습니다. 주 레코드에 판정을 태그하고( guardrail_result: "block" ) 전체 레코드를 별도 스트림으로 라우팅하십시오. 안전 이벤트로 간주되는 것은 가드레일 이벤트 가이드에서 다룹니다.
...