任务背景:

一个应用程序运行的时候,可能需要查看多个模块的状态,如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
 fiif [ "$1"x != "start"x ] ; then
     echo "do not use any parm, please use like bash /bin/flow_show.sh"
     exit
 fitmp_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