📚 엔지니어 · 세션 · 실전 노트 (수정중) · L02

L24 — 에이전트에게 증거를 떠먹이기: psql·서버 로그·traceback 접근을 제도화하기 (수정중)

작성일자 2026-06-28  ·  수정일자 2026-06-28  ·  강의차수 L02  ·  예상소요 30분

목표 이 강의가 끝나면 학습자가 "에이전트가 디버깅 증거(DB·로그·traceback)에 스스로 닿게 하는 장치(agent·hook·CLAUDE.md·접근권한)를 어떻게 짜는지" 설명할 수 있다.

L23은 "증거(로그·응답 본문)를 먼저 봐라" 였다. 이 강의는 그 짝이다 — 에이전트가 그 증거에 닿을 수 있게 프로젝트를 어떻게 설계했나. L23의 디버깅이 가능했던 전제 조건을 푼다.

핵심 명제 하나로 시작하자.

AI 에이전트의 디버깅 품질 = 닿을 수 있는 증거의 양. 더 똑똑한 프롬프트가 아니라, 더 많은 진짜 증거 통로(DB·로그·traceback)를 주는 게 효과가 크다. 증거에 못 닿는 에이전트는 추측만 한다 — L23에서 Claude가 두 번 헛다리를 짚은 게 그 증거다.

이 강의는 그 증거 통로를 세 개 열고, 그걸 제도화(말→강제→격리) 한 이야기다.


1. 증거 통로 ① — traceback이 로그에 남게 한다

로그를 읽어도 traceback이 없으면 소용없다. except 에서 예외를 잡고 logger.error(e) 만 쓰면 메시지만 남고 스택은 버려진다 — 어디서 터졌는지 모른다.

같은 예외를 두 방식으로 로깅해 보면 차이가 한눈에 보인다.

# ✗ logger.error(e) — 메시지 한 줄만 남는다
try:
    summary = transcribe(meeting_audio)
except Exception as e:
    logger.error(e)         # 로그: "division by zero"  ← 어디서? 모른다
    raise

# ✓ logger.exception(...) — except 안에서 호출하면 traceback이 자동으로 붙는다
try:
    summary = transcribe(meeting_audio)
except Exception:
    logger.exception("회의록 전사 실패")   # 인자로 예외를 넘길 필요도 없다
    raise

logger.exception 이 로그에 남기는 건 메시지 + 스택 전체다 — 에이전트(와 사람)가 진짜 터진 줄에 바로 닿는다:

ERROR 회의록 전사 실패
Traceback (most recent call last):
  File "meetings/services.py", line 88, in transcribe
    ratio = total / silence_frames
            ~~~~~~^~~~~~~~~~~~~~~~~
ZeroDivisionError: division by zero

위쪽 로그(division by zero 한 줄)로는 services.py:88 을 절대 못 짚는다. 아래쪽이 있어야 디버깅이 시작된다.

그래서 규칙을 박았다 — CLAUDE.md ## Code Patterns > Error Handling:

except 블록에서 예외를 로깅할 때는 logger.error/logger.warning 대신 반드시 logger.exception 을 쓴다. (traceback이 무조건 로그에 남아야 진단이 된다.)

말로만 두면 안 지켜진다. 그래서 hook으로 강제한다 — .claude/hooks/check-except-logger-exception.sh → .claude/hooks/check_except_logger.py. 이건 커밋 시점에 staged .py 를 AST로 파싱해, except 블록 안에서 logger.error/.warning 을 쓴 곳을 잡아 커밋을 차단한다(exit 2).

두 가지 설계 포인트: - AST 파싱 — 단순 grep 이 아니라 구문 트리로 "except 블록 안" 인지 본다. except 밖의 논리적 실패 로깅(logger.error)은 정상이므로 안 잡는다. - 신규 위반만 — 이번 커밋에서 추가된 라인만 본다. 기존 코드의 옛 위반으로 커밋이 막히는 노이즈를 피한다.

출처는 popax의 "전사/회의록 task traceback 유실" 사건이다 — 한 번 데인 교훈이 1층 코드 규칙 + hook으로 굳었다.


