Refactor: AI generated enhanced version

This commit is contained in:
Reza Esmaeili
2025-12-04 13:50:26 +03:30
parent 56dab0ea90
commit 3c88f013a4
3 changed files with 491 additions and 86 deletions

View File

@@ -1,129 +1,341 @@
#!/bin/bash
#!/usr/bin/env bash
#
# Advanced ADB Logcat Monitor for Android apps
#
# Author: Reza Esmaeili
# Description:
# Tail logcat for a specific Android app process (by package) with:
# - Device selection (auto or explicit)
# - Configurable filters / logcat args
# - Optional app restart
# - Safe cleanup on exit
# - Log files with timestamps in a dedicated directory
#
# Usage:
# ./adb-log-monitor.sh [options] [LOGCAT_FILTER...]
#
# Examples:
# ./adb-log-monitor.sh
# ./adb-log-monitor.sh -p ir.avaair.avaair -a avaair -f '*:E'
# ./adb-log-monitor.sh -d emulator-5554 --no-restart
#
# Options:
# -p PACKAGE Android package name (default: ir.avaair.avaair)
# -a APP_NAME Logical app name used for messages & filenames (default: derived from package)
# -d DEVICE Device ID (adb serial). If omitted, will prompt if multiple devices.
# -o DIR Logs output directory (default: logs)
# -f FILTER Logcat filter spec (default: *:V) can also pass as positional args
# --no-restart Do NOT auto-restart the app when process dies/startup fails
# --help Show this help and exit
#
# Exit codes:
# 0 Success / user interrupt
# 1 Usage / config error
# 2 adb / device error
# 3 (unused now, kept for compatibility if you want to reintroduce timeouts)
#
################################################
# Advanced ADB Logcat Monitor for android apps #
# Author: Reza Esmaeili #
################################################
set -Euo pipefail
APP_NAME="avaair"
PACKAGE_NAME="ir.avaair" # ← Change if needed
LOG_DIR="logs"
mkdir -p "$LOG_DIR"
########################
# Default configuration
########################
DEFAULT_PACKAGE_NAME="ir.avaair.avaair"
DEFAULT_LOG_DIR="logs"
DEFAULT_FILTER="*:V"
PACKAGE_NAME="${PACKAGE_NAME:-$DEFAULT_PACKAGE_NAME}"
APP_NAME="${APP_NAME:-}"
DEVICE="${DEVICE:-}"
LOG_DIR="${LOG_DIR:-$DEFAULT_LOG_DIR}"
FILTER="${DEFAULT_FILTER}"
AUTO_RESTART=true
########################
# Pretty output helpers
########################
# Colors for nice console output
RED="\033[0;31m"
GREEN="\033[0;32m"
YELLOW="\033[1;33m"
BLUE="\033[0;34m"
NC="\033[0m" # No Color
FILTER="${1:-*:V}" # Default to verbose logs if no argument passed
START_TIME=$(date +%s)
log_info() { echo -e "${BLUE}[INFO]${NC} $*"; }
log_ok() { echo -e "${GREEN}[ OK ]${NC} $*"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
log_error() { echo -e "${RED}[ERR ]${NC} $*" >&2; }
# --- Cleanup on exit ---
cleanup() {
echo -e "\n${YELLOW}🧹 Cleaning up...${NC}"
pkill -f "adb -s $DEVICE logcat --pid" 2>/dev/null
echo -e "${GREEN}✅ Done.${NC}"
exit 0
########################
# Usage
########################
usage() {
sed -n '1,80p' "$0" | sed 's/^# \{0,1\}//'
}
trap cleanup SIGINT SIGTERM
# --- Device selection ---
echo -e "${BLUE}🔍 Checking connected devices...${NC}"
DEVICES=($(adb devices | grep -w "device" | awk '{print $1}'))
########################
# Parse CLI args
########################
if [ ${#DEVICES[@]} -eq 0 ]; then
echo -e "${RED}❌ No devices found. Connect a device or ensure it's authorized.${NC}"
exit 1
ARGS=()
while [[ $# -gt 0 ]]; do
case "$1" in
--no-restart)
AUTO_RESTART=false
shift
;;
--help|-h)
usage
exit 0
;;
-p)
[[ $# -lt 2 ]] && { log_error "-p requires PACKAGE"; exit 1; }
PACKAGE_NAME="$2"
shift 2
;;
-a)
[[ $# -lt 2 ]] && { log_error "-a requires APP_NAME"; exit 1; }
APP_NAME="$2"
shift 2
;;
-d)
[[ $# -lt 2 ]] && { log_error "-d requires DEVICE serial"; exit 1; }
DEVICE="$2"
shift 2
;;
-o)
[[ $# -lt 2 ]] && { log_error "-o requires DIR"; exit 1; }
LOG_DIR="$2"
shift 2
;;
-f)
[[ $# -lt 2 ]] && { log_error "-f requires FILTER"; exit 1; }
FILTER="$2"
shift 2
;;
--)
shift
break
;;
-*)
log_error "Unknown option: $1"
usage
exit 1
;;
*)
ARGS+=("$1")
shift
;;
esac
done
# If user passed extra args as filters, override FILTER
if [[ ${#ARGS[@]} -gt 0 ]]; then
FILTER="${ARGS[*]}"
fi
# Handle unauthorized/offline states
if adb devices | grep -q "unauthorized"; then
echo -e "${RED}⚠️ Device unauthorized. Accept RSA prompt on your device.${NC}"
exit 1
# Derive APP_NAME from PACKAGE_NAME if not set
if [[ -z "${APP_NAME}" ]]; then
APP_NAME="${PACKAGE_NAME##*.}"
fi
if adb devices | grep -q "offline"; then
echo -e "${RED}⚠️ Device offline. Reconnect or restart adb server.${NC}"
exit 1
mkdir -p "${LOG_DIR}"
########################
# Cleanup
########################
LOGCAT_PID=""
cleanup() {
log_warn "Cleaning up..."
if [[ -n "${LOGCAT_PID}" ]]; then
kill "${LOGCAT_PID}" 2>/dev/null || true
fi
log_ok "Done."
}
trap cleanup EXIT
trap 'exit 0' SIGINT SIGTERM
########################
# Preconditions
########################
if ! command -v adb >/dev/null 2>&1; then
log_error "'adb' command not found. Install Android platform-tools and ensure it's in PATH."
exit 2
fi
if [ ${#DEVICES[@]} -gt 1 ]; then
echo -e "${YELLOW}📱 Multiple devices found:${NC}"
i=1
for d in "${DEVICES[@]}"; do
########################
# Device selection
########################
select_device() {
local devices
mapfile -t devices < <(adb devices | awk 'NR>1 && $2=="device" {print $1}')
if adb devices | grep -q "unauthorized"; then
log_error "Device unauthorized. Accept the RSA prompt on your device."
exit 2
fi
if adb devices | grep -q "offline"; then
log_error "Device offline. Reconnect or restart adb server."
exit 2
fi
if [[ ${#devices[@]} -eq 0 ]]; then
log_error "No devices found. Connect a device and ensure it's authorized."
exit 2
fi
if [[ -n "${DEVICE}" ]]; then
if printf '%s\n' "${devices[@]}" | grep -qx "${DEVICE}"; then
log_ok "Using specified device: ${DEVICE}"
return
else
log_error "Specified device '${DEVICE}' is not connected."
exit 2
fi
fi
if [[ ${#devices[@]} -eq 1 ]]; then
DEVICE="${devices[0]}"
log_ok "Using single connected device: ${DEVICE}"
return
fi
log_warn "Multiple devices found:"
local i=1
for d in "${devices[@]}"; do
echo " [$i] $d"
((i++))
done
read -p "👉 Select device number: " choice
DEVICE=${DEVICES[$((choice-1))]}
else
DEVICE=${DEVICES[0]}
echo -e "${GREEN}📱 Using single device:${NC} $DEVICE"
fi
# --- Helper: choose ps command dynamically ---
detect_ps_cmd() {
if adb -s "$DEVICE" shell ps -A >/dev/null 2>&1; then
echo "ps -A"
else
echo "ps"
local choice
read -r -p "Select device number: " choice
if ! [[ "$choice" =~ ^[0-9]+$ ]] || (( choice < 1 || choice > ${#devices[@]} )); then
log_error "Invalid selection."
exit 1
fi
DEVICE="${devices[$((choice-1))]}"
log_ok "Selected device: ${DEVICE}"
}
PS_CMD=$(detect_ps_cmd)
########################
# PID helpers
########################
# --- Get PID for app ---
get_pid() {
adb -s "$DEVICE" shell $PS_CMD | grep "$APP_NAME" | awk '{print $2}' | head -n 1
local pid=""
# Try pidof first
if adb -s "${DEVICE}" shell "command -v pidof >/dev/null 2>&1" >/dev/null 2>&1; then
pid=$(adb -s "${DEVICE}" shell "pidof -s '${PACKAGE_NAME}' 2>/dev/null || true" | tr -d '\r' || true)
fi
# Fallback to ps if pidof gave nothing
if [[ -z "${pid}" ]]; then
if adb -s "${DEVICE}" shell "ps -A >/dev/null 2>&1" >/dev/null 2>&1; then
pid=$(adb -s "${DEVICE}" shell "ps -A | grep '${PACKAGE_NAME}' | awk '{print \$2}' | head -n 1" 2>/dev/null | tr -d '\r' || true)
else
pid=$(adb -s "${DEVICE}" shell "ps | grep '${PACKAGE_NAME}' | awk '{print \$2}' | head -n 1" 2>/dev/null | tr -d '\r' || true)
fi
fi
echo "${pid}"
return 0
}
# --- Start logcat session ---
start_logcat() {
local PID=$1
local LOG_FILE="$LOG_DIR/${APP_NAME}_$(date +'%Y-%m-%d_%H-%M-%S').log"
########################
# App restart helper
########################
echo -e "${GREEN}📜 Logging PID $PID ($APP_NAME) → ${LOG_FILE}${NC}"
echo -e "${BLUE}🔎 Filter:${NC} $FILTER"
echo "----------------------------------------"
adb -s "$DEVICE" logcat --pid=$PID $FILTER | tee -a "$LOG_FILE"
}
# --- Restart app helper (optional) ---
restart_app() {
if [ -n "$PACKAGE_NAME" ]; then
echo -e "${YELLOW}🔁 Restarting app $PACKAGE_NAME...${NC}"
adb -s "$DEVICE" shell am force-stop "$PACKAGE_NAME"
adb -s "$DEVICE" shell monkey -p "$PACKAGE_NAME" 1 >/dev/null 2>&1
if [[ "${AUTO_RESTART}" != true ]]; then
return
fi
log_warn "Restarting app ${PACKAGE_NAME}..."
adb -s "${DEVICE}" shell am force-stop "${PACKAGE_NAME}" >/dev/null 2>&1 || true
adb -s "${DEVICE}" shell monkey -p "${PACKAGE_NAME}" -c android.intent.category.LAUNCHER 1 >/dev/null 2>&1 || true
}
# --- Main loop ---
LAST_PID=""
########################
# Start logcat
########################
while true; do
PID=$(get_pid)
start_logcat() {
local pid="$1"
local log_file="${LOG_DIR}/${APP_NAME}_$(date +'%Y-%m-%d_%H-%M-%S')_pid_${pid}.log"
if [ -z "$PID" ]; then
echo -e "${YELLOW}⚠️ Waiting for process '$APP_NAME' to start...${NC}"
sleep 3
continue
fi
log_ok "Logging PID ${pid} (${PACKAGE_NAME}) -> ${log_file}"
log_info "Device: ${DEVICE}"
log_info "Filter: ${FILTER}"
echo "------------------------------------------------------------"
if [ "$PID" != "$LAST_PID" ]; then
if [ -n "$LAST_PID" ]; then
echo -e "${BLUE}🔁 Process restarted (old: $LAST_PID → new: $PID).${NC}"
(
adb -s "${DEVICE}" logcat --pid="${pid}" ${FILTER} | tee -a "${log_file}"
) &
LOGCAT_PID=$!
}
########################
# Main loop
########################
main() {
select_device
log_info "Monitoring app logs for package: ${PACKAGE_NAME} (name: ${APP_NAME})"
log_info "Log directory: ${LOG_DIR}"
log_info "Auto-restart: ${AUTO_RESTART}"
local last_pid=""
local consecutive_misses=0
while true; do
local pid
pid="$(get_pid)"
if [[ -z "${pid}" ]]; then
((consecutive_misses++))
if (( consecutive_misses == 1 )); then
log_warn "Process '${PACKAGE_NAME}' not running. Waiting for it to start..."
restart_app
else
log_info "Still waiting for process '${PACKAGE_NAME}' to start..."
fi
sleep 3
continue
fi
LAST_PID=$PID
if (( consecutive_misses > 0 )); then
log_ok "Process '${PACKAGE_NAME}' started with PID ${pid}."
fi
consecutive_misses=0
# Stop previous logcat if running
pkill -f "adb -s $DEVICE logcat --pid=$LAST_PID" 2>/dev/null
if [[ "${pid}" != "${last_pid}" ]]; then
if [[ -n "${last_pid}" ]]; then
log_info "Process restarted (old PID: ${last_pid} -> new PID: ${pid})."
fi
# Start new logcat in background
start_logcat "$PID" &
fi
if [[ -n "${LOGCAT_PID}" ]]; then
kill "${LOGCAT_PID}" 2>/dev/null || true
LOGCAT_PID=""
fi
sleep 5
done
last_pid="${pid}"
start_logcat "${pid}"
fi
sleep 5
done
}
main "$@"