iBetter Books
수정

실전: 로그 분석 보고서

웹 서버를 운영하다 보면 매일 접근 로그가 쌓입니다. 트래픽이 갑자기 늘었는지, 어떤 URL이 가장 많이 호출됐는지, 에러가 얼마나 발생했는지 파악해야 합니다. 상용 분석 도구를 쓸 수도 있지만, awk와 sort, head를 조합하면 같은 정보를 스크립트 하나로 뽑아낼 수 있습니다.

샘플 로그 파일

Apache Combined Log Format 형식의 샘플 로그를 준비합니다. 실제 접근 로그와 동일한 형식입니다.

# 파일: /tmp/access.logcat > /tmp/access.log << 'EOF'192.168.1.10 - - [15/Mar/2024:08:05:11 +0900] "GET /index.html HTTP/1.1" 200 2048192.168.1.20 - - [15/Mar/2024:08:12:33 +0900] "GET /about.html HTTP/1.1" 200 1536192.168.1.30 - - [15/Mar/2024:08:35:55 +0900] "POST /api/login HTTP/1.1" 401 512192.168.1.10 - - [15/Mar/2024:09:01:22 +0900] "GET /dashboard HTTP/1.1" 200 4096192.168.1.40 - - [15/Mar/2024:09:15:44 +0900] "GET /index.html HTTP/1.1" 200 2048192.168.1.50 - - [15/Mar/2024:09:22:10 +0900] "GET /api/users HTTP/1.1" 200 8192192.168.1.30 - - [15/Mar/2024:09:45:30 +0900] "POST /api/login HTTP/1.1" 200 256192.168.1.60 - - [15/Mar/2024:10:02:15 +0900] "GET /index.html HTTP/1.1" 200 2048192.168.1.70 - - [15/Mar/2024:10:11:08 +0900] "GET /contact.html HTTP/1.1" 404 0192.168.1.80 - - [15/Mar/2024:10:25:42 +0900] "GET /api/items HTTP/1.1" 200 3072192.168.1.10 - - [15/Mar/2024:10:33:19 +0900] "DELETE /api/users/5 HTTP/1.1" 500 128192.168.1.20 - - [15/Mar/2024:10:50:05 +0900] "GET /dashboard HTTP/1.1" 301 0192.168.1.90 - - [15/Mar/2024:11:05:33 +0900] "GET /index.html HTTP/1.1" 200 2048192.168.1.10 - - [15/Mar/2024:11:18:47 +0900] "GET /api/users HTTP/1.1" 200 8192192.168.1.30 - - [15/Mar/2024:11:30:22 +0900] "POST /api/items HTTP/1.1" 201 512192.168.1.40 - - [15/Mar/2024:11:45:00 +0900] "GET /about.html HTTP/1.1" 200 1536192.168.1.50 - - [15/Mar/2024:12:02:11 +0900] "GET /index.html HTTP/1.1" 200 2048192.168.1.60 - - [15/Mar/2024:12:15:30 +0900] "GET /api/items HTTP/1.1" 404 0192.168.1.70 - - [15/Mar/2024:12:28:44 +0900] "GET /dashboard HTTP/1.1" 200 4096192.168.1.80 - - [15/Mar/2024:12:40:55 +0900] "GET /index.html HTTP/1.1" 200 2048EOF

로그 형식을 파악합니다.

