客观的
因此,我编写了一个脚本,用于记录日志并在日志达到一定大小时轮换日志。我希望捕获其他命令的所有错误和输出,而不是我故意回显到日志中的错误和输出。
我实现这一目标的方法是将 STDIN 和 STDOUT 重定向到tee
进程替换中,并编写了一个用于savelog
轮换日志的小函数。
#!/bin/bash
LOGFILE="/var/log/ps.log"
exec > >(tee "$LOGFILE") 2>&1
LOGPATH="/var/log"
MAX_LOGFILE_SIZE=5
rotateLog() {
currentsize=$(du -k $LOGFILE | cut -f1)
if [ $currentsize -ge $MAX_LOGFILE_SIZE ]; then
savelog -dn $LOGFILE &>/dev/null
fi
}
while :; do
echo "A computer program can easily produce gibberish - especially if it has been provided with garbage beforehand. This program does something a little different. It takes a block of text as input and works out the proportion of characters within the text according to a chosen order. For example, an order of 2 means the program looks at pairs of letters, an order of 3 means triplets of letters and so on. The software can regurgitate random text that is controlled by the proportion of characters. The results can be quite surprising."
rotateLog
sleep 5
done
问题
问题是,即使日志ps.log
旋转到后ps.log.20180829131658
,所有日志仍会写入ps.log.20180829131658
并引发以下错误:
du: 无法访问 '/var/log/ps.log': 没有这样的文件或目录 ./ps.sh: 第 12 行: [: -ge: 需要一元运算符
导致日志不再进一步旋转!
假设
我假设一旦ps.log
旋转到,就会创建ps.log.20180829131658
一个新的日志文件。ps.log
但情况绝对不是这样,因为exec
只执行一次(谁能解释一下这里到底发生了什么?) 即在脚本的开头。
观察
>(tee "$LOGFILE")
我还观察到,为该点创建了一个新的文件描述符,/var/log/p.log
并将其重新分配给p.log.20180829131658
!这显然导致日志仍然被写入p.log.20180829131658
。谁能解释一下这种行为吗?
root@b537ccc2c1ab:/var/log# ls -lrt
-rw-r--r-- 1 root root 7248 Aug 29 13:16 ps.log
root@b537ccc2c1ab:/var/log# ls -lrt /proc/*/fd
/proc/8979/fd:
total 0
l-wx------ 1 root root 64 Aug 29 13:16 3 -> /var/log/ps.log
lrwx------ 1 root root 64 Aug 29 13:16 2 -> /dev/pts/17
lrwx------ 1 root root 64 Aug 29 13:16 1 -> /dev/pts/17
lr-x------ 1 root root 64 Aug 29 13:16 0 -> pipe:[3889791]
root@b537ccc2c1ab:/var/log# ls -lrt
-rw-r--r-- 1 root root 11098 Aug 29 13:17 ps.log.20180829131658
root@b537ccc2c1ab:/var/log# ls -lrt /proc/*/fd
/proc/8979/fd:
total 0
l-wx------ 1 root root 64 Aug 29 13:16 3 -> /var/log/ps.log.20180829131658
lrwx------ 1 root root 64 Aug 29 13:16 2 -> /dev/pts/17
lrwx------ 1 root root 64 Aug 29 13:16 1 -> /dev/pts/17
lr-x------ 1 root root 64 Aug 29 13:16 0 -> pipe:[3889791]
如何通过这种日志记录和日志轮换方案实现我的目标?特别是,除了日志轮换之外,我的脚本如何写入较新的日志文件,同时捕获脚本中所有其他命令的错误和输出?
答案1
一旦进程打开文件,您就可以对该文件执行各种操作,例如重命名、截断甚至删除它,但进程仍会打开该文件。一个常见的错误是,当日志文件用尽所有磁盘空间时,人们会删除该文件以试图释放空间。但是,写入日志文件的进程仍将其打开,因此空间不会被释放。只有当进程关闭文件时,分配给该文件的块才会被释放。(这里的解决方法是截断文件,即> logfile
。)
在您的情况下,您已重命名该文件,但写入该文件的进程不知道或不关心这一点。
该logcheck
实用程序有一个copytruncate
针对此类情况的选项:它将日志文件复制到轮换名称,然后截断原始日志文件。你可以做同样的事情:
rotateLog() {
currentsize=$(du -k $LOGFILE | cut -f1)
if [ $currentsize -ge $MAX_LOGFILE_SIZE ]; then
ROTATEDLOG=$LOGFILE.$(date +%Y%m%d%H%M%S)
cp -p $LOGFILE $ROTATEDLOG && true > $LOGFILE
fi
}
更好的替代方案是修改进程以理解例如关闭并重新打开日志文件的 SIGHUP 信号。请参阅 shelltrap
命令来处理此问题。
答案2
基于@wurtel建议的替代解决方案,我让它像这样工作,
#!/bin/bash
LOGFILE="/root/logr/simple.log"
function sighuphandler() {
exec > >(tee "$LOGFILE") 2>&1
}
trap sighuphandler SIGHUP
LOGPATH="/root/logr"
MAX_LOGFILE_SIZE=5
rotateLog() {
currentsize=$(du -k $LOGFILE | cut -f1)
if [ $currentsize -ge $MAX_LOGFILE_SIZE ]; then
savelog -dn $LOGFILE &>/dev/null
kill -s SIGHUP $$
fi
}
sighuphandler
while :; do
echo "[`date "+%Y-%m-%d %H:%M:%S"`] [INFO] - A computer program can easily produce gibberish - especially if it has been provided with garbage beforehand. This program does something a little different. It takes a block of text as input and works out the proportion of characters within the text according to a chosen order. For example, an order of 2 means the program looks at pairs of letters, an order of 3 means triplets of letters and so on. The software can regurgitate random text that is controlled by the proportion of characters. The results can be quite surprising."
ls +
rotateLog
sleep 5
done