背景

服务器有敏感数据,不允许直接登陆服务器查看日志文件,也不允许使用如ELK等日志功能收集日志,所以只能使用最简单的办法,只将错误日志进行收集,然后通过应用的webhook进行收集。

思路

两种思路:

  1. 周期轮询
  2. 实时抓取

周期轮询

每隔一分钟去抓取一次,错误日志的内容上送webhook。
这样的好处是不会抓到过多的错误日志,但是也有个问题,有可能会错过关键的错误日志。
假如每一分钟一轮询,这一次查询刚好没有错误日志产生,而这一个轮询时刻的一分钟内产生了错误日志,就会错过。

如果到到轮询时刻去统计这一分钟到上一分钟之间的错误日志,是可以,但是如果错误在轮询完成后的这一刻发生,需要等到一分钟之后才会告警出来,缺乏实时性,如果对实时性要求不高可以使用这种方式。

实时抓取

这个思路很简单,就是实时抓取ERROR日志,有ERROR就推送webhook。
实现思路:

  1. 使用 tail 查询日志
  2. 倒序获取第一条
  3. 关键字可指定
  4. 过滤关键字

下面这个脚本实现以上的几个思路,算是一种简单的实现,我一直觉得脚本这东西不要写的太复杂,需要考虑后面的人维护的成本。另外脚本尽量使用python而不是shell,python更好维护,也利于扩展。写shell是因为历史原因。

三个关键的文件
errorword.txt 是错误关键字
exclude.txt 是排除的关键字
error_test.log 是错误日志

如果测试的话,使用 echo >> 重定向进去,如果是使用vim编辑保存的话,tail 会抓会量日志,不是脚本有问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!/bin/bash
IP=`echo $(curl -s ifconfig.me)`

ERROR_WORD=/data/errorword.txt
EXCLUDE=/data/exclude.txt
SLACK=https://hooks.slack.com/services/test_webhook
LOG_DIR=/Users/liukai/workspaces/temp/shell/alert/error_test.log

tail -Fn0 $LOG_DIR| \
while read line;do
echo $line | grep -i -f $ERROR_WORD >/dev/null
if [ $? -eq 0 ];then
echo $line | grep -i -f $EXCLUDE >/dev/null
## white list
if [ $? -gt 0 ]; then
new_line=`echo $line | sed s/\"//g`
message='{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "服务异常日志, IP: '"$IP"' \n>'$new_line'\n"
}
}
]
}'
alert=`echo $message | sed s/\'//g`
#echo "$IP,$(date),$line" >> /Users/liukai/workspaces/temp/shell/alert/error_report.log
curl -H "Content-Type: application/json" -X POST -d "$alert" $SLACK
fi
fi
done

改进版本

让任务在后台执行,上一个版本是用来验证这个功能,实际使用当中需要放到后台当中持续运行。
使用方式:

sh alert.sh start | stop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#!/bin/bash
IP=`echo $(curl -s ifconfig.me)`
OPT=$1
ALERT_DIR=/data/alert
ERROR_WORD=$ALERT_DIR/errorword.txt
EXCLUDE=$ALERT_DIR/exclude.txt
LOG_DIR=/data/logs/tron.log
SLACK=https://hooks.slack.com/services/test_webhook

start() {
nohup tail -Fn0 $LOG_DIR| \
while read line;do
echo $line | grep -i -f $ERROR_WORD >/dev/null
if [ $? -eq 0 ];then
echo $line | grep -i -f $EXCLUDE >/dev/null
## white list
if [ $? -gt 0 ]; then
new_line=`echo $line | sed s/\"//g`
message='{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "服务异常日志, IP: '"$IP"' \n>'$new_line'\n"
}
}
]
}'
alert=`echo $message | sed s/\'//g`
curl -H "Content-Type: application/json" -X POST -d "$alert" $SLACK
fi
fi
done &
}

stop() {
kill -9 `ps -ef |grep 'tail -Fn0'|grep -v 'grep' |awk {'print $2'}`
}

if [[ $OPT == 'start' ]]; then
start
if [[ !? -eq 0 ]]; then
echo "start alert"
else
echo "start fail"
fi
elif [[ $OPT == 'stop' ]];then
stop
echo "stop alert"
fi

总结

尽量使用简单的脚本,不要让脚本变的复杂。