Almacenar la salida de un comando en un ring-buffer

16

Tengo un comando de larga ejecución que genera mucha salida en stdout. Me gustaría poder retener, por ejemplo, solo los últimos tres días o el último gibibyte (evitando líneas de corte en el medio) y, si es posible, en trozos de archivo de no más de 20 MiB. Cada fragmento de archivo se nombra con un sufijo numérico o una marca de tiempo.

Algo como:

my-cmd | magic-command --output-file-template=my-cmd-%t \
                       --keep-bytes=1G \
                       --keep-time=3d \
                       --max-chunk-size=20M \
                       --compress=xz

Escribiría:

my-cmd-2014-09-05T10:04:23Z

Cuando llegue a 20M, lo comprimiría y abriría uno nuevo, y así sucesivamente, y después de un tiempo comenzaría a eliminar los archivos más antiguos.

¿Existe tal comando?

Soy consciente de logrotatesu capacidad para administrar archivos escritos por otras aplicaciones, pero estoy buscando algo más simple que no implique tener que configurar un trabajo cron, especificar reglas, suspender el proceso, etc.

Stéphane Chazelas
fuente
¿Qué es un "gibibyte"?
Peter Mortensen el
@PeterMortensen Wikipedia: Gibibyte
jw013

Respuestas:

6

Puede obtener algo de lo que desea a través de pipelog , que "permite rotar o borrar el registro de un proceso en ejecución al canalizarlo a través de un intermedio que responde a señales externas", por ejemplo:

spewstuff | pipelog spew.log -p /tmp/spewpipe.pid -x "gzip spew.log.1"

Luego puede obtener el pid de /tmp/spewpipe.pidy:

kill -s USR1 $(</tmp/spewpipe.pid)

Pero eso tendrías que configurarlo con cron o algo así. Sin embargo, hay una trampa para esto. Aviso I gzip spew.log.1: esto se debe a que el -xcomando se ejecuta después de rotar el registro. Por lo tanto, tiene el problema adicional de sobrescribir spew.log.1.gzcada vez, a menos que escriba un script corto para hacer el gzip y luego mueva el archivo, y lo use como -xcomando.

Divulgación completa: escribí esto, por lo que, por supuesto, funciona perfectamente . ;) Voy a tener en cuenta una opción de compresión, o algo que lo facilite mejor, para la versión 0.2 (el propósito previsto -xes algo diferente, pero funcionará como se indicó anteriormente). También el rollover automático es una buena idea ... la primera versión es intencionalmente mínima ya que resistí la tentación de agregar características que no eran necesarias (después de todo, no es tan difícil configurar un trabajo cron para esto).

Tenga en cuenta que está destinado a la salida de texto ; si hay posibles bytes nulos, debe usar -z, que reemplaza el cero con otra cosa. Esto fue una compensación para simplificar la implementación.

encerrada dorada
fuente
Gracias. Estoy deseando pipelog-0.3;-). También me encontré con metacpan.org/release/File-Write-Rotate . Tenga en cuenta que los trabajos cron no ayudarán mucho para rotar según el tamaño del archivo.
Stéphane Chazelas
¡Rotación basada en el tamaño! Mantiene la salida enjuagada, por lo que puede registrar el archivo a intervalos ...
goldilocks
De esa manera, no podría mantener el tamaño por debajo de 20M (como en los requisitos de mi pregunta).
Stéphane Chazelas
La otra cosa es que es casi solo texto (agregué un párrafo final sobre eso).
Ricitos de oro
4

El multilog de Dan Bernstein aparentemente puede hacer esto, o tal vez la mayor parte, al tiempo que proporciona una salida a través de descriptores de archivo al procesador para compensar la diferencia que desee, aunque las especificaciones de tamaño 20M / 1G pueden tomar un poco de dificultad, ya que parece que 16M es su Límite exterior por registro. Lo que sigue es, en su mayoría, una selección de copiar + pegar del enlace de arriba, aunque el enlace también detalla otras opciones, como la marca de tiempo por línea, manteniendo [otro] archivo [s] que contiene solo el patrón de coincidencia de línea más reciente y más .

Interfaz

 multilog script

... el script consta de cualquier número de argumentos. Cada argumento especifica una acción. Las acciones se llevan a cabo en orden para cada línea de entrada.

Seleccionar líneas

Cada línea se selecciona inicialmente. La acción...

-pattern

... deselecciona la línea si el patrón coincide con la línea. La acción...

+pattern

selecciona la línea si el patrón coincide con la línea.

... el patrón es una cadena de estrellas y no estrellas. Coincide con cualquier concatenación de cadenas que coincidan con todas las estrellas y no estrellas en el mismo orden. Un no-estrella se iguala a sí mismo. Una estrella antes del final del patrón coincide con cualquier cadena que no incluya el siguiente carácter en el patrón. Una estrella al final del patrón coincide con cualquier cadena.

