#!/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) # set -Euo pipefail ######################## # 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 ######################## RED="\033[0;31m" GREEN="\033[0;32m" YELLOW="\033[1;33m" BLUE="\033[0;34m" NC="\033[0m" # No Color 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; } ######################## # Usage ######################## usage() { sed -n '1,80p' "$0" | sed 's/^# \{0,1\}//' } ######################## # Parse CLI args ######################## 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 # Derive APP_NAME from PACKAGE_NAME if not set if [[ -z "${APP_NAME}" ]]; then APP_NAME="${PACKAGE_NAME##*.}" fi 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 ######################## # 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 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}" } ######################## # PID helpers ######################## get_pid() { 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 } ######################## # App restart helper ######################## restart_app() { 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 } ######################## # Start logcat ######################## start_logcat() { local pid="$1" local log_file="${LOG_DIR}/${APP_NAME}_$(date +'%Y-%m-%d_%H-%M-%S')_pid_${pid}.log" log_ok "Logging PID ${pid} (${PACKAGE_NAME}) -> ${log_file}" log_info "Device: ${DEVICE}" log_info "Filter: ${FILTER}" echo "------------------------------------------------------------" ( 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 if (( consecutive_misses > 0 )); then log_ok "Process '${PACKAGE_NAME}' started with PID ${pid}." fi consecutive_misses=0 if [[ "${pid}" != "${last_pid}" ]]; then if [[ -n "${last_pid}" ]]; then log_info "Process restarted (old PID: ${last_pid} -> new PID: ${pid})." fi if [[ -n "${LOGCAT_PID}" ]]; then kill "${LOGCAT_PID}" 2>/dev/null || true LOGCAT_PID="" fi last_pid="${pid}" start_logcat "${pid}" fi sleep 5 done } main "$@"