任务背景:
一个应用程序运行的时候,可能需要查看多个模块的状态,如gps是否锁定,eeprom是否读写正常等。通常我们是通过过滤log的方式进行查看。看到有的公司提供了界面化的工具,显示当前进程执行了哪些步骤,以及哪些步骤未执行。因此想仿写一个简化版。
实际工作中,遇到了多板卡开发的情况,每个板卡都有一个终端,查看模块状态非常麻烦。以小区建站、时钟同步为例,经常需要在不同的板卡开log,grep 关键字。如果监控的模块多了,简直就是反人类了。。。
预计方案:
1,每个板卡加一个log收集进程,持续查看各板卡的log文件,并将结果发送到网络。在某一板卡或者电脑上接收并展示。(每个板卡需要一个独立发送进程,需要接收端)
2,修改原有的log收集逻辑,在保存本地的同时,发送到网络。在某一板卡或者电脑上接收并展示。(需要改动zlog配置文件,不确定能不能行,需要接收端)
3,在电脑或者某一板卡上,通过sshpass + tail 收集其它板卡的log,然后统一显示。
最终方案:(使用预计方案3)
BBU/zpp 收集所有板卡的信息,并统一显示。(不用主控的原因是,在主控上对BBU用ssppass会失败,同时最终成品,zk没有对外接口)
1,新增shell脚本,用于收集信息,定时刷新,颜色区分显示。
2,新增cfg文件,用于配置有几个板卡,显示多少条SHOW_NAME。每条SHOW_NAME变色的条件是?并打印信息。
3,创建tmp目录,分等目录,然后tail -F收集log并写入。这里面要有断线重连功能。
配置文件说明:
#item like: info##title
#item like: show##dev##SHOW_NAME##LOG_OK##LOG_ERR
#item like: hide##dev##SHOW_NAME##LOG_OK##LOG_ERR
#dev bbu, qck, zkb, zpp
#show use 3 color ,grey(no find log) ,green(find ok log), red(find err log)
#hide use 2 color ,no show(no find log) ,green(find ok log), red(find err log)
#if you do not want use LOG_OK or LOG_ERR, use log_def_ok or log_def_err instead
#support chinese, but should change terminal font to UTF-8.
示例:
info##
info##Status meaning: 1_unready 2_ready 3_setuping 4_setuped 5_error
show##zkb##APK link status ##apk link status connected##apk link status disconnect
hide##zpp##Optical port err##log_def_ok##oam_zpp_optical_port_reset error
没有收到消息时,展示所有的SHOW_NAME,并且都是灰码展示。
如果收到 MQTT 消息,则文字产生变化。
如果收到 LOG_OK 消息,则SHOW_NAME发生变化。例如变绿
如果收到 LOG_ERR 消息,则SHOW_NAME发生变化。例如变红
每5S刷新一次。
1, 先解析配置文件,确认哪些东西需要展示,并顺序显示初始状态。
1.1 while循环,从配置文件中顺序把需要展示的数据弄出来,在shell中存储。这部分的目的是过滤注释。
1.2 创建string buffer: str_type[] , str_name[] , str_msg[] , show_flag[] , show_buf[]
str_type的作用是存储配置文件中的SHOW_TYPE
str_name的作用是存储配置文件中的SHOW_NAME
str_msg的作用是存储配置文件中的SHOW_MSG
show_flag的作用是判断是否收到相应的log或者MQTT
show_buf的作用是存储将要输出到终端的字符串
2, 收集需要的log消息,准备进行处理。
2.1 开启新线程,收集log消息,并保存在本地。 bbu_log,fh_log,zk_log,zpp_log,
这里有个问题啊,tail -F收集log的时候不能中断。如果log太多,是不是要做ring_buffer?
2.2 过滤log,与str_msg进行比对,根据比对结果更新show_flag。(5S循环一次)
3, 处理完log消息后,更新显示的状态。
3.1 while循环后,每5S刷新一次显示,输出show_buf ,每个小字符串80字符长度。。
由于shell对二维数组的支持较差,所以选择用给一个大数组来弄。然后取出每个block的范围。
由于不能用二维数组,eval在有的板卡上不能使用,导致用起来不是很顺手。
4,get log的问题。
log可能会很大,为了不占用太大的存储资源,因此使用循环buffer的方法。放弃了,循环buf收益比想象中小。主要是发现空间足够,爽。
具体实现:
#!/bin/bash
#### need null str check
sh_home=$(cd "$(dirname "$0")";pwd)
if [ -z "$1" ] ; then
echo "exec stdbuf -oL bash ${sh_home}/flow_show.sh start"
exec stdbuf -oL bash ${sh_home}/flow_show.sh start
fi
if [ "$1"x != "start"x ] ; then
echo "do not use any parm, please use like bash /bin/flow_show.sh"
exit
fi
tmp_dir=/home/fshow_tmp
cfg_file=/conf/cam/flow_show.cfg
cur_dev="bbu"
dev_name=("zkb" "bbu" "qck" "zpp")
dev_ip=("10.16.1.1" "10.16.1.10" "10.16.1.12" "10.16.1.13")
dev_usr=("root" "root" "root" "root")
dev_psw=("root" "root" "root" "root")
cfg_len=0
dev_log_path=("/mnt/log/cam.log" "/var/log/cam.log" "/flashDev/log/cam.log" "/flashDev/log/cam.log")
exit_flag=1
echo_time="\033[40;33;4m"
echo_ok="\033[40;32;1m"
echo_err="\033[40;31;5m"
echo_nofind="\033[40;37;2m"
echo_end="\033[0m"
echo "dev_name len ${#dev_name[*]}"
set -u
##${#array}计算数组长度
##func
#msg_type: time, nofind, hide, ok, err, info.
init_func()
{
rm -fr ${tmp_dir}
mkdir -p ${tmp_dir}
pkill -9 sshpass
pkill -9 tail
trap "echo ' trapped Ctrl+C' ; pkill -9 sshpass ; pkill -9 tail ; exit 0" SIGINT
echo "init; trapped Ctrl+C; pkill -9 sshpass; pkill -9 tail"
}
read_cfg()
{
local i=0
local tmp_line=""
local dev=0
for dev in ${dev_name[@]}
do
msg_type[$i]="time"
str_dev[$i]=${dev}
str_name[$i]="[${dev}_time]:"
msg_ok[$i]="init"
msg_err[$i]="init"
let i++
done
while read readline
do
tmp_line=`echo ${readline} | tr -d "\r\n"`
#echo readline $readline
#echo tmp_line $tmp_line
if [ "show"x == "${tmp_line: 0 : 4}"x ] ; then
msg_type[$i]="nofind"
elif [ "hide"x == "${tmp_line: 0 : 4}"x ] ; then
msg_type[$i]="hide"
elif [ "info"x == "${tmp_line: 0 : 4}"x ] ; then
msg_type[$i]="info"
str_name[$i]=${tmp_line: 6}
str_dev[$i]="init"
msg_ok[$i]="init"
msg_err[$i]="init"
echo "info $i ${str_name[$i]} "
let i++
continue
else
continue
fi
str_dev[$i]="${tmp_line: 6 : 3}"
tmp_str=${tmp_line: 11}
str_name[$i]=`echo $tmp_str | awk -F"##" '{print $1}'`
msg_ok[$i]=`echo $tmp_str | awk -F"##" '{print $2}'`
msg_err[$i]=`echo $tmp_str | awk -F"##" '{print $3}'`
#replace '.' '[' '-' '!' for grep
msg_ok[$i]=`echo ${msg_ok[$i]} | sed 's#\[#\\\[#g;s#\.#\\\.#g;s#\^#\\\^#g;s#\!#\\\!#g;s#\-#\\\-#g'`
msg_err[$i]=`echo ${msg_err[$i]} | sed 's#\[#\\\[#g;s#\.#\\\.#g;s#\^#\\\^#g;s#\!#\\\!#g;s#\-#\\\-#g'`
echo "log dev:${str_dev[$i]} name:${str_name[$i]};type:${msg_type[$i]};ok:${msg_ok[$i]};err:${msg_err[$i]}"
let i++
done < ${cfg_file}
cfg_len=$i
echo "read finished, cfg len ${cfg_len}"
return
}
get_log()
{
echo "${cur_dev} get_log start"
local loop_max=${#dev_name[@]}
let loop_max--
for i in $(seq 0 ${loop_max})
do
if [ "${cur_dev}"x == "${dev_name[${i}]}"x ] ; then
echo "tail -F ${dev_log_path[${i}]} >> ${tmp_dir}/${dev_name[${i}]}.log x"
tail -F ${dev_log_path[${i}]} | tr -s '\n' >> ${tmp_dir}/${dev_name[${i}]}.log &
else
echo "sshpass -p ${dev_psw[${i}]} ssh ${dev_usr[${i}]}@${dev_ip[${i}]} "tail -F ${dev_log_path[${i}]}" | tr -s '\n'"
sshpass -p ${dev_psw[${i}]} ssh ${dev_usr[${i}]}@${dev_ip[${i}]} "tail -F ${dev_log_path[${i}]}" | tr -s '\n' >> ${tmp_dir}/${dev_name[${i}]}.log &
fi
#touch ${tmp_dir}/${dev_name[${i}]}.show
done
}
update_show()
{
local dev=0
local i=0
local str_tmp=""
local log_ok_idx=0
local log_err_idx=0
local loop_start=${#dev_name[@]}
local loop_max=${cfg_len}
let loop_max--
while [ ${exit_flag} -ne 0 ]
do
i=0
##prepare show time
for dev in ${dev_name[@]}
do
if [ ! -f ${tmp_dir}/${dev}.log ] ; then
echo "can not find ${tmp_dir}/${dev}.log"
else
str_tmp="`tail -n 1 ${tmp_dir}/${dev}.log | awk '{print $1 " " $2}'`"
fi
if [ -z "${str_tmp}" ] ; then
str_tmp="connecting......"
fi
show_buf[$i]="${str_name[$i]} ${str_tmp}"
let i++
done
##prepare show msg
for i in $(seq ${loop_start} ${loop_max} )
do
if [ "${msg_type[$i]}"x = "info"x ] ; then
show_buf[$i]="${str_name[$i]}"
let i++
continue
fi
dev=${str_dev[$i]}
if [ ! -f ${tmp_dir}/${dev}.log ] ; then
show_buf[$i]="dev:${dev} not find"
let i++
continue
fi
show_buf[$i]="[${dev}] ${str_name[$i]} : no find......"
str_tmp=""
log_ok_idx=0
find_msg=${msg_ok[$i]}
str_tmp=`cat ${tmp_dir}/${dev}.log | grep -n "${find_msg}"|tail -n 1`
if [ ! -z "${str_tmp}" ] ; then
msg_type[$i]="ok"
log_ok_idx=`echo ${str_tmp} | cut -d ':' -f 1`
show_buf[$i]="[${dev}] ${str_name[$i]} : ${str_tmp}"
fi
str_tmp=""
log_err_idx=0
find_msg=${msg_err[$i]}
str_tmp=`cat ${tmp_dir}/${dev}.log | grep -n "${find_msg}"|tail -n 1`
if [ ! -z "${str_tmp}" ] ; then
log_err_idx=`echo ${str_tmp} | cut -d ':' -f 1`
if [ ${log_err_idx} -ge ${log_ok_idx} ] ; then
msg_type[$i]="err"
show_buf[$i]="[${dev}] ${str_name[$i]} : ${str_tmp}"
fi
fi
#echo -e "${show_buf[$i]}"
let i++
done
clear
##show
loop_max=${cfg_len}
let loop_max--
for i in $(seq 0 ${loop_max})
do
if [ "${msg_type[$i]}"x == "time"x ] ; then
echo -e "${echo_time} ${show_buf[i]} ${echo_end}"
elif [ "${msg_type[$i]}"x == "nofind"x ] ; then
echo -e "${echo_nofind} ${show_buf[i]} ${echo_end}"
elif [ "${msg_type[$i]}"x == "hide"x ] ; then
continue
elif [ "${msg_type[$i]}"x == "ok"x ] ; then
echo -e "${echo_ok} ${show_buf[i]} ${echo_end}"
elif [ "${msg_type[$i]}"x == "err"x ] ; then
echo -e "${echo_err} ${show_buf[i]} ${echo_end}"
elif [ "${msg_type[$i]}"x == "info"x ] ; then
echo "${show_buf[i]}"
else
echo "err msg_type[$i] num : ${msg_type[$i]} "
fi
done
echo ""
echo ""
echo ""
sleep 5
done
}
################################
##start main
################################
init_func
read_cfg
get_log
update_show
exit
LOG##fh_show_name3##module_log_show_default_ok##camnetlink.c:5
dev:zpp
LOG##start##start ok##start err
LOG##zpp_show_name2##camnetlink.c:1##camnetlink.c:3
LOG##zpp_show_name3##camnetlink.c:4##camnetlink.c:5
LOG##zpp show test4##camnetlink.c:9##camnetlink.c:8