我想捕获UNIX进程的输出,但限制最大文件大小和/或旋转到一个新文件。
我见过logrotate,但它不能实时工作。根据我的理解,这是一个并行运行的"清理"工作。
正确的解决方案是什么?我想我会写一个小脚本来做这件事,但我希望有一个简单的方法与现有的文本工具。
想象:
my_program | tee——max-bytes 100000 log/my_program_log
会给…总是将最新的日志文件写为:日志/my_program_log
然后,当它填充…
use split:
my_program | tee >(split -d -b 100000 -)
或者,如果你不想看到输出,你可以直接管道分割:
my_program | split -d -b 100000 -
至于日志旋转,coretils中没有自动完成它的工具。您可以创建一个符号链接,并使用bash命令定期更新它:
while ((1)); do ln -fns target_log_name $(ls -t | head -1); sleep 1; done
在apache2-utils
包中有一个名为rotatelogs
的实用程序,它完全满足您的要求。
剧情简介:
rotatelogs [- l] [- l 链接名] [p 程序][f] [t] [v] [e] [c] [n 文件数量)日志文件 rotationtime | 文件大小 (B K | | | G) (抵消)
的例子:
your_program | rotatelogs -n 5 /var/log/logfile 1M
您可以在这个链接上阅读完整的手册
或者使用awk
program | awk 'BEGIN{max=100} {n+=length($0); print $0 > "log."int(n/max)}'
它将行放在一起,所以最大值不是精确的,但这对于日志目的来说可能很好。您可以使用awk的sprintf来格式化文件名。
下面是一个可执行的脚本,使用awk#!/bin/bash
maxb=$((1024*1024)) # default 1MiB
out="log" # output file name
width=3 # width: log.001, log.002
while getopts "b:o:w:" opt; do
case $opt in
b ) maxb=$OPTARG;;
o ) out="$OPTARG";;
w ) width=$OPTARG;;
* ) echo "Unimplented option."; exit 1
esac
done
shift $(($OPTIND-1))
IFS='n' # keep leading whitespaces
if [ $# -ge 1 ]; then # read from file
cat $1
else # read from pipe
while read arg; do
echo $arg
done
fi | awk -v b=$maxb -v o="$out" -v w=$width '{
n+=length($0); print $0 > sprintf("%s.%0.*d",o,w,n/b)}'
保存到一个名为'bee'的文件中,运行' chmod +x bee
',你可以使用它作为
program | bee
或将现有文件分割为
bee -b1000 -o proglog -w8 file
要将大小限制为100字节,只需使用dd:
my_program | dd bs=1 count=100 > log
当写入100字节时,dd将关闭管道,my_program接收EPIPE。
解决这个问题最直接的方法可能是使用python和专门为此目的设计的日志模块。创建一个从stdin
读取并写入stdout
的脚本,并实现下面描述的日志轮换。
"logging"模块提供了
class logging.handlers.RotatingFileHandler(filename, mode='a', maxBytes=0,
backupCount=0, encoding=None, delay=0)
做的正是你想要的。
从docs.python.org您可以使用maxBytes和backupCount值来允许文件以预定的大小滚动。
有时您想让日志文件增长到一定大小,然后打开一个新文件并记录该文件。您可能希望保留一定数量的这些文件,并且当创建了那么多文件时,请旋转这些文件,以便文件的数量和文件的大小都保持有限。对于这种使用模式,日志包提供了一个RotatingFileHandler:
import glob
import logging
import logging.handlers
LOG_FILENAME = 'logging_rotatingfile_example.out'
# Set up a specific logger with our desired output level
my_logger = logging.getLogger('MyLogger')
my_logger.setLevel(logging.DEBUG)
# Add the log message handler to the logger
handler = logging.handlers.RotatingFileHandler(
LOG_FILENAME, maxBytes=20, backupCount=5)
my_logger.addHandler(handler)
# Log some messages
for i in range(20):
my_logger.debug('i = %d' % i)
# See what files are created
logfiles = glob.glob('%s*' % LOG_FILENAME)
for filename in logfiles:
print(filename)
结果应该是6个单独的文件,每个文件都包含应用程序的部分日志历史记录:
logging_rotatingfile_example.out
logging_rotatingfile_example.out.1
logging_rotatingfile_example.out.2
logging_rotatingfile_example.out.3
logging_rotatingfile_example.out.4
logging_rotatingfile_example.out.5
最新的文件总是logging_rotatingfile_example。输出,并且每次它达到大小限制时,它都用后缀.1重命名。对每个现有备份文件进行重命名,以增加后缀(。1变成。2,等等),.6文件被擦除。
作为一个极端的例子,显然这个例子设置的日志长度太小了。您需要将maxBytes设置为合适的值。
另一个解决方案是使用Apache的rotatelogs实用程序。
或以下脚本:
#!/bin/ksh
#rotatelogs.sh -n numberOfFiles pathToLog fileSize[B|K|M|G]
numberOfFiles=10
while getopts "n:fltvecp:L:" opt; do
case $opt in
n) numberOfFiles="$OPTARG"
if ! printf '%sn' "$numberOfFiles" | grep '^[0-9][0-9]*$' >/dev/null; then
printf 'Numeric numberOfFiles required %s. rotatelogs.sh -n numberOfFiles pathToLog fileSize[B|K|M|G]n' "$numberOfFiles" 1>&2
exit 1
elif [ $numberOfFiles -lt 3 ]; then
printf 'numberOfFiles < 3 %s. rotatelogs.sh -n numberOfFiles pathToLog fileSize[B|K|M|G]n' "$numberOfFiles" 1>&2
fi
;;
*) printf '-%s ignored. rotatelogs.sh -n numberOfFiles pathToLog fileSize[B|K|M|G]n' "$opt" 1>&2
;;
esac
done
shift $(( $OPTIND - 1 ))
pathToLog="$1"
fileSize="$2"
if ! printf '%sn' "$fileSize" | grep '^[0-9][0-9]*[BKMG]$' >/dev/null; then
printf 'Numeric fileSize followed by B|K|M|G required %s. rotatelogs.sh -n numberOfFiles pathToLog fileSize[B|K|M|G]n' "$fileSize" 1>&2
exit 1
fi
sizeQualifier=`printf "%sn" "$fileSize" | sed "s%^[0-9][0-9]*([BKMG])$%1%"`
multip=1
case $sizeQualifier in
B) multip=1 ;;
K) multip=1024 ;;
M) multip=1048576 ;;
G) multip=1073741824 ;;
esac
fileSize=`printf "%sn" "$fileSize" | sed "s%^([0-9][0-9]*)[BKMG]$%1%"`
fileSize=$(( $fileSize * $multip ))
fileSize=$(( $fileSize / 1024 ))
if [ $fileSize -le 10 ]; then
printf 'fileSize %sKB < 10KB. rotatelogs.sh -n numberOfFiles pathToLog fileSize[B|K|M|G]n' "$fileSize" 1>&2
exit 1
fi
if ! touch "$pathToLog"; then
printf 'Could not write to log file %s. rotatelogs.sh -n numberOfFiles pathToLog fileSize[B|K|M|G]n' "$pathToLog" 1>&2
exit 1
fi
lineCnt=0
while read line
do
printf "%sn" "$line" >>"$pathToLog"
lineCnt=$(( $lineCnt + 1 ))
if [ $lineCnt -gt 200 ]; then
lineCnt=0
curFileSize=`du -k "$pathToLog" | sed -e 's/^[ ][ ]*//' -e 's%[ ][ ]*$%%' -e 's/[ ][ ]*/[ ]/g' | cut -f1 -d" "`
if [ $curFileSize -gt $fileSize ]; then
DATE=`date +%Y%m%d_%H%M%S`
cat "$pathToLog" | gzip -c >"${pathToLog}.${DATE}".gz && cat /dev/null >"$pathToLog"
curNumberOfFiles=`ls "$pathToLog".[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]_[0-9][0-9][0-9][0-9][0-9][0-9].gz | wc -l | sed -e 's/^[ ][ ]*//' -e 's%[ ][ ]*$%%' -e 's/[ ][ ]*/[ ]/g'`
while [ $curNumberOfFiles -ge $numberOfFiles ]; do
fileToRemove=`ls "$pathToLog".[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]_[0-9][0-9][0-9][0-9][0-9][0-9].gz | head -1`
if [ -f "$fileToRemove" ]; then
rm -f "$fileToRemove"
curNumberOfFiles=`ls "$pathToLog".[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]_[0-9][0-9][0-9][0-9][0-9][0-9].gz | wc -l | sed -e 's/^[ ][ ]*//' -e 's%[ ][ ]*$%%' -e 's/[ ][ ]*/[ ]/g'`
else
break
fi
done
fi
fi
done
限制最大尺寸也可以通过head:
my_program | head -c 100 # Limit to 100 first bytes
查看dd: https://unix.stackexchange.com/a/121888/