LLM 앱 로깅, 프롬프트와 응답을 어디까지 남겨야 할까

LLM 앱 로깅

새벽에 “환불 절차 알려줘”라고 물었던 사용자 문의가, 로그 서버에는 이름과 전화번호까지 통째로 남아 있는 걸 발견한 적 있으신가요? LLM 앱 로깅은 디버깅을 위해 반드시 필요하지만, 프롬프트와 응답을 원문 그대로 저장하면 그 자체가 개인정보 저장소가 되어버립니다. 결론부터 말씀드리면 원문 전체 저장 대신 마스킹·요약·짧은 보관 기간을 조합하는 쪽이 안전하면서도 디버깅 효율을 크게 해치지 않습니다.

이 글은 Python 백엔드에서 OpenAI·Anthropic 같은 LLM API 호출을 로깅 미들웨어로 감싸는 구조를 전제로 씁니다. 프론트엔드에서 직접 로깅하는 경우나 온디바이스 LLM은 상황이 다를 수 있습니다.

로그에 남는 순간 개인정보가 되는 이유

LLM 앱은 사용자가 입력한 문장을 그대로 API로 보내고, 그 응답을 화면에 뿌립니다. 문제는 이 과정에서 요청·응답 로그를 남기면 이름, 이메일, 전화번호, 주소, 심지어 병력이나 계좌번호 같은 민감정보까지 원문 그대로 저장 서버에 쌓인다는 점입니다.

OWASP가 정리한 LLM 애플리케이션 보안 위험 목록에서도 민감정보 노출을 별도 항목으로 다루고 있습니다. OWASP Top 10 for LLM Applications의 LLM02 항목은 모델이 학습 데이터나 대화 맥락에 포함된 개인정보·기밀정보를 응답에 노출시키거나, 그런 정보가 로그·캐시에 남아 유출되는 상황을 위험 요소로 명시하고 있습니다.

즉 모델 자체의 응답 품질 문제가 아니라, 로깅 파이프라인 설계가 곧 보안 경계선이 된다는 뜻입니다. 디버깅용 로그라고 해서 예외가 되지 않습니다.

프롬프트와 응답, 어디까지 남겨야 디버깅이 가능한가요?

실무에서 흔히 겪는 딜레마는 “다 지우면 버그를 못 잡고, 다 남기면 위험하다”는 것입니다. 이럴 때는 로그 항목을 세 층위로 나눠 생각하면 정리가 됩니다.

  • 메타데이터: 요청 시각, 모델명, 토큰 수, 응답 지연시간, 에러 코드 — 개인정보가 섞이지 않으므로 원본 그대로 장기 보관해도 무방합니다.
  • 구조화된 신호: 사용자가 어떤 기능(요약/번역/코드생성)을 썼는지, 응답이 중간에 끊겼는지 여부 — 원문 없이도 문제 유형을 좁힐 수 있습니다.
  • 원문 텍스트: 프롬프트·응답 전문 — 마스킹을 거치거나, 샘플링(예: 1% 무작위 또는 오류 발생 건만)해서 짧게만 보관합니다.

서버 로그 화면에 코드와 데이터가 표시된 모습

버그 재현이 필요한 순간은 대부분 “에러가 난 그 요청”이지 전체 트래픽이 아닙니다. 정상 응답 로그는 메타데이터 수준으로 충분한 경우가 많고, 예외·저품질 응답만 원문을 남기는 식으로 좁히면 저장량과 위험이 동시에 줄어듭니다.

마스킹과 해싱으로 로그 다이어트 해보기

Python 표준 라이브러리 relogging만으로도 저장 직전에 민감정보를 걸러내는 미들웨어를 만들 수 있습니다. 아래는 이메일과 국내 휴대전화 번호 패턴을 마스킹한 뒤 로깅하는 예시입니다.

import re
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("llm_app")

EMAIL_RE = re.compile(r"[\w\.-]+@[\w\.-]+\.\w+")
PHONE_RE = re.compile(r"01[016789]-?\d{3,4}-?\d{4}")

def mask(text: str) -> str:
    text = EMAIL_RE.sub("[EMAIL]", text)
    text = PHONE_RE.sub("[PHONE]", text)
    return text

prompt = "제 이메일은 hong@example.com이고 전화번호는 010-1234-5678입니다. 환불 절차 알려주세요."
logger.info("prompt=%s", mask(prompt))

실행하면 아래처럼 원문 대신 마스킹된 문자열이 로그에 남습니다.

INFO:llm_app:prompt=제 이메일은 [EMAIL]이고 전화번호는 [PHONE]입니다. 환불 절차 알려주세요.

