#!/bin/bash # ============================================================ # GUARDiA SM | log_analysis.sh # 대상: 다목적 서버 로그 분석 스크립트 # 파라미터: LOG_FILES="/app/logs/app.log /var/log/messages" # ANALYZE_HOURS=1 (최근 N시간) # ERROR_PATTERN="ERROR|FATAL|Exception|OOM|killed" # WARN_PATTERN="WARN|WARNING|timeout|refused" # TOP_N=10 (상위 N개 오류 패턴) # LOG_SIZE_WARN_MB=500 ROTATE_CHECK=true # ============================================================ set -euo pipefail LOG_FILES=${LOG_FILES:-""} ANALYZE_HOURS=${ANALYZE_HOURS:-1} ERROR_PATTERN=${ERROR_PATTERN:-'ERROR|FATAL|Exception|OutOfMemory|killed process|Caused by'} WARN_PATTERN=${WARN_PATTERN:-'WARN|WARNING|timeout|refused|connection reset|too many'} TOP_N=${TOP_N:-10} LOG_SIZE_WARN_MB=${LOG_SIZE_WARN_MB:-500} ROTATE_CHECK=${ROTATE_CHECK:-true} OK="[OK]"; WARN="[WARN]"; CRIT="[CRIT]" SEP="─────────────────────────────────────────" RESULT=0 echo "======================================================" echo " GUARDiA SM 점검 | 로그 분석 | $(hostname -s)" echo " 점검 시각: $(date '+%Y-%m-%d %H:%M:%S %Z')" echo " 분석 범위: 최근 ${ANALYZE_HOURS}시간" echo "======================================================" # 로그 파일이 미지정인 경우 자동 탐색 if [ -z "$LOG_FILES" ]; then LOG_FILES="" for AUTO_LOG in \ /opt/tomcat/logs/catalina.out \ /app/was/logs/catalina.out \ /opt/wildfly/standalone/log/server.log \ /opt/jeus/logs/*.log \ /var/log/httpd/error_log \ /var/log/nginx/error.log \ /var/log/messages \ /var/log/syslog; do ls $AUTO_LOG 2>/dev/null | while read F; do [ -r "$F" ] && LOG_FILES="${LOG_FILES} ${F}" done done fi if [ -z "${LOG_FILES// }" ]; then echo " ${WARN} 분석할 로그 파일이 없음 (LOG_FILES 환경변수 설정 필요)" exit 1 fi # ── 각 로그 파일 분석 ───────────────────────────────────── FILE_NUM=0 for LOGFILE in $LOG_FILES; do [ -r "$LOGFILE" ] || continue FILE_NUM=$(( FILE_NUM + 1 )) FILESIZE_MB=$(du -m "$LOGFILE" 2>/dev/null | awk '{print $1}' || echo 0) echo echo "[$SEP] 파일 ${FILE_NUM}: ${LOGFILE}" echo " 파일 크기: ${FILESIZE_MB} MB 수정: $(stat -c '%y' "$LOGFILE" 2>/dev/null | cut -c1-19 || echo 'N/A')" # 파일 크기 경고 if [ "${FILESIZE_MB:-0}" -gt "$LOG_SIZE_WARN_MB" ]; then echo " ${WARN} 로그 파일 크기 과다 (${FILESIZE_MB}MB > ${LOG_SIZE_WARN_MB}MB) — 로테이션 권고" [ $RESULT -lt 1 ] && RESULT=1 fi # 최근 라인 수 결정 (파일이 크면 더 많이) TAIL_LINES=5000 [ "$FILESIZE_MB" -gt 100 ] && TAIL_LINES=10000 RECENT=$(tail -${TAIL_LINES} "$LOGFILE" 2>/dev/null || echo "") if [ -z "$RECENT" ]; then echo " ${WARN} 내용을 읽을 수 없음" continue fi # 총 라인 수 TOTAL_LINES=$(echo "$RECENT" | wc -l) echo " 분석 라인 수: ${TOTAL_LINES} (최근 ${TAIL_LINES}줄)" # ── 에러 집계 ───────────────────────────────────────── ERR_COUNT=$(echo "$RECENT" | grep -cE "$ERROR_PATTERN" || echo 0) WARN_COUNT=$(echo "$RECENT" | grep -cE "$WARN_PATTERN" | grep -v -cE "$ERROR_PATTERN" || echo 0) if [ "${ERR_COUNT:-0}" -gt 50 ]; then echo " ${CRIT} 에러 ${ERR_COUNT}건 감지 (임계: 50)" RESULT=2 elif [ "${ERR_COUNT:-0}" -gt 0 ]; then echo " ${WARN} 에러 ${ERR_COUNT}건 감지" [ $RESULT -lt 1 ] && RESULT=1 else echo " ${OK} 에러 없음" fi echo " 경고 수: ${WARN_COUNT}건" # ── 상위 에러 패턴 ──────────────────────────────────── if [ "${ERR_COUNT:-0}" -gt 0 ]; then echo echo " [상위 에러 패턴 (Top ${TOP_N})]" echo "$RECENT" | grep -E "$ERROR_PATTERN" | \ sed 's/[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}//g' | \ sed 's/[0-9]\{2\}:[0-9]\{2\}:[0-9]\{2\}//g' | \ sed 's/\[.*\]//g' | \ sort | uniq -c | sort -rn | head "${TOP_N}" | \ awk '{count=$1; $1=""; printf " %5d회 %s\n", count, substr($0,2,100)}' || true echo echo " [최근 에러 5건]" echo "$RECENT" | grep -E "$ERROR_PATTERN" | tail -5 | sed 's/^/ /' fi # ── OOM 감지 ────────────────────────────────────────── OOM=$(echo "$RECENT" | grep -iE "OutOfMemoryError|out of memory|java heap space|GC overhead" | tail -5 || echo "") if [ -n "$OOM" ]; then echo echo " ${CRIT} OOM 이벤트 감지:" echo "$OOM" | sed 's/^/ /' RESULT=2 fi # ── 예외 스택트레이스 감지 ──────────────────────────── EXCEPTIONS=$(echo "$RECENT" | grep -cE "^(Caused by:|at [a-z].*\()" || echo 0) echo " 스택트레이스 관련 라인: ${EXCEPTIONS}건" # ── HTTP 에러 코드 집계 (웹 로그 형식 감지) ─────────── HTTP5XX=$(echo "$RECENT" | grep -cE '" 5[0-9]{2} ' || echo 0) HTTP4XX=$(echo "$RECENT" | grep -cE '" 4[0-9]{2} ' || echo 0) if [ "${HTTP5XX:-0}" -gt 0 ] || [ "${HTTP4XX:-0}" -gt 0 ]; then echo echo " [HTTP 에러 코드 집계]" echo " 5xx 에러: ${HTTP5XX}건" echo " 4xx 에러: ${HTTP4XX}건" [ "${HTTP5XX:-0}" -gt 10 ] && echo " ${WARN} 5xx 에러 과다" && [ $RESULT -lt 1 ] && RESULT=1 fi # ── 주요 키워드 타임라인 (마지막 발생 시각) ────────── echo echo " [주요 이벤트 마지막 발생 시각]" for KW in "ERROR" "WARN" "OutOfMemory" "Connection refused" "timeout"; do LAST=$(echo "$RECENT" | grep -i "$KW" | tail -1 | cut -c1-30 || echo "") [ -n "$LAST" ] && printf " %-20s: %s\n" "$KW" "$LAST" done done [ "$FILE_NUM" -eq 0 ] && echo " ${WARN} 읽을 수 있는 로그 파일 없음" # ── 시스템 로그 (dmesg) ─────────────────────────────────── echo echo "[$SEP] 시스템 커널 메시지 (최근 1시간)" DMESG=$(dmesg --since="1 hour ago" 2>/dev/null | \ grep -iE "error|oom|kill|fault|panic|blocked for" | tail -10 || \ dmesg 2>/dev/null | tail -50 | grep -iE "error|oom|kill|fault" | tail -10 || echo "") if [ -n "$DMESG" ]; then echo " ${WARN} 커널 메시지:" echo "$DMESG" | sed 's/^/ /' [ $RESULT -lt 1 ] && RESULT=1 else echo " ${OK} 이상 없음" fi # ── 로그 로테이션 상태 ─────────────────────────────────── if [ "$ROTATE_CHECK" = "true" ]; then echo echo "[$SEP] 로그 로테이션 설정" if [ -f /etc/logrotate.conf ]; then echo " ${OK} logrotate 설정 존재" CRON_ROTATE=$(crontab -l 2>/dev/null | grep -i logrotate || \ ls /etc/cron.daily/logrotate 2>/dev/null && echo "cron.daily 설정" || echo "") [ -n "$CRON_ROTATE" ] && echo " ${OK} logrotate 예약 실행: ${CRON_ROTATE}" || \ echo " ${WARN} logrotate cron 미설정" else echo " ${WARN} logrotate 설정 없음" [ $RESULT -lt 1 ] && RESULT=1 fi fi # ── 요약 ───────────────────────────────────────────────── echo echo "======================================================" case $RESULT in 0) echo " 최종 결과: ${OK} 로그 정상" ;; 1) echo " 최종 결과: ${WARN} 주의 항목 있음" ;; 2) echo " 최종 결과: ${CRIT} 심각한 오류 감지" ;; esac echo " 점검 완료: $(date '+%Y-%m-%d %H:%M:%S')" echo "======================================================" exit $RESULT