星期三, 六月 13, 2007

logcheck hack for logfiles updated by rsync

由于硬件资源的限制,对于日志分析中日志的集中过程无法利用 syslog-ng 这样的远程记录方式来完成,否则网络带宽就会成为瓶颈,而日志丢包的问题又不知道如何解决。所以使用更稳妥的办法就是利用 rsync 来同步日志到一个中央的存储主机上,然后日志分析的工作比如 logcheck for log filter 就在这里集中进行。

然而遇到一个问题,就是 logcheck 调用 logtail 时,会记录原日志文件的 inode 和 size 到一个 offset_file,其格式是:
$inode
$last_size
下次 logcheck 运行时,首先通过文件大小(wc -c)和 offset_file 中的 $last_size 进行比较,如果 $current_size "<" $last_size,那么 logcheck 就判定已经做个轮转,于是转到 logfile.1,否则仍然检查 logfile,然后会调用 logtail,logtail 会应用那个 offset_file 到选定的日志文件,通过 $last_size 记录从上次结束的地方开始读取...

但是如果使用 rsync 同步日志时,这个 inode 不会被保留,每一次 rsync 之后,日志的 inode 都会被更改,导致 logcheck 无法正确找到上一次结束的位置。如果使用 cp -f,则 indoe 会被保留,但 cp 的开销太大,特别是对于日志这样的大文件。于是我对 logcheck 又做了一个小 hack,增加了一个 --rsynced 参数:
-y    = adjust the inode number if the logfiles are updated by rsync
最终的 patch 文件是这样的:
sh$ expand -t4 logcheck-1.2.45-rsynced.patch
diff -Naur logcheck-1.2.45.old/src/logcheck logcheck-1.2.45.new/src/logcheck
--- logcheck-1.2.45.old/src/logcheck 2006-07-06 18:16:42.000000000 +0800
+++ logcheck-1.2.45.new/src/logcheck 2007-06-15 10:50:34.000000000 +0800
@@ -49,7 +49,7 @@
ATTACK=0

# Set the getopts string
-GETOPTS="c:dhH:l:L:m:opr:RsS:tTuvw"
+GETOPTS="c:dhH:l:L:m:opr:RsS:tTuvwy"

# Get the details for the email message
DATE="$(date +'%Y-%m-%d %H:%M')"
@@ -90,6 +90,10 @@
LOCKDIR=/var/lock/logcheck
LOCKFILE="$LOCKDIR/logcheck"

+RSYNCED=0
+# If the logfiles are centralized by rsync, the inode number will not be
+# reserved, so add an option for this condition
+
# Carry out the clean up tasks
cleanup() {

@@ -208,7 +212,9 @@
mkdir $cleaned \
|| error "Could not make dir $cleaned for cleaned rulefiles."
fi
- for rulefile in $(run-parts --list $dir); do
+ debug "find $dir, pwd: `pwd`"
+ for rulefile in $(find $dir -type f -perm +0100); do
+ debug "rulefile $rulefile"
rulefile=$(basename $rulefile)
if [ -f ${dir}/${rulefile} ]; then
debug "cleanrules: ${dir}/${rulefile}"
@@ -406,6 +412,18 @@
fi
}

+rsynced() {
+ logfile=$1
+ offsetfile=$2
+ if [ $RSYNCED -eq 1 ]; then
+ debug "$logfile rsync specified"
+ new_inode=$(ls -i $logfile | awk '{print $1}')
+ old_inode=$(head -n1 $offsetfile)
+ debug "replace $old_inode with $new_inode"
+ sed -i "1s/^.*$/$new_inode/" $offsetfile
+ fi
+}
+
# Get the yet unseen part of one logfile.
logoutput() {
file=$1
@@ -415,11 +433,13 @@
if [ -f "$file" ]; then
offsetfile="$STATEDIR/offset$(echo $file | tr / .)"
if [ -s "$offsetfile" -a -r "$offsetfile" ]; then
+ rsynced $file $offsetfile
if [[ $(wc -c < "$file") -lt $(tail -n 1 "$offsetfile") ]]; then
# assume the log is rotated by savelog(8)
# syslog-ng leaves old files here
if [ -e "$file.0" -a "$file.0" -nt "$file.1.gz" ]; then
debug "Running logtail on rotated: $file.0"
+ rsynced $file.0 $offsetfile
$LOGTAIL -f "$file.0" -o "$offsetfile" $LOGTAIL_OPTS > \
$TMPDIR/logoutput/$(basename "$file") 2>&1 \
|| error "Could not run logtail or save output"
@@ -429,6 +449,7 @@
# should also probably check if file is still fresh
elif [ -e "$file.1" ]; then
debug "Running logtail on rotated: $file.1"
+ rsynced $file.1 $offsetfile
$LOGTAIL -f "$file.1" -o "$offsetfile" $LOGTAIL_OPTS > \
$TMPDIR/logoutput/$(basename "$file") 2>&1 \
|| error "Could not run logtail or save output"
@@ -452,7 +473,7 @@
debug "usage: Printing usage and exiting"
cat"<<"EOF
usage: logcheck [-c CFG] [-d] [-h] [-H HOST] [-l LOG] [-L CFG] [-m MAIL] [-o]
- [-r DIR] [-s|-p|-w] [-R] [-S DIR] [-t] [-T] [-u]
+ [-r DIR] [-s|-p|-w] [-R] [-S DIR] [-t] [-T] [-u] [-y]
-c CFG = override default configuration file
-d = debug mode
-h = print this usage information and exit
@@ -471,6 +492,7 @@
-u = enable syslog-summary
-v = print version
-w = use the "workstation" runlevel
+ -y = adjust the offset inode number if the logfiles are updated by rsync remotely
EOF
}

@@ -600,6 +622,10 @@
debug "Setting REPORTLEVEL to workstation"
REPORTLEVEL="workstation"
;;
+ y)
+ debug "Setting RSYNCED"
+ RSYNCED=1
+ ;;
\?)
usage
exit 1
这里没有使用长格式的参数 --rsynced,而是短格式 -y(-r/-s 都已经被使用了),因为 logcheck 使用的是 bash 内置 getopts 而不是 GNU 的 getopt,而 getopts 不支持长格式的参数,如果改为使用 getopt,则要更改的内容太多,所以最后还是选择一个比较简单的方法吧。

注意这里轮转后的日志由于也是 rsync 同步的,所以也要应用 rsynced() 函数对 inode 做调整。

另外,从 root 调用 logcheck 的时候,可以使用 sudo 或 su,但记得要更改 $HOME 目录,对 su,可以使用 su - logcheck,对 sudo,可以使用 sudo -u logcheck -H。如果不更改 $HOME,会导致 find 命令出错(find $dir -type f -perm +0100,就是前面为避免使用 run-parts --list 而做的一个小 hack):
find: cannot get current directory: Permission denied
所以调用脚本可以写成:
#!/bin/sh

PATH=$PATH:/usr/sbin
datadir=/data/hosts
echo "DEBUG: $datadir"

find $datadir -type f | xargs setfacl -m user:logcheck:4
find $datadir -type d | xargs setfacl -m user:logcheck:5
# sudo -u logcheck -H /usr/sbin/logcheck $@
su - logcheck -c "/usr/sbin/logcheck $@"

没有评论: