上帝反馈后
在碰到一些奇奇怪怪的系统问题时,如视频无法播放、网络异常、触摸无响应等等疑难杂症单单靠应用级别的异常信息怕是不能正常分析的,如果没有相应的日志获取能力,那面对用户的应该就是一块板砖,而面对我们的可能就是来自上帝的批判,因此如何在上帝反馈问题后第一时间能够获取到有用的信息并给予用户正向的反馈是及其重要的。
因此,在系统层面设计了一种持久化转储日志信息的三把刀方案:logdump、kdump、xdump。
logdump
logdump最初的设计初衷是为了持久化存储Android log,能够在用户机器出现问题时提供给研发端更多的信息便于分析定位。起初在设计时V1.0版本时只能保存最大两次开机后的日志信息,并且没有增加时间戳区别,回传的日志文件比较不清晰,在进行二次重构时,充分考虑了时间因素,增加了时间戳,能更明显的辨别日志记录的起始位置,由于持久化记录日志会对emmc有损耗,因此,结合公司的物联服务做了下发开关的操作,使得能够进行按需操作:
#!/system/bin/sh
LOG_PATH="/data/logdump"
LOG_DIR_MAX_COUNT=5
if [ ! -d "$LOG_PATH" ]; then
mkdir -p $LOG_PATH
fi
# remove files not in correct directory.
REMOVE_FILES=`ls -F $LOG_PATH | grep -v /$`
for r_file in $REMOVE_FILES
do
rm -f $LOG_PATH/$r_file
done
CUR_LOG_DIR_COUNT=`ls -F $LOG_PATH | grep /$ | wc -l`
if [ "$CUR_LOG_DIR_COUNT" -ge "$LOG_DIR_MAX_COUNT" ]; then
REMOVE_DIR_COUNT=`expr $CUR_LOG_DIR_COUNT - $LOG_DIR_MAX_COUNT + 1`
REMOVE_DIRS=`ls -F $LOG_PATH | grep /$ | head -$REMOVE_DIR_COUNT`
for r_dir in $REMOVE_DIRS
do
rm -rf $LOG_PATH/$r_dir
done
fi
if [ "$CUR_LOG_DIR_COUNT" -gt "0" ]; then
COPY_DIR=`ls -F $LOG_PATH | grep /$ | tail -1`
cp -rf /sys/fs/pstore/ $LOG_PATH/$COPY_DIR
fi
CUR_LOG_DIR_WITH_TIMESTAMP=`date +%Y-%m-%d_%H%M%S`
mkdir -p $LOG_PATH/$CUR_LOG_DIR_WITH_TIMESTAMP
logcat -G 16m
logcat -v threadtime -r8192 -n6 -f $LOG_PATH/$CUR_LOG_DIR_WITH_TIMESTAMP/log.txt &
在boot阶段logcat的服务已经启动并开始记录日志,因此在init.rc配置中增加了如下属性检测,当检测到persist.sys.logdump=true时则启动logdump的service。
service logdump /system/bin/logdump
class main
user root
group root lodump system
seclabel u:r:logdump:s0
oneshot
on boot && property:persist.sys.logdump=true
start logdump
而persist.sys.logdump的开关控制则由物联平台进行管理下发,实现了远程按需配置的功能。
kdump
通常在userdebug中获取kernel的日志时都是通过dmesg获取,但是dmesg仅仅只能将目前内核日志缓冲区打印出来,而内核日志缓冲区大小是有限的,往往我们在发现问题时再去获取内核日志时打印的dmesg已经不是现场了,对与内核相关问题的调试成为业务很大痛点。并且userdebug我们还能使用adb进行打印,但是出货的user版本我们根本无法获取现场的内核信息,若出现内核相关问题,只能换机调回来调式,因此非常有必要集成一个持续获取内核日志的工具服务。
采用从init起一个服务的方法,专门用于往/data/kdump/中记录kernel log,最大保存5次开机以来的kernel log,每次存储的日志元数据单文件最大支持20M,采用gzip流压缩方式,将占用空间大小尽可能缩减,避免更多的EMMC占用,采用shell编写了一个覆盖算法将新日志保存,实现旧日志清除。
#!/system/bin/sh
LOG_ROOT_DIR="/data/kdump"
KMSG_DIR_MAX_COUNT=5
KMSG_FILE_MAX_COUNT=10
# Create kmsg root directory if not exist.
if [ ! -f "$LOG_ROOT_DIR" ]; then
mkdir -p $LOG_ROOT_DIR
fi
# Remove files which're not in correct directory.
REMOVE_FILES=`ls -F $LOG_ROOT_DIR | grep -v /$`
for r_file in $REMOVE_FILES
do
rm -f $LOG_ROOT_DIR/$r_file
done
# Remove more than $KMSG_DIR_MAX_COUNT kmsg storage directories.
CUR_LOG_DIR_COUNT=`ls -F $LOG_ROOT_DIR | grep /$ | wc -l`
if [ "$CUR_LOG_DIR_COUNT" -ge "$KMSG_DIR_MAX_COUNT" ]; then
REMOVE_DIR_COUNT=`expr $CUR_LOG_DIR_COUNT - $KMSG_DIR_MAX_COUNT + 1`
REMOVE_DIRS=`ls -F $LOG_ROOT_DIR | grep /$ | head -$REMOVE_DIR_COUNT`
for r_dir in $REMOVE_DIRS
do
rm -rf $LOG_ROOT_DIR/$r_dir
done
fi
# Create new kmsg storage directory with named timestamp.
CUR_LOG_DIR_WITH_TIMESTAMP=`date +%Y-%m-%d_%H%M%S`
mkdir -p $LOG_ROOT_DIR/$CUR_LOG_DIR_WITH_TIMESTAMP
echo "Create $LOG_ROOT_DIR/$CUR_LOG_DIR_WITH_TIMESTAMP"
do_kmsg_dump(){
while :
do
for i in $(seq 1 $KMSG_FILE_MAX_COUNT)
do
f_index=$(expr $KMSG_FILE_MAX_COUNT - $i)
# Move new kmsg to overlay old.
if [ -f "$LOG_ROOT_DIR/$CUR_LOG_DIR_WITH_TIMESTAMP/kernel_msg_$(expr $f_index - 1)".gz ]; then
mv $LOG_ROOT_DIR/$CUR_LOG_DIR_WITH_TIMESTAMP/kernel_msg_$(expr $f_index - 1).gz \
$LOG_ROOT_DIR/$CUR_LOG_DIR_WITH_TIMESTAMP/kernel_msg_$f_index.gz -f
fi
done
# Save up to 40MB kmsg metadata in every dump file, use gzip to compress it.
dd if=/proc/kmsg bs=1k count=40960 | gzip > $LOG_ROOT_DIR/$CUR_LOG_DIR_WITH_TIMESTAMP/kernel_msg_0.gz
done
}
do_kmsg_dump
在设置单文件元数据大小时发现dd的流采集大小设置为20M时实际最多采集8MB大小原始kmsg,因此调整数据流大小最大40MB,压缩后的gzip包最大不超过2MB,日志目录保存最大10个文件,单目录最大20MBEMMC损耗,全系统最大20*5=100MBEMMC空间损耗,对用户来说整体空间损耗可忽略不计。
xdump
前两个dump功能是日志转储的话,xdump则是进行日志收集功能,将所有的有用信息进行可选择性的收集及上报操作。
在设计时采用了getopt进行参数解析,使得容错性更强,采用主从分离架构,使得单个脚本能够单独使用,由xdump作为总执行脚本放在/system/xbin/xdump下,其余脚本放在/system/etc/xdumps/script下,分别有:android_base_dump、data_data_dump、kernel_base_dump、android_apk_dump:
xdump v2.00 (2021-08-31) log catcher tool.
usage: xdump [COMMAND] [ARGS]
-a: dump all logs
-b [args]: dump android base logs
all : dump all android base log
system : dump system core info message.
media : dump media [video|aduio] info.
net : dump network info.
graphic : dump graphic [activity|screen|window] info.
log : dump system log [anr|dropbox|logdump] info.
-d [args|<package name>]: dump /data/data packages files.
all : dump all packages data files
thirdpart : dump all thirdpart packages data files
company : dump all company packages data files
<package name> : dump specific packages data files,
each package split with ':'.
-k: dump kernel base logs
-s [all|<package name>]: dump company apk xlog files.
all : dump all packages data files
<package name> : dump specific packages data files,
each package split with ':'.
-P <path>: dump files path.
-z : pack xdump files with gzip.
-h: show this message.
#!/bin/sh
############################################
# @Description: xdump log tool
# @Version: V2.0 2021-08-31
############################################
DEFAULT_XDUMP_ROOT_PATH=/sdcard
xdump_log_root_path=$DEFAULT_XDUMP_ROOT_PATH
XDUMP_SCRIPT_PATH=/system/etc/xdumps/script
XDUMP_LOG_ROOT_NAME=$(getprop ro.serialno)
XDUMP_PACK_FILE_NAME=`date +%Y-%m-%d_%H%M%S`
......
#Help
do_help()
{
......
}
do_start()
{
echo "------------------start xdump------------------"
start_time=$(date +%s)
}
do_finish()
{
end_time=$(date +%s)
delta_time=$(expr ${end_time} - ${start_time})
echo "-----finish xdump. spend: ${delta_time} s------"
if [ $XDUMP_PACK_FLAG -eq 1 ]; then
echo "Log file path: $xdump_file_path/${XDUMP_PACK_FILE_NAME}.tgz"
else
echo "Log file path: $xdump_file_path/${XDUMP_LOG_ROOT_NAME}"
fi
}
create_xdump_log_dir()
{
xdump_file_path=$xdump_log_root_path/xdumps
echo "create xdump log dir: $xdump_file_path"
create_dir_success=`mkdir -p $xdump_file_path 2>&1 > /dev/null ; echo $?`
if [ $create_dir_success -ne 0 ]; then
echo "Failed to create $xdump_file_path, please check path."
exit 1
fi
}
parse_android_base_params()
{
......
}
parse_data_data_params()
{
......
}
parse_android_apk_params()
{
......
}
parse_arguments()
{
XDUMP_PACK_FLAG=0
ALL_DUMP_FLAG=0
ANDROID_BASE_DUMP_FLAG=0
KERNEL_DUMP_FLAG=0
DATA_DUMP_FLAG=0
ANDROID_APK_DUMP_FLAG=0
if [ $# -le 0 ]; then
ALL_DUMP_FLAG=1
XDUMP_PACK_FLAG=1
return
fi
while getopts ":hab:d:ks:P:z" opt
do
case $opt in
h)
do_help
exit 0
;;
a)
ALL_DUMP_FLAG=1
;;
b)
ANDROID_BASE_DUMP_FLAG=1
xdump_android_base_args=$OPTARG
parse_android_base_params
;;
d)
DATA_DUMP_FLAG=1
xdump_data_data_args=$OPTARG
parse_data_data_params
;;
k)
KERNEL_DUMP_FLAG=1
;;
s)
ANDROID_APK_DUMP_FLAG=1
xdump_android_apk_args=$OPTARG
parse_android_apk_params
;;
P)
xdump_log_root_path=$OPTARG
;;
z)
XDUMP_PACK_FLAG=1
;;
?)
echo "Unrecognized parameter."
do_help
exit 1
;;
esac
done
shift $(($OPTIND - 1))
if [ $# -gt 0 ]; then
echo "Invalid parameter, command has non-parameters."
do_help
exit 1
fi
}
dump_android_base()
{
if [ $XDUMP_PACK_FLAG -eq 1 ]; then
android_base_args="-z $android_base_args"
fi
sh $XDUMP_SCRIPT_PATH/android_base_dump \
-P $xdump_file_path/${XDUMP_LOG_ROOT_NAME} \
$android_base_args
}
dump_data_data()
{
if [ $XDUMP_PACK_FLAG -eq 1 ]; then
data_data_args="-z $data_data_args"
fi
sh $XDUMP_SCRIPT_PATH/data_data_dump \
-P $xdump_file_path/${XDUMP_LOG_ROOT_NAME} \
$data_data_args
}
dump_kernel_base()
{
if [ $XDUMP_PACK_FLAG -eq 1 ]; then
kernel_base_args="-z $kernel_base_args"
fi
sh $XDUMP_SCRIPT_PATH/kernel_base_dump \
-P $xdump_file_path/${XDUMP_LOG_ROOT_NAME} \
$kernel_base_args
}
dump_android_apk()
{
if [ $XDUMP_PACK_FLAG -eq 1 ]; then
android_apk_args="-z $android_apk_args"
fi
sh $XDUMP_SCRIPT_PATH/android_apk_dump \
-P $xdump_file_path/${XDUMP_LOG_ROOT_NAME} \
$android_apk_args
}
dump_all()
{
android_base_args="-a"
data_data_args="-a"
android_apk_args="-a"
dump_android_base &
dump_data_data &
dump_kernel_base &
dump_android_apk &
wait
}
execute_scripts()
{
if [ $ALL_DUMP_FLAG -eq 1 ]; then
dump_all
else
if [ $ANDROID_BASE_DUMP_FLAG -eq 1 ]; then
dump_android_base &
fi
if [ $KERNEL_DUMP_FLAG -eq 1 ]; then
dump_kernel_base &
fi
if [ $DATA_DUMP_FLAG -eq 1 ]; then
dump_data_data &
fi
if [ $ANDROID_APK_DUMP_FLAG -eq 1 ]; then
dump_android_apk &
fi
wait
fi
}
pack_xdump_files()
{
if [ -d $xdump_file_path/${XDUMP_LOG_ROOT_NAME} ]; then
cd $xdump_file_path
tar zcf ${XDUMP_PACK_FILE_NAME}.tgz ./${XDUMP_LOG_ROOT_NAME}
rm -rf $xdump_file_path/${XDUMP_LOG_ROOT_NAME}
fi
}
do_xdump()
{
do_start
parse_arguments $*
create_xdump_log_dir
execute_scripts
if [ $XDUMP_PACK_FLAG -eq 1 ]; then
pack_xdump_files
fi
do_finish
}
do_xdump $*
Android基础信息可选化导出功能
由于公司物联平台的上行带宽并不宽裕,且并非每次回传都需要获取所有完整日志,因此,在设计时加入了针对system core、network、media等等的分类可选参数,使得我们能够自己选择回传哪些日志信息:
usage: android_base_dump [ARGS]
-a: dump all android base logs.
-s: dump system core info message.
-m: dump media [video|aduio] info.
-n: dump network info.
-g: dump graphic [activity|screen|window] info.
-l: dump system log [anr|dropbox|logdump] info.
-P <path>: dump files path.
-z : pack files with gzip.
-h: show this message.
#!/bin/sh
DEFAULT_ANDROID_BASE_ROOT_PATH=/sdcard/xdumps
dump_android_base_root_path=$DEFAULT_ANDROID_BASE_ROOT_PATH
#Help
do_help()
{
......
}
create_log_dir()
{
android_base_log_path=$dump_android_base_root_path/android_base
# echo "create android base log dir: $android_base_log_path"
create_dir_success=`mkdir -p $android_base_log_path 2>&1 > /dev/null ; echo $?`
if [ $create_dir_success -ne 0 ]; then
echo "Failed to create $android_base_log_path, please check path."
exit 1
fi
}
clear_default_history()
{
if [ -e $DEFAULT_ANDROID_BASE_ROOT_PATH/android_base ]; then
echo "clear history default dump path."
rm -rf $DEFAULT_ANDROID_BASE_ROOT_PATH/android_base
fi
}
parse_arguments()
{
ALL_FLAG=0
PACK_FLAG=0
SYSTEM_DUMP_FLAG=0
MEDIA_DUMP_FLAG=0
NETWORK_DUMP_FLAG=0
GRAPHIC_DUMP_FLAG=0
LOG_DUMP_FLAG=0
if [ $# -le 0 ]; then
echo "Invalid call, an arguments excpet -P must be specified."
do_help
exit 1
fi
while getopts ":hasmnglP:z" opt
do
case $opt in
h)
do_help
exit 0
;;
a)
ALL_FLAG=1
;;
s)
SYSTEM_DUMP_FLAG=1
;;
m)
MEDIA_DUMP_FLAG=1
;;
n)
NETWORK_DUMP_FLAG=1
;;
g)
GRAPHIC_DUMP_FLAG=1
;;
l)
LOG_DUMP_FLAG=1
;;
P)
dump_android_base_root_path=$OPTARG
;;
z)
PACK_FLAG=1
;;
?)
echo "Invalid parameters."
do_help
exit 1
;;
esac
done
shift $(($OPTIND - 1))
if [ $dump_android_base_root_path != $DEFAULT_ANDROID_BASE_ROOT_PATH ]; then
if [ $ALL_FLAG -eq 0 -a $SYSTEM_DUMP_FLAG -eq 0 -a $MEDIA_DUMP_FLAG -eq 0 -a \
$NETWORK_DUMP_FLAG -eq 0 -a $GRAPHIC_DUMP_FLAG -eq 0 -a $LOG_DUMP_FLAG -eq 0 ]; then
echo "Invalid call, an arguments excpet -P must be specified."
do_help
exit 1
fi
fi
if [ $# -gt 0 ]; then
echo "Invalid parameter, command has non-parameters."
do_help
exit 1
fi
if [ $ALL_FLAG -eq 1 ]; then
SYSTEM_DUMP_FLAG=1
MEDIA_DUMP_FLAG=1
NETWORK_DUMP_FLAG=1
GRAPHIC_DUMP_FLAG=1
LOG_DUMP_FLAG=1
fi
}
# system dump
system_dump()
{
SYSTEM_LOGPATH=${android_base_log_path}/system
mkdir -p ${SYSTEM_LOGPATH}
chmod 755 ${SYSTEM_LOGPATH}
dumpsys meminfo > ${SYSTEM_LOGPATH}/dumpsys_meminfo.txt
dumpsys cpuinfo > ${SYSTEM_LOGPATH}/dumpsys_cpuinfo.txt
dumpsys mount > ${SYSTEM_LOGPATH}/dumpsys_mount.txt
dumpsys input > ${SYSTEM_LOGPATH}/dumpsys_input.txt
pm list packages -f > ${SYSTEM_LOGPATH}/package_list.txt
df -h > ${SYSTEM_LOGPATH}/disk_info.txt
free -h > ${SYSTEM_LOGPATH}/free_info.txt
ps -efZ > ${SYSTEM_LOGPATH}/ps.txt
lsmod > ${SYSTEM_LOGPATH}/lsmod.txt
lsusb > ${SYSTEM_LOGPATH}/lsusb.txt
service list > ${SYSTEM_LOGPATH}/service_list.txt
cat /proc/meminfo > ${SYSTEM_LOGPATH}/meminfo.txt
cat /proc/cpuinfo > ${SYSTEM_LOGPATH}/cpuinfo.txt
}
# Media dump
media_dump()
{
MEDIA_LOGPATH=${android_base_log_path}/media
mkdir -p ${MEDIA_LOGPATH}
chmod 755 ${MEDIA_LOGPATH}
dumpsys media.audio_policy > ${MEDIA_LOGPATH}/dumpsys_audio_policy.txt
dumpsys media.audio_flinger > ${MEDIA_LOGPATH}/dumpsys_audio_flinger.txt
dumpsys audio > ${MEDIA_LOGPATH}/dumpsys_audio.txt
dumpsys media.camera > ${MEDIA_LOGPATH}/dumpsys_camera.txt
}
#network
network_dump()
{
NETWORK_LOGPATH=${android_base_log_path}/network
mkdir -p ${NETWORK_LOGPATH}
chmod 755 ${NETWORK_LOGPATH}
ifconfig -a > ${NETWORK_LOGPATH}/ifconfig.txt
ip rule > ${NETWORK_LOGPATH}/ip_rule.txt
ip route show table main > ${NETWORK_LOGPATH}/ip_route.txt
dumpsys wifi > ${NETWORK_LOGPATH}/dumpsys_wifi.txt
dumpsys ethernet > ${NETWORK_LOGPATH}/dumpsys_ethernet.txt
iptables -t filter -nvL > ${NETWORK_LOGPATH}/iptables_filter.txt
iptables -t nat -nvL > ${NETWORK_LOGPATH}/iptables_nat.txt
iptables -t mangle -nvL > ${NETWORK_LOGPATH}/iptables_mangle.txt
}
#capture
graphic_dump()
{
GRAPHIC_LOGPATH=${android_base_log_path}/graphic
mkdir -p ${GRAPHIC_LOGPATH}
chmod 755 ${GRAPHIC_LOGPATH}
dumpsys activity > ${GRAPHIC_LOGPATH}/dumpsys_activity.txt
dumpsys window > ${GRAPHIC_LOGPATH}/dumpsys_window.txt
screencap -p ${GRAPHIC_LOGPATH}/`date +%Y%m%d%H%M%S`.png
#echo capture > /proc/msp/hifb0
}
#copy
log_dump()
{
if [ -e /data/anr ]; then
cp -R /data/anr ${android_base_log_path}/
fi
if [ -e /data/tombstones ]; then
cp -R /data/tombstones ${android_base_log_path}/
fi
if [ -e /data/system/dropbox ]; then
cp -R /data/system/dropbox ${android_base_log_path}/
fi
if [ -e /data/logdump ]; then
cp -R /data/logdump ${android_base_log_path}/
fi
}
pack_log_files()
{
if [ -d $android_base_log_path ]; then
cd $android_base_log_path/..
tar zcf $(basename $android_base_log_path).tgz ./$(basename $android_base_log_path)
rm -rf $android_base_log_path
fi
}
do_dump_android_base()
{
parse_arguments $*
clear_default_history
create_log_dir
# echo "Dumping android_base logs ..."
if [ $SYSTEM_DUMP_FLAG -eq 1 ]; then
system_dump &
fi
if [ $MEDIA_DUMP_FLAG -eq 1 ]; then
media_dump &
fi
if [ $NETWORK_DUMP_FLAG -eq 1 ]; then
network_dump &
fi
if [ $GRAPHIC_DUMP_FLAG -eq 1 ]; then
graphic_dump &
fi
if [ $LOG_DUMP_FLAG -eq 1 ]; then
log_dump &
fi
wait
if [ $PACK_FLAG -eq 1 ]; then
pack_log_files
fi
}
do_dump_android_base $*
指定包名获取data/data/文件功能
在各种售后问题中,需要获取三方应用/data分区数据来分析问题的情况不在少数,作为之前一直遗留的业务痛点,此次在设计中增加了针对应用包名的/data/data/包名的文件导出功能,能够在三方应用出现问题时有能力获取其私有目录,提供给三方应用开发方一同分析解决。
#!/bin/sh
DEFAULT_THIRDPART_ROOT_PATH=/sdcard/xdumps
dump_data_root_path=$DEFAULT_THIRDPART_ROOT_PATH
#Help
do_help()
{
......
}
clear_history()
{
......
}
create_data_data_log_dir()
{
......
}
create_third_part_log_dir()
{
thirdpart_log_path=$data_data_log_path/thirdpart
# echo "create data thirdpart log dir: $thirdpart_log_path"
create_thirdpart_dir_success=`mkdir -p $thirdpart_log_path 2>&1 > /dev/null ; echo $?`
if [ $create_thirdpart_dir_success -ne 0 ]; then
echo "Failed to create $thirdpart_log_path, please check path."
exit 1
fi
}
parse_arguments()
{
ALL_FLAG=0
PACK_FLAG=0
THIRTPART_ALL_FLAG=0
ANDROID_ALL_FLAG=0
while getopts ":hatsP:z" opt
do
case $opt in
h)
do_help
exit 0
;;
a)
ALL_FLAG=1
;;
t)
THIRTPART_ALL_FLAG=1
;;
P)
dump_data_root_path=$OPTARG
;;
z)
PACK_FLAG=1
;;
?)
echo "Invalid parameters."
do_help
exit 1
;;
esac
done
shift $(($OPTIND - 1))
if [ $# -le 0 ]; then
if [ $ALL_FLAG -eq 0 -a $ANDROID_ALL_FLAG -eq 0 -a $THIRTPART_ALL_FLAG -eq 0 ]; then
echo "Invalid parameter, application package name was not specified."
do_help
exit 1
fi
fi
specific_packages=$*
}
# Only dump non-system_app in /data/data/
dump_thirdpart_all()
{
create_third_part_log_dir
thirdpart_all_packages_in_data=`ls /data/data |\
grep -v -E "^com.android|^com.mediatek|^android"`
for thirdpart_package in $thirdpart_all_packages_in_data
do
is_system_app=`pm list packages -f | grep $thirdpart_package |\
grep -E "\/system\/app\/|\/system\/priv-app\/" 2>&1 > /dev/null ; \
echo $?`
if [ $is_system_app -eq 0 ]; then
continue
fi
# echo "Dumping package $thirdpart_package ..."
cp -R /data/data/$thirdpart_package $thirdpart_log_path/ 2>&1 >> /dev/null
done
}
dump_all()
{
dump_thirdpart_all &
wait
}
dump_specific_packages()
{
create_third_part_log_dir
for thirdpart_package in $specific_packages
do
if [ -e /data/data/$thirdpart_package ]; then
# echo "Dumping $thirdpart_package ..."
cp -R /data/data/$thirdpart_package $thirdpart_log_path/ 2>&1 >> /dev/null
else
echo "Specific package $thirdpart_package not exist in /data/data, skip it."
fi
done
}
pack_log_files()
{
if [ -d $data_data_log_path ]; then
cd $data_data_log_path/..
tar zcf $(basename $data_data_log_path).tgz ./$(basename $data_data_log_path)
rm -rf $data_data_log_path
fi
}
execute_dump()
{
......
}
execute_dump $*
指定kernel日志导出功能
基于kdump的日志持久化转储功能,在执行xdump时能够选择性将转储的kernel日志进行导出:
#!/bin/sh
DEFAULT_KERNEL_BASE_ROOT_PATH=/sdcard/xdumps
dump_kernel_root_path=$DEFAULT_KERNEL_BASE_ROOT_PATH
#Help
do_help()
{
......
}
create_kernel_log_dir()
{
kernel_log_path=$dump_kernel_root_path/kernel
# echo "create kernel base log dir: $kernel_log_path"
create_dir_success=`mkdir -p $kernel_log_path 2>&1 > /dev/null ; echo $?`
if [ $create_dir_success -ne 0 ]; then
echo "Failed to create $kernel_log_path, please check path."
exit 1
fi
}
clear_default_history()
{
if [ -e $DEFAULT_KERNEL_BASE_ROOT_PATH/kernel ]; then
echo "clear history default dump path."
rm -rf $DEFAULT_KERNEL_BASE_ROOT_PATH/kernel
fi
}
parse_arguments()
{
PACK_FLAG=0
while getopts ":hP:z" opt
do
case $opt in
h)
do_help
exit 0
;;
P)
dump_kernel_root_path=$OPTARG
;;
z)
PACK_FLAG=1
;;
?)
echo "Invalid parameters."
do_help
exit 1
;;
esac
done
shift $(($OPTIND - 1))
if [ $# -gt 0 ]; then
echo "Invalid parameter, command has non-parameters."
do_help
exit 1
fi
}
pack_log_files()
{
if [ -d $kernel_log_path ]; then
cd $kernel_log_path/..
tar zcf $(basename $kernel_log_path).tgz ./$(basename $kernel_log_path)
rm -rf $kernel_log_path
fi
}
kernel_dump()
{
KDUMP_LOG_PATH=/data/kdump
if [ -d $KDUMP_LOG_PATH ]; then
cp -rRf $KDUMP_LOG_PATH/. $kernel_log_path
else
dmesg > ${kernel_log_path}/dmesg.txt
fi
if [ ${PACK_FLAG} -eq 1 ]; then
pack_log_files
fi
}
do_dump_kernel_base()
{
parse_arguments $*
clear_default_history
create_kernel_log_dir
kernel_dump
}
do_dump_kernel_base $*
指定包名获取xlog功能
由于公司开发的应用均集成了腾讯的xlog,因此做了专项导出的操作,可应用记录的xlog通过包名可选性导出,功能的实现大同小异,此处只展示usage help:
usage: android_apk_dump [ARGS] <package names>\n
-a: dump all packages sdcard data files
-P <path>: dump files path.
-z : pack files with gzip.
-h: show this message.
日志本地U盘自动导出功能
在设计时增加了U盘自动导出功能,使用时需要在U盘根目录增加校验文件,插入U盘后系统将自动进行文件校验,若校验成功则开始进行日志导出,配合应用端增加导出开始和导出完成的toast提醒。
为了避免系统各个服务间的强耦合阻塞关系,采用了initrc机制进行事件触发的架构。在Android中所有的USB时间由kernel进行监听处理,当检测到U盘插入后进行挂载之后上报uevent事件,之后由Native层Vold执行挂载后的操作,挂载完成后会将事件上报到framework层,ExternalStorageProvider能够获取U盘挂载的绝对路径,因此在ExternalStorageProvider中进行获取挂载路径,并进行文件校验,若检测到U盘中存在上述校验文件后将获取到的U盘挂载路径写入属性sys.local.xdump.root中,并设置标志位属性sys.local.xdump为1,触发系统进行日志捕获,捕获日志完成后与IOT服务联动发出toast提醒:
private void checkExecLocalXdumpIfNeeded(File xdumpLicenseKeyRoot) {
if (xdumpLicenseKeyRoot == null) {
Log.e(TAG, "Get null pointer mountPath, return.");
return;
}
String xdumpLicenseKeyPath = ".xdump";
File xdumpLicenseKeyFile = new File(xdumpLicenseKeyRoot, xdumpLicenseKeyPath);
if (xdumpLicenseKeyFile != null) {
if (xdumpLicenseKeyFile.exists()) {
Log.d(TAG, "Xdump license file validation succeeded, do local xdump.");
String xdumpLicenseKeyRootAbsPath = xdumpLicenseKeyRoot.getAbsolutePath();
if (xdumpLicenseKeyRootAbsPath != null && xdumpLicenseKeyRootAbsPath.length() != 0) {
SystemProperties.set("sys.local.xdump.root", xdumpLicenseKeyRootAbsPath);
SystemProperties.set("sys.local.xdump", "1");
} else {
Log.e(TAG, "Get xdumpLicenseKeyRootAbsPath null or empty.");
return;
}
}
}
}
private void updateVolumesLocked() {
......
if (disk != null && disk.isSd()) {
root.flags |= Root.FLAG_REMOVABLE_SD;
} else if (disk != null && disk.isUsb()) {
root.flags |= Root.FLAG_REMOVABLE_USB;
if (volume.isMountedWritable()) {
boolean bootCompleted = "1".equals(SystemProperties.get("sys.boot_completed"));
if (bootCompleted) {
String isXdumpRunning = SystemProperties.get("sys.local.xdump", "");
Log.d(TAG, "isXdumpRunning = [" + isXdumpRunning + "]");
if (isXdumpRunning != null && !isXdumpRunning.equals("running")) {
File mountExternalStroagePath = volume.getPath();
if (mountExternalStroagePath != null) {
Log.d(TAG, "Found public writable volume: " + volume);
checkExecLocalXdumpIfNeeded(mountExternalStroagePath);
}
}
}
}
}
......
}
init.xdump.rc
service local_xdump /system/bin/local_xdump
class main
user root
group root system
seclabel u:r:xdump:s0
oneshot
on property:sys.boot_completed=1 && property:sys.local.xdump=1
start local_xdump
......
#!/bin/sh
xdump_license_key_file=".xdump"
pre_finish()
{
# Unlock xdump running.
setprop sys.local.xdump.root ""
setprop sys.local.xdump ""
}
check_environment_ready()
{
# Check boot completed.
boot_completed=$(getprop sys.boot_completed)
if [ "$boot_completed" != "1" ]; then
echo "Not boot completed, exit."
pre_finish
exit 1
fi
# Check xdump property enable.
local_xdump_enable=$(getprop sys.local.xdump)
if [ "$local_xdump_enable" != "1" ]; then
echo "Local xdump not avalibe, exit."
pre_finish
exit 1
fi
# Check the mount path and the license key file effective .
mount_path=$(getprop sys.local.xdump.root)
if [ ! -f $mount_path/$xdump_license_key_file ]; then
echo "xdump license file validation failed, exit."
pre_finish
exit 1
fi
echo "check mount path success."
}
do_local_xdump()
{
check_environment_ready
# Lock the xdump running.
setprop sys.local.xdump "running"
setenforce 0
am broadcast -a com.android.action.LOCAL_XDUMP_START
xdump_success=$(/system/xbin/xdump -a -z 2>&1 >> /dev/null ; echo $?)
if [ $xdump_success -ne 0 ]; then
echo "Error, failed to execuate xdump scripts."
pre_finish
exit 1
fi
# Unlock xdump running.
setprop sys.local.xdump.root ""
setprop sys.local.xdump ""
# Do xdump file copy.
cp -R /sdcard/xdumps $mount_path
rm -rf /sdcard/xdumps
am broadcast -a com.android.action.LOCAL_XDUMP_FINISHED
setenforce 1
}
do_local_xdump
执行local_xdump前和执行完成后分别发送了两个broadcast:
- com.android.action.LOCAL_XDUMP_START
- com.android.action.LOCAL_XDUMP_FINISHED
这两个广播由CIOTExtendService接收并执行弹出toast提醒的操作:
IotService::LocalXDumpReceiver.kt
class LocalXDumpReceiver : BroadcastReceiver() {
private var isRegisterReceiver = false
override fun onReceive(context: Context?, intent: Intent?) {
if (context != null && intent != null) {
when (intent.action) {
localXDumpStartIntentAction ->
Toast.makeText(context, R.string.local_dump_start_text, Toast.LENGTH_LONG).show()
localXDumpFinishIntentAction ->
Toast.makeText(context, R.string.local_dump_finish_text, Toast.LENGTH_LONG).show()
}
}
}
fun registerLocalXDumpReceiver(mContext: Context) {
if (!isRegisterReceiver) {
isRegisterReceiver = true
val filter = IntentFilter()
filter.addAction(localXDumpStartIntentAction)
filter.addAction(localXDumpFinishIntentAction)
mContext.registerReceiver(this, filter)
}
}
fun unRegisterLocalXDumpReceiver(mContext: Context) {
if (isRegisterReceiver) {
isRegisterReceiver = false
mContext.unregisterReceiver(this)
}
}
companion object {
private const val TAG = "LocalXDmpReceiver"
private const val localXDumpStartIntentAction = "com.android.action.LOCAL_XDUMP_START"
private const val localXDumpFinishIntentAction = "com.android.action.LOCAL_XDUMP_FINISHED"
}
}
远程下发日志导出机制
在架构设计时考虑到解耦合,同样采用了initrc事件机制进行处理,IotService接收到后台发送的日志请求后进行参数解析,设置Settings数据库Settings.Secure.remote_xdump_parms,保存xdump执行的参数,之后设置sys.remote.xdump属性为1触发远程xdump:
IotService::LogsReportHandler.kt
class LogsReportHandler : IServiceHandler.Stub(), CoroutineScope {
private val TAG = "LogsReportHandler"
override val coroutineContext: CoroutineContext
get() = Dispatchers.Default
override fun onRequest(topic: String?, method: String?, params: String?, traceId: String?) {
val remoteXDumpParams = JSONObject(params).get("params") as String?
setRemoteXDumpParams(remoteXDumpParams)
setRemoteXDumpEnabled()
}
private fun setRemoteXDumpParams(params: String?) {
......
}
fun setRemoteXDumpEnabled() {
val remoteXDumpStatus = SystemPropertiesUtils.get(REMOTE_XDUMP_STATUS_PROPERTY, "stopped")
val remoteXDumpEnabled = SystemPropertiesUtils.get(REMOTE_XDUMP_ENABLE_PROPERTY, "0")
......
Log.d(TAG, "Execute remote xdump...")
SystemPropertiesUtils.setInt(REMOTE_XDUMP_ENABLE_PROPERTY, 1)
}
}
init.xdump.rc
service remote_xdump /system/bin/remote_xdump
class main
user root
group root system
seclabel u:r:xdump:s0
oneshot
on property:init.svc.remote_xdump=stopped
setprop sys.remote.xdump ""
on property:sys.boot_completed=1 && property:sys.remote.xdump=1
start remote_xdump
remote_xdump
#!/bin/sh
# Settings secure remote_xdump_parms
SETTING_REMOTE_XDUMP_PARMS=remote_xdump_parms
REMOTE_XDUMP_ENABLE=sys.remote.xdump
# Do some clean operation at pre finish.
pre_finish()
{
settings delete secure $SETTING_REMOTE_XDUMP_PARMS
setprop $REMOTE_XDUMP_ENABLE ""
}
# Send remote xdump start action to CIOT.
send_start()
{
am broadcast -a com.android.action.LOCAL_XDUMP_START
}
# Send remote xdump finished action to CIOT.
send_finish()
{
am broadcast -a com.android.action.LOCAL_XDUMP_FINISHED
}
# Send remote xdump failed action to CIOT.
send_failed()
{
err_msg=$1
echo $err_msg
am broadcast -a com.android.action.REMOTE_XDUMP_FAILED --es msg $err_msg
}
# Send remote xdump success action to CIOT with xdump log path.
send_success()
{
echo "Remote xdump execute success."
am broadcast -a com.android.action.REMOTE_XDUMP_SUCCESS --es path $1
}
# Check xdump environment is ready.
check_environment_ready()
{
# Check boot completed.
boot_completed=$(getprop sys.boot_completed)
if [ "$boot_completed" != "1" ]; then
echo "Not boot completed, exit."
pre_finish
exit 1
fi
# Check xdump property enable && xdump is not running.
remote_xdump_enabled=$(getprop $REMOTE_XDUMP_ENABLE)
if [ "$remote_xdump_enabled" != "1" ]; then
if [ "$remote_xdump_enabled" == "running" ]; then
send_failed "Remote xdump is running."
exit 1
fi
send_failed "Remote xdump not avalibe."
pre_finish
exit 2
fi
echo "check environment success."
}
# Check xdump params is valid.
check_xdump_params_vaild()
{
# If not set xdump params then set params to default.
xdump_parms=$(settings get secure $SETTING_REMOTE_XDUMP_PARMS)
if [ -z "$xdump_parms" -o "$xdump_parms" == "null"]; then
xdump_parms="-a -z"
fi
}
# Do remote xdump
do_remote_xdump()
{
check_environment_ready
# send_start
# Lock the xdump running.
setprop $REMOTE_XDUMP_ENABLE "running"
setenforce 0
check_xdump_params_vaild
xdump_res=$(/system/xbin/xdump $xdump_parms | tail -1 | awk -F':' '{print $2}')
if [ -z "$xdump_res" -o "$xdump_res" == "" ]; then
send_failed "Remote xdump execute failed."
else
xdump_log_path=$(echo $xdump_res)
send_success $xdump_log_path
fi
# send_finish
pre_finish
setenforce 1
}
do_remote_xdump
IotService::RemoteXDumpReceiver.kt
class RemoteXDumpReceiver : BroadcastReceiver() {
private var isRegisterReceiver = false
override fun onReceive(context: Context?, intent: Intent?) {
if (context != null && intent != null) {
when (intent.action) {
remoteXDumpSuccessIntentAction -> {
val xDumpFilePath = intent.getStringExtra("path")
Log.d(TAG, "onReceive: Get remote xdump log path -> [$xDumpFilePath].")
if (!xDumpFilePath.isNullOrEmpty()) {
val remoteXDumpLogFile = File(xDumpFilePath)
if (remoteXDumpLogFile.exists()) {
val cstoreListener = CstoreListener()
cstoreListener.uploadCStoreFile(xDumpFilePath)
} else {
Log.e(TAG, "$remoteXDumpLogFile is not exist.")
}
}
}
remoteXDumpFiledIntentAction -> {
val errMsg = intent.getStringExtra("msg")
if (!errMsg.isNullOrEmpty()) {
Log.e(TAG, "onReceive: Get remote xdump execute failed.")
}
}
}
}
}
......
companion object {
private const val TAG = "RemoteXDmpReceiver"
private const val remoteXDumpFiledIntentAction = "com.android.action.REMOTE_XDUMP_FAILED"
private const val remoteXDumpSuccessIntentAction = "com.android.action.REMOTE_XDUMP_SUCCESS"
const val REMOTE_XDUMP_STATUS_PROPERTY = "init.svc.remote_xdump"
const val REMOTE_XDUMP_ENABLE_PROPERTY = "sys.remote.xdump"
const val REMOTE_XDUMP_PARMS_SETTINGS_KEY = "remote_xdump_parms"
const val REMOTE_XDUMP_PARMS_DEFAULT = "-b all -k -s all -z"
}
}
总结
至此,就是日志持久化转储及回传的完整闭环方案了,欢迎各位大佬点评指正。