GrepBoardLog

로그 파일을 아무 데나 끌어다 놓으세요

파일 여러 개, 폴더째, .gz 압축도 됩니다. Ctrl+V 로 글을 붙여 넣어도 됩니다.

nginx 접근 로그 · 오류 로그 칸별 풀이

nginx access.log 나 error.log 를 이 페이지에 놓으면 상태 코드, 오류 경로, 상위 클라이언트, 지연 분위수, 분 단위 시간축이 보드로 나옵니다. 브라우저 밖으로는 아무것도 나가지 않습니다.

아래에는 combined 형식의 칸별 뜻, 시간 변수 두 개를 읽는 법, 흔한 오류 로그 줄이 말하는 것, 그리고 서버에서 같은 숫자를 내는 셸 명령을 정리했습니다.

combined 형식

nginx 에 미리 정의된 형식은 combined 하나입니다. ngx_http_log_module 의 정의는 다음과 같습니다.

log_format combined '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent"';

시간 변수 두 개를 끝에 붙인 실제 줄입니다(다음 절 참고).

192.0.2.15 - - [18/Oct/2026:10:02:09 +0000] "GET /api/items?page=3 HTTP/1.1" 200 790 "-" "okhttp/4.12.0" 2.912 0.140
awk 칸변수예시 값뜻
$1$remote_addr192.0.2.15연결을 연 클라이언트 주소입니다. 로드밸런서 뒤라면 헤더로 주소를 바꿔 넣지 않는 한 로드밸런서 주소가 찍힙니다.
$2(고정 문자)-이 형식에서는 늘 대시입니다.
$3$remote_user-HTTP Basic 인증 사용자 이름, 없으면 대시입니다.
$4 $5[$time_local][18/Oct/2026:10:02:09 +0000]Common Log Format 의 현지 시각입니다. 시차 앞 공백 때문에 awk 칸 두 개로 나뉩니다.
$6 $7 $8"$request""GET /api/items?page=3 HTTP/1.1"요청 줄 전체(메서드, 쿼리 포함 경로, 프로토콜)입니다. awk -F'"' 로 자르면 $2 입니다.
$9$status200클라이언트에 보낸 응답 상태입니다.
$10$body_bytes_sent790클라이언트에 보낸 응답 본문 바이트이며 헤더는 빠집니다.
$11"$http_referer""-"Referer 요청 헤더, 없으면 대시입니다.
$12 …"$http_user_agent""okhttp/4.12.0"User-Agent 헤더입니다. 공백이 들어 있으므로 awk -F'"' 로 자르고 $6 을 씁니다.
끝에서 둘째$request_time2.912초 단위, 밀리초 해상도입니다. combined 에는 없고 덧붙인 칸입니다.
마지막$upstream_response_time0.140초 단위, 밀리초 해상도입니다. 덧붙인 칸입니다.

기본 이스케이프에서는 변수 안의 " · \ 와 32 미만 · 126 초과 바이트를 \xXX 로 쓰고, 값이 없는 변수는 대시 하나로 남깁니다. 그래서 요청 줄은 보통 정확히 세 낱말이고 상태 코드는 $9 에 옵니다. 다만 쓰레기 요청 줄(평문 HTTP 포트로 들어온 TLS 핸드셰이크 등)은 이 가정을 깨므로, 인터넷에 열린 서버라면 $9 에 이상한 값이 몇 개 섞입니다.

$request_time 과 $upstream_response_time

형식을 늘리는 이유가 이 두 변수입니다. 끝에 붙이면 combined 의 칸이 하나도 움직이지 않으므로, 칸 위치로 읽는 해석기도 그대로 동작합니다.

log_format timed '$remote_addr - $remote_user [$time_local] '
                 '"$request" $status $body_bytes_sent '
                 '"$http_referer" "$http_user_agent" '
                 '$request_time $upstream_response_time';
access_log /var/log/nginx/access.log timed;
변수nginx 가 재는 것단위
$request_time클라이언트에게서 첫 바이트를 읽은 때부터, 마지막 바이트를 보낸 뒤 로그를 쓸 때까지입니다.초, 밀리초 해상도
$upstream_response_time업스트림 서버에게서 응답을 받는 데 걸린 시간입니다.초, 밀리초 해상도
$upstream_connect_time업스트림과 연결을 맺는 데 걸린 시간입니다. https 업스트림이면 TLS 핸드셰이크가 들어갑니다.초, 밀리초 해상도
$upstream_header_time업스트림 응답 헤더를 받을 때까지의 시간입니다.초, 밀리초 해상도

