用 shell 脚本做 restful api 接口监控

问题的提出

基于历史原因,公司有一个“三无”采集服务——无人员、无运维、无监控——有能力做的部门不想接、接了的部门没能力。于是就一直这样裸奔,直到前几天一个依赖于这个采集服务的大数据分析服务入口流量锐减,才发现居然是这个采集服务出问题了!而且问题不是简单的挂掉,而是这个采集服务给客户端下发的采集策略中,产品列表为空了!当时事出紧急,把所有产品开关挨个打开了一遍,算是临时解决了这个问题。事后复盘这个问题,从问题出现、到问题被感知到、再到问题被临时解决,这中间消耗的时间太长了,在新的采集服务上线之前,需要随时监控老的采集服务的接口状态,一旦有问题就可以立即处理。

问题的解决

对于后台开发或自动化测试来说,搞个监控是分分钟的事,对于我们这种客户端开发就不一样了,如果用 c/c++ 写代码倒是可以实现,但是一来慢、二来不灵活、三也不值当。于是重操旧业,用 shell 脚本搞起!话说我用的是 Windows 系统,为了在上面跑 shell 脚本,事先装了一个 msys2 系统 —— git bash,这段之前很多文章涉及过了,就不再赘述,就是对我的开发环境做个简要交待。

 

环境有了,现在整理一下我的思路,我希望做的是:访问后台 restful api 接口,从返回的结果中得到开启的产品数量,如果数量小于某个值,就向相关人员发送报警邮件,并记录日志。每隔一小时检查一次。

检查接口返回内容

访问 restful api 一般是通过 http 协议,这里我们选取 curl 做为拉取工具,写脚本如下:

curl -s "http://***.******.***/v3/server_status?type=100&data_version=2.4"

出于安全考虑,这里域名被我用星号代替了,后面的两个 url 参数分别是请求的类型(100 表示获取产品列表)和当前协议版本号(2.4),如果一切正常的话,你会得到下面这一堆数据:

 

本身是个 json,有用的域是 message 字段,它本身又是加密的 (为毛不直接走 https?)。好吧,我们需要一个解密工具,对于客户端开发来说手到擒来,直接把测试用例改改就弄出来一个:

curl -s "http://***.******.***/v3/server_status?type=100&data_version=2.4" | ./jq -r ".message" | xargs ./test-decode

 

相比上面的语句,多了两个命令,其中 jq 是用来解析 json 的,负责将 message 字段提取出来,msys2 默认是不带这个命令的,可以访问以下网址获取:stedolan.github.com/jq/download/ ,安装后将命令所在目录添加到 PATH 环境变量、并重启系统后,就可以在 msys2 系统中使用 jq 了,不过我这里是直接把命令复制到了脚本所在目录,所以需要使用 ./jq 来指明;test-decode 就是我写的解密工具啦,它从命令行参数读取加密数据 (所以需要 xargs 进行转换,不然可以直接用管道线连接啦),将解密后的数据输出到标准输出。经过上面的处理,这一坨数据就能被人类所识别了:

 

在网页里显示会自动换行,实际上这段输出只有两行,而第二行才是我们需要的。提取第二行后交给 jq 解析出 products 域中的产品数据:

curl -s "http://***.******.***/v3/server_status?type=100&data_version=2.4" | ./jq -r ".message" | xargs ./test-decode | tail -1 | ./jq ".products|.[]"

 

其中 jq ”.products|.[]“ 将把外层的元素去掉,对剩下的“纯纯“的内容进行 beautify 处理:

