mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 13:16:47 +00:00
feat: optimize log system and add mo log command
- Add get_timestamp() helper and optimize log rotation - Create mo log viewer with search/filter capabilities - Improve test coverage to 18.4% with better assertions - Add security fixes for grep injection prevention
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -69,5 +69,10 @@ tests/tmp-*/
|
|||||||
tests/*.tmp
|
tests/*.tmp
|
||||||
tests/*.log
|
tests/*.log
|
||||||
|
|
||||||
|
# Go test coverage files
|
||||||
|
*.out
|
||||||
|
coverage.out
|
||||||
|
coverage.html
|
||||||
|
|
||||||
session.json
|
session.json
|
||||||
run_tests.ps1
|
run_tests.ps1
|
||||||
|
|||||||
224
bin/log.sh
Executable file
224
bin/log.sh
Executable file
@@ -0,0 +1,224 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Mole - Operations Log Viewer
|
||||||
|
# Query and analyze operation logs
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
LIB_DIR="${SCRIPT_DIR}/../lib"
|
||||||
|
|
||||||
|
# shellcheck source=lib/core/base.sh
|
||||||
|
source "$LIB_DIR/core/base.sh"
|
||||||
|
# shellcheck source=lib/core/log.sh
|
||||||
|
source "$LIB_DIR/core/log.sh"
|
||||||
|
|
||||||
|
show_help() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: mo log [OPTIONS]
|
||||||
|
|
||||||
|
View and analyze Mole operation logs.
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
--tail <n> Show last N entries (default: 50)
|
||||||
|
--search <term> Search for specific pattern
|
||||||
|
--stats Show operation statistics
|
||||||
|
--today Show only today's operations
|
||||||
|
--command <cmd> Filter by command (clean/uninstall/optimize/purge)
|
||||||
|
--help Show this help message
|
||||||
|
|
||||||
|
EXAMPLES:
|
||||||
|
mo log # Show last 50 operations
|
||||||
|
mo log --tail 100 # Show last 100 operations
|
||||||
|
mo log --search node_modules # Search for node_modules operations
|
||||||
|
mo log --stats # Show statistics
|
||||||
|
mo log --today # Show today's operations only
|
||||||
|
mo log --command clean # Show only clean operations
|
||||||
|
|
||||||
|
LOG LOCATION:
|
||||||
|
${OPERATIONS_LOG_FILE}
|
||||||
|
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
show_tail() {
|
||||||
|
local count="${1:-50}"
|
||||||
|
|
||||||
|
if [[ ! -f "$OPERATIONS_LOG_FILE" ]]; then
|
||||||
|
echo -e "${YELLOW}No operation log found.${NC}"
|
||||||
|
echo "Run some commands (e.g., mo clean) to generate logs."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${BLUE}Last ${count} operations:${NC}"
|
||||||
|
echo "────────────────────────────────────────────────────────────────"
|
||||||
|
tail -n "$count" "$OPERATIONS_LOG_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
search_log() {
|
||||||
|
local term="$1"
|
||||||
|
|
||||||
|
if [[ -z "$term" ]]; then
|
||||||
|
echo -e "${RED}Error: Search term required${NC}"
|
||||||
|
echo "Usage: mo log --search <term>"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "$OPERATIONS_LOG_FILE" ]]; then
|
||||||
|
echo -e "${YELLOW}No operation log found.${NC}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${BLUE}Searching for: ${term}${NC}"
|
||||||
|
echo "────────────────────────────────────────────────────────────────"
|
||||||
|
|
||||||
|
local results
|
||||||
|
results=$(grep -iF -- "$term" "$OPERATIONS_LOG_FILE" 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [[ -z "$results" ]]; then
|
||||||
|
echo -e "${YELLOW}No matches found.${NC}"
|
||||||
|
else
|
||||||
|
echo "$results"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
show_stats() {
|
||||||
|
if [[ ! -f "$OPERATIONS_LOG_FILE" ]]; then
|
||||||
|
echo -e "${YELLOW}No operation log found.${NC}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${BLUE}Operation Statistics${NC}"
|
||||||
|
echo "────────────────────────────────────────────────────────────────"
|
||||||
|
|
||||||
|
local total_lines
|
||||||
|
total_lines=$(grep -c '^\[' "$OPERATIONS_LOG_FILE" 2>/dev/null || echo 0)
|
||||||
|
echo -e "${GREEN}Total operations:${NC} $total_lines"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo -e "${GREEN}By command:${NC}"
|
||||||
|
grep -o '\[clean\]\|\[uninstall\]\|\[optimize\]\|\[purge\]' "$OPERATIONS_LOG_FILE" 2>/dev/null |
|
||||||
|
sort | uniq -c | sort -rn | sed 's/\[//g; s/\]//g' |
|
||||||
|
awk '{printf " %-15s %s\n", $2":", $1}' || echo " No command data"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo -e "${GREEN}By action:${NC}"
|
||||||
|
grep -o 'REMOVED\|SKIPPED\|FAILED\|REBUILT' "$OPERATIONS_LOG_FILE" 2>/dev/null |
|
||||||
|
sort | uniq -c | sort -rn |
|
||||||
|
awk '{printf " %-15s %s\n", $2":", $1}' || echo " No action data"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo -e "${GREEN}Recent sessions:${NC}"
|
||||||
|
grep 'session started' "$OPERATIONS_LOG_FILE" 2>/dev/null | tail -n 5 || echo " No session data"
|
||||||
|
}
|
||||||
|
|
||||||
|
show_today() {
|
||||||
|
if [[ ! -f "$OPERATIONS_LOG_FILE" ]]; then
|
||||||
|
echo -e "${YELLOW}No operation log found.${NC}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local today
|
||||||
|
today=$(date '+%Y-%m-%d')
|
||||||
|
|
||||||
|
echo -e "${BLUE}Today's operations (${today}):${NC}"
|
||||||
|
echo "────────────────────────────────────────────────────────────────"
|
||||||
|
|
||||||
|
local results
|
||||||
|
results=$(grep "^\[$today" "$OPERATIONS_LOG_FILE" 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [[ -z "$results" ]]; then
|
||||||
|
echo -e "${YELLOW}No operations today.${NC}"
|
||||||
|
else
|
||||||
|
echo "$results"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
filter_by_command() {
|
||||||
|
local cmd="$1"
|
||||||
|
|
||||||
|
if [[ -z "$cmd" ]]; then
|
||||||
|
echo -e "${RED}Error: Command name required${NC}"
|
||||||
|
echo "Usage: mo log --command <name>"
|
||||||
|
echo "Available commands: clean, uninstall, optimize, purge"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "$OPERATIONS_LOG_FILE" ]]; then
|
||||||
|
echo -e "${YELLOW}No operation log found.${NC}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${BLUE}Operations for command: ${cmd}${NC}"
|
||||||
|
echo "────────────────────────────────────────────────────────────────"
|
||||||
|
|
||||||
|
local results
|
||||||
|
results=$(grep -F -- "[$cmd]" "$OPERATIONS_LOG_FILE" 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [[ -z "$results" ]]; then
|
||||||
|
echo -e "${YELLOW}No operations found for ${cmd}.${NC}"
|
||||||
|
else
|
||||||
|
echo "$results"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
if [[ "${MO_NO_OPLOG:-}" == "1" ]]; then
|
||||||
|
echo -e "${YELLOW}Operation logging is disabled (MO_NO_OPLOG=1).${NC}"
|
||||||
|
echo "Enable it by unsetting the MO_NO_OPLOG environment variable."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $# -eq 0 ]]; then
|
||||||
|
show_tail 50
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--tail)
|
||||||
|
shift
|
||||||
|
show_tail "${1:-50}"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--search)
|
||||||
|
shift
|
||||||
|
if [[ -z "${1:-}" ]]; then
|
||||||
|
echo -e "${RED}Error: --search requires an argument${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
search_log "$1"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--stats)
|
||||||
|
show_stats
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--today)
|
||||||
|
show_today
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--command)
|
||||||
|
shift
|
||||||
|
if [[ -z "${1:-}" ]]; then
|
||||||
|
echo -e "${RED}Error: --command requires an argument${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
filter_by_command "$1"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--help | -h)
|
||||||
|
show_help
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo -e "${RED}Unknown option: $1${NC}"
|
||||||
|
echo "Use 'mo log --help' for usage information."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
# shellcheck disable=SC2317
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
@@ -39,6 +39,95 @@ func TestFormatRate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestColorizePercent(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
percent float64
|
||||||
|
input string
|
||||||
|
expectDanger bool
|
||||||
|
expectWarn bool
|
||||||
|
expectOk bool
|
||||||
|
}{
|
||||||
|
{"low usage", 30.0, "30%", false, false, true},
|
||||||
|
{"just below warn", 59.9, "59.9%", false, false, true},
|
||||||
|
{"at warn threshold", 60.0, "60%", false, true, false},
|
||||||
|
{"mid range", 70.0, "70%", false, true, false},
|
||||||
|
{"just below danger", 84.9, "84.9%", false, true, false},
|
||||||
|
{"at danger threshold", 85.0, "85%", true, false, false},
|
||||||
|
{"high usage", 95.0, "95%", true, false, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := colorizePercent(tt.percent, tt.input)
|
||||||
|
|
||||||
|
if got == "" {
|
||||||
|
t.Errorf("colorizePercent(%v, %q) returned empty string", tt.percent, tt.input)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := ""
|
||||||
|
if tt.expectDanger {
|
||||||
|
expected = dangerStyle.Render(tt.input)
|
||||||
|
} else if tt.expectWarn {
|
||||||
|
expected = warnStyle.Render(tt.input)
|
||||||
|
} else if tt.expectOk {
|
||||||
|
expected = okStyle.Render(tt.input)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got != expected {
|
||||||
|
t.Errorf("colorizePercent(%v, %q) = %q, want %q (danger=%v warn=%v ok=%v)",
|
||||||
|
tt.percent, tt.input, got, expected, tt.expectDanger, tt.expectWarn, tt.expectOk)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestColorizeBattery(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
percent float64
|
||||||
|
input string
|
||||||
|
expectDanger bool
|
||||||
|
expectWarn bool
|
||||||
|
expectOk bool
|
||||||
|
}{
|
||||||
|
{"critical low", 10.0, "10%", true, false, false},
|
||||||
|
{"just below low", 19.9, "19.9%", true, false, false},
|
||||||
|
{"at low threshold", 20.0, "20%", false, true, false},
|
||||||
|
{"mid range", 35.0, "35%", false, true, false},
|
||||||
|
{"just below ok", 49.9, "49.9%", false, true, false},
|
||||||
|
{"at ok threshold", 50.0, "50%", false, false, true},
|
||||||
|
{"healthy", 80.0, "80%", false, false, true},
|
||||||
|
{"full", 100.0, "100%", false, false, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := colorizeBattery(tt.percent, tt.input)
|
||||||
|
|
||||||
|
if got == "" {
|
||||||
|
t.Errorf("colorizeBattery(%v, %q) returned empty string", tt.percent, tt.input)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := ""
|
||||||
|
if tt.expectDanger {
|
||||||
|
expected = dangerStyle.Render(tt.input)
|
||||||
|
} else if tt.expectWarn {
|
||||||
|
expected = warnStyle.Render(tt.input)
|
||||||
|
} else if tt.expectOk {
|
||||||
|
expected = okStyle.Render(tt.input)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got != expected {
|
||||||
|
t.Errorf("colorizeBattery(%v, %q) = %q, want %q (danger=%v warn=%v ok=%v)",
|
||||||
|
tt.percent, tt.input, got, expected, tt.expectDanger, tt.expectWarn, tt.expectOk)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestShorten(t *testing.T) {
|
func TestShorten(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ MOLE_COMMANDS=(
|
|||||||
"status:Monitor system health"
|
"status:Monitor system health"
|
||||||
"purge:Remove old project artifacts"
|
"purge:Remove old project artifacts"
|
||||||
"installer:Find and remove installer files"
|
"installer:Find and remove installer files"
|
||||||
|
"log:View operation logs"
|
||||||
"touchid:Configure Touch ID for sudo"
|
"touchid:Configure Touch ID for sudo"
|
||||||
"completion:Setup shell tab completion"
|
"completion:Setup shell tab completion"
|
||||||
"update:Update to latest version"
|
"update:Update to latest version"
|
||||||
|
|||||||
@@ -44,70 +44,89 @@ rotate_log_once() {
|
|||||||
export MOLE_LOG_ROTATED=1
|
export MOLE_LOG_ROTATED=1
|
||||||
|
|
||||||
local max_size="$LOG_MAX_SIZE_DEFAULT"
|
local max_size="$LOG_MAX_SIZE_DEFAULT"
|
||||||
if [[ -f "$LOG_FILE" ]] && [[ $(get_file_size "$LOG_FILE") -gt "$max_size" ]]; then
|
if [[ -f "$LOG_FILE" ]]; then
|
||||||
mv "$LOG_FILE" "${LOG_FILE}.old" 2> /dev/null || true
|
local size
|
||||||
|
size=$(get_file_size "$LOG_FILE")
|
||||||
|
if [[ "$size" -gt "$max_size" ]]; then
|
||||||
|
mv "$LOG_FILE" "${LOG_FILE}.old" 2>/dev/null || true
|
||||||
ensure_user_file "$LOG_FILE"
|
ensure_user_file "$LOG_FILE"
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Rotate operations log (5MB limit)
|
# Rotate operations log (5MB limit)
|
||||||
if [[ "${MO_NO_OPLOG:-}" != "1" ]]; then
|
if [[ "${MO_NO_OPLOG:-}" != "1" ]]; then
|
||||||
local oplog_max_size="$OPLOG_MAX_SIZE_DEFAULT"
|
local oplog_max_size="$OPLOG_MAX_SIZE_DEFAULT"
|
||||||
if [[ -f "$OPERATIONS_LOG_FILE" ]] && [[ $(get_file_size "$OPERATIONS_LOG_FILE") -gt "$oplog_max_size" ]]; then
|
if [[ -f "$OPERATIONS_LOG_FILE" ]]; then
|
||||||
mv "$OPERATIONS_LOG_FILE" "${OPERATIONS_LOG_FILE}.old" 2> /dev/null || true
|
local size
|
||||||
|
size=$(get_file_size "$OPERATIONS_LOG_FILE")
|
||||||
|
if [[ "$size" -gt "$oplog_max_size" ]]; then
|
||||||
|
mv "$OPERATIONS_LOG_FILE" "${OPERATIONS_LOG_FILE}.old" 2>/dev/null || true
|
||||||
ensure_user_file "$OPERATIONS_LOG_FILE"
|
ensure_user_file "$OPERATIONS_LOG_FILE"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Logging Functions
|
# Logging Functions
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
|
# Get current timestamp (centralized for consistency)
|
||||||
|
get_timestamp() {
|
||||||
|
date '+%Y-%m-%d %H:%M:%S'
|
||||||
|
}
|
||||||
|
|
||||||
# Log informational message
|
# Log informational message
|
||||||
log_info() {
|
log_info() {
|
||||||
echo -e "${BLUE}$1${NC}"
|
echo -e "${BLUE}$1${NC}"
|
||||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
local timestamp
|
||||||
echo "[$timestamp] INFO: $1" >> "$LOG_FILE" 2> /dev/null || true
|
timestamp=$(get_timestamp)
|
||||||
|
echo "[$timestamp] INFO: $1" >>"$LOG_FILE" 2>/dev/null || true
|
||||||
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
||||||
echo "[$timestamp] INFO: $1" >> "$DEBUG_LOG_FILE" 2> /dev/null || true
|
echo "[$timestamp] INFO: $1" >>"$DEBUG_LOG_FILE" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Log success message
|
# Log success message
|
||||||
log_success() {
|
log_success() {
|
||||||
echo -e " ${GREEN}${ICON_SUCCESS}${NC} $1"
|
echo -e " ${GREEN}${ICON_SUCCESS}${NC} $1"
|
||||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
local timestamp
|
||||||
echo "[$timestamp] SUCCESS: $1" >> "$LOG_FILE" 2> /dev/null || true
|
timestamp=$(get_timestamp)
|
||||||
|
echo "[$timestamp] SUCCESS: $1" >>"$LOG_FILE" 2>/dev/null || true
|
||||||
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
||||||
echo "[$timestamp] SUCCESS: $1" >> "$DEBUG_LOG_FILE" 2> /dev/null || true
|
echo "[$timestamp] SUCCESS: $1" >>"$DEBUG_LOG_FILE" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Log warning message
|
# shellcheck disable=SC2329
|
||||||
log_warning() {
|
log_warning() {
|
||||||
echo -e "${YELLOW}$1${NC}"
|
echo -e "${YELLOW}$1${NC}"
|
||||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
local timestamp
|
||||||
echo "[$timestamp] WARNING: $1" >> "$LOG_FILE" 2> /dev/null || true
|
timestamp=$(get_timestamp)
|
||||||
|
echo "[$timestamp] WARNING: $1" >>"$LOG_FILE" 2>/dev/null || true
|
||||||
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
||||||
echo "[$timestamp] WARNING: $1" >> "$DEBUG_LOG_FILE" 2> /dev/null || true
|
echo "[$timestamp] WARNING: $1" >>"$DEBUG_LOG_FILE" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Log error message
|
# shellcheck disable=SC2329
|
||||||
log_error() {
|
log_error() {
|
||||||
echo -e "${YELLOW}${ICON_ERROR}${NC} $1" >&2
|
echo -e "${YELLOW}${ICON_ERROR}${NC} $1" >&2
|
||||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
local timestamp
|
||||||
echo "[$timestamp] ERROR: $1" >> "$LOG_FILE" 2> /dev/null || true
|
timestamp=$(get_timestamp)
|
||||||
|
echo "[$timestamp] ERROR: $1" >>"$LOG_FILE" 2>/dev/null || true
|
||||||
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
||||||
echo "[$timestamp] ERROR: $1" >> "$DEBUG_LOG_FILE" 2> /dev/null || true
|
echo "[$timestamp] ERROR: $1" >>"$DEBUG_LOG_FILE" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Debug logging (active when MO_DEBUG=1)
|
# shellcheck disable=SC2329
|
||||||
debug_log() {
|
debug_log() {
|
||||||
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
||||||
echo -e "${GRAY}[DEBUG]${NC} $*" >&2
|
echo -e "${GRAY}[DEBUG]${NC} $*" >&2
|
||||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] DEBUG: $*" >> "$DEBUG_LOG_FILE" 2> /dev/null || true
|
local timestamp
|
||||||
|
timestamp=$(get_timestamp)
|
||||||
|
echo "[$timestamp] DEBUG: $*" >>"$DEBUG_LOG_FILE" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,12 +158,12 @@ log_operation() {
|
|||||||
[[ -z "$path" ]] && return 0
|
[[ -z "$path" ]] && return 0
|
||||||
|
|
||||||
local timestamp
|
local timestamp
|
||||||
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
timestamp=$(get_timestamp)
|
||||||
|
|
||||||
local log_line="[$timestamp] [$command] $action $path"
|
local log_line="[$timestamp] [$command] $action $path"
|
||||||
[[ -n "$detail" ]] && log_line+=" ($detail)"
|
[[ -n "$detail" ]] && log_line+=" ($detail)"
|
||||||
|
|
||||||
echo "$log_line" >> "$OPERATIONS_LOG_FILE" 2> /dev/null || true
|
echo "$log_line" >>"$OPERATIONS_LOG_FILE" 2>/dev/null || true
|
||||||
}
|
}
|
||||||
|
|
||||||
# Log session start marker
|
# Log session start marker
|
||||||
@@ -154,16 +173,15 @@ log_operation_session_start() {
|
|||||||
|
|
||||||
local command="${1:-mole}"
|
local command="${1:-mole}"
|
||||||
local timestamp
|
local timestamp
|
||||||
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
timestamp=$(get_timestamp)
|
||||||
|
|
||||||
{
|
{
|
||||||
echo ""
|
echo ""
|
||||||
echo "# ========== $command session started at $timestamp =========="
|
echo "# ========== $command session started at $timestamp =========="
|
||||||
} >> "$OPERATIONS_LOG_FILE" 2> /dev/null || true
|
} >>"$OPERATIONS_LOG_FILE" 2>/dev/null || true
|
||||||
}
|
}
|
||||||
|
|
||||||
# Log session end with summary
|
# shellcheck disable=SC2329
|
||||||
# Usage: log_operation_session_end <command> <items_count> <total_size>
|
|
||||||
log_operation_session_end() {
|
log_operation_session_end() {
|
||||||
oplog_enabled || return 0
|
oplog_enabled || return 0
|
||||||
|
|
||||||
@@ -171,18 +189,18 @@ log_operation_session_end() {
|
|||||||
local items="${2:-0}"
|
local items="${2:-0}"
|
||||||
local size="${3:-0}"
|
local size="${3:-0}"
|
||||||
local timestamp
|
local timestamp
|
||||||
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
timestamp=$(get_timestamp)
|
||||||
|
|
||||||
local size_human=""
|
local size_human=""
|
||||||
if [[ "$size" =~ ^[0-9]+$ ]] && [[ "$size" -gt 0 ]]; then
|
if [[ "$size" =~ ^[0-9]+$ ]] && [[ "$size" -gt 0 ]]; then
|
||||||
size_human=$(bytes_to_human "$((size * 1024))" 2> /dev/null || echo "${size}KB")
|
size_human=$(bytes_to_human "$((size * 1024))" 2>/dev/null || echo "${size}KB")
|
||||||
else
|
else
|
||||||
size_human="0B"
|
size_human="0B"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
{
|
{
|
||||||
echo "# ========== $command session ended at $timestamp, $items items, $size_human =========="
|
echo "# ========== $command session ended at $timestamp, $items items, $size_human =========="
|
||||||
} >> "$OPERATIONS_LOG_FILE" 2> /dev/null || true
|
} >>"$OPERATIONS_LOG_FILE" 2>/dev/null || true
|
||||||
}
|
}
|
||||||
|
|
||||||
# Enhanced debug logging for operations
|
# Enhanced debug logging for operations
|
||||||
@@ -200,7 +218,7 @@ debug_operation_start() {
|
|||||||
echo ""
|
echo ""
|
||||||
echo "=== $operation_name ==="
|
echo "=== $operation_name ==="
|
||||||
[[ -n "$operation_desc" ]] && echo "Description: $operation_desc"
|
[[ -n "$operation_desc" ]] && echo "Description: $operation_desc"
|
||||||
} >> "$DEBUG_LOG_FILE" 2> /dev/null || true
|
} >>"$DEBUG_LOG_FILE" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,7 +232,7 @@ debug_operation_detail() {
|
|||||||
echo -e "${GRAY}[DEBUG] $detail_type: $detail_value${NC}" >&2
|
echo -e "${GRAY}[DEBUG] $detail_type: $detail_value${NC}" >&2
|
||||||
|
|
||||||
# Also log to file
|
# Also log to file
|
||||||
echo "$detail_type: $detail_value" >> "$DEBUG_LOG_FILE" 2> /dev/null || true
|
echo "$detail_type: $detail_value" >>"$DEBUG_LOG_FILE" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,7 +252,7 @@ debug_file_action() {
|
|||||||
echo -e "${GRAY}[DEBUG] $action: $msg${NC}" >&2
|
echo -e "${GRAY}[DEBUG] $action: $msg${NC}" >&2
|
||||||
|
|
||||||
# Also log to file
|
# Also log to file
|
||||||
echo "$action: $msg" >> "$DEBUG_LOG_FILE" 2> /dev/null || true
|
echo "$action: $msg" >>"$DEBUG_LOG_FILE" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,7 +273,7 @@ debug_risk_level() {
|
|||||||
echo -e "${GRAY}[DEBUG] Risk Level: ${color}${risk_level}${GRAY}, $reason${NC}" >&2
|
echo -e "${GRAY}[DEBUG] Risk Level: ${color}${risk_level}${GRAY}, $reason${NC}" >&2
|
||||||
|
|
||||||
# Also log to file
|
# Also log to file
|
||||||
echo "Risk Level: $risk_level, $reason" >> "$DEBUG_LOG_FILE" 2> /dev/null || true
|
echo "Risk Level: $risk_level, $reason" >>"$DEBUG_LOG_FILE" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,7 +285,7 @@ log_system_info() {
|
|||||||
|
|
||||||
# Reset debug log file for this new session
|
# Reset debug log file for this new session
|
||||||
ensure_user_file "$DEBUG_LOG_FILE"
|
ensure_user_file "$DEBUG_LOG_FILE"
|
||||||
if ! : > "$DEBUG_LOG_FILE" 2> /dev/null; then
|
if ! : >"$DEBUG_LOG_FILE" 2>/dev/null; then
|
||||||
echo -e "${YELLOW}${ICON_WARNING}${NC} Debug log not writable: $DEBUG_LOG_FILE" >&2
|
echo -e "${YELLOW}${ICON_WARNING}${NC} Debug log not writable: $DEBUG_LOG_FILE" >&2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -280,19 +298,19 @@ log_system_info() {
|
|||||||
echo "Hostname: $(hostname)"
|
echo "Hostname: $(hostname)"
|
||||||
echo "Architecture: $(uname -m)"
|
echo "Architecture: $(uname -m)"
|
||||||
echo "Kernel: $(uname -r)"
|
echo "Kernel: $(uname -r)"
|
||||||
if command -v sw_vers > /dev/null; then
|
if command -v sw_vers >/dev/null; then
|
||||||
echo "macOS: $(sw_vers -productVersion), $(sw_vers -buildVersion)"
|
echo "macOS: $(sw_vers -productVersion), $(sw_vers -buildVersion)"
|
||||||
fi
|
fi
|
||||||
echo "Shell: ${SHELL:-unknown}, ${TERM:-unknown}"
|
echo "Shell: ${SHELL:-unknown}, ${TERM:-unknown}"
|
||||||
|
|
||||||
# Check sudo status non-interactively
|
# Check sudo status non-interactively
|
||||||
if sudo -n true 2> /dev/null; then
|
if sudo -n true 2>/dev/null; then
|
||||||
echo "Sudo Access: Active"
|
echo "Sudo Access: Active"
|
||||||
else
|
else
|
||||||
echo "Sudo Access: Required"
|
echo "Sudo Access: Required"
|
||||||
fi
|
fi
|
||||||
echo "----------------------------------------------------------------------"
|
echo "----------------------------------------------------------------------"
|
||||||
} >> "$DEBUG_LOG_FILE" 2> /dev/null || true
|
} >>"$DEBUG_LOG_FILE" 2>/dev/null || true
|
||||||
|
|
||||||
# Notification to stderr
|
# Notification to stderr
|
||||||
echo -e "${GRAY}[DEBUG] Debug logging enabled. Session log: $DEBUG_LOG_FILE${NC}" >&2
|
echo -e "${GRAY}[DEBUG] Debug logging enabled. Session log: $DEBUG_LOG_FILE${NC}" >&2
|
||||||
@@ -304,7 +322,7 @@ log_system_info() {
|
|||||||
|
|
||||||
# Run command silently (ignore errors)
|
# Run command silently (ignore errors)
|
||||||
run_silent() {
|
run_silent() {
|
||||||
"$@" > /dev/null 2>&1 || true
|
"$@" >/dev/null 2>&1 || true
|
||||||
}
|
}
|
||||||
|
|
||||||
# Run command with error logging
|
# Run command with error logging
|
||||||
@@ -312,12 +330,12 @@ run_logged() {
|
|||||||
local cmd="$1"
|
local cmd="$1"
|
||||||
# Log to main file, and also to debug file if enabled
|
# Log to main file, and also to debug file if enabled
|
||||||
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
||||||
if ! "$@" 2>&1 | tee -a "$LOG_FILE" | tee -a "$DEBUG_LOG_FILE" > /dev/null; then
|
if ! "$@" 2>&1 | tee -a "$LOG_FILE" | tee -a "$DEBUG_LOG_FILE" >/dev/null; then
|
||||||
log_warning "Command failed: $cmd"
|
log_warning "Command failed: $cmd"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
if ! "$@" 2>&1 | tee -a "$LOG_FILE" > /dev/null; then
|
if ! "$@" 2>&1 | tee -a "$LOG_FILE" >/dev/null; then
|
||||||
log_warning "Command failed: $cmd"
|
log_warning "Command failed: $cmd"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
3
mole
3
mole
@@ -777,6 +777,9 @@ main() {
|
|||||||
"installer")
|
"installer")
|
||||||
exec "$SCRIPT_DIR/bin/installer.sh" "${args[@]:1}"
|
exec "$SCRIPT_DIR/bin/installer.sh" "${args[@]:1}"
|
||||||
;;
|
;;
|
||||||
|
"log")
|
||||||
|
exec "$SCRIPT_DIR/bin/log.sh" "${args[@]:1}"
|
||||||
|
;;
|
||||||
"touchid")
|
"touchid")
|
||||||
exec "$SCRIPT_DIR/bin/touchid.sh" "${args[@]:1}"
|
exec "$SCRIPT_DIR/bin/touchid.sh" "${args[@]:1}"
|
||||||
;;
|
;;
|
||||||
|
|||||||
Reference in New Issue
Block a user