Files
Linux-Server-Management-Too…/modules/website/500-error-tracker.sh
T
cschantz 472a9f3f88 Enhance 500 tracker error log detection and .htaccess diagnosis
IMPROVED ERROR LOG DETECTION:
- Now checks 5 different locations for error logs:
  • /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
- Increased tail from 100 to 500 lines for better error capture

NEW .HTACCESS DETECTION:
- If no error_log found, checks for .htaccess file
- Looks for RewriteRules, php_value, php_flag directives
- If found, classifies as 'HTACCESS_LIKELY' instead of 'NO_ERROR_LOG_FILE'
- Provides specific .htaccess troubleshooting steps

BETTER ROOT CAUSE CATEGORIES:
- HTACCESS_LIKELY: Has .htaccess with rules, likely syntax error
- NO_ERROR_LOG_FILE: Checked all locations, truly not found
- NO_PHP_ERROR_LOGGED: Error log exists but empty (Apache/config issue)

This should catch most of the 'NO_ERROR_LOG_FILE' cases and
correctly identify them as .htaccess syntax errors.
2025-11-03 20:42:19 -05:00

298 lines
12 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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%%-*}"
# 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