192.168.1.10 - - [15/Mar/2024:08:05:11 +0900] "GET /index.html HTTP/1.1" 200 2048
$1           $2$3 $4                            $5  $6           $7        $8  $9
  • $1: IP 주소
  • $4: 날짜/시간 ([15/Mar/2024:08:05:11)
  • $6: 요청 URL
  • $9: HTTP 상태 코드
  • $10: 응답 크기(바이트)

분석 스크립트

# 파일: /tmp/log_report.sh#!/bin/bash# Apache access.log 분석 보고서 생성# 사용법: ./log_report.sh [로그파일]LOG_FILE="${1:-/tmp/access.log}"if [[ ! -f "$LOG_FILE" ]]; then    echo "오류: 로그 파일이 없습니다: $LOG_FILE" >&2    exit 1fiecho "============================================"echo "  웹 서버 접근 로그 분석 보고서"echo "  파일: $LOG_FILE"echo "  생성: $(date '+%Y-%m-%d %H:%M:%S')"echo "============================================"echo# ──────────────────────────────────────────# 1. 총 요청 수# ──────────────────────────────────────────echo "[ 1. 총 요청 수 ]"awk 'END { print "  총 요청:", NR "건" }' "$LOG_FILE"echo# ──────────────────────────────────────────# 2. HTTP 상태별 집계# ──────────────────────────────────────────echo "[ 2. HTTP 상태 코드별 집계 ]"awk '{    status[$9]++}END {    printf "  %-8s %6s\n", "상태코드", "건수"    printf "  %-8s %6s\n", "--------", "----"    for (s in status) {        printf "  %-8s %6d\n", s, status[s]    }}' "$LOG_FILE" | sort -k1echo# ──────────────────────────────────────────# 3. 시간대별 요청 분포# ──────────────────────────────────────────echo "[ 3. 시간대별 요청 분포 ]"awk '{    # $4 형식: [15/Mar/2024:08:05:11    # 8번째 문자부터 2글자가 시간(HH)    hour = substr($4, 14, 2)    hourly[hour]++}END {    printf "  %-6s %6s %s\n", "시간대", "요청수", "막대그래프"    printf "  %-6s %6s %s\n", "------", "------", "----------"    for (h in hourly) {        bar = ""        for (i = 0; i < hourly[h]; i++) bar = bar "#"        printf "  %s시    %6d %s\n", h, hourly[h], bar    }}' "$LOG_FILE" | sort -k1echo# ──────────────────────────────────────────# 4. 상위 10개 요청 URL# ──────────────────────────────────────────echo "[ 4. 상위 10개 요청 URL ]"awk '{ print $6 }' "$LOG_FILE" \    | sort \    | uniq -c \    | sort -rn \    | head -10 \    | awk '{ printf "  %6d  %s\n", $1, $2 }'echo# ──────────────────────────────────────────# 5. 상위 5개 접속 IP# ──────────────────────────────────────────echo "[ 5. 상위 5개 접속 IP ]"awk '{ print $1 }' "$LOG_FILE" \    | sort \    | uniq -c \    | sort -rn \    | head -5 \    | awk '{ printf "  %6d  %s\n", $1, $2 }'echo# ──────────────────────────────────────────# 6. 오류 요청 목록 (4xx, 5xx)# ──────────────────────────────────────────echo "[ 6. 오류 요청 (4xx, 5xx) ]"awk '$9 >= 400 {    printf "  %s  %-6s  %s\n", $1, $9, $6}' "$LOG_FILE"echo# ──────────────────────────────────────────# 7. 총 전송량# ──────────────────────────────────────────echo "[ 7. 총 전송량 ]"awk '{ total += $10 }END {    if (total >= 1048576)        printf "  %.2f MB\n", total / 1048576    else if (total >= 1024)        printf "  %.2f KB\n", total / 1024    else        printf "  %d bytes\n", total}' "$LOG_FILE"echoecho "============================================"echo "  분석 완료"echo "============================================"

실행 결과

chmod +x /tmp/log_report.sh/tmp/log_report.sh /tmp/access.log
============================================
  웹 서버 접근 로그 분석 보고서
  파일: /tmp/access.log
  생성: 2024-03-15 14:30:00
============================================

[ 1. 총 요청 수 ]
  총 요청: 20건

[ 2. HTTP 상태 코드별 집계 ]
  상태코드   건수
  --------   ----
  200          14
  201           1
  301           1
  401           1
  404           2
  500           1

[ 3. 시간대별 요청 분포 ]
  시간대   요청수  막대그래프
  ------   ------  ----------
  08시          3  ###
  09시          4  ####
  10시          5  #####
  11시          4  ####
  12시          4  ####

[ 4. 상위 10개 요청 URL ]
       6  /index.html
       3  /dashboard
       2  /about.html
       2  /api/items
       2  /api/users
       2  /api/login
       1  /contact.html
       1  /api/items
       1  /api/users/5

[ 5. 상위 5개 접속 IP ]
       4  192.168.1.10
       3  192.168.1.30
       2  192.168.1.20
       2  192.168.1.40
       2  192.168.1.50

[ 6. 오류 요청 (4xx, 5xx) ]
  192.168.1.30  401     /api/login
  192.168.1.70  404     /contact.html
  192.168.1.10  500     /api/users/5
  192.168.1.60  404     /api/items

[ 7. 총 전송량 ]
  43.00 KB

============================================
  분석 완료
============================================

스크립트 핵심 포인트

필드 파싱. Apache 로그는 공백으로 구분되지만 날짜 필드에 공백이 없으므로 그냥 4,4, 6, 9,9, 10으로 접근할 수 있습니다. 필드 번호는 로그 형식에 따라 달라지므로 처음에 확인해야 합니다.

substr로 시간 추출. $4[15/Mar/2024:08:05:11 형식이므로 14번째 문자부터 2글자가 시간입니다. substr로 잘라냈습니다.

awk + sort + uniq 조합. awk로 필드만 뽑고, sort로 정렬하고, uniq -c로 중복 횟수를 세고, sort -rn으로 내림차순 정렬합니다. 이 네 명령어의 조합은 빈도 분석의 기본 패턴입니다.

텍스트 막대그래프. for (i = 0; i < count; i++) bar = bar "#" 패턴으로 간단한 시각화를 만들 수 있습니다. 큰 숫자에는 적합하지 않으므로 최대값으로 나눠 정규화하는 방법을 함께 쓰면 좋습니다.

단위 변환. 바이트를 KB나 MB로 변환할 때 if-else로 분기했습니다. 10진수(1000 단위)와 2진수(1024 단위) 중 상황에 맞는 단위를 선택하면 됩니다.

PART 07에서 sed와 awk를 모두 살펴봤습니다. sed는 텍스트를 변환하고 설정 파일을 자동으로 수정합니다. awk는 데이터를 집계하고 보고서를 만듭니다. 두 도구를 자유롭게 조합하면 별도의 프로그램 없이도 복잡한 텍스트 처리 파이프라인을 구성할 수 있습니다.