Registros rotados automáticamente

Si dir comienza con un punto o una barra, entonces la acción ...

 dir

... agrega cada línea seleccionada a un registro llamado dir . Si dir no existe, lo multilogcrea.

El formato de registro es el siguiente:

  1. dir es un directorio que contiene cierto número de archivos de registro antiguos, un archivo de registro denominado actual y otros archivos para multilogrealizar un seguimiento de sus acciones.

  2. Cada archivo de registro antiguo tiene un nombre que comienza con @ , continúa con una marca de tiempo precisa que muestra cuándo se terminó el archivo y termina con uno de los siguientes códigos:

    • .s : este archivo está completamente procesado y escrito de forma segura en el disco.
    • .u : este archivo se estaba creando en el momento de una interrupción. Puede haber sido truncado. No ha sido procesado.

La acción...

 ssize

... establece el tamaño máximo de archivo para acciones dir posteriores . multilogdecidirá que la corriente es lo suficientemente grande si la corriente tiene bytes de tamaño . ( multilogtambién decidirá que la corriente es lo suficientemente grande si ve una nueva línea dentro de los 2000 bytes del tamaño máximo del archivo; intenta finalizar los archivos de registro en los límites de la línea). El tamaño debe estar entre 4096 y 16777215. El tamaño máximo predeterminado del archivo es 99999.

En las versiones 0.75 y superiores: si multilogrecibe una señal ALRM , inmediatamente decide que la corriente es lo suficientemente grande, si la corriente no es vacía.

(Nota: sospecho que se zsh schedulepodría persuadir fácilmente a la unidad para que envíe una ALRMa intervalos específicos si es necesario).

La acción...

 nnum

... establece el número de archivos de registro para acciones dir posteriores . Después de renombrar actual , si multilogve num o más archivos de registro antiguos, elimina el archivo de registro anterior con la marca de tiempo más pequeña. num debe ser al menos 2. El número predeterminado de archivos de registro es 10.

La acción...

 !processor

... establece un procesador para acciones dir posteriores . multilogalimentará la corriente a través del procesador y guardará la salida como un archivo de registro antiguo en lugar de actual . multilogtambién guardará cualquier salida que el procesador escriba en el descriptor 5, y hará que esa salida sea legible en el descriptor 4 cuando ejecute el procesador en el siguiente archivo de registro. Para mayor confiabilidad, el procesador debe salir de cero si tiene algún problema para crear su salida; multilogluego lo ejecutará nuevamente. Tenga en cuenta que el procesador en ejecución puede bloquear cualquier entrada de alimentación de programa multilog.

mikeserv
fuente
2

Lo mejor que pude encontrar hasta ahora como una aproximación que no implica escribir grandes piezas de código es este zshcódigo:

autoload zmv
mycmd |
  while head -c20M > mycmd.log && [ -s mycmd.log ]; do
    zmv -f '(mycmd.log)(|.(<->))(|.gz)(#qnOn)' '$1.$(($3+1))$4'
    {rm -f mycmd.log.1 mycmd.log.50.gz; (gzip&) > mycmd.log.1.gz} < mycmd.log.1
  done

Aquí dividiendo y rotando en un máximo de 51 archivos grandes de 20MiB.

Stéphane Chazelas
fuente
tal vez ... montajes en bucle? btrfsTambién se puede montar con compress-force=zlib.
mikeserv
2

Aquí hay un script pirateado pirateado para hacer algo como lo que está solicitando:

#!/bin/sh
''':'
exec python "$0" "$@"
'''

KEEP = 10
MAX_SIZE = 1024 # bytes
LOG_BASE_NAME = 'log'

from sys import stdin
from subprocess import call

log_num = 0
log_size = 0
log_name = LOG_BASE_NAME + '.' + str(log_num)
log_fh = open(log_name, 'w', 1)

while True:
        line = stdin.readline()
        if len(line) == 0:
                log_fh.close()
                call(['gzip', '-f', log_name])
                break
        log_fh.write(line)
        log_size += len(line)
        if log_size >= MAX_SIZE:
                log_fh.close()
                call(['gzip', '-f', log_name])
                if log_num < KEEP:
                        log_num += 1
                else:
                        log_num = 0
                log_size = 0
                log_name = LOG_BASE_NAME + '.' + str(log_num)
                log_fh = open(log_name, 'w', 1)
Mark Wagner
fuente
1
¿Hay alguna razón para tenerlo como un script de shell que sea execpython como lo primero en lugar de usar el pythono env pythonhashbang?
Peter