Refactor: AI generated enhanced version
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
logs/
|
||||
192
README.md
Normal file
192
README.md
Normal file
@@ -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/<app_name>_YYYY-MM-DD_HH-MM-SS_pid_<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 <keyword>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
MIT License — feel free to modify and extend.
|
||||
380
log_avaair.sh
380
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}"
|
||||
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=""
|
||||
|
||||
# --- Start logcat session ---
|
||||
start_logcat() {
|
||||
local PID=$1
|
||||
local LOG_FILE="$LOG_DIR/${APP_NAME}_$(date +'%Y-%m-%d_%H-%M-%S').log"
|
||||
|
||||
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
|
||||
# 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
|
||||
}
|
||||
|
||||
# --- Main loop ---
|
||||
LAST_PID=""
|
||||
########################
|
||||
# App restart helper
|
||||
########################
|
||||
|
||||
while true; do
|
||||
PID=$(get_pid)
|
||||
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
|
||||
|
||||
if [ -z "$PID" ]; then
|
||||
echo -e "${YELLOW}⚠️ Waiting for process '$APP_NAME' to start...${NC}"
|
||||
sleep 3
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ "$PID" != "$LAST_PID" ]; then
|
||||
if [ -n "$LAST_PID" ]; then
|
||||
echo -e "${BLUE}🔁 Process restarted (old: $LAST_PID → new: $PID).${NC}"
|
||||
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
|
||||
|
||||
LAST_PID=$PID
|
||||
if [[ -n "${LOGCAT_PID}" ]]; then
|
||||
kill "${LOGCAT_PID}" 2>/dev/null || true
|
||||
LOGCAT_PID=""
|
||||
fi
|
||||
|
||||
# Stop previous logcat if running
|
||||
pkill -f "adb -s $DEVICE logcat --pid=$LAST_PID" 2>/dev/null
|
||||
|
||||
# Start new logcat in background
|
||||
start_logcat "$PID" &
|
||||
last_pid="${pid}"
|
||||
start_logcat "${pid}"
|
||||
fi
|
||||
|
||||
sleep 5
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
Reference in New Issue
Block a user