日志模块2.0代码放在git:
具体实现方法
改造Warn函数
日志模块1.0的Warn函数实现了2个功能:记录日志和退出脚本。2.0中用Shell特性分别实现。
- 1.0手工判断脚本执行是否出错,2.0使用
bash -e
开启错误检测模式。任何一行命令的返回值$?
不为0,都会认为出错并退出。
- 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 "这行不会被执行到"
注册回调函数并触发,分析一下过程
- 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 "这行有可能会被执行到"
可以看到EXITTRAP在脚本结束时执行,不关心结束原因是不是报错。
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点亮的技能树,不单独占篇幅了