日志模块2.0代码放在git:


具体实现方法


改造Warn函数

日志模块1.0的Warn函数实现了2个功能:记录日志和退出脚本。2.0中用Shell特性分别实现。


  1. 1.0手工判断脚本执行是否出错,2.0使用bash -e开启错误检测模式。任何一行命令的返回值$?不为0,都会认为出错并退出。


  1. trap命令可以捕捉到上述错误,可以像注册回调函数一样,让trap调用函数记录日志
#!/bin/bash
LogsDIR=logs
LogFile=output.log
mkdir -p ${LogsDIR}
PS4=' [\d \t] [PID=$$] [UID=${UID}] [${FUNCNAME[0]}:${LINENO}] '
exec 2>>${LogsDIR}/${LogFile}
set -x

# 开启错误调试
set -e

# 后续每一行$?不为0时脚本退出

function ERRTRAP {
    echo "[LINE:$1] Execute FAIL: [${BASH_COMMAND}] exited with status $?"
}

# ERRTRAP函数注册给trap ERR
# 出错时先执行 "ERRTRAP 当前行号" 再退出脚本
trap 'ERRTRAP ${LINENO}' ERR

# ls不存在的文件,脚本报错退出
ls -l /not_exist

echo "这行不会被执行到"

Shell脚本日志模块 - 实现日志模块2.0 (3) _Shell

注册回调函数并触发,分析一下过程

  • ls不存在的文件的$?值是2,由于开启set -e,脚本需要立即退出
  • 由于有trap ERR,脚本退出需要调用ERRTRAP,出错的行号${LINENO}作为参数传给ERRTRAP
  • ${BASH_COMMAND} 是内置变量

至此我们把Warn函数改造完成,无需再判断$?,脚本出错也会及时停止执行。


改造Final函数

使用trap还可以在脚本退出时执行函数,需要捕获EXIT并写对应函数。

#!/bin/bash
LogsDIR=logs
LogFile=output.log
mkdir -p ${LogsDIR}
PS4=' [\d \t] [PID=$$] [UID=${UID}] [${FUNCNAME[0]}:${LINENO}] '
exec 2>>${LogsDIR}/${LogFile}
set -xe

function ERRTRAP {
    echo "[LINE:$1] Execute FAIL: [${BASH_COMMAND}] exited with status $?"
}

trap 'ERRTRAP ${LINENO}' ERR

function EXITTRAP {
    echo "脚本执行结束"
}

trap 'EXITTRAP' EXIT

# ls不存在的文件,脚本报错退出
# 给脚本传个参数触发,如bash exit.sh param
[[ $# -eq 1 ]] && ls -l /not_exist

echo "这行有可能会被执行到"

Shell脚本日志模块 - 实现日志模块2.0 (3) _Shell_02

可以看到EXITTRAP在脚本结束时执行,不关心结束原因是不是报错。

Shell脚本日志模块 - 实现日志模块2.0 (3) _Bash_03

EXITTRAP借助trap实现了日志模块1.0的Final函数,且不再需要主动调用。trap更主要的用途是捕获并处理脚本运行中收到的各种信号。


捕获并处理信号

脚本进程只有被kill -9时才直接退出,不会执行trap设置的任何函数,因为产生的SIGKILL信号不可被捕捉和忽略。脚本会按默认和自定义的设置处理信号,之前写过几个常见用途:


  • 用户无意按到键盘Ctrl + C,脚本可以忽略信号或记日志后退出,避免脚本用户和开发者对本次执行结果产生争议
  • 其他用户直接kill脚本进程,处理方法同上
  • USR1和USR2预留给用户自行处理,例如Nginx处理USR2时会重新加载二进制,平滑重启Nginx

处理SIGINT(Ctrl+C)时会产生$?值130,所以需要考虑:要么忽略SIGINT,要么在ERRTRAP中专门处理$?值130

#!/bin/bash
LogsDIR=logs
LogFile=output.log
mkdir -p ${LogsDIR}
PS4=' [\d \t] [PID=$$] [UID=${UID}] [${FUNCNAME[0]}:${LINENO}] '
exec 2>>${LogsDIR}/${LogFile}
set -xe

function INTTRAP {
    echo "用户按 Ctrl+C"
}
trap 'INTTRAP' SIGINT

function TERMTRAP {
    echo "kill进程 $$"
    exit 0
}
trap 'TERMTRAP' SIGTERM

总结日志模块2.0

总结一下写日志模块2.0点亮的技能树,不单独占篇幅了

Shell脚本日志模块 - 实现日志模块2.0 (3) _Shell_04