Loading...
Tracks error patterns and alerts when error rates spike
{
"hookConfig": {
"hooks": {
"notification": {
"script": "./.claude/hooks/error-rate-monitor.sh"
}
}
},
"scriptContent": "#!/usr/bin/env bash\n\necho \"đ Monitoring error rates across log files...\" >&2\n\n# Configurable thresholds (can be overridden by environment variables)\nERROR_THRESHOLD_PER_FILE=${ERROR_THRESHOLD_PER_FILE:-5}\nTOTAL_ERROR_THRESHOLD=${TOTAL_ERROR_THRESHOLD:-10}\nLOG_LINES_TO_CHECK=${LOG_LINES_TO_CHECK:-100}\nMAX_SAMPLE_ERRORS=${MAX_SAMPLE_ERRORS:-3}\n\n# Initialize counters\nTOTAL_ERRORS=0\nFILES_WITH_ERRORS=0\nCRITICAL_FILES=()\nERROR_SAMPLES=()\n\n# Define error patterns with severity levels\nFATAL_PATTERNS=(\"fatal\" \"critical\" \"panic\" \"abort\" \"segfault\")\nERROR_PATTERNS=(\"error\" \"exception\" \"failed\" \"failure\" \"timeout\")\nWARNING_PATTERNS=(\"warning\" \"warn\" \"deprecated\" \"notice\")\n\n# Function to count errors by severity\ncount_errors_by_severity() {\n local log_file=\"$1\"\n local fatal_count=0\n local error_count=0\n local warning_count=0\n \n if [ ! -f \"$log_file\" ]; then\n return\n fi\n \n # Check last N lines of the log file\n local recent_logs=$(tail -\"$LOG_LINES_TO_CHECK\" \"$log_file\" 2>/dev/null || echo \"\")\n \n if [ -z \"$recent_logs\" ]; then\n return\n fi\n \n # Count fatal errors\n for pattern in \"${FATAL_PATTERNS[@]}\"; do\n fatal_count=$((fatal_count + $(echo \"$recent_logs\" | grep -icE \"\\\\b$pattern\\\\b\" || echo \"0\")))\n done\n \n # Count errors (excluding fatals already counted)\n for pattern in \"${ERROR_PATTERNS[@]}\"; do\n error_count=$((error_count + $(echo \"$recent_logs\" | grep -icE \"\\\\b$pattern\\\\b\" || echo \"0\")))\n done\n \n # Count warnings\n for pattern in \"${WARNING_PATTERNS[@]}\"; do\n warning_count=$((warning_count + $(echo \"$recent_logs\" | grep -icE \"\\\\b$pattern\\\\b\" || echo \"0\")))\n done\n \n echo \"$fatal_count $error_count $warning_count\"\n}\n\n# Function to extract error samples\nextract_error_samples() {\n local log_file=\"$1\"\n local sample_count=\"$2\"\n \n if [ ! -f \"$log_file\" ]; then\n return\n fi\n \n # Get recent error lines with timestamps if available\n tail -\"$LOG_LINES_TO_CHECK\" \"$log_file\" 2>/dev/null | \\\n grep -iE '(fatal|critical|error|exception|failed)' | \\\n head -\"$sample_count\" | \\\n while IFS= read -r line; do\n # Truncate very long lines\n if [ ${#line} -gt 120 ]; then\n echo \"${line:0:120}...\"\n else\n echo \"$line\"\n fi\n done\n}\n\n# Function to check log files in a directory\ncheck_log_directory() {\n local dir=\"$1\"\n local pattern=\"$2\"\n \n if [ ! -d \"$dir\" ]; then\n return\n fi\n \n find \"$dir\" -name \"$pattern\" -type f 2>/dev/null | while read -r log_file; do\n echo \"$log_file\"\n done\n}\n\n# Collect all log files to check\nLOG_FILES=()\n\n# Standard log locations\nfor pattern in \"*.log\" \"*.out\" \"*.err\"; do\n while IFS= read -r -d '' file; do\n LOG_FILES+=(\"$file\")\n done < <(find . -maxdepth 1 -name \"$pattern\" -type f -print0 2>/dev/null)\ndone\n\n# Common log directories\nLOG_DIRS=(\"logs\" \"log\" \"var/log\" \".logs\" \"tmp/logs\")\nfor log_dir in \"${LOG_DIRS[@]}\"; do\n if [ -d \"$log_dir\" ]; then\n while IFS= read -r -d '' file; do\n LOG_FILES+=(\"$file\")\n done < <(find \"$log_dir\" -name \"*.log\" -o -name \"*.out\" -o -name \"*.err\" -type f -print0 2>/dev/null)\n fi\ndone\n\n# Framework-specific log locations\nif [ -f \"package.json\" ]; then\n # Node.js specific logs\n for pattern in \"npm-debug.log\" \"yarn-error.log\" \"pnpm-debug.log\"; do\n [ -f \"$pattern\" ] && LOG_FILES+=(\"$pattern\")\n done\n \n # Next.js logs\n [ -d \".next\" ] && find .next -name \"*.log\" -type f 2>/dev/null | while read -r file; do\n LOG_FILES+=(\"$file\")\n done\nfi\n\n# Python specific logs\nif [ -f \"requirements.txt\" ] || [ -f \"pyproject.toml\" ]; then\n for pattern in \"django.log\" \"flask.log\" \"celery.log\" \"pytest.log\"; do\n [ -f \"$pattern\" ] && LOG_FILES+=(\"$pattern\")\n done\nfi\n\n# Docker logs if Docker is available\nif command -v docker &> /dev/null && docker info &> /dev/null 2>&1; then\n # Check for recent container logs with errors\n CONTAINERS=$(docker ps --format \"{{.Names}}\" 2>/dev/null | head -5)\n for container in $CONTAINERS; do\n if [ -n \"$container\" ]; then\n ERROR_COUNT=$(docker logs \"$container\" --since=10m 2>&1 | grep -icE '(fatal|critical|error|exception)' || echo \"0\")\n if [ \"$ERROR_COUNT\" -gt 0 ]; then\n echo \"đŗ Container '$container' has $ERROR_COUNT recent errors\" >&2\n TOTAL_ERRORS=$((TOTAL_ERRORS + ERROR_COUNT))\n \n # Get error samples from container logs\n CONTAINER_ERRORS=$(docker logs \"$container\" --since=10m 2>&1 | grep -iE '(fatal|critical|error|exception)' | head -2)\n if [ -n \"$CONTAINER_ERRORS\" ]; then\n echo \"đ Sample from $container:\" >&2\n echo \"$CONTAINER_ERRORS\" | head -1 >&2\n fi\n fi\n fi\n done\nfi\n\n# Remove duplicates from LOG_FILES array\nreadarray -t UNIQUE_LOG_FILES < <(printf '%s\\n' \"${LOG_FILES[@]}\" | sort -u)\n\necho \"đ Checking ${#UNIQUE_LOG_FILES[@]} log files for error patterns...\" >&2\n\n# Check each log file\nfor log_file in \"${UNIQUE_LOG_FILES[@]}\"; do\n if [ ! -f \"$log_file\" ]; then\n continue\n fi\n \n # Get error counts by severity\n read -r fatal_count error_count warning_count <<< \"$(count_errors_by_severity \"$log_file\")\"\n \n file_total_errors=$((fatal_count + error_count))\n TOTAL_ERRORS=$((TOTAL_ERRORS + file_total_errors))\n \n if [ \"$file_total_errors\" -gt 0 ]; then\n FILES_WITH_ERRORS=$((FILES_WITH_ERRORS + 1))\n \n log_basename=$(basename \"$log_file\")\n \n # Report file-level errors\n if [ \"$fatal_count\" -gt 0 ]; then\n echo \"đ¨ CRITICAL: $log_basename has $fatal_count fatal errors\" >&2\n CRITICAL_FILES+=(\"$log_file\")\n fi\n \n if [ \"$file_total_errors\" -gt \"$ERROR_THRESHOLD_PER_FILE\" ]; then\n echo \"â ī¸ ERROR SPIKE: $log_basename has $file_total_errors errors (fatal: $fatal_count, error: $error_count)\" >&2\n \n # Extract error samples\n echo \"đ Recent error samples from $log_basename:\" >&2\n extract_error_samples \"$log_file\" \"$MAX_SAMPLE_ERRORS\" | while IFS= read -r sample; do\n echo \" â $sample\" >&2\n done\n elif [ \"$file_total_errors\" -gt 0 ]; then\n echo \"âšī¸ $log_basename: $file_total_errors errors detected\" >&2\n fi\n \n if [ \"$warning_count\" -gt 0 ]; then\n echo \"â ī¸ $log_basename: $warning_count warnings\" >&2\n fi\n fi\ndone\n\n# Overall error rate analysis\necho \"\" >&2\necho \"đ Error Rate Summary:\" >&2\necho \" đ Files checked: ${#UNIQUE_LOG_FILES[@]}\" >&2\necho \" đ Files with errors: $FILES_WITH_ERRORS\" >&2\necho \" đĸ Total errors: $TOTAL_ERRORS\" >&2\necho \" đ¨ Critical files: ${#CRITICAL_FILES[@]}\" >&2\n\n# Alert on high error rates\nif [ \"$TOTAL_ERRORS\" -gt \"$TOTAL_ERROR_THRESHOLD\" ]; then\n echo \"\" >&2\n echo \"đ¨ HIGH ERROR RATE DETECTED!\" >&2\n echo \"â ī¸ Total errors ($TOTAL_ERRORS) exceed threshold ($TOTAL_ERROR_THRESHOLD)\" >&2\n \n if [ ${#CRITICAL_FILES[@]} -gt 0 ]; then\n echo \"đĨ Critical files requiring immediate attention:\" >&2\n for critical_file in \"${CRITICAL_FILES[@]}\"; do\n echo \" â $(basename \"$critical_file\")\" >&2\n done\n fi\n \nelif [ \"$TOTAL_ERRORS\" -gt 0 ]; then\n echo \"âšī¸ Errors detected but within acceptable threshold\" >&2\nelse\n echo \"â
No errors detected in monitored log files\" >&2\nfi\n\n# Performance recommendations\nif [ ${#UNIQUE_LOG_FILES[@]} -gt 20 ]; then\n echo \"\" >&2\n echo \"đĄ Performance tip: Consider log rotation or filtering for faster monitoring\" >&2\nfi\n\necho \"\" >&2\necho \"đ§ Monitoring Configuration:\" >&2\necho \" âĸ Error threshold per file: $ERROR_THRESHOLD_PER_FILE\" >&2\necho \" âĸ Total error threshold: $TOTAL_ERROR_THRESHOLD\" >&2\necho \" âĸ Lines checked per file: $LOG_LINES_TO_CHECK\" >&2\necho \"\" >&2\necho \"đĄ Customize thresholds with environment variables:\" >&2\necho \" export ERROR_THRESHOLD_PER_FILE=10\" >&2\necho \" export TOTAL_ERROR_THRESHOLD=25\" >&2\n\nexit 0"
}.claude/hooks/~/.claude/hooks/{
"hooks": {
"notification": {
"script": "./.claude/hooks/error-rate-monitor.sh"
}
}
}#!/usr/bin/env bash
echo "đ Monitoring error rates across log files..." >&2
# Configurable thresholds (can be overridden by environment variables)
ERROR_THRESHOLD_PER_FILE=${ERROR_THRESHOLD_PER_FILE:-5}
TOTAL_ERROR_THRESHOLD=${TOTAL_ERROR_THRESHOLD:-10}
LOG_LINES_TO_CHECK=${LOG_LINES_TO_CHECK:-100}
MAX_SAMPLE_ERRORS=${MAX_SAMPLE_ERRORS:-3}
# Initialize counters
TOTAL_ERRORS=0
FILES_WITH_ERRORS=0
CRITICAL_FILES=()
ERROR_SAMPLES=()
# Define error patterns with severity levels
FATAL_PATTERNS=("fatal" "critical" "panic" "abort" "segfault")
ERROR_PATTERNS=("error" "exception" "failed" "failure" "timeout")
WARNING_PATTERNS=("warning" "warn" "deprecated" "notice")
# Function to count errors by severity
count_errors_by_severity() {
local log_file="$1"
local fatal_count=0
local error_count=0
local warning_count=0
if [ ! -f "$log_file" ]; then
return
fi
# Check last N lines of the log file
local recent_logs=$(tail -"$LOG_LINES_TO_CHECK" "$log_file" 2>/dev/null || echo "")
if [ -z "$recent_logs" ]; then
return
fi
# Count fatal errors
for pattern in "${FATAL_PATTERNS[@]}"; do
fatal_count=$((fatal_count + $(echo "$recent_logs" | grep -icE "\\b$pattern\\b" || echo "0")))
done
# Count errors (excluding fatals already counted)
for pattern in "${ERROR_PATTERNS[@]}"; do
error_count=$((error_count + $(echo "$recent_logs" | grep -icE "\\b$pattern\\b" || echo "0")))
done
# Count warnings
for pattern in "${WARNING_PATTERNS[@]}"; do
warning_count=$((warning_count + $(echo "$recent_logs" | grep -icE "\\b$pattern\\b" || echo "0")))
done
echo "$fatal_count $error_count $warning_count"
}
# Function to extract error samples
extract_error_samples() {
local log_file="$1"
local sample_count="$2"
if [ ! -f "$log_file" ]; then
return
fi
# Get recent error lines with timestamps if available
tail -"$LOG_LINES_TO_CHECK" "$log_file" 2>/dev/null | \
grep -iE '(fatal|critical|error|exception|failed)' | \
head -"$sample_count" | \
while IFS= read -r line; do
# Truncate very long lines
if [ ${#line} -gt 120 ]; then
echo "${line:0:120}..."
else
echo "$line"
fi
done
}
# Function to check log files in a directory
check_log_directory() {
local dir="$1"
local pattern="$2"
if [ ! -d "$dir" ]; then
return
fi
find "$dir" -name "$pattern" -type f 2>/dev/null | while read -r log_file; do
echo "$log_file"
done
}
# Collect all log files to check
LOG_FILES=()
# Standard log locations
for pattern in "*.log" "*.out" "*.err"; do
while IFS= read -r -d '' file; do
LOG_FILES+=("$file")
done < <(find . -maxdepth 1 -name "$pattern" -type f -print0 2>/dev/null)
done
# Common log directories
LOG_DIRS=("logs" "log" "var/log" ".logs" "tmp/logs")
for log_dir in "${LOG_DIRS[@]}"; do
if [ -d "$log_dir" ]; then
while IFS= read -r -d '' file; do
LOG_FILES+=("$file")
done < <(find "$log_dir" -name "*.log" -o -name "*.out" -o -name "*.err" -type f -print0 2>/dev/null)
fi
done
# Framework-specific log locations
if [ -f "package.json" ]; then
# Node.js specific logs
for pattern in "npm-debug.log" "yarn-error.log" "pnpm-debug.log"; do
[ -f "$pattern" ] && LOG_FILES+=("$pattern")
done
# Next.js logs
[ -d ".next" ] && find .next -name "*.log" -type f 2>/dev/null | while read -r file; do
LOG_FILES+=("$file")
done
fi
# Python specific logs
if [ -f "requirements.txt" ] || [ -f "pyproject.toml" ]; then
for pattern in "django.log" "flask.log" "celery.log" "pytest.log"; do
[ -f "$pattern" ] && LOG_FILES+=("$pattern")
done
fi
# Docker logs if Docker is available
if command -v docker &> /dev/null && docker info &> /dev/null 2>&1; then
# Check for recent container logs with errors
CONTAINERS=$(docker ps --format "{{.Names}}" 2>/dev/null | head -5)
for container in $CONTAINERS; do
if [ -n "$container" ]; then
ERROR_COUNT=$(docker logs "$container" --since=10m 2>&1 | grep -icE '(fatal|critical|error|exception)' || echo "0")
if [ "$ERROR_COUNT" -gt 0 ]; then
echo "đŗ Container '$container' has $ERROR_COUNT recent errors" >&2
TOTAL_ERRORS=$((TOTAL_ERRORS + ERROR_COUNT))
# Get error samples from container logs
CONTAINER_ERRORS=$(docker logs "$container" --since=10m 2>&1 | grep -iE '(fatal|critical|error|exception)' | head -2)
if [ -n "$CONTAINER_ERRORS" ]; then
echo "đ Sample from $container:" >&2
echo "$CONTAINER_ERRORS" | head -1 >&2
fi
fi
fi
done
fi
# Remove duplicates from LOG_FILES array
readarray -t UNIQUE_LOG_FILES < <(printf '%s\n' "${LOG_FILES[@]}" | sort -u)
echo "đ Checking ${#UNIQUE_LOG_FILES[@]} log files for error patterns..." >&2
# Check each log file
for log_file in "${UNIQUE_LOG_FILES[@]}"; do
if [ ! -f "$log_file" ]; then
continue
fi
# Get error counts by severity
read -r fatal_count error_count warning_count <<< "$(count_errors_by_severity "$log_file")"
file_total_errors=$((fatal_count + error_count))
TOTAL_ERRORS=$((TOTAL_ERRORS + file_total_errors))
if [ "$file_total_errors" -gt 0 ]; then
FILES_WITH_ERRORS=$((FILES_WITH_ERRORS + 1))
log_basename=$(basename "$log_file")
# Report file-level errors
if [ "$fatal_count" -gt 0 ]; then
echo "đ¨ CRITICAL: $log_basename has $fatal_count fatal errors" >&2
CRITICAL_FILES+=("$log_file")
fi
if [ "$file_total_errors" -gt "$ERROR_THRESHOLD_PER_FILE" ]; then
echo "â ī¸ ERROR SPIKE: $log_basename has $file_total_errors errors (fatal: $fatal_count, error: $error_count)" >&2
# Extract error samples
echo "đ Recent error samples from $log_basename:" >&2
extract_error_samples "$log_file" "$MAX_SAMPLE_ERRORS" | while IFS= read -r sample; do
echo " â $sample" >&2
done
elif [ "$file_total_errors" -gt 0 ]; then
echo "âšī¸ $log_basename: $file_total_errors errors detected" >&2
fi
if [ "$warning_count" -gt 0 ]; then
echo "â ī¸ $log_basename: $warning_count warnings" >&2
fi
fi
done
# Overall error rate analysis
echo "" >&2
echo "đ Error Rate Summary:" >&2
echo " đ Files checked: ${#UNIQUE_LOG_FILES[@]}" >&2
echo " đ Files with errors: $FILES_WITH_ERRORS" >&2
echo " đĸ Total errors: $TOTAL_ERRORS" >&2
echo " đ¨ Critical files: ${#CRITICAL_FILES[@]}" >&2
# Alert on high error rates
if [ "$TOTAL_ERRORS" -gt "$TOTAL_ERROR_THRESHOLD" ]; then
echo "" >&2
echo "đ¨ HIGH ERROR RATE DETECTED!" >&2
echo "â ī¸ Total errors ($TOTAL_ERRORS) exceed threshold ($TOTAL_ERROR_THRESHOLD)" >&2
if [ ${#CRITICAL_FILES[@]} -gt 0 ]; then
echo "đĨ Critical files requiring immediate attention:" >&2
for critical_file in "${CRITICAL_FILES[@]}"; do
echo " â $(basename "$critical_file")" >&2
done
fi
elif [ "$TOTAL_ERRORS" -gt 0 ]; then
echo "âšī¸ Errors detected but within acceptable threshold" >&2
else
echo "â
No errors detected in monitored log files" >&2
fi
# Performance recommendations
if [ ${#UNIQUE_LOG_FILES[@]} -gt 20 ]; then
echo "" >&2
echo "đĄ Performance tip: Consider log rotation or filtering for faster monitoring" >&2
fi
echo "" >&2
echo "đ§ Monitoring Configuration:" >&2
echo " âĸ Error threshold per file: $ERROR_THRESHOLD_PER_FILE" >&2
echo " âĸ Total error threshold: $TOTAL_ERROR_THRESHOLD" >&2
echo " âĸ Lines checked per file: $LOG_LINES_TO_CHECK" >&2
echo "" >&2
echo "đĄ Customize thresholds with environment variables:" >&2
echo " export ERROR_THRESHOLD_PER_FILE=10" >&2
echo " export TOTAL_ERROR_THRESHOLD=25" >&2
exit 0Hook runs continuously causing terminal spam
Add sleep interval or debounce logic to notification hook: sleep 60 between checks. Reduce LOG_LINES_TO_CHECK to 50 for faster processing with less output.
Docker container log checks fail with permission denied
Ensure user is in docker group: sudo usermod -aG docker $USER and restart session. Alternatively, skip Docker checks: remove docker logs section from script.
Log file pattern matching misses framework-specific logs
Add custom log paths to LOG_DIRS array: LOG_DIRS+=('build/logs' '.next/logs'). Extend file patterns: find with -name '*.out' -o -name 'app*.log' for comprehensive coverage.
Error rate threshold alerts for normal warning messages
Separate ERROR_PATTERNS from WARNING_PATTERNS in severity classification. Only count fatal+error toward threshold, exclude warnings: TOTAL_ERRORS=$((fatal_count + error_count)).
Hook performance degrades with many large log files
Limit file search depth: find . -maxdepth 2 instead of recursive. Increase LOG_LINES_TO_CHECK interval but reduce file count: head -5 on find results for performance.
Loading reviews...
Join our community of Claude power users. No spam, unsubscribe anytime.
Automated accessibility testing and compliance checking for web applications following WCAG guidelines
Automatically generates or updates API documentation when endpoint files are modified
Automatically formats code files after Claude writes or edits them using Prettier, Black, or other formatters