다만 이 방식에는 명확한 한계가 있습니다. 정규식은 정해진 패턴(이메일, 전화번호 형식)만 잡아내기 때문에 “저는 서울 강남구에 살고 계좌는 국민은행 123-456입니다” 같은 자유 서술형 개인정보는 놓칩니다. 완전한 탐지가 필요하다면 규칙 기반 마스킹에 개체명 인식(NER) 모델을 얹는 방향을 검토해야 하며, 이 경우 처리 지연이 늘어나는 트레이드오프가 생깁니다. 마스킹을 지나치게 세게 걸면 정작 버그 재현에 필요한 문맥까지 지워져서, 개발자가 사용자에게 다시 물어봐야 하는 역효과도 발생할 수 있습니다.

보관 기간과 접근 권한, 법이 요구하는 최소 기준

개인정보보호법은 개인정보 처리 목적을 달성했으면 그 정보를 지체 없이 파기하도록 규정하고 있습니다(제21조). 디버깅 로그도 예외가 아니라서, “혹시 나중에 필요할지 모르니” 식으로 무기한 보관하는 관행은 법적 근거가 약합니다. 개인정보보호위원회는 처리 목적에 맞춰 보관 기간을 명시하고, 목적 달성 후에는 파기하거나 다른 개인정보와 분리 보관하도록 안내하고 있습니다.

AI 챗봇 대화에서 개인정보를 보호하는 개념 이미지

실무에서 적용할 만한 기준은 이렇습니다.

로그 유형 권장 보관 기간 접근 권한
메타데이터(토큰 수, 지연시간) 장기(수개월~1년) 운영팀 전체
오류 발생 시 원문 프롬프트 짧게(1~4주) 담당 개발자만, 감사 로그 필수
정상 응답 원문 저장 안 함 또는 샘플링만 최소 인원

원문을 저장한다면 별도 테이블·버킷에 분리하고, 접근할 때마다 누가 언제 조회했는지 감사 로그를 남기는 편이 좋습니다. 개인정보가 섞인 로그와 일반 운영 로그를 같은 테이블에 두면, 권한 관리가 한 번 뚫렸을 때 피해 범위가 걷잡을 수 없이 커집니다.

Langfuse·LangSmith·자체 구축 로깅 비교

직접 마스킹 로직을 짜는 대신 이미 나와 있는 LLM 관측 도구를 쓰는 방법도 있습니다. 대표적으로 오픈소스 진영의 Langfuse와 LangChain 팀이 만든 LangSmith가 자주 언급됩니다.

항목 Langfuse LangSmith 자체 구축
배포 방식 셀프호스팅 또는 클라우드 선택 가능 LangChain 클라우드 중심 직접 서버·DB 설계
데이터 보관 위치 셀프호스팅 시 자체 인프라 내 보관 서비스 제공사 서버 원하는 리전에 자유 배치
마스킹 커스터마이징 트레이싱 훅에 마스킹 함수 삽입 가능 프로젝트 설정에서 일부 조정 가능 전적으로 직접 구현
적합한 상황 국내 데이터 보관 요건이 엄격한 팀 LangChain·LangGraph를 이미 쓰는 팀 로깅 요구사항이 매우 특수한 경우

셀프호스팅이 가능한 도구는 로그 데이터가 회사 인프라 밖으로 나가지 않는다는 점에서 개인정보 처리방침을 지키기 수월합니다. 반대로 클라우드형 서비스는 설정과 운영 부담이 적은 대신, 원문 프롬프트가 외부 서버를 거친다는 사실을 이용약관에서 반드시 확인해야 합니다. 요금제나 세부 기능은 각 도구가 자주 업데이트하므로, 도입 전에는 공식 문서에서 최신 사양을 직접 확인하시길 권합니다.

지금 로깅 코드에 마스킹 미들웨어부터 넣어보세요

LLM 앱 로깅은 끄고 켜는 문제가 아니라 “무엇을, 얼마나, 누가 볼 수 있게” 남기느냐의 설계 문제입니다. 메타데이터는 넉넉히 남기고, 원문은 오류가 난 요청에 한해 짧은 기간만 마스킹해서 보관하는 방식이 디버깅 능력과 개인정보 보호 사이에서 현실적인 균형점입니다.

당장 적용할 수 있는 순서는 이렇습니다. 먼저 지금 쌓이는 로그에 이메일·전화번호 같은 원문이 그대로 들어가는지 최근 로그 파일을 열어 확인해 보세요. 그다음 위 코드 예시처럼 저장 직전에 거치는 마스킹 함수를 한 곳에 만들어 모든 로깅 호출이 그 함수를 통과하도록 강제하고, 원문 프롬프트 로그의 보관 기간을 1~4주로 짧게 잘라보시기 바랍니다.

LLM 배치 API로 비용 절반 줄이기, 되는 작업과 안 되는 작업 구분법

Leave a Comment