585785847b
FILTERED LOG FILES: - proxy (Apache reverse proxy logs) - localhost (local connections) - default (default vhost) - cpanel, webmail, whm (cPanel services) - cpcalendars, cpcontacts, webdisk (cPanel apps) These are cPanel system services, not actual customer domains. They were showing as 'unknown' user and cluttering results. Now only tracks actual customer domain 500 errors. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
301 lines
12 KiB
Bash
Executable File
301 lines
12 KiB
Bash
Executable File
#!/bin/bash
|
||
|
||
################################################################################
|
||
# Fast 500 Error Tracker
|
||
################################################################################
|
||
# Purpose: ONLY track 500 errors - find them fast and show WHY
|
||
# No menus, no options - just find 500s and diagnose the root cause
|
||
################################################################################
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||
source "$SCRIPT_DIR/lib/common-functions.sh"
|
||
|
||
print_banner "Fast 500 Error Tracker"
|
||
|
||
echo ""
|
||
echo "Scanning for 500 errors and diagnosing root causes..."
|
||
echo ""
|
||
|
||
# Ask for time range
|
||
echo -e "${CYAN}How far back to scan?${NC}"
|
||
echo " 1) Last 24 hours (default)"
|
||
echo " 2) Last 7 days"
|
||
echo " 3) Last 30 days"
|
||
echo ""
|
||
read -p "Select option [1]: " time_choice
|
||
time_choice=${time_choice:-1}
|
||
|
||
case $time_choice in
|
||
1) HOURS_TO_SCAN=24 ;;
|
||
2) HOURS_TO_SCAN=168 ;;
|
||
3) HOURS_TO_SCAN=720 ;;
|
||
*) HOURS_TO_SCAN=24 ;;
|
||
esac
|
||
|
||
echo ""
|
||
echo "→ Scanning last $HOURS_TO_SCAN hours of access logs..."
|
||
echo ""
|
||
|
||
# Temporary files
|
||
TEMP_DIR="/tmp/500-tracker-$$"
|
||
mkdir -p "$TEMP_DIR"
|
||
trap "rm -rf $TEMP_DIR" EXIT
|
||
|
||
ERRORS_500="$TEMP_DIR/errors_500.txt"
|
||
ERROR_DETAILS="$TEMP_DIR/error_details.txt"
|
||
|
||
# Configuration
|
||
DOMLOGS_DIR="/var/log/apache2/domlogs"
|
||
|
||
# Get cutoff time
|
||
cutoff_time=$(date -d "$HOURS_TO_SCAN hours ago" +%s 2>/dev/null || echo "0")
|
||
|
||
declare -A domain_count
|
||
declare -A domain_user
|
||
total_500s=0
|
||
|
||
# Scan all domain access logs for 500 errors
|
||
for log in "$DOMLOGS_DIR"/*; do
|
||
[ -f "$log" ] || continue
|
||
[[ "$log" =~ (bytes_log|offset|error_log|ftpxferlog|-ssl_log)$ ]] && continue
|
||
|
||
domain="${log##*/}"
|
||
domain="${domain%%-*}"
|
||
|
||
# Skip non-domain system logs (proxy, localhost, etc.)
|
||
[[ "$domain" =~ ^(proxy|localhost|default|cpanel|webmail|whm|cpcalendars|cpcontacts|webdisk)$ ]] && continue
|
||
|
||
# Find cPanel user for this domain
|
||
user=$(grep -l "DNS.*$domain" /var/cpanel/users/* 2>/dev/null | head -1 | xargs basename 2>/dev/null)
|
||
[ -z "$user" ] && user="unknown"
|
||
|
||
domain_user["$domain"]="$user"
|
||
|
||
# Scan for 500 errors in this log
|
||
while IFS= read -r line; do
|
||
# Check for 500 status code
|
||
if [[ "$line" =~ '"'[[:space:]](5[0-9]{2})[[:space:]] ]]; then
|
||
status="${BASH_REMATCH[1]}"
|
||
|
||
# Time filtering
|
||
if [ "$cutoff_time" != "0" ]; then
|
||
if [[ "$line" =~ \[([0-9]{2}/[A-Z][a-z]{2}/[0-9]{4}:[0-9]{2}:[0-9]{2}:[0-9]{2}) ]]; then
|
||
log_date="${BASH_REMATCH[1]}"
|
||
log_time=$(date -d "${log_date/:/ }" +%s 2>/dev/null || echo "0")
|
||
[ "$log_time" != "0" ] && [ "$log_time" -lt "$cutoff_time" ] && continue
|
||
fi
|
||
fi
|
||
|
||
# Parse log line
|
||
read -r ip _ _ timestamp _ request _ _ <<< "$line"
|
||
|
||
# Extract URL
|
||
if [[ "$request" =~ '"'[A-Z]+[[:space:]]([^[:space:]]+) ]]; then
|
||
url="${BASH_REMATCH[1]:0:80}"
|
||
else
|
||
url="/"
|
||
fi
|
||
|
||
# Extract timestamp
|
||
if [[ "$line" =~ \[([^]]+)\] ]]; then
|
||
timestamp="${BASH_REMATCH[1]}"
|
||
else
|
||
timestamp="unknown"
|
||
fi
|
||
|
||
((domain_count["$domain"]++))
|
||
((total_500s++))
|
||
|
||
# Save for analysis
|
||
echo "$domain|$user|$status|$url|$timestamp|$ip" >> "$ERRORS_500"
|
||
fi
|
||
done < <(tail -n 100000 "$log" 2>/dev/null)
|
||
done
|
||
|
||
if [ "$total_500s" -eq 0 ]; then
|
||
echo ""
|
||
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||
echo -e "${GREEN} ✓ NO 500 ERRORS FOUND IN LAST $HOURS_TO_SCAN HOURS!${NC}"
|
||
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||
echo ""
|
||
exit 0
|
||
fi
|
||
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo " 🔥 500 ERRORS DETECTED: $total_500s errors"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo ""
|
||
|
||
echo -e "${RED}${BOLD}TOP AFFECTED DOMAINS:${NC}"
|
||
echo ""
|
||
|
||
# Sort and display top domains
|
||
for domain in "${!domain_count[@]}"; do
|
||
echo "${domain_count[$domain]} $domain ${domain_user[$domain]}"
|
||
done | sort -rn | head -10 | while read count domain user; do
|
||
printf " ${RED}●${NC} %-40s ${BOLD}%4d errors${NC} (user: %s)\n" "$domain" "$count" "$user"
|
||
done
|
||
|
||
echo ""
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo " 🔍 DIAGNOSING ROOT CAUSES"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo ""
|
||
|
||
# For each affected domain, check the actual PHP error logs
|
||
declare -A diagnosed_causes
|
||
declare -A cause_examples
|
||
|
||
while IFS='|' read -r domain user status url timestamp ip; do
|
||
[ -z "$domain" ] && continue
|
||
|
||
# Find PHP error log - check multiple locations
|
||
error_log=""
|
||
if [ "$user" != "unknown" ]; then
|
||
# Try multiple possible locations
|
||
for potential_log in \
|
||
"/home/$user/public_html/error_log" \
|
||
"/home/$user/logs/error_log" \
|
||
"/home/$user/error_log" \
|
||
"/var/log/apache2/domlogs/$domain-error_log" \
|
||
"/usr/local/apache/domlogs/$domain"; do
|
||
|
||
if [ -f "$potential_log" ]; then
|
||
error_log="$potential_log"
|
||
break
|
||
fi
|
||
done
|
||
fi
|
||
|
||
# Check if error log exists and has recent errors
|
||
if [ -n "$error_log" ] && [ -f "$error_log" ]; then
|
||
# Look for errors around the same time as the 500
|
||
recent_error=$(tail -500 "$error_log" | grep -E "Fatal error|Parse error|syntax error|memory.*exhausted|database|MySQL|Permission denied" | tail -1)
|
||
|
||
if [ -n "$recent_error" ]; then
|
||
# Determine cause
|
||
cause="UNKNOWN"
|
||
if [[ "$recent_error" =~ (memory.*exhausted|Allowed memory size) ]]; then
|
||
cause="PHP_MEMORY_EXHAUSTED"
|
||
cause_examples["$cause"]="$domain: $recent_error"
|
||
elif [[ "$recent_error" =~ (Fatal error|PHP Fatal error) ]]; then
|
||
if [[ "$recent_error" =~ (undefined function|Call to undefined function) ]]; then
|
||
cause="MISSING_PHP_FUNCTION"
|
||
cause_examples["$cause"]="$domain: $recent_error"
|
||
else
|
||
cause="PHP_FATAL_ERROR"
|
||
cause_examples["$cause"]="$domain: $recent_error"
|
||
fi
|
||
elif [[ "$recent_error" =~ (Parse error|syntax error) ]]; then
|
||
cause="PHP_SYNTAX_ERROR"
|
||
cause_examples["$cause"]="$domain: $recent_error"
|
||
elif [[ "$recent_error" =~ (database|MySQL|Can\'t connect) ]]; then
|
||
cause="DATABASE_CONNECTION"
|
||
cause_examples["$cause"]="$domain: $recent_error"
|
||
fi
|
||
|
||
((diagnosed_causes["$cause"]++))
|
||
else
|
||
((diagnosed_causes["NO_PHP_ERROR_LOGGED"]++))
|
||
fi
|
||
else
|
||
# No error log found - likely .htaccess issue
|
||
# Check if .htaccess exists and might have issues
|
||
if [ "$user" != "unknown" ] && [ -f "/home/$user/public_html/.htaccess" ]; then
|
||
# Check for common .htaccess syntax errors
|
||
htaccess_check=$(grep -E "RewriteEngine|RewriteRule|RewriteCond|php_value|php_flag" "/home/$user/public_html/.htaccess" 2>/dev/null)
|
||
if [ -n "$htaccess_check" ]; then
|
||
((diagnosed_causes["HTACCESS_LIKELY"]++))
|
||
cause_examples["HTACCESS_LIKELY"]="$domain (user: $user) - has .htaccess with rewrite rules, check syntax"
|
||
else
|
||
((diagnosed_causes["NO_ERROR_LOG_FILE"]++))
|
||
cause_examples["NO_ERROR_LOG_FILE"]="$domain (user: $user) - checked multiple locations, no error_log found"
|
||
fi
|
||
else
|
||
((diagnosed_causes["NO_ERROR_LOG_FILE"]++))
|
||
cause_examples["NO_ERROR_LOG_FILE"]="$domain (user: $user) - checked multiple locations, no error_log found"
|
||
fi
|
||
fi
|
||
done < "$ERRORS_500"
|
||
|
||
# Display diagnosed causes
|
||
echo -e "${CYAN}${BOLD}ROOT CAUSES IDENTIFIED:${NC}"
|
||
echo ""
|
||
|
||
for cause in "${!diagnosed_causes[@]}"; do
|
||
count="${diagnosed_causes[$cause]}"
|
||
echo "$count|$cause"
|
||
done | sort -rn | while IFS='|' read count cause; do
|
||
clean_cause=$(echo "$cause" | tr '_' ' ')
|
||
|
||
case "$cause" in
|
||
PHP_MEMORY_EXHAUSTED)
|
||
echo -e "${RED}🔥 $clean_cause${NC} - $count occurrences"
|
||
echo " ${YELLOW}Fix:${NC} Increase memory_limit in /home/USER/public_html/.user.ini"
|
||
echo " ${YELLOW}Set:${NC} memory_limit = 512M"
|
||
;;
|
||
PHP_FATAL_ERROR)
|
||
echo -e "${RED}⚠️ $clean_cause${NC} - $count occurrences"
|
||
echo " ${YELLOW}Fix:${NC} Review recent code/plugin changes"
|
||
;;
|
||
PHP_SYNTAX_ERROR)
|
||
echo -e "${RED}📝 $clean_cause${NC} - $count occurrences"
|
||
echo " ${YELLOW}Fix:${NC} Check for PHP syntax errors in recent file edits"
|
||
;;
|
||
MISSING_PHP_FUNCTION)
|
||
echo -e "${RED}📦 $clean_cause${NC} - $count occurrences"
|
||
echo " ${YELLOW}Fix:${NC} Install missing PHP extension (check error for which one)"
|
||
;;
|
||
DATABASE_CONNECTION)
|
||
echo -e "${RED}🗄️ $clean_cause${NC} - $count occurrences"
|
||
echo " ${YELLOW}Fix:${NC} Check database credentials or MySQL service"
|
||
;;
|
||
HTACCESS_LIKELY)
|
||
echo -e "${RED}⚙️ .HTACCESS SYNTAX ERROR (LIKELY)${NC} - $count occurrences"
|
||
echo " ${YELLOW}Fix:${NC} Check .htaccess file for syntax errors"
|
||
echo " ${YELLOW}Test:${NC} Temporarily rename .htaccess to .htaccess.bak and test"
|
||
echo " ${YELLOW}Common issues:${NC} Invalid RewriteRule, bad php_value directives"
|
||
;;
|
||
NO_ERROR_LOG_FILE)
|
||
echo -e "${YELLOW}📄 $clean_cause${NC} - $count occurrences"
|
||
echo " ${YELLOW}Note:${NC} Checked multiple error_log locations - none found"
|
||
echo " ${YELLOW}Check:${NC} May need to enable PHP error logging"
|
||
;;
|
||
NO_PHP_ERROR_LOGGED)
|
||
echo -e "${YELLOW}❓ $clean_cause${NC} - $count occurrences"
|
||
echo " ${YELLOW}Note:${NC} 500 error but no PHP error in log - likely .htaccess or Apache config"
|
||
;;
|
||
*)
|
||
echo -e "${INFO_COLOR}• $clean_cause${NC} - $count occurrences"
|
||
;;
|
||
esac
|
||
|
||
# Show example if we have one
|
||
if [ -n "${cause_examples[$cause]}" ]; then
|
||
example="${cause_examples[$cause]}"
|
||
echo " ${DIM}Example: ${example:0:120}...${NC}"
|
||
fi
|
||
echo ""
|
||
done
|
||
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo " 📋 DETAILED 500 ERROR LIST"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo ""
|
||
|
||
# Show most frequent URLs getting 500s
|
||
echo "Most Frequent 500 Error URLs:"
|
||
echo ""
|
||
cut -d'|' -f1,4 "$ERRORS_500" | sort | uniq -c | sort -rn | head -15 | while read count domain_url; do
|
||
domain=$(echo "$domain_url" | cut -d'|' -f1)
|
||
url=$(echo "$domain_url" | cut -d'|' -f2)
|
||
user="${domain_user[$domain]}"
|
||
printf " ${RED}%4d×${NC} %s%s ${DIM}(user: %s)${NC}\n" "$count" "$domain" "$url" "$user"
|
||
done
|
||
|
||
echo ""
|
||
echo "Full error list saved to: $ERRORS_500"
|
||
echo ""
|
||
|
||
press_enter
|