2011-07-15 10 views
12

Me gustaría capturar la salida de un proceso UNIX pero limitar el tamaño máximo de archivo y/o rotar a un nuevo archivo.Use tee (o equivalente) pero limite el tamaño máximo de archivo o gírelo al nuevo archivo

He visto logrotate, pero no funciona en tiempo real. Según entiendo, es un trabajo de "limpieza" que se ejecuta en paralelo.

¿Cuál es la solución correcta? Supongo que escribiré un pequeño guión para hacerlo, pero esperaba que hubiera una manera simple con las herramientas de texto existentes.

Imagínese:

my_program | tee --max-bytes 100000 log/my_program_log

daría ... escritura siempre más reciente archivo de registro como: registro/my_program_log

Luego, a medida que se llena ... renombrado a log/my_program_log000001 e iniciar un nuevo registro/my_program_log.

Respuesta

14

utilización fraccionada:

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

O si no desea ver la salida, se puede directamente tubería para dividir:

my_program | split -d -b 100000 - 

En cuanto a la rotación de registros, no hay herramienta en coreutils que lo hace de forma automática. Se puede crear un enlace simbólico y actualizar periódicamente usando un comando bash:

while ((1)); do ln -fns target_log_name $(ls -t | head -1); sleep 1; done 
+0

Bah ... Me olvidé del operador>() en Bash (y algunos otros proyectiles). Lo uso muy infrecuentemente. La tuya es la respuesta más concisa. – kevinarpe

1

La manera más directa de resolver esto es probablemente usar python y el logging module que fue diseñado para este propósito. Cree una secuencia de comandos que lea desde stdin y escriba en stdout e implemente la rotación de registros que se describe a continuación.

El "registro" módulo provides la

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

que hace exactamente lo que usted está planteando.

Puede utilizar los valores maxBytes y backupCount para permitir que el archivo se transfiera a un tamaño predeterminado.

De docs.python.org

A veces desea que un archivo de registro crezca a un tamaño determinado, a continuación, abra un nuevo archivo y conectarse a eso. Es posible que desee conservar un cierto número de estos archivos, y cuando se hayan creado tantos archivos, gire los archivos para que el número de archivos y el tamaño de los archivos permanezcan delimitados. Para este patrón de uso, el paquete de registro proporciona una 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) 

El resultado debe ser de 6 archivos separados, cada uno con parte del historial de registro de la aplicación:

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 

El archivo más reciente es siempre logging_rotatingfile_example.out, y cada vez que alcanza el límite de tamaño se renombra con el sufijo .1. Cada uno de los archivos de copia de seguridad existentes se renombra para incrementar el sufijo (.1 se convierte en .2, etc.) y el archivo .6 se borra.

Obviamente, este ejemplo establece la longitud del registro demasiado demasiado pequeña como un ejemplo extremo. Desea establecer maxBytes en un valor apropiado.

+0

Estoy confundido. Mi programa no es Python. ¿Cómo esto me ayuda? Quiero usar coreutils estándar de GNU: awk/tee/split/etc. – kevinarpe

5

o usar awk

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

Mantiene líneas juntas, por lo que el máximo no es exacta, pero esto podría ser agradable, especialmente para fines de registro. Puedes usar el sprintf de awk para formatear el nombre del archivo.

Aquí es un script pipable, usando 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)}' 

guardar a un archivo llamado 'abeja', ejecute 'chmod +x bee' y se puede utilizar como

program | bee 

o dividir una existente archivo como

bee -b1000 -o proglog -w8 file 
+0

Estoy de acuerdo con su comentario: "Mantiene las líneas juntas, por lo que el máximo no es exacto, pero esto podría ser bueno especialmente para fines de registro". – kevinarpe

2

Para limitar el tamaño a 100 bytes, simplemente puede usar dd:

my_program | dd bs=1 count=100 > log 

Cuando se escriben 100 bytes, dd cerrará el conducto y my_program recibe EPIPE.

0

Otra solución será utilizar la utilidad Apache rotatelogs.

O script siguiente:

#!/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 
1

En el paquete está presente apache2-utils utilidad llamada rotatelogs, que se ajustan por completo a sus necesidades.

Sinopsis:

rotatelogs [-l] [-L nombreenlace] [-p programa] [-f] [-t] [-v] [-e] [-c] [ -n número de archivos] archivo de registro tiempo de rotación | tamaño del archivo (B | K | M | G) [compensado]

Ejemplo:

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

Manual completo se puede leer en this link.

Cuestiones relacionadas