Files
Linux-Server-Management-Too…/modules/website/website-error-analyzer.sh
T
cschantz 5e517b9858 Add user/domain filtering and ModSecurity support to error analyzer
- Added per-user analysis (select cPanel user from list)
- Added per-domain analysis (enter specific domain)
- ModSecurity audit log integration
- Detects SQL injection, XSS, command injection blocks by ModSec
- Filters logs by user or domain when specified
- Shows ModSecurity blocks in error report
- Provides specific recommendations for ModSec false positives
- Matches bot analyzer's filtering capabilities
2025-11-03 19:08:53 -05:00

568 lines
21 KiB
Bash
Executable File

#!/bin/bash
################################################################################
# Intelligent Website Error Analyzer
################################################################################
# Purpose: Find REAL website issues affecting REAL users
# Filters out: Bots, plugin warnings, deprecation notices, minor issues
# Focuses on: Critical errors that break sites for actual visitors
################################################################################
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
source "$SCRIPT_DIR/lib/common-functions.sh"
source "$SCRIPT_DIR/lib/system-detect.sh"
source "$SCRIPT_DIR/lib/user-manager.sh"
# Configuration
APACHE_ERROR_LOG="/var/log/apache2/error_log"
DOMLOGS_DIR="/var/log/apache2/domlogs"
MODSEC_AUDIT_LOG="/usr/local/apache/logs/modsec_audit.log"
HOURS_TO_ANALYZE=24
FILTER_USER=""
FILTER_DOMAIN=""
print_banner "Intelligent Website Error Analyzer"
echo ""
echo "Finding REAL issues affecting REAL users..."
echo ""
echo "What we filter out:"
echo " ✗ Bot/scanner activity"
echo " ✗ Plugin deprecation warnings"
echo " ✗ PHP notices (non-critical)"
echo " ✗ Random 404s for missing favicons/assets"
echo " ✗ Security probe attempts"
echo ""
echo "What we focus on:"
echo " ✓ 500 errors users actually see"
echo " ✓ Fatal PHP errors breaking functionality"
echo " ✓ Database connection failures"
echo " ✓ Permission issues blocking access"
echo " ✓ Memory exhaustion affecting performance"
echo ""
# Ask for filtering scope
echo -e "${CYAN}Analysis Scope:${NC}"
echo " 1) All users/domains (default)"
echo " 2) Specific cPanel user"
echo " 3) Specific domain"
echo ""
read -p "Select option [1]: " scope_choice
scope_choice=${scope_choice:-1}
case $scope_choice in
2)
# Select specific user
select_user_interactive "Select cPanel user to analyze"
if [ -n "$SELECTED_USER" ]; then
FILTER_USER="$SELECTED_USER"
echo "→ Filtering for user: $FILTER_USER"
fi
;;
3)
# Enter specific domain
echo ""
read -p "Enter domain name (e.g., example.com): " FILTER_DOMAIN
if [ -n "$FILTER_DOMAIN" ]; then
echo "→ Filtering for domain: $FILTER_DOMAIN"
fi
;;
*)
echo "→ Analyzing all users/domains"
;;
esac
echo ""
# Ask for time range
echo -e "${CYAN}How far back should we analyze?${NC}"
echo " 1) Last 1 hour"
echo " 2) Last 6 hours"
echo " 3) Last 24 hours (default)"
echo " 4) Last 7 days"
echo " 5) Last 30 days"
echo ""
read -p "Select option [3]: " time_choice
time_choice=${time_choice:-3}
case $time_choice in
1) HOURS_TO_ANALYZE=1 ;;
2) HOURS_TO_ANALYZE=6 ;;
3) HOURS_TO_ANALYZE=24 ;;
4) HOURS_TO_ANALYZE=168 ;;
5) HOURS_TO_ANALYZE=720 ;;
*) HOURS_TO_ANALYZE=24 ;;
esac
echo ""
echo "→ Analyzing last $HOURS_TO_ANALYZE hours..."
echo ""
# Temporary files
TEMP_DIR="/tmp/website-error-analysis-$$"
mkdir -p "$TEMP_DIR"
trap "rm -rf $TEMP_DIR" EXIT
CRITICAL_ERRORS="$TEMP_DIR/critical.txt"
USER_IMPACT_ERRORS="$TEMP_DIR/user_impact.txt"
LOG_FILES_LIST="$TEMP_DIR/log_files.txt"
# Discover all log file locations
echo "→ Discovering log file locations..."
> "$LOG_FILES_LIST"
# Apache main error log (only if not filtering by user)
if [ -z "$FILTER_USER" ] && [ -z "$FILTER_DOMAIN" ]; then
for log in /var/log/apache2/error_log /usr/local/apache/logs/error_log /var/log/httpd/error_log; do
[ -f "$log" ] && echo "$log|apache_main" >> "$LOG_FILES_LIST"
done
fi
# Per-domain PHP error logs in public_html
if [ -n "$FILTER_USER" ]; then
# Specific user
find "/home/$FILTER_USER" -name "error_log" -type f 2>/dev/null | while read -r log; do
echo "$log|php_$FILTER_USER" >> "$LOG_FILES_LIST"
done
elif [ -n "$FILTER_DOMAIN" ]; then
# Try to find domain's user and error logs
user=$(grep -l "DNS.*$FILTER_DOMAIN" /var/cpanel/users/* 2>/dev/null | head -1 | xargs basename 2>/dev/null)
if [ -n "$user" ]; then
find "/home/$user" -name "error_log" -type f 2>/dev/null | while read -r log; do
echo "$log|php_$FILTER_DOMAIN" >> "$LOG_FILES_LIST"
done
fi
else
# All users
find /home/*/public_html -name "error_log" -type f 2>/dev/null | while read -r log; do
username=$(echo "$log" | grep -oE '/home/[^/]+' | sed 's|/home/||')
echo "$log|php_$username" >> "$LOG_FILES_LIST"
done
fi
# Per-domain Apache logs in domlogs
if [ -d "$DOMLOGS_DIR" ]; then
if [ -n "$FILTER_DOMAIN" ]; then
# Specific domain
for log in "$DOMLOGS_DIR/$FILTER_DOMAIN" "$DOMLOGS_DIR/$FILTER_DOMAIN-"*; do
[ -f "$log" ] && echo "$log|domlog_$FILTER_DOMAIN" >> "$LOG_FILES_LIST"
done
elif [ -n "$FILTER_USER" ]; then
# Specific user - find their domains
if [ -f "/var/cpanel/users/$FILTER_USER" ]; then
grep "^DNS" "/var/cpanel/users/$FILTER_USER" | awk '{print $2}' | while read -r domain; do
for log in "$DOMLOGS_DIR/$domain" "$DOMLOGS_DIR/$domain-"*; do
[ -f "$log" ] && echo "$log|domlog_$domain" >> "$LOG_FILES_LIST"
done
done
fi
else
# All domains
for log in "$DOMLOGS_DIR"/*; do
[ -f "$log" ] && ! [[ "$log" =~ (bytes_log|offset|error_log)$ ]] && \
echo "$log|domlog_$(basename "$log")" >> "$LOG_FILES_LIST"
done
fi
fi
# ModSecurity audit log (only if not filtering or matches filter)
if [ -f "$MODSEC_AUDIT_LOG" ]; then
if [ -z "$FILTER_USER" ] && [ -z "$FILTER_DOMAIN" ]; then
echo "$MODSEC_AUDIT_LOG|modsec_audit" >> "$LOG_FILES_LIST"
elif [ -n "$FILTER_DOMAIN" ]; then
# Will filter ModSec entries during processing
echo "$MODSEC_AUDIT_LOG|modsec_audit" >> "$LOG_FILES_LIST"
fi
fi
log_count=$(wc -l < "$LOG_FILES_LIST" 2>/dev/null || echo "0")
echo " Found $log_count log files to analyze"
if [ -n "$FILTER_USER" ]; then
echo " Scope: User '$FILTER_USER'"
elif [ -n "$FILTER_DOMAIN" ]; then
echo " Scope: Domain '$FILTER_DOMAIN'"
fi
echo ""
################################################################################
# Intelligent Filtering
################################################################################
is_noise() {
local line="$1"
# Bot/Scanner patterns
if echo "$line" | grep -qiE "bot|crawler|spider|scanner|nikto|nmap|masscan|sqlmap|nessus|acunetix|burp|shodan|censys|zgrab|nuclei|semrush|ahrefs|mj12"; then
return 0
fi
# Known scanner IPs
if echo "$line" | grep -qE "45\.148\.|185\.220\.|89\.248\.165\."; then
return 0
fi
# WordPress plugin deprecation warnings (not user-facing)
if echo "$line" | grep -qiE "PHP Deprecated|Deprecated.*plugin|call_user_func_array.*deprecated"; then
return 0
fi
# Non-critical PHP notices
if echo "$line" | grep -qiE "PHP Notice.*Undefined (variable|index|offset)" && \
! echo "$line" | grep -qiE "fatal|error 500|white screen"; then
return 0
fi
# Missing favicon/common assets (not real errors)
if echo "$line" | grep -qiE "favicon\.ico|apple-touch-icon|robots\.txt|sitemap\.xml.*404"; then
return 0
fi
# Security probes
if echo "$line" | grep -qiE "wp-admin|wp-login|phpMyAdmin|phpmyadmin|\.env|\.git|config\.php|xmlrpc|eval-stdin|shell\.php|c99\.php|r57\.php|adminer\.php" && \
echo "$line" | grep -qiE "404|403|not found"; then
return 0
fi
# WordPress auto-updates and cron (normal operations)
if echo "$line" | grep -qiE "wp-cron\.php|doing_cron|auto.*update"; then
return 0
fi
# Common plugin update checks (not errors)
if echo "$line" | grep -qiE "update-check|version-check|api\.wordpress\.org"; then
return 0
fi
return 1
}
is_critical_user_facing() {
local line="$1"
# 500 Internal Server Error (users see white page)
if echo "$line" | grep -qiE " 500 |Internal Server Error"; then
return 0
fi
# PHP Fatal Errors (breaks functionality)
if echo "$line" | grep -qiE "PHP Fatal error|Fatal error:.*in /"; then
return 0
fi
# PHP Parse Errors (site completely broken)
if echo "$line" | grep -qiE "PHP Parse error|syntax error"; then
return 0
fi
# Database connection failures (site down)
if echo "$line" | grep -qiE "Error establishing.*database|Can't connect.*MySQL|Access denied for user.*database|Too many connections|MySQL server has gone away"; then
return 0
fi
# Memory exhaustion (white screen/incomplete pages)
if echo "$line" | grep -qiE "Allowed memory size.*exhausted|Out of memory|Fatal.*memory"; then
return 0
fi
# Segmentation fault (complete crash)
if echo "$line" | grep -qiE "Segmentation fault|signal 11"; then
return 0
fi
# Permission denied on critical files
if echo "$line" | grep -qiE "Permission denied" && \
echo "$line" | grep -qE "index\.php|wp-config\.php|\.htaccess|config\.php"; then
return 0
fi
# File not found for MAIN page (404 on homepage/index)
if echo "$line" | grep -qiE "File does not exist.*index\.(php|html)" || \
echo "$line" | grep -qE " 404 .*\" \"GET / "; then
return 0
fi
return 1
}
extract_useful_info() {
local line="$1"
# Extract domain
domain=$(echo "$line" | grep -oE '\[vhost [^:]+' | sed 's/\[vhost //' || \
echo "$line" | grep -oE '[a-zA-Z0-9.-]+\.(com|net|org|io|co|uk|us|dev)' | head -1 || \
echo "$line" | grep -oE '/home/[^/]+' | sed 's|/home/||' || echo "unknown")
# Extract file path if PHP error
file_path=$(echo "$line" | grep -oE "in /[^ ]+\.php" | sed 's/in //' || echo "")
# Extract error message (simplified)
error_msg=$(echo "$line" | sed 's/^\[.*\] //' | sed 's/\[client [^]]*\] //' | cut -c1-100)
echo "$domain|$file_path|$error_msg"
}
################################################################################
# Main Analysis
################################################################################
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " SCANNING FOR USER-IMPACTING ERRORS"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
total_lines=0
filtered_out=0
critical_found=0
# Analyze all discovered log files
while IFS='|' read -r log_path log_type; do
echo " Analyzing: $(basename "$log_path")..."
# Determine log type
is_access_log=false
is_modsec_log=false
[[ "$log_type" == domlog_* ]] && ! [[ "$log_path" =~ error ]] && is_access_log=true
[[ "$log_type" == modsec_* ]] && is_modsec_log=true
if $is_modsec_log; then
# ModSecurity audit log - parse for blocks/denials
while IFS= read -r line; do
((total_lines++))
# Skip if not a security event
echo "$line" | grep -qiE "ModSecurity.*Action.*Intercepted|status: (403|406|500)" || continue
# Extract domain/host if filtering
if [ -n "$FILTER_DOMAIN" ]; then
echo "$line" | grep -q "$FILTER_DOMAIN" || continue
fi
# Extract useful info
if echo "$line" | grep -qiE "SQL Injection|XSS|Command Injection|Path Traversal"; then
((critical_found++))
attack_type=$(echo "$line" | grep -oiE "SQL Injection|XSS|Command Injection|Path Traversal" | head -1)
uri=$(echo "$line" | grep -oE "uri: [^ ]+" | sed 's/uri: //' || echo "")
domain=$(echo "$line" | grep -oE "hostname: [^ ]+" | sed 's/hostname: //' || echo "unknown")
echo "$domain||ModSecurity blocked: $attack_type $uri" >> "$CRITICAL_ERRORS"
fi
done < <(tail -n 5000 "$log_path" 2>/dev/null)
elif $is_access_log; then
# Access log - look for 5xx status codes
while IFS= read -r line; do
((total_lines++))
# Skip if bot/scanner
if is_noise "$line"; then
((filtered_out++))
continue
fi
# Extract status code and URL
if echo "$line" | grep -qE '" 5[0-9]{2} '; then
status=$(echo "$line" | grep -oE '" 5[0-9]{2} ' | tr -d '" ')
url=$(echo "$line" | awk '{print $7}' | cut -c1-80)
ip=$(echo "$line" | awk '{print $1}')
domain=$(basename "$log_path" | sed 's/-.*//')
# Apply domain filter if set
if [ -n "$FILTER_DOMAIN" ] && [ "$domain" != "$FILTER_DOMAIN" ]; then
continue
fi
((critical_found++))
echo "$domain||HTTP $status - $url (from $ip)" >> "$CRITICAL_ERRORS"
fi
done < <(tail -n 5000 "$log_path" 2>/dev/null)
else
# Error log - look for critical errors
while IFS= read -r line; do
((total_lines++))
# Skip noise
if is_noise "$line"; then
((filtered_out++))
continue
fi
# Apply user/domain filter if set
if [ -n "$FILTER_USER" ]; then
echo "$line" | grep -q "/home/$FILTER_USER" || continue
fi
if [ -n "$FILTER_DOMAIN" ]; then
echo "$line" | grep -q "$FILTER_DOMAIN" || continue
fi
# Check if it's critical and user-facing
if is_critical_user_facing "$line"; then
((critical_found++))
extract_useful_info "$line" >> "$CRITICAL_ERRORS"
fi
done < <(tail -n 10000 "$log_path" 2>/dev/null)
fi
done < "$LOG_FILES_LIST"
echo ""
echo "Scanned: $total_lines log entries"
echo "Filtered: $filtered_out noise entries (bots, warnings, etc.)"
echo "Found: $critical_found critical user-facing errors"
echo ""
################################################################################
# Generate Report
################################################################################
if [ ! -f "$CRITICAL_ERRORS" ] || [ ! -s "$CRITICAL_ERRORS" ]; then
echo ""
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${GREEN} ✓ NO CRITICAL USER-FACING ERRORS FOUND!${NC}"
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
echo "Your websites appear to be functioning normally for real users."
echo "All errors found were from bots, scanners, or non-critical warnings."
echo ""
exit 0
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " ⚠️ CRITICAL ERRORS AFFECTING REAL USERS"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
# Group by domain and count
echo -e "${RED}${BOLD}AFFECTED WEBSITES:${NC}"
echo ""
cat "$CRITICAL_ERRORS" | cut -d'|' -f1 | sort | uniq -c | sort -rn | head -10 | while read -r count domain; do
printf " ${RED}${NC} %-40s ${BOLD}%4d errors${NC}\n" "$domain" "$count"
done
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " ERROR DETAILS"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
# Show errors grouped by domain
current_domain=""
domain_count=0
sort "$CRITICAL_ERRORS" | while IFS='|' read -r domain file_path error_msg; do
if [ "$domain" != "$current_domain" ]; then
if [ -n "$current_domain" ]; then
echo ""
fi
((domain_count++))
[ $domain_count -gt 5 ] && break
echo -e "${YELLOW}$domain${NC}"
current_domain="$domain"
fi
if [ -n "$file_path" ]; then
echo " File: $file_path"
fi
echo " Error: $error_msg"
echo ""
done
################################################################################
# Intelligent Recommendations
################################################################################
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " 🔧 RECOMMENDED ACTIONS"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
# Detect issue types and provide specific recommendations
if grep -qiE "500|Internal Server" "$CRITICAL_ERRORS"; then
echo -e "${RED}● 500 Internal Server Errors detected${NC}"
echo " Priority: URGENT - Users see broken pages"
echo ""
echo " Actions:"
echo " 1. Check PHP error logs for affected domains:"
for domain in $(grep -iE "500|Internal Server" "$CRITICAL_ERRORS" | cut -d'|' -f1 | sort -u | head -3); do
echo " tail -100 /home/$domain/public_html/error_log"
done
echo ""
echo " 2. Common causes:"
echo " - PHP syntax errors (check recent file changes)"
echo " - .htaccess syntax errors"
echo " - Incorrect file permissions"
echo " - Missing PHP modules"
echo ""
fi
if grep -qiE "Fatal error|Parse error" "$CRITICAL_ERRORS"; then
echo -e "${RED}● PHP Fatal/Parse Errors detected${NC}"
echo " Priority: URGENT - Site functionality broken"
echo ""
echo " Actions:"
echo " 1. Review recent plugin/theme updates"
echo " 2. Check PHP version compatibility"
echo " 3. Look for syntax errors in custom code"
echo " 4. Temporarily disable recently added plugins"
echo ""
fi
if grep -qiE "database|MySQL|Connection" "$CRITICAL_ERRORS"; then
echo -e "${RED}● Database Connection Errors detected${NC}"
echo " Priority: CRITICAL - Site may be completely down"
echo ""
echo " Actions:"
echo " 1. Check MySQL service: systemctl status mysql"
echo " 2. Verify database credentials in config files"
echo " 3. Check max_connections: mysql -e 'SHOW VARIABLES LIKE \"max_connections\"'"
echo " 4. Review current connections: mysql -e 'SHOW PROCESSLIST'"
echo ""
fi
if grep -qiE "memory.*exhausted|Out of memory" "$CRITICAL_ERRORS"; then
echo -e "${RED}● Memory Exhaustion detected${NC}"
echo " Priority: HIGH - Pages loading slowly or incompletely"
echo ""
echo " Current PHP memory limit: $(php -r 'echo ini_get("memory_limit");' 2>/dev/null)"
echo ""
echo " Actions:"
echo " 1. Increase memory_limit in php.ini or .user.ini"
echo " 2. Recommended: 256M minimum, 512M for WooCommerce"
echo " 3. Check for memory leaks in plugins"
echo " 4. Optimize database queries"
echo ""
fi
if grep -qiE "ModSecurity" "$CRITICAL_ERRORS"; then
echo -e "${YELLOW}● ModSecurity Blocks detected${NC}"
echo " Priority: MEDIUM - Security rules blocking requests"
echo ""
echo " Actions:"
echo " 1. Review ModSecurity audit log for false positives"
echo " 2. Check if legitimate functionality is being blocked"
echo " 3. Consider whitelisting specific rules if needed"
echo " 4. Review blocked attack types to understand threats"
echo " 5. ModSecurity log: $MODSEC_AUDIT_LOG"
echo ""
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Full analysis saved to: $CRITICAL_ERRORS"
echo ""
echo "Next steps:"
echo " 1. Address critical errors first (500s, database, fatal errors)"
echo " 2. Check error_log files for affected domains"
echo " 3. Test affected pages after fixes"
echo " 4. Re-run this tool to verify issues are resolved"
echo ""
press_enter