1
0
mirror of https://github.com/tw93/Mole.git synced 2026-02-04 13:16:47 +00:00

feat: log cleanup operations for troubleshooting

This commit is contained in:
tw93
2026-01-26 15:22:07 +08:00
parent e0aba780c8
commit 8c4cd7f82e
7 changed files with 267 additions and 119 deletions

View File

@@ -76,6 +76,7 @@ mo purge --paths # Configure project scan directories
- **Safety**: Built with strict protections. See [Security Audit](SECURITY_AUDIT.md). Preview changes with `mo clean --dry-run`. - **Safety**: Built with strict protections. See [Security Audit](SECURITY_AUDIT.md). Preview changes with `mo clean --dry-run`.
- **Be Careful**: Although safe by design, file deletion is permanent. Please review operations carefully. - **Be Careful**: Although safe by design, file deletion is permanent. Please review operations carefully.
- **Debug Mode**: Use `--debug` for detailed logs (e.g., `mo clean --debug`). Combine with `--dry-run` for comprehensive preview including risk levels and file details. - **Debug Mode**: Use `--debug` for detailed logs (e.g., `mo clean --debug`). Combine with `--dry-run` for comprehensive preview including risk levels and file details.
- **Operation Log**: File operations are logged to `~/.config/mole/operations.log` for troubleshooting. Disable with `MO_NO_OPLOG=1`.
- **Navigation**: Supports arrow keys and Vim bindings (`h/j/k/l`). - **Navigation**: Supports arrow keys and Vim bindings (`h/j/k/l`).
- **Status Shortcuts**: In `mo status`, press `k` to toggle cat visibility and save preference, `q` to quit. - **Status Shortcuts**: In `mo status`, press `k` to toggle cat visibility and save preference, `q` to quit.
- **Configuration**: Run `mo touchid` for Touch ID sudo, `mo completion` for shell tab completion, `mo clean --whitelist` to manage protected paths. - **Configuration**: Run `mo touchid` for Touch ID sudo, `mo completion` for shell tab completion, `mo clean --whitelist` to manage protected paths.

View File

