2026년 4월 21일 | 개발 깨알 상식_Tips
Nginx access_log를 보다 보면 ChatGPT-User 같은 요청과 실제 사람이 클릭해서 들어온 방문이 한 숫자로 뭉개질 때가 있다. 나도 한동안은 "AI가 나를 읽었다"와 "AI가 나를 추천해서 사람이 들어왔다"를 대충 같은 AI 유입으로 묶어 봤는데, 최근 SurfacedBy 글를 읽고 생각이 확실히 바뀌었다. 이 둘은 로그에서 보이는 모양도 다르고, 해석해야 하는 의미도 완전히 다르다.
내가 오늘 바로 메모한 팁은 단순하다. AI 트래픽을 한 숫자로 세지 말고, provider-side fetch와 real clickthrough를 먼저 나눠라. 그리고 기본 combined 로그로는 이 차이가 잘 안 보이니, 최소한 user-agent, referer, accept, request_uri 정도는 따로 남기는 probe를 하나 만들어 두는 편이 낫다.
Figure 1. 핵심은 "AI가 답변을 만들기 위해 원문을 읽으러 온 요청"과 "사람이 답변을 보고 링크를 눌러 들어온 방문"을 같은 줄로 보지 않는 데 있다.
1. 먼저 나눌 숫자는 두 개였다
provider fetch는 모델이나 에이전트가 내 origin을 직접 읽으러 오는 요청이다. 이 경우는 전용 user-agent가 찍히고 referer가 비어 있는 경우가 많다. 반대로 real visit은 사람이 답변 안의 링크를 눌러 들어오는 방문이라서, 일반 브라우저 user-agent에 chatgpt.com, claude.ai, perplexity.ai, gemini.google.com 같은 referer가 붙는 식으로 보인다.
이 둘을 합치면 숫자는 그럴듯해 보여도 판단이 흐려진다. provider fetch는 모델이 실제로 내 페이지를 retrieval에 썼는지를 말해 주고, clickthrough는 답변이 사람을 데려왔는지를 말해 준다. GEO나 검색 노출을 보려는 사람과 실제 유입 전환을 보려는 사람은 관심사가 다르니, 로그도 처음부터 분리해 두는 편이 맞다.
| 신호 | 로그에서 보이는 것 | 내가 읽는 의미 |
|---|---|---|
| provider fetch | 전용 UA, referer 없음, probe용 query string 일치 | 모델이 실제로 원문을 읽으러 왔는가 |
| real clickthrough | 일반 브라우저 UA + assistant referer | 사람이 답변을 보고 링크를 눌렀는가 |
| index/training bot | GPTBot, ClaudeBot, Googlebot 같은 장기 크롤러 | 특정 질문 응답이 아니라 장기 인덱싱·학습 활동 |
2. combined 로그 대신 probe 포맷을 하나 더 두는 편이 훨씬 낫다
Nginx 공식 log module 문서를 다시 보니, log_format에서 필요한 변수를 얼마든지 뽑아 별도 포맷으로 남길 수 있고 JSON escaping도 지원한다. 나는 이런 상황이면 기존 access log를 버리기보다, probe용 로그를 옆에 하나 더 붙이는 쪽이 마음이 편하다.
log_format ai_probe escape=json '{'
'"time":"$time_iso8601",'
'"uri":"$request_uri",'
'"status":$status,'
'"ua":"$http_user_agent",'
'"referer":"$http_referer",'
'"accept":"$http_accept"'
'}';
access_log /var/log/nginx/ai-probe.log ai_probe;
실전에서는 probe URL도 같이 분리해 두는 편이 좋았다. 예를 들어 /?ai=chatgpt, /?ai=claude처럼 assistant마다 query string을 다르게 주면 grep 한 번으로 어느 테스트에서 나온 요청인지 바로 좁혀진다. 원문 글도 이 패턴을 썼고, 나도 이런 방식이 제일 덜 헷갈렸다.
jq -r 'select(.uri | test("ai=")) | [.time, .uri, .referer, .ua] | @tsv' /var/log/nginx/ai-probe.log
3. map으로 분류값을 미리 달아 두면 나중에 덜 헤맨다
여기서 내가 한 번 더 좋게 본 건 map module 문서였다. 문서에도 나오듯 map 변수는 실제로 사용할 때만 평가되기 때문에, 이런 분류 레이어를 미리 붙여 둬도 부담이 크지 않다. 나중에 로그를 볼 때 정규식으로 다시 파지 말고, 애초에 bucket을 하나 찍어 두는 편이 훨씬 편하다.
map $http_user_agent $ai_fetch_bucket {
default "";
~*ChatGPT-User openai_fetch;
~*Claude-User anthropic_fetch;
~*Perplexity-User perplexity_fetch;
~*Manus-User manus_fetch;
~*(meta-webindexer|Meta-ExternalFetcher) meta_fetch;
}
map $http_referer $ai_click_bucket {
default "";
~*chatgpt\.com chatgpt_click;
~*claude\.ai claude_click;
~*perplexity\.ai perplexity_click;
~*(gemini\.google\.com|google\.com) gemini_click;
~*(copilot\.microsoft\.com|bing\.com) copilot_click;
~*grok\.com grok_click;
}
이렇게 나눠 두면 나중에는 provider fetch와 clickthrough를 따로 집계하기 쉬워진다. 특히 assistant마다 관찰 가능성이 다르기 때문에, 분류 키를 한 칸 더 두는 게 중요하다.
4. 해석에서 제일 자주 틀리는 부분은 "안 보이면 없었다"고 말하는 순간이다
원문에서 제일 실무적으로 좋았던 대목도 이 부분이었다. Claude-User는 /robots.txt를 먼저 치는 패턴이 보였고, ChatGPT-User는 여러 IP에서 한 번에 후보 페이지를 당기는 식으로 보였다고 한다. 반면 Gemini는 provider-fetch 로그가 아예 안 잡히고, 대신 gemini.google.com이나 google.com referer가 붙은 clickthrough만 보였다고 한다. 이건 "Gemini가 너를 안 읽는다"보다 이 vendor는 로그에서 관찰 가능한 신호가 비대칭적이다에 더 가깝다.
Google 문서도 비슷한 방향을 뒷받침한다. Google-Extended는 별도 HTTP user-agent가 아니라 robots.txt control token이고, 실제 크롤링은 기존 Google user-agent로 이뤄진다고 적혀 있다. 즉 Google-Extended가 access_log에 안 찍히는 건 정상이다. Copilot이나 Grok처럼 plain browser처럼 보이는 케이스도 원문에서 같이 나왔는데, 이런 쪽은 로그만으로는 긍정 귀속이 거의 안 된다.
여기서 내가 남긴 메모는 하나다. retrieval bot, clickthrough visit, indexing bot, training bot을 절대 한 bucket에 넣지 말 것. GPTBot, ClaudeBot, Googlebot은 특정 사용자 질문에 답하려고 지금 들어온 fetch가 아닐 수 있다. 이 셋을 묶어 놓고 "AI가 오늘 나를 몇 번 읽었다"고 말하면 금방 숫자가 이상해진다.
5. 내가 바로 적용하고 싶은 최소 체크리스트
- probe 로그를 분리: 기존 access log는 두고, ai_probe 같은 별도 포맷을 하나 더 둔다.
- assistant별 query string을 분리:
?ai=chatgpt,?ai=claude처럼 테스트 흔적을 남긴다. - fetch/click/index/training을 분리 집계: 같은 "AI 트래픽"으로 합치지 않는다.
- robots 규칙을 bot 단위로 적는다: 특히 Claude-User, ClaudeBot, PerplexityBot, Google-Extended처럼 역할이 다른 토큰을 섞지 않는다.
Nginx 로그는 원래도 많은 걸 말해 주지만, AI 쪽에서는 무엇을 한 줄에 같이 세지 않을지를 먼저 정하는 쪽이 더 중요하다고 느꼈다. 오늘 내 기준에서 제일 유용했던 건 거창한 GEO 이론이 아니라, access_log에 user-agent와 referer를 한 칸 더 남겨 두는 습관이었다.
원문은 SurfacedBy의 nginx probe 글, HN 토론, Nginx log module 문서, Nginx map module 문서, Anthropic crawler 문서, Perplexity bots 문서, Google common crawlers 문서를 같이 보면 맥락이 더 또렷하다.