From 3c88f013a476eed51bfec977a92a76450ace6e14 Mon Sep 17 00:00:00 2001 From: Reza Esmaeili Date: Thu, 4 Dec 2025 13:50:26 +0330 Subject: [PATCH] Refactor: AI generated enhanced version --- .gitignore | 1 + README.md | 192 +++++++++++++++++++++++++ log_avaair.sh | 384 +++++++++++++++++++++++++++++++++++++++----------- 3 files changed, 491 insertions(+), 86 deletions(-) create mode 100644 .gitignore create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..333c1e9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +logs/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..f7d4ffc --- /dev/null +++ b/README.md @@ -0,0 +1,192 @@ +# ADB Log Monitor (Enhanced) + +A robust, configurable, enterprise-grade logcat watcher for Android development. +Automatically detects devices, tracks app restarts, captures logs into timestamped files, and optionally restarts the target app if its process disappears. + +**Author:** Reza Esmaeili +**Purpose:** Reliable long-running log capture for debugging and QA. + +--- + +## Features + +* πŸ” **Automatic device detection** (handles single/multiple devices) +* πŸ“¦ **Monitors any Android package** via `pidof` or `ps` +* πŸ” **Auto-restart option** for apps that crash or reboot +* πŸ“ **Writes logs to timestamped files** in a dedicated directory +* πŸŽ› **Fully configurable CLI flags** +* 🧼 **Graceful cleanup** on exit (`Ctrl+C`) +* βš™οΈ **Strict scripting style** for reliability +* πŸš€ **Drop-in ready for CI, QA, or development machines** + +--- + +## Requirements + +* Android `adb` (Platform-tools) installed and in PATH +* A connected Android device or emulator authorized for debugging + +--- + +## Installation + +Place the script anywhere in your project, e.g.: + +``` +scripts/adb-log-monitor.sh +``` + +Make it executable: + +```bash +chmod +x adb-log-monitor.sh +``` + +(Optional) Add to PATH: + +```bash +sudo ln -s /path/to/adb-log-monitor.sh /usr/local/bin/adb-log-monitor +``` + +--- + +## Usage + +Basic usage: + +```bash +./adb-log-monitor.sh +``` + +This will: + +* Detect the connected device +* Wait for the app to start +* Start logging when the process becomes available +* Write logs into `logs/` + +--- + +## CLI Options + +``` +-p PACKAGE Android package name (default: ir.avaair.avaair) +-a APP_NAME Friendly name for log filenames (default: derived from package) +-d DEVICE adb device ID (serial). If omitted: auto-detect +-o DIR Output directory for logs (default: logs) +-f FILTER Logcat filter (default: "*:V") +--no-restart Do NOT restart the app automatically +--help Show usage +``` + +You may also pass filters as positional args: + +```bash +./adb-log-monitor.sh *:E +``` + +--- + +## Examples + +### Log all messages for default package + +```bash +./adb-log-monitor.sh +``` + +### Log only errors + +```bash +./adb-log-monitor.sh -f "*:E" +``` + +### Specify a different package + +```bash +./adb-log-monitor.sh -p com.example.myapp +``` + +### Target a specific device + +```bash +./adb-log-monitor.sh -d emulator-5554 +``` + +### Disable auto-restart behavior + +```bash +./adb-log-monitor.sh --no-restart +``` + +### Save logs into a custom directory + +```bash +./adb-log-monitor.sh -o /tmp/mylogs +``` + +--- + +## Log Output + +Each log session is written to: + +``` +logs/_YYYY-MM-DD_HH-MM-SS_pid_.log +``` + +Example: + +``` +logs/avaair_2025-01-14_11-07-22_pid_1234.log +``` + +--- + +## Behavior Summary + +* If the app is **not running**, the script waits indefinitely. +* When the process **starts**, logging begins immediately. +* If the process **restarts**, logging restarts seamlessly. +* On **Ctrl+C**, the script kills the background logcat and exits cleanly. + +--- + +## Suggested `.gitignore` + +``` +logs/ +``` + +This keeps your repo clean and prevents accidental log uploads. + +--- + +## Troubleshooting + +### "Device unauthorized" + +Accept the ADB RSA prompt on your device. + +### β€œDevice offline” + +Restart: + +```bash +adb kill-server +adb start-server +``` + +### No logs generated + +Ensure the package name is correct: + +```bash +adb shell pm list packages | grep +``` + +--- + +## License + +MIT License β€” feel free to modify and extend. diff --git a/log_avaair.sh b/log_avaair.sh index 5bcb505..9adf8df 100755 --- a/log_avaair.sh +++ b/log_avaair.sh @@ -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 "$@"