Используйте тройник (или эквивалент), но ограничьте максимальный размер файла или повернуть в новый файл
Я хотел бы захватить вывод из процесса UNIX, но ограничить максимальный размер файла и/или повернуть на новый файл.
Я видел logrotate, но он не работает в режиме реального времени. Как я понимаю, это "очистка" задания, которое выполняется параллельно.
Какое правильное решение? Думаю, я напишу крошечный script, чтобы сделать это, но я надеялся, что есть простой способ с существующими текстовыми инструментами.
Представьте себе:
my_program | tee --max-bytes 100000 log/my_program_log
Дала бы...
Всегда записывать последний файл журнала:
Журнал /my _program_log
Затем, когда он заполняет... переименовывается в log/my_program_log000001 и запускает новый журнал/my_program_log.
Ответы
Ответ 1
использовать split:
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
Ответ 2
или используя awk
program | awk 'BEGIN{max=100} {n+=length($0); print $0 > "log."int(n/max)}'
Он объединяет линии, поэтому max не является точным, но это может быть приятно, особенно для ведения журналов. Вы можете использовать awk sprintf для форматирования имени файла.
Здесь можно использовать script, используя 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
Ответ 3
Чтобы ограничить размер до 100 байт, вы можете просто использовать dd:
my_program | dd bs=1 count=100 > log
Когда записано 100 байт, dd закроет канал, а my_program получит EPIPE.
Ответ 4
Самый простой способ решить это, вероятно, использовать python и модуль протоколирования, который был разработан для этой цели. Создайте script, который читается с stdin
и напишите на stdout
и реализуйте описанный ниже поворот журнала.
Модуль "logging" предоставляет
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 на соответствующее значение.
Ответ 5
В пакете apache2-utils
присутствует утилита с именем rotatelogs
, она полностью соответствует вашим требованиям.
Описание:
rotatelogs [-l] [-L linkname] [-p program] [-f] [-t] [-v] [-e] [-c] [-n number-of-files] время работы журнала | filesize (B | K | M | G) [смещение]
Пример:
your_program | rotatelogs -n 5 /var/log/logfile 1M
Полное руководство, которое вы можете прочитать на этой ссылке.
Ответ 6
Другим решением будет использование утилиты Apache rotatelogs.
Или после script:
#!/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