2. 증거 통로 ② — 서버 로그에 닿게 한다

traceback이 로그에 남아도, 에이전트가 그 로그를 읽을 수 있어야 한다. 그래서 로그 읽기를 전담하는 read-only 서브에이전트를 만들었다 — .claude/agents/debug-server-log.md.

name: debug-server-log
tools: Read, Grep, Glob, Bash     # ← 쓰기 도구(Edit/Write) 없음 = read-only 강제

이 에이전트는 추측이나 코드 추정 전에 로그부터 읽어 traceback과 요청 상태를 보고한다. 로그 위치도 명시돼 있다:

환경 로그 접근
dev (runserver) Django 콘솔 stdout BashOutput(백그라운드 shell)
prod (Apache) error 로그 (Django traceback) sudo tail /var/log/apache2/*-error.log
prod (Apache) access 로그 (요청·상태·응답크기) sudo tail /var/log/apache2/*-access.log

닿기 위한 접근권한도 깔려 있다 — prod 박스는 개인키 SSH(~/.ssh/popup-chris-prod.pem)와 devteam NOPASSWD sudo(팀 wiki infrastructure/sudo-policy.md 참고)로 로그를 읽는다.

예를 들어 "로그인이 403으로 막힌다"는 제보가 오면, 이 에이전트는 코드를 보기 전에 prod access 로그부터 길어 온다:

$ sudo tail -n 3 /var/log/apache2/education-prod-access.log
1.2.3.4 - - [30/Jun/2026:23:10:02] "POST /api/login/ HTTP/1.1" 403 748   ← 사용자(브라우저)
5.6.7.8 - - [30/Jun/2026:23:11:40] "POST /api/login/ HTTP/1.1" 403 5209  ← 내 curl 재현

상태코드(403)는 둘 다 같다. 단서는 응답 크기다 — 사용자는 748, 내 재현은 5209. 크기가 다르면 둘은 다른 403 이고, "내 재현이 실패 조건과 다르다"(L23 3번 규칙)는 자각이 여기서 나온다. 그다음 error 로그의 응답 본문({"detail":"Invalid username/password."})이 진짜 원인(BasicAuth)을 지목한다. 에이전트가 access 로그에 닿지 못했으면 이 한 줄을 영영 못 본다.

왜 read-only 전용 에이전트인가 — L12에서 봤듯, tools 에 Edit/Write 를 안 주면 "로그만 보고 코드는 못 고친다"가 구조적으로 강제된다. 조사와 수정을 분리해, 조사 단계에서 섣불리 코드를 건드리는 사고를 막는다. (수정 결정은 메인 Claude 가 한다.)


3. 증거 통로 ③ — DB에 닿게 한다

"분명히 저장했는데" 류의 버그는 DB를 직접 봐야 풀린다 — 추정하면 틀린다. 이건 별도 스킬이 필요 없었다.

  • Bash 도구로 psql 을 직접 치거나, python manage.py shell_plus(CLAUDE.md Common Commands) 로 ORM을 직접 굴린다.
  • prod DB도 같은 SSH + sudo로 닿는다.

L23에서 "유저 chris@popupstudio.ai 가 실제로 존재하는가"를 psql로 1초 만에 확인한 게 이 통로다.

$ psql education_prod -c "SELECT id, email, is_active FROM accounts_user WHERE email='chris@popupstudio.ai';"
 id |        email          | is_active
----+-----------------------+-----------
  7 | chris@popupstudio.ai  | t
(1 row)

(1 row) 한 줄이 "유저는 멀쩡히 있다"를 확정한다 — 그러니 403의 원인은 유저 부재가 아니다. 추측이라면 "혹시 계정이 없나? 비활성인가?"를 한참 맴돌았겠지만, DB를 직접 보면 그 가지가 즉시 잘린다. 남는 가설(인증 헤더 문제)로 곧장 좁혀지는 것이다.

만약 이걸 못 봤으면 "유저가 없어서 그런가?"를 한참 추측했을 것이다.


4. 제도화 — 말 → 강제 → 격리 (M3 3종 세트)

증거 통로 세 개를, 사람이 매번 챙기지 않아도 굴러가게 제도화했다. M3에서 배운 세 장치가 그대로 쓰인다.

층 장치 역할 강의
말 CLAUDE.md ## Debugging Rules "로그 먼저, 응답 본문 보기, 재현 조건 확인, DB 직접" 을 문서로 박음 template CLAUDE.md
강제 hook (check_except_logger.py) logger.exception 안 쓰면 커밋 차단 (말이 안 지켜질 때의 안전망) L13
격리 agent (debug-server-log) 로그 읽기를 read-only 전용 창으로 분리 L12

CLAUDE.md(말)는 어기기 쉽고, hook(강제)은 어길 수 없지만 만들기 번거롭다. 반복해서 데는 규칙만 hook으로 승격한다 — L13의 "말→강제 승격" 원칙 그대로다.


5. payoff — 이번 세션이 그 증명

L23의 진짜 원인(브라우저 캐시 Authorization: Basic + DRF BasicAuthentication)은 세 통로가 다 있었기에 찾았다.

  • DB 접근(psql) → 유저 존재 확인, "익명 요청인데 왜 인증 에러?" 의 단서.
  • prod 로그 접근(journalctl·Apache access) → 사용자 403의 응답 크기(748)가 내 재현(5209)과 다름을 발견 → "내 재현은 가짜"를 자각.
  • 실제 응답 본문 접근 → {"detail":"Invalid username/password."} 한 줄이 BasicAuth를 지목.

셋 중 하나라도 없었으면 추측에서 끝났을 것이다. 증거 통로가 곧 디버깅 능력이라는 명제의 산 증거다.


6. 일반화 — 컨텍스트 엔지니어링 체크리스트

새 프로젝트에서 에이전트의 디버깅 컨텍스트를 깔 때 점검할 것.

  1. traceback이 로그에 남는가 — except 로깅이 logger.exception 인가. (hook으로 강제)
  2. 에이전트가 로그를 읽을 수 있는가 — dev/prod 로그 경로 + 읽기 권한(SSH·sudo) + 로그 읽기 전용 에이전트.
  3. DB를 직접 볼 수 있는가 — psql/shell_plus 접근.
  4. 이 통로들이 말→강제→격리로 제도화돼 있는가 — CLAUDE.md(말) / hook(강제) / read-only agent(격리).
  5. 안전한가 — 조사 도구는 read-only로 격리(tools 제한). 증거 접근을 넓히되 사고 반경은 좁힌다.

한 줄 요약: 에이전트에게 줄 것은 더 긴 프롬프트가 아니라 더 많은 진짜 증거 통로다 — 단, 격리와 강제를 곁들여서.


오늘 정리 + 다음

  • 정리: 에이전트의 디버깅 품질은 닿을 수 있는 증거(traceback·로그·DB)의 양으로 결정된다. 우리는 세 통로(logger.exception로 traceback 보장 / debug-server-log 에이전트·SSH·sudo로 로그 / psql·shell_plus로 DB)를 열고, 이를 CLAUDE.md(말)·hook(강제)·read-only agent(격리)의 3층으로 제도화했다. L23의 디버깅이 가능했던 건 이 전제 덕이다.
  • 흔한 함정: except 에서 logger.error(e) 만 쓰는 것 — 로그를 봐도 traceback이 없어 "왜 터졌는지 모르는" 상태가 된다. (그래서 hook으로 막는다.)
  • 다음 시간: 조사 도구의 read-only 격리를 어디까지 넓힐지 — MCP·외부 도구 접근과 권한 경계.
  • 자습 권장: .claude/agents/debug-server-log.md, .claude/hooks/check_except_logger.py 를 직접 열어보기. 디버깅을 겪은 사례는 L23. 접근권한(SSH·sudo)은 팀 wiki infrastructure/sudo-policy.md·prod-server-host.md 참고(자습용).
이 강의를 학습하셨나요?