두 값을 함께 읽는 법

  • 둘 다 크고 비슷합니다: 백엔드가 느립니다. 애플리케이션, 데이터베이스, 큐를 보십시오.
  • $request_time 이 $upstream_response_time 보다 훨씬 큽니다: 시간은 클라이언트 쪽에서 쓰였습니다. 느린 업로드이거나, 큰 응답이 느리거나 먼 클라이언트로 흘러가는 중이었습니다. 위 예시 줄이 이 경우입니다(전체 2.912초, 업스트림 0.140초).
  • $upstream_response_time 이 - 입니다: 이 요청은 업스트림과 말을 하지 않았습니다(정적 파일, 리다이렉트, 캐시 응답, 프록시 전에 거절).
  • 0.120, 1.702 처럼 값이 여럿입니다: 한 요청에 업스트림 서버를 둘 이상 시도했습니다. $upstream_addr 의 주소처럼 쉼표로 나뉘고, 내부 리다이렉트로 다른 업스트림 묶음으로 넘어가면 콜론으로 나뉩니다. 첫 서버가 무엇을 답했는지는 $upstream_status 로 봅니다.

둘째 값에 ", " 가 들어갈 수 있어 awk 칸 두 개로 쪼개질 수 있습니다. 아래 명령은 큰따옴표로 자른 뒤 User-Agent 뒤의 글자를 읽어 이 문제를 피합니다.

오류 로그

오류 로그는 모양이 고정돼 있습니다. 날짜와 시각, 대괄호 안의 수준, pid#tid:, 그다음 *N(연결 번호)과 메시지입니다. 요청에 관한 줄이면 client · server · request · upstream · host 를 덧붙입니다.

2026/10/18 10:02:03 [error] 2817#2817: *91512 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 198.51.100.7, server: shop.example.com, request: "POST /api/upload HTTP/1.1", upstream: "http://10.0.2.12:8080/api/upload", host: "shop.example.com"

수준은 심각한 것부터 emerg · alert · crit · error · warn · notice · info · debug 입니다. 연결 번호 *91512 는 한 연결에 관한 줄 모두에 같으므로 grep '\*91512 ' error.log 로 한 연결의 경위를 모을 수 있습니다.

메시지대개 뜻하는 것
connect() failed (111: Connection refused) while connecting to upstream업스트림 주소 · 포트에서 아무도 듣고 있지 않습니다. 백엔드가 죽었거나 재시작 중이거나 포트가 틀렸습니다.
upstream timed out (110: Connection timed out) while reading response header from upstream백엔드가 요청은 받았지만 읽기 시간 제한 안에 응답 헤더를 보내지 않았습니다. 대개 느리거나 멈춘 애플리케이션입니다.
upstream prematurely closed connection while reading response header from upstream백엔드가 헤더를 다 보내기 전에 연결을 닫았습니다. 프로세스 비정상 종료, 요청 도중 죽은 워커, 백엔드 쪽 시간 제한 등입니다.
no live upstreams업스트림 묶음의 서버가 모두 실패로 사용 불가 표시가 돼 있어 nginx 가 시도조차 하지 않았습니다.

「while」 앞은 오류, 뒤는 그때 nginx 가 하던 일입니다. connecting to upstream(연결 중), sending request to upstream(요청 보내는 중), reading response header from upstream(응답 헤더 읽는 중), reading upstream(본문 읽는 중), sending to client(클라이언트로 보내는 중)가 있습니다.

nginx 로그에서 보드가 보여 주는 것

  • 상태 코드 — 5xx 와 4xx 를 나눠 보이고, 서버 오류 비율을 냅니다.
  • 오류가 나는 곳 — 5xx · 4xx 건수로 줄 세운 경로입니다.
  • 상위 경로, 상위 클라이언트 IP, 누가 부르나(User-Agent 분류), 경로별 전송량.
  • $request_time 으로 그린 지연 분포와 느린 경로, p50 · p90 · p99.
  • 언제 몰리나 — 분 단위 시간축이며 끌어서 모든 카드의 기간을 좁힙니다.
  • 스캐너 탐침 — wp-login.php · .env · .git/ 같은 경로를 찾는 요청입니다.
  • 먼저 볼 것 — 「서버 오류의 82% 가 10:02–10:06 에 /api/upload」, 「요청 시간이 업스트림 시간의 20배 — 느린 쪽은 클라이언트 회선」 같은 규칙 기반 문장입니다.

nginx 로그용 한 줄 명령

모두 시간 칸 두 개를 덧붙인 combined 표본 로그로 실행해 확인했습니다. 기본 combined 배치를 가정하므로 log_format 이 다르면 칸 번호를 고치십시오. 회전된 로그는 파일 이름 대신 zcat -f access.log* 를 파이프로 넣으면 됩니다.

  • 파일에 요청이 몇 건인가

    wc -l < access.log
  • 상태 코드 분포, 많은 순

    awk '{print $9}' access.log | sort | uniq -c | sort -rn
  • 서버 오류는 몇 건인가

    awk '$9 ~ /^5/' access.log | wc -l
  • 5xx 를 내는 경로는 어디인가

    awk '$9 ~ /^5/ {print $7}' access.log | sort | uniq -c | sort -rn | head
  • 요청을 가장 많이 보낸 클라이언트

    cut -d' ' -f1 access.log | sort | uniq -c | sort -rn | head
  • 서로 다른 클라이언트 주소는 몇 개인가

    cut -d' ' -f1 access.log | sort -u | wc -l
  • 분당 요청 수 — 급증 구간이 큰 숫자로 보입니다(로그가 시간순이라 됩니다)

    cut -d[ -f2 access.log | cut -d: -f1-3 | uniq -c
  • $request_time 으로 본 가장 느린 요청과 요청 줄

    awk -F'"' '{split($NF, t, " "); print t[1], $2}' access.log | sort -rn | head
  • $request_time 의 p99(최근접 순위)

    awk -F'"' '{split($NF, t, " "); print t[1]}' access.log | sort -n | awk '{a[NR]=$1} END {i=int(NR*0.99); if (i<NR*0.99) i++; print a[i]}'
  • nginx 가 업스트림보다 1초 넘게 더 쓴 요청 — 느린 클라이언트나 느린 업로드

    awk -F'"' '{n = split($NF, t, " "); u = 0; for (i = 2; i <= n; i++) u += t[i]; if (t[2] != "-" && t[1] - u > 1) printf "%s %.3f %s\n", t[1], u, $2}' access.log
  • 전송량이 가장 많은 경로

    awk '{b[$7]+=$10} END {for (p in b) print b[p], p}' access.log | sort -rn | head
  • 가장 흔한 User-Agent

    awk -F'"' '{print $6}' access.log | sort | uniq -c | sort -rn | head
  • wp-login · .env · .git 을 찾는 스캐너 요청 수

    grep -cE 'wp-login|\.env|\.git/' access.log
  • 줄을 공유하기 전에 IPv4 주소 뒤 두 자리를 가리기

    sed -E 's/^([0-9]+\.[0-9]+)\.[0-9]+\.[0-9]+/\1.x.x/' access.log | head -3
  • 오류 로그: 수준별 줄 수

    awk '{print $3}' error.log | sort | uniq -c | sort -rn
  • 오류 로그: 어떤 업스트림 실패가 몇 번인가

    grep -oE 'upstream timed out|connect\(\) failed|no live upstreams|upstream prematurely closed connection' error.log | sort | uniq -c | sort -rn
  • 오류 로그: 실패가 가리키는 업스트림 주소

    grep -oE 'upstream: "[^"]+"' error.log | sort | uniq -c | sort -rn

자주 묻는 질문

$request_time 의 단위는 무엇인가요?

초이며 밀리초 해상도입니다(예: 0.233). $upstream_response_time · $upstream_connect_time · $upstream_header_time 도 같습니다.

$upstream_response_time 이 대시인 이유는?

업스트림에 묻지 않고 답한 요청입니다. 정적 파일, 리다이렉트, 캐시 응답, 프록시 전에 거절된 요청 등입니다. nginx 는 값이 없는 변수를 대시로 남깁니다.

$upstream_response_time 에 숫자가 두 개인 이유는?

그 요청에 업스트림 서버를 둘 이상 시도했습니다. 대개 첫 서버가 실패해 proxy_next_upstream 이 다음으로 넘긴 경우입니다. 시도한 순서대로 쉼표로 나열됩니다.

$request_time 이 $upstream_response_time 보다 훨씬 큰 이유는?

남는 시간은 클라이언트와 주고받는 데 쓰였습니다. 느린 업로드를 받거나 큰 응답을 느린 회선으로 보내는 중이었습니다. 느린 쪽은 백엔드가 아닙니다.

다른 도구를 깨지 않고 시간 칸을 넣으려면?

combined 끝에 " $request_time $upstream_response_time" 을 붙인 새 log_format 을 만들고 access_log 가 그것을 쓰게 합니다. User-Agent 앞의 칸은 움직이지 않으므로 위치로 읽는 combined 해석기는 그대로 동작하고, 이 보드는 덧붙인 칸까지 읽습니다.

오류 로그도 읽나요?

읽습니다. error.log 만 놓거나 접근 로그와 함께 놓으십시오. 수준, 업스트림 실패, 클라이언트 주소가 카드로 나옵니다.

escape=json 으로 만든 log_format 은요?

한 줄에 JSON 객체 하나인 로그는 JSON 줄로 읽습니다. 키마다 칸이 되고, 형식 탭에서 어느 칸이 시각 · 상태 · 경로 · 지연인지 표시하면 됩니다.

🔒 아무것도 브라우저 밖으로 나가지 않습니다.

로그는 이 브라우저 안, 내 컴퓨터에서만 해석하고 셉니다.