前言
在基于 Docker 运行多个 Android 模拟器实例的云手机/自动化测试场景中,日志管理是一个绕不开的运维难题。每台设备都在源源不断地产生 logcat 日志,如果不加以管理,要么日志丢失、要么磁盘被撑爆。
本文介绍一套完整的多设备 Logcat 自动采集方案,具备以下能力:
- ✅ 支持同时采集多个 Android 容器的 logcat
- ✅ 日志按天自动切割,文件命名清晰
- ✅ 自动保留最近 3 天日志,超期自动清理
- ✅ 历史日志 gzip 压缩,节省磁盘空间
- ✅ 容器重启后自动重连,采集不中断
- ✅ systemd 托管,主机重启自动恢复
环境说明
本文方案基于以下典型部署结构:
宿主机(Ubuntu)
├── phone0 ← android:14_2.5.0 容器
├── phone1 ← android:14_2.6.0 容器
├── phone2 ← android:14_2.6.0 容器
├── phone3 ← android:14_2.6.0 容器
└── ...
每个 Android 容器通过 Docker 运行,执行 logcat 的方式为:
bash
sudo docker exec phone0 logcat
⚠️ 注意:后台运行时不能加
-it参数,否则会报错the input device is not a TTY,应直接使用docker exec <容器名> logcat。
方案架构
/opt/logcat-collector/collect.sh ← 核心采集脚本
│
├── 为每个设备 fork 一个后台子进程
│ │
│ ├── 检测容器是否运行
│ ├── docker exec <name> logcat >> 当天日志文件
│ └── 异常退出后自动重试
│
└── 主进程每小时执行:
├── 清理 3 天前的旧日志
└── gzip 压缩昨天的日志
/etc/systemd/system/logcat-collector.service ← systemd 服务
/var/log/android_logcat/ ← 日志存储目录
实现步骤
第一步:创建采集脚本
bash
sudo mkdir -p /opt/logcat-collector
sudo tee /opt/logcat-collector/collect.sh << 'EOF'
#!/bin/bash
# ============================================
# Android Logcat 自动采集脚本(docker exec 方式)
# 支持多设备、按天切割、保留最近3天
# ============================================
LOG_BASE="/var/log/android_logcat"
RETENTION_DAYS=3
PIDFILE_DIR="/tmp/logcat_pids"
# 设备列表:和容器名保持一致
DEVICES=(
"phone0"
"phone1"
"phone2"
"phone3"
)
mkdir -p "$LOG_BASE" "$PIDFILE_DIR"
# ---- 清理过期日志 ----
cleanup_old_logs() {
find "$LOG_BASE" -name "*.log" -mtime +${RETENTION_DAYS} -delete
find "$LOG_BASE" -name "*.log.gz" -mtime +${RETENTION_DAYS} -delete
echo "[$(date '+%F %T')] 已清理 ${RETENTION_DAYS} 天前的旧日志"
}
# ---- 单设备采集(后台循环) ----
collect_device() {
local name="$1"
local log_dir="${LOG_BASE}/${name}"
mkdir -p "$log_dir"
echo "[$(date '+%F %T')] [$name] 启动采集" | tee -a "${log_dir}/collector.log"
while true; do
# 检查容器是否正在运行
if ! docker ps --format '{{.Names}}' | grep -q "^${name}$"; then
echo "[$(date '+%F %T')] [$name] 容器未运行,等待..." >> "${log_dir}/collector.log"
sleep 10
continue
fi
local today
today=$(date '+%Y-%m-%d')
local log_file="${log_dir}/${today}.log"
echo "[$(date '+%F %T')] [$name] 开始写入 → $log_file" >> "${log_dir}/collector.log"
# 核心命令:docker exec 执行 logcat(不加 -it,适合后台运行)
docker exec "${name}" logcat -v threadtime "*:V" \
>> "$log_file" 2>> "${log_dir}/collector.log"
echo "[$(date '+%F %T')] [$name] logcat 中断,5秒后重试..." >> "${log_dir}/collector.log"
sleep 5
done
}
# ---- 停止所有采集进程 ----
stop_all() {
echo "[$(date '+%F %T')] 收到停止信号,终止所有采集..."
for f in "$PIDFILE_DIR"/*.pid; do
[ -f "$f" ] || continue
pid=$(cat "$f")
kill -- -"$pid" 2>/dev/null || kill "$pid" 2>/dev/null
rm -f "$f"
done
exit 0
}
trap stop_all SIGTERM SIGINT
# ---- 主逻辑 ----
cleanup_old_logs
for name in "${DEVICES[@]}"; do
collect_device "$name" &
child_pid=$!
echo "$child_pid" > "${PIDFILE_DIR}/${name}.pid"
echo "[$(date '+%F %T')] [$name] 采集进程 PID=$child_pid"
done
# 每小时清理 + 压缩昨天日志
while true; do
sleep 3600
cleanup_old_logs
yesterday=$(date -d "yesterday" '+%Y-%m-%d')
for name in "${DEVICES[@]}"; do
f="${LOG_BASE}/${name}/${yesterday}.log"
if [ -f "$f" ] && [ ! -f "${f}.gz" ]; then
gzip -9 "$f" && echo "[$(date '+%F %T')] [$name] 已压缩 ${yesterday}.log"
fi
done
done
EOF
sudo chmod +x /opt/logcat-collector/collect.sh
第二步:配置 systemd 服务
将采集脚本托管到 systemd,实现开机自启和异常自动重启:
bash
sudo tee /etc/systemd/system/logcat-collector.service << 'EOF'
[Unit]
Description=Android Logcat Multi-Device Collector
After=docker.service
Requires=docker.service
[Service]
Type=simple
ExecStart=/opt/logcat-collector/collect.sh
ExecStop=/bin/kill -SIGTERM $MAINPID
Restart=on-failure
RestartSec=30
StandardOutput=journal
StandardError=journal
SyslogIdentifier=logcat-collector
User=root
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable logcat-collector
sudo systemctl start logcat-collector
第三步:验证运行状态
bash
# 查看服务状态
sudo systemctl status logcat-collector
# 实时查看采集器日志
sudo journalctl -u logcat-collector -f
# 查看某设备今天的 logcat 输出
tail -f /var/log/android_logcat/phone0/$(date '+%Y-%m-%d').log
# 查看全部日志文件结构
find /var/log/android_logcat/ -type f | sort
日志目录结构
正常运行后,日志目录结构如下:
/var/log/android_logcat/
├── phone0/
│ ├── collector.log ← 采集器自身运行日志
│ ├── 2025-02-22.log.gz ← 已压缩的历史日志
│ ├── 2025-02-23.log.gz
│ └── 2025-02-24.log ← 今天实时写入中
├── phone1/
│ └── ...
├── phone2/
│ └── ...
└── phone3/
└── ...
常见问题
Q1:logcat 命令一直没有输出?
先手动测试命令是否可用:
bash
sudo docker exec phone0 logcat -v threadtime "*:V" | head -20
如果有输出说明命令正常,脚本即可正常工作。
Q2:如何新增一台设备?
编辑 /opt/logcat-collector/collect.sh,在 DEVICES 数组中追加容器名:
bash
DEVICES=(
"phone0"
"phone1"
"phone2"
"phone3"
"phone4" # 新增
)
然后重启服务:
bash
sudo systemctl restart logcat-collector
Q3:如何修改日志保留天数?
修改脚本顶部的变量:
bash
RETENTION_DAYS=7 # 改为保留7天
重启服务生效。
Q4:磁盘空间担心不够怎么办?
logcat 日志量取决于应用行为,可以通过过滤级别减少日志量:
bash
# 只采集 Warning 及以上级别
docker exec "${name}" logcat -v threadtime "*:W"
# 只采集特定 Tag
docker exec "${name}" logcat -v threadtime MyApp:D "*:S"
方案对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| 手动执行 logcat | 简单直接 | 不持久、重启丢失 |
| ADB 方式采集 | 标准接口 | 需要端口映射、ADB 连接管理复杂 |
| docker exec 方式(本文) | 无需额外端口、直接访问容器 | 需要宿主机有 docker 权限 |
| 容器内自采集 | 与宿主机解耦 | 需修改容器镜像 |
总结
本方案核心思路:
- 用
docker exec <容器名> logcat替代 ADB,省去端口映射和连接管理的复杂性 - 每个设备独立一个后台子进程,互不干扰,任一设备故障不影响其他设备
- 按天切割日志文件,便于查找和归档
- 通过
find -mtime实现自动清理,gzip压缩节省空间 - systemd 托管保证服务可靠运行
整套方案纯 Shell 实现,无第三方依赖,适合在嵌入式 Linux、ARM 服务器等资源受限环境中部署。
如果本文对你有帮助,欢迎点赞收藏~有问题欢迎评论区交流。