复制代码
{
  "id": 140,
  "name": "GrandDog",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": true
}
{
  "id": 178,
  "name": "CubicostTRB",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": true
}
{
  "id": 78,
  "name": "GTJ2017",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": true
}
{
  "id": 137,
  "name": "GMD2017",
  "aggre_status": true,
  "start": true,
  "enable_auto": false,
  "enable_filter": true
}
{
  "id": 180,
  "name": "GDraw",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": true
}
{
  "id": 276,
  "name": "GLC",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": true
}
{
  "id": 164,
  "name": "GUX",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": true
}
{
  "id": 67,
  "name": "GCCP5",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": true
}
{
  "id": 261,
  "name": "GCCP6",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": true
}
{
  "id": 17,
  "name": "TME",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 25,
  "name": "GWS",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 36,
  "name": "MOZIDIFFER",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 40,
  "name": "GMJ",
  "aggre_status": true,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 44,
  "name": "GCL2013",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 45,
  "name": "GGJ2013",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 56,
  "name": "MD_GMA",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 75,
  "name": "GDQ2015",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 76,
  "name": "GQI2017",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 77,
  "name": "GJG2015",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 80,
  "name": "GMP2016",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 83,
  "name": "Revit2GFC4GMP",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 100,
  "name": "GTJ2017CAD",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 112,
  "name": "GYZB2017",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 114,
  "name": "BIM5D_PC",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 115,
  "name": "GFYCM",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 125,
  "name": "GBCB",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 128,
  "name": "CubicostTAS",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 129,
  "name": "GMD",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 131,
  "name": "GAQ2017",
  "aggre_status": true,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 132,
  "name": "GBCB2017",
  "aggre_status": true,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 133,
  "name": "GBS2017",
  "aggre_status": true,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 134,
  "name": "GFYC2017",
  "aggre_status": true,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 135,
  "name": "GFYCM2017",
  "aggre_status": true,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 136,
  "name": "GMJ2017",
  "aggre_status": true,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 138,
  "name": "GSJ2017",
  "aggre_status": true,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 139,
  "name": "GJH2017",
  "aggre_status": true,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 142,
  "name": "TeamViewer",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 148,
  "name": "ZPert",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 160,
  "name": "GBS",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 162,
  "name": "GIR_C",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 163,
  "name": "TBQ2017",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 167,
  "name": "GYJC2017",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 177,
  "name": "GSXGZT2016",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 181,
  "name": "TBQD",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 182,
  "name": "TTED",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 183,
  "name": "TCFD",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 188,
  "name": "GSCApp",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 200,
  "name": "GFYC",
  "aggre_status": true,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 207,
  "name": "GDQ2017",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 217,
  "name": "GO",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 218,
  "name": "AppGbmp",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 222,
  "name": "GQI2018",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 226,
  "name": "GDS2017",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 228,
  "name": "GLDTCS",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 231,
  "name": "TenderGo",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 232,
  "name": "GDQ2018",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 233,
  "name": "SectionManual",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 234,
  "name": "BeamGo",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 235,
  "name": "GJG2018",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 236,
  "name": "RevitViewer",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 237,
  "name": "BIM5D_PC_TEST",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 238,
  "name": "BIM5D_PC_TRIAL",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 239,
  "name": "GEC5",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 240,
  "name": "GFYQ",
  "aggre_status": true,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 241,
  "name": "RoadDesigner",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 242,
  "name": "CECS100G",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 243,
  "name": "GBES",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 244,
  "name": "Ceshi",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 245,
  "name": "dpUpdate",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 246,
  "name": "GFY4",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 248,
  "name": "GGPT",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 249,
  "name": "GMA2020",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 250,
  "name": "JZYK",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 251,
  "name": "GVB5",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 252,
  "name": "GHW5",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 253,
  "name": "GUp",
  "aggre_status": true,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 254,
  "name": "BIM_COST",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 255,
  "name": "GICP5",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 256,
  "name": "bim5d_basic",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 257,
  "name": "GWH5",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 258,
  "name": "GFY4_2019",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 259,
  "name": "GDD2019",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 260,
  "name": "GCCP5_ShanDong_64",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 262,
  "name": "GSC6",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 263,
  "name": "GCCP6_WP",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 264,
  "name": "GEB6",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 265,
  "name": "GSH6",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 266,
  "name": "GTech2019",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 267,
  "name": "GPC5",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 268,
  "name": "GTJ2021",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 269,
  "name": "GDE2019",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 270,
  "name": "CubicostTIO",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 271,
  "name": "GCA5",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 272,
  "name": "GLC5",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 273,
  "name": "GMT5",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 274,
  "name": "GCN5",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 275,
  "name": "GHC5",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 277,
  "name": "GVB6",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 278,
  "name": "GJG2021",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 279,
  "name": "GJG",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 280,
  "name": "GAP",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 281,
  "name": "GSTP",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 283,
  "name": "TRS2021",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 284,
  "name": "TMEC",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 285,
  "name": "CubicostTMEC",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 286,
  "name": "GGF5",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 287,
  "name": "GRE5",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
{
  "id": 310,
  "name": "GA_CloudPlugin",
  "aggre_status": false,
  "start": true,
  "enable_auto": false,
  "enable_filter": false
}
View Code
复制代码

 

然后就可以得到下发产品列表中的产品数量了:

1 lines=$(curl -s "http://***.******.***/v3/server_status?type=100&data_version=2.4" | ./jq -r ".message" | xargs ./test-decode | tail -1 | ./jq ".products|.[]" | wc -l)
2 # each item takes 8 line
3 prods=$(($lines/8))
4 echo "product count : $prods" >> log.txt

 

没有找到 jq 怎么输出 json 数组元素个数,这里直接用 wc 计算行数除以 8 (每个产品占用 8 行)得到。同理得到密钥的数量(每个产品一个单独的密钥):

1 lines=$(curl -s "http://***.******.***/v3/server_status?type=101&data_version=2.4" | ./jq -r ".message" | xargs ./test-decode | tail -1 | ./jq ".keys|.[]" | wc -l)
2 # each item takes 4 line
3 keys=$(($lines/4))
4 echo "keys count : $keys" >> log.txt

 

这两个数量必需一致,且大于某个域值,这里设定为 100.

复制代码
 1 limit=100
 2 if [ $prods -ne $keys ]; then 
 3     echo "product count != key count, fatal error" >> log.txt
 4     send_email "$prods" "$keys" "$limit"
 5     exit
 6 fi
 7 
 8 if [ $prods -lt $limit ]; then 
 9     echo "products list too less, warning" >> log.txt
10     send_email "$prods" "$keys" "$limit"
11     exit
12 fi
13 
14 echo "gux server ok" >> log.txt
复制代码

 

如果有任何异常发生,通过 send_mail 函数来向相关人员发送告警邮件。

异常情况下发送报警邮件

铛铛铛~ 进入本文核心,现在如果接口中的产品数据为空或减少到特定值(产品一般只增加不减少),就需要通过邮件通知相关责任人了。这里使用的是 sendmail 命令,在 msys2 环境中,没有自带这个命令,需要事先安装 Windows 版本的压缩包、并设置 PATH 环境变量包含该命令所在的目录,重启机器后就可以在 msys2 环境中直接访问了。这里给一个下载链接:www.glob.com.au/sendmail/sendmail.zip。注意这个命令只是模拟 sendmail -t 选项,并不是原生的 sendmail 命令,但是用法和原生命令没什么区别。在开始看 send_email 函数之前,让我们先了解一些基本原理。

邮箱工作原理

你通过 qq 的账号给 126 发了一封邮件,它经历了哪些流程呢?请看下图

 

抛开复杂的协议不谈,单说整个流程,就是你先通过邮件客户端把邮件发到 qq 的邮件服务器,后者通过地址路邮给 126 的邮件服务器,最后你的邮件客户端再去后者的邮件服务器里拉取收到的邮件。对于 sendmail 命令而言,最主要就是进行发送账号的各种配置。

配置发送账号

在配置发送账号前,我们先选择一个可靠的账号,这里我选取 QQ(不要问为什么,问就是请参考附录)。首先登录 mail.qq.com

 

在邮箱的设置中找到账户相关设置,开通 IMAP/SMTP 服务:

 

开启后可以获取授权码,一个账户可以获取多个授权码,用在不同应用里:

 

出于安全考虑,这里把授权码隐掉了。

配置 sendmail 命令

有了账号和授权码就可以配置 sendmail 命令啦,下面打开命令目录内的 sendmail.ini 文件,加入以下几行:

复制代码
 1 ; configuration for fake sendmail
 2 
 3 ; if this file doesn't exist, sendmail.exe will look for the settings in
 4 ; the registry, under HKLM\Software\Sendmail
 5 
 6 [sendmail]
 7 
 8 ; you must change mail.mydomain.com to your smtp server,
 9 ; or to IIS's "pickup" directory.  (generally C:\Inetpub\mailroot\Pickup)
10 ; emails delivered via IIS's pickup directory cause sendmail to
11 ; run quicker, but you won't get error messages back to the calling
12 ; application.
13 
14 smtp_server=smtp.qq.com
15 
16 ; smtp port (normally 25)
17 
18 smtp_port=25
19 
20 ; SMTPS (SSL) support
21 ;   auto = use SSL for port 465, otherwise try to use TLS
22 ;   ssl  = alway use SSL
23 ;   tls  = always use TLS
24 ;   none = never try to use SSL
25 
26 smtp_ssl=auto
27 
28 ; the default domain for this server will be read from the registry
29 ; this will be appended to email addresses when one isn't provided
30 ; if you want to override the value in the registry, uncomment and modify
31 
32 ;default_domain=mydomain.com
33 
34 ; log smtp errors to error.log (defaults to same directory as sendmail.exe)
35 ; uncomment to enable logging
36 
37 error_logfile=error.log
38 
39 ; create debug log as debug.log (defaults to same directory as sendmail.exe)
40 ; uncomment to enable debugging
41 
42 ;debug_logfile=debug.log
43 
44 ; if your smtp server requires authentication, modify the following two lines
45 
46 auth_username=2057975342@qq.com
47 auth_password=*****************
48 
49 ; if your smtp server uses pop3 before smtp authentication, modify the 
50 ; following three lines.  do not enable unless it is required.
51 
52 pop3_server=
53 pop3_username=
54 pop3_password=
55 
56 ; force the sender to always be the following email address
57 ; this will only affect the "MAIL FROM" command, it won't modify 
58 ; the "From: " header of the message content
59 
60 force_sender=
61 
62 ; force the sender to always be the following email address
63 ; this will only affect the "RCTP TO" command, it won't modify 
64 ; the "To: " header of the message content
65 
66 force_recipient=
67 
68 ; sendmail will use your hostname and your default_domain in the ehlo/helo
69 ; smtp greeting.  you can manually set the ehlo/helo name if required
70 
71 hostname=
复制代码

 

文中标黄的就是我们要加入的配置,这里授权码同样被我隐掉了(哎呀,邮箱暴露了~)。命令配置好了以后,就可以在 shell 脚本里调用 sendmail 命令了

使用 sendmail 命令发送邮件

终于可以回到我们之前提到的 send_mail 函数了,它有三个参数,分别是产品数量、密钥数量和最小限制值:

复制代码
 1 function send_email() 
 2 {
 3     pc=$1 # product count
 4     kc=$2 # key count
 5     lm=$3 # count limit
 6     from="2057975342@qq.com"
 7     # for multiple receiver, not work
 8     #to="yunh@******.com;anlj@******.com"
 9     # using an array of receivers
10     to=("yunh@******.com" "anlj@******.com" "yuyf-a@******.com" "duanxd@******.com" "zhangcj-c@******.com" "linc@******.com" "sunyd@******.com" "zhangb-l@******.com")
11     subject="gux server exception"
12     content="gux restful api exception: \nproducts: $pc \nkeys: $kc \n\nproducts count != keys count \nor products count < $lm !!\n\n\n"
13     mail="From: 'gux monitor' <$from>\nSubject: $subject\n\n$content"
14 
15     for var in ${to[@]};
16     do
17         mail=$(echo -e "To: <$var>\n$mail")
18     done
19     echo -e "$mail" >> mail.txt
20     echo -e "$mail" | sendmail -t
21 }
复制代码

 

获取各个参数 (line 3~5) 用于后续拼接邮件正文 (line 12),对于 sendmail 命令来说 (line 20) 使用 -t 参数后允许将所有参数通过一个格式化的文本来设置,这个格式类似这样:

Subject: title
From: sender@mail.com
To: receiver@mail.com
Cc: copy@mail.com

body...

 

由两部分组成,邮件标题由 Subject(抬头)、From(发件人)、To(收件人)、Cc(抄送)……组成,两个空行之后是邮件正文。上面大段代码都是在设置这些内容 (line 6 ~ 13),注意为了减少对我 qq 账号的关注,这里对 From 域设置了别名 ‘gux monitor’,这样收到邮件就比较直观啦。下面是一次真实的报警邮件:

 

另外对于群发,不能简单的在 To 域设置多个接收人,而是要分别对每个接收人进行一次单独的 To 域设置(允许多个 To 域),这里使用 shell 数组对收件人进行了遍历处理 (line 10,15 ~ 18),最后一次性发送 (line 20),下面是打印到 mail.txt 里的邮件原文:

复制代码
To: <zhangb-l@******.com>
To: <sunyd@******.com>
To: <linc@******.com>
To: <zhangcj-c@******.com>
To: <duanxd@******.com>
To: <yuyf-a@******.com>
To: <anlj@******.com>
To: <yunh@******.com>
From: 'gux monitor' <2057975342@qq.com>
Subject: gux server exception

gux restful api exception: 
products: 0 
keys: 0 

products count != keys count 
or products count < 100 !!
复制代码

 

注意由于遍历时是向邮件头添加 To 域,整个收件人列表是呈倒序排列的。然后效果就是上面截图的啦~

设置定时任务

最后就是把这个脚本设置成定时执行的任务了,这块可以参考之前写过的一篇文章 《查看博客园积分与排名趋势图的工具 》,第 3 节。下面是经过一段时间后,脚本输出的日志:

输出太长,中间有省略。可以观察到 7.13 日 14:00 有一次报警 (不过后来证明是一次乌龙,汗~)

结语

后来查出问题的根因,居然是采集服务在和产品中心同步产品时(管理员登录的情况下),如果后者恰巧挂了,会导致前者同步不到数据,就会把所有产品开关重置掉!一个隐藏了 3+ years 的 bug 啊,教训深刻。不过话说回来,不管代码怎么 low,接口监控是不可少的。除了用来作接口监控,我还用 shell 脚本给其它服务做简单测试,例如验证升级服务能否正常下发版本、验证用户中心能否正常登录等等,凡是通过 restful api 提供服务的,基本可以通过 curl + jq 搞定,甚至通过 tcp 长连接实现的消息推送服务也可以用 shell 脚本来验证。不过这一系列的内容,因为涉及接口安全,我没法提供 git 下载地址(压根没有相应的 git 库),望大家理解,有需要的同学可以照猫画虎,把里面的接口换成自己能访问的,来动手验证一下。

参考

[1]. 在windows下配置sendmail服务器

[2]. Using Sendmail on Windows

[3]. Linux简单配置SendMail发送邮件

[4]. linux shell 发送email 邮件

[5]. 不可或缺的 sendEmail

[6]. 配置mail命令的IMAP和SMTP,接收邮件和发送邮件

[7]. shell 发邮件命令之 sendmail

[8]. jq : Linux下json的命令行工具

[9]. 使用jq工具在Shell命令行处理JSON数据

[10]. 命令行 JSON 处理工具 jq 的使用介绍

[11]. shell脚本处理JSON数据工具jq

[12]. HowTo: Install jq

[13]. https://github.com/stedolan/jq

posted @   goodcitizen  阅读(8221)  评论(1编辑  收藏  举报
编辑推荐:
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· DeepSeek 解答了困扰我五年的技术问题
· 为什么说在企业级应用开发中,后端往往是效率杀手?
阅读排行:
· 10亿数据,如何做迁移?
· 推荐几款开源且免费的 .NET MAUI 组件库
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· c# 半导体/led行业 晶圆片WaferMap实现 map图实现入门篇
· 易语言 —— 开山篇
点击右上角即可分享
微信分享提示