32

我想捕获 UNIX 进程的输出,但限制最大文件大小和/或旋转到新文件。

我见过 logrotate,但它不能实时工作。据我了解,这是一项并行运行的“清理”工作。

什么是正确的解决方案?我想我会写一个小脚本来做到这一点,但我希望现有的文本工具有一种简单的方法。

想象:

我的程序 | tee --max-bytes 100000 日志/my_program_log

会给...总是写最新的日志文件为:log/my_program_log

然后,当它填满时……重命名为 log/my_program_log000001 并开始一个新的 log/my_program_log。

4

7 回答 7

32

使用拆分:

my_program | tee >(split -d -b 100000 -)

或者如果您不想看到输出,您可以直接通过管道进行拆分:

my_program | split -d -b 100000 -

至于日志轮换,coreutils 中没有自动执行此操作的工具。您可以创建一个符号链接并使用 bash 命令定期更新它:

while ((1)); do ln -fns target_log_name $(ls -t | head -1); sleep 1; done
于 2011-09-03T07:31:04.997 回答
7

apache2-utils中存在名为 的实用程序rotatelogs,它完全符合您的要求。

概要:

rotatelogs [ -l ] [ -L链接名] [ -p程序] [ -f ] [ -t ] [ -v ] [ -e ] [ -c ] [ -n文件数] 日志文件轮换时间| 文件大小(B|K|M|G) [偏移量]

例子:

your_program | rotatelogs -n 5 /var/log/logfile 1M

您可以在此链接上阅读完整的手册。

于 2017-07-01T13:56:46.480 回答
5

或使用 awk

program | awk 'BEGIN{max=100} {n+=length($0); print $0 > "log."int(n/max)}'

它将线条保持在一起,因此最大值不准确,但这可能很好,特别是用于记录目的。您可以使用 awk 的 sprintf 来格式化文件名。

这是一个 pipable 脚本,使用 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
于 2012-01-26T17:44:56.660 回答
4

要将大小限制为 100 字节,您可以简单地使用 dd:

my_program | dd bs=1 count=100 > log

当写入 100 个字节时,dd 将关闭管道并且 my_program 接收 EPIPE。

于 2012-05-07T17:08:10.180 回答
2

解决这个问题最直接的方法可能是使用 python 和为此目的设计的日志记录模块。创建一个脚本来读取stdin和写入stdout并实现下面描述的日志轮换。

“日志”模块提供

class logging.handlers.RotatingFileHandler(filename, mode='a', maxBytes=0,
              backupCount=0, encoding=None, delay=0)

这正是您要问的。

您可以使用 maxBytes 和 backupCount 值来允许文件以预定大小翻转。

来自docs.python.org

有时您想让日志文件增长到一定大小,然后打开一个新文件并记录到该文件。您可能希望保留一定数量的这些文件,并在创建了这么多文件后,旋转文件以使文件数量和文件大小都保持有限。对于这种使用模式,日志包提供了一个 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.out,每次达到大小限制时,都会使用后缀 .1 重命名。每个现有备份文件都被重命名以增加后缀(.1 变为 .2,等等),并且 .6 文件被删除。

显然,作为一个极端示例,此示例将日志长度设置得太小。您可能希望将 maxBytes 设置为适当的值。

于 2011-07-15T18:41:51.923 回答
1

另一种解决方案是使用 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 '%s\n' "$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 '%s\n' "$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 "%s\n" "$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 "%s\n" "$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 "%s\n" "$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
于 2016-06-01T18:36:31.177 回答
0

限制最大尺寸也可以用 head 来完成:

my_program | head -c 100  # Limit to 100 first bytes

有关 dd 的好处,请参阅此内容:https ://unix.stackexchange.com/a/121888/

于 2020-07-12T00:27:03.287 回答