@@ -365,6 +365,7 @@ safe_clean() {
if should_protect_path "$path"; then if should_protect_path "$path"; then
skip=true skip=true
((skipped_count++)) ((skipped_count++))
log_operation "clean" "SKIPPED" "$path" "protected"
fi fi
[[ "$skip" == "true" ]] && continue [[ "$skip" == "true" ]] && continue
@@ -372,6 +373,7 @@ safe_clean() {
if is_path_whitelisted "$path"; then if is_path_whitelisted "$path"; then
skip=true skip=true
((skipped_count++)) ((skipped_count++))
log_operation "clean" "SKIPPED" "$path" "whitelist"
fi fi
[[ "$skip" == "true" ]] && continue [[ "$skip" == "true" ]] && continue
[[ -e "$path" ]] && existing_paths+=("$path") [[ -e "$path" ]] && existing_paths+=("$path")
@@ -699,6 +701,10 @@ safe_clean() {
} }
start_cleanup() { start_cleanup() {
# Set current command for operation logging
export MOLE_CURRENT_COMMAND="clean"
log_operation_session_start "clean"
if [[ -t 1 ]]; then if [[ -t 1 ]]; then
printf '\033[2J\033[H' printf '\033[2J\033[H'
fi fi
@@ -1065,6 +1071,9 @@ perform_cleanup() {
set -e set -e
fi fi
# Log session end with summary
log_operation_session_end "clean" "$files_cleaned" "$total_size_cleaned"
print_summary_block "$summary_heading" "${summary_details[@]}" print_summary_block "$summary_heading" "${summary_details[@]}"
printf '\n' printf '\n'
} }

View File

@@ -357,6 +357,8 @@ cleanup_all() {
stop_inline_spinner 2>/dev/null || true stop_inline_spinner 2>/dev/null || true
stop_sudo_session stop_sudo_session
cleanup_temp_files cleanup_temp_files
# Log session end
log_operation_session_end "optimize" "${OPTIMIZE_SAFE_COUNT:-0}" "0"
} }
handle_interrupt() { handle_interrupt() {
@@ -365,6 +367,9 @@ handle_interrupt() {
} }
main() { main() {
# Set current command for operation logging
export MOLE_CURRENT_COMMAND="optimize"
local health_json local health_json
for arg in "$@"; do for arg in "$@"; do
case "$arg" in case "$arg" in
@@ -381,6 +386,8 @@ main() {
esac esac
done done
log_operation_session_start "optimize"
trap cleanup_all EXIT trap cleanup_all EXIT
trap handle_interrupt INT TERM trap handle_interrupt INT TERM

View File

@@ -42,6 +42,10 @@ note_activity() {
# Main purge function # Main purge function
start_purge() { start_purge() {
# Set current command for operation logging
export MOLE_CURRENT_COMMAND="purge"
log_operation_session_start "purge"
# Clear screen for better UX # Clear screen for better UX
if [[ -t 1 ]]; then if [[ -t 1 ]]; then
printf '\033[2J\033[H' printf '\033[2J\033[H'
@@ -214,6 +218,9 @@ perform_purge() {
summary_details+=("Free space now: $(get_free_space)") summary_details+=("Free space now: $(get_free_space)")
fi fi
# Log session end
log_operation_session_end "purge" "${total_items_cleaned:-0}" "${total_size_cleaned:-0}"
print_summary_block "$summary_heading" "${summary_details[@]}" print_summary_block "$summary_heading" "${summary_details[@]}"
printf '\n' printf '\n'
} }

View File

@@ -374,6 +374,8 @@ cleanup() {
wait "$sudo_keepalive_pid" 2> /dev/null || true wait "$sudo_keepalive_pid" 2> /dev/null || true
sudo_keepalive_pid="" sudo_keepalive_pid=""
fi fi
# Log session end
log_operation_session_end "uninstall" "${files_cleaned:-0}" "${total_size_cleaned:-0}"
show_cursor show_cursor
exit "${1:-0}" exit "${1:-0}"
} }
@@ -381,6 +383,10 @@ cleanup() {
trap cleanup EXIT INT TERM trap cleanup EXIT INT TERM
main() { main() {
# Set current command for operation logging
export MOLE_CURRENT_COMMAND="uninstall"
log_operation_session_start "uninstall"
local force_rescan=false local force_rescan=false
# Global flags # Global flags
for arg in "$@"; do for arg in "$@"; do

View File

@@ -197,6 +197,18 @@ safe_remove() {
debug_log "Removing: $path" debug_log "Removing: $path"
# Calculate size before deletion for logging
local size_kb=0
local size_human=""
if oplog_enabled; then
if [[ -e "$path" ]]; then
size_kb=$(get_path_size_kb "$path" 2>/dev/null || echo "0")
if [[ "$size_kb" =~ ^[0-9]+$ ]] && [[ "$size_kb" -gt 0 ]]; then
size_human=$(bytes_to_human "$((size_kb * 1024))" 2>/dev/null || echo "${size_kb}KB")
fi
fi
fi
# Perform the deletion # Perform the deletion
# Use || to capture the exit code so set -e won't abort on rm failures # Use || to capture the exit code so set -e won't abort on rm failures
local error_msg local error_msg
@@ -204,6 +216,8 @@ safe_remove() {
error_msg=$(rm -rf "$path" 2>&1) || rm_exit=$? # safe_remove error_msg=$(rm -rf "$path" 2>&1) || rm_exit=$? # safe_remove
if [[ $rm_exit -eq 0 ]]; then if [[ $rm_exit -eq 0 ]]; then
# Log successful removal
log_operation "${MOLE_CURRENT_COMMAND:-clean}" "REMOVED" "$path" "$size_human"
return 0 return 0
else else
# Check if it's a permission error # Check if it's a permission error
@@ -212,8 +226,10 @@ safe_remove() {
MOLE_PERMISSION_DENIED_COUNT=$((MOLE_PERMISSION_DENIED_COUNT + 1)) MOLE_PERMISSION_DENIED_COUNT=$((MOLE_PERMISSION_DENIED_COUNT + 1))
export MOLE_PERMISSION_DENIED_COUNT export MOLE_PERMISSION_DENIED_COUNT
debug_log "Permission denied: $path, may need Full Disk Access" debug_log "Permission denied: $path, may need Full Disk Access"
log_operation "${MOLE_CURRENT_COMMAND:-clean}" "FAILED" "$path" "permission denied"
else else
[[ "$silent" != "true" ]] && log_error "Failed to remove: $path" [[ "$silent" != "true" ]] && log_error "Failed to remove: $path"
log_operation "${MOLE_CURRENT_COMMAND:-clean}" "FAILED" "$path" "error"
fi fi
return 1 return 1
fi fi
@@ -276,11 +292,25 @@ safe_sudo_remove() {
debug_log "Removing, sudo: $path" debug_log "Removing, sudo: $path"
# Calculate size before deletion for logging
local size_kb=0
local size_human=""
if oplog_enabled; then
if sudo test -e "$path" 2>/dev/null; then
size_kb=$(sudo du -sk "$path" 2>/dev/null | awk '{print $1}' || echo "0")
if [[ "$size_kb" =~ ^[0-9]+$ ]] && [[ "$size_kb" -gt 0 ]]; then
size_human=$(bytes_to_human "$((size_kb * 1024))" 2>/dev/null || echo "${size_kb}KB")
fi
fi
fi
# Perform the deletion # Perform the deletion
if sudo rm -rf "$path" 2>/dev/null; then # SAFE: safe_sudo_remove implementation if sudo rm -rf "$path" 2>/dev/null; then # SAFE: safe_sudo_remove implementation
log_operation "${MOLE_CURRENT_COMMAND:-clean}" "REMOVED" "$path" "$size_human"
return 0 return 0
else else
log_error "Failed to remove, sudo: $path" log_error "Failed to remove, sudo: $path"
log_operation "${MOLE_CURRENT_COMMAND:-clean}" "FAILED" "$path" "sudo error"
return 1 return 1
fi fi
} }

View File

@@ -23,10 +23,15 @@ fi
readonly LOG_FILE="${HOME}/.config/mole/mole.log" readonly LOG_FILE="${HOME}/.config/mole/mole.log"
readonly DEBUG_LOG_FILE="${HOME}/.config/mole/mole_debug_session.log" readonly DEBUG_LOG_FILE="${HOME}/.config/mole/mole_debug_session.log"
readonly OPERATIONS_LOG_FILE="${HOME}/.config/mole/operations.log"
readonly LOG_MAX_SIZE_DEFAULT=1048576 # 1MB readonly LOG_MAX_SIZE_DEFAULT=1048576 # 1MB
readonly OPLOG_MAX_SIZE_DEFAULT=5242880 # 5MB
# Ensure log directory and file exist with correct ownership # Ensure log directory and file exist with correct ownership
ensure_user_file "$LOG_FILE" ensure_user_file "$LOG_FILE"
if [[ "${MO_NO_OPLOG:-}" != "1" ]]; then
ensure_user_file "$OPERATIONS_LOG_FILE"
fi
# ============================================================================ # ============================================================================
# Log Rotation # Log Rotation
@@ -43,6 +48,15 @@ rotate_log_once() {
mv "$LOG_FILE" "${LOG_FILE}.old" 2>/dev/null || true mv "$LOG_FILE" "${LOG_FILE}.old" 2>/dev/null || true
ensure_user_file "$LOG_FILE" ensure_user_file "$LOG_FILE"
fi fi
# Rotate operations log (5MB limit)
if [[ "${MO_NO_OPLOG:-}" != "1" ]]; then
local oplog_max_size="$OPLOG_MAX_SIZE_DEFAULT"
if [[ -f "$OPERATIONS_LOG_FILE" ]] && [[ $(get_file_size "$OPERATIONS_LOG_FILE") -gt "$oplog_max_size" ]]; then
mv "$OPERATIONS_LOG_FILE" "${OPERATIONS_LOG_FILE}.old" 2>/dev/null || true
ensure_user_file "$OPERATIONS_LOG_FILE"
fi
fi
} }
# ============================================================================ # ============================================================================
@@ -97,6 +111,80 @@ debug_log() {
fi fi
} }
# ============================================================================
# Operation Logging (Enabled by default)
# ============================================================================
# Records all file operations for user troubleshooting
# Disable with MO_NO_OPLOG=1
oplog_enabled() {
[[ "${MO_NO_OPLOG:-}" != "1" ]]
}
# Log an operation to the operations log file
# Usage: log_operation <command> <action> <path> [detail]
# Example: log_operation "clean" "REMOVED" "/path/to/file" "15.2MB"
# Example: log_operation "clean" "SKIPPED" "/path/to/file" "whitelist"
# Example: log_operation "uninstall" "REMOVED" "/Applications/App.app" "150MB"
log_operation() {
# Allow disabling via environment variable
oplog_enabled || return 0
local command="${1:-unknown}" # clean/uninstall/optimize/purge
local action="${2:-UNKNOWN}" # REMOVED/SKIPPED/FAILED/REBUILT
local path="${3:-}"
local detail="${4:-}"
# Skip if no path provided
[[ -z "$path" ]] && return 0
local timestamp
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
local log_line="[$timestamp] [$command] $action $path"
[[ -n "$detail" ]] && log_line+=" ($detail)"
echo "$log_line" >>"$OPERATIONS_LOG_FILE" 2>/dev/null || true
}
# Log session start marker
# Usage: log_operation_session_start <command>
log_operation_session_start() {
oplog_enabled || return 0
local command="${1:-mole}"
local timestamp
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
{
echo ""
echo "# ========== $command session started at $timestamp =========="
} >>"$OPERATIONS_LOG_FILE" 2>/dev/null || true
}
# Log session end with summary
# Usage: log_operation_session_end <command> <items_count> <total_size>
log_operation_session_end() {
oplog_enabled || return 0
local command="${1:-mole}"
local items="${2:-0}"
local size="${3:-0}"
local timestamp
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
local size_human=""
if [[ "$size" =~ ^[0-9]+$ ]] && [[ "$size" -gt 0 ]]; then
size_human=$(bytes_to_human "$((size * 1024))" 2>/dev/null || echo "${size}KB")
else
size_human="0B"
fi
{
echo "# ========== $command session ended at $timestamp, $items items, $size_human =========="
} >>"$OPERATIONS_LOG_FILE" 2>/dev/null || true
}
# Enhanced debug logging for operations # Enhanced debug logging for operations
debug_operation_start() { debug_operation_start() {
local operation_name="$1" local operation_name="$1"