Svuotare un file senza interrompere la pipe che scrive su di esso


12

Ho un programma il cui output reindirizzo a un file di registro:

./my_app > log

Vorrei cancellare (cioè svuotare) il registro di volta in volta (su richiesta) e provare varie cose come

cat "" > log

Tuttavia sembra sempre che la pipe originale venga quindi interrotta e il programma non reindirizza più il suo output al file di registro.

C'è un modo per farlo?

Aggiornare

Si noti che non è possibile modificare l'applicazione che produce l'output. Lo sputa semplicemente su stdout e voglio salvarlo in un registro in modo da poterlo ispezionare quando ne ho bisogno e cancellarlo quando voglio. Tuttavia non dovrei aver bisogno di riavviare l'applicazione.


ecco perché di solito usi un demone logging per registrare le cose ...
Kiwy,

@Kiwy puoi approfondire come risolverebbe il problema?
bangnab,

bene di solito usi un demone log o permetti alla tua app di gestire il log, perché scrivere cose sull'output e reindirizzarlo non è affidabile. dovresti dare un'occhiata syslogdologrotate
Kiwy,

2
Le cose funzionano se lo fai ./my_app >> log(per forzare l'aggiunta) e cp /dev/null logper troncarlo?
Mark Plotnick,

1
Che messaggio di errore ricevi? Che comportamento vedi? "Non reindirizza più il suo output al file di registro" non è molto specifico. Inoltre, cat "" > lognon è un catcomando valido poiché non è stato chiamato alcun file "".
Mikel,

Risposte:


13

Un'altra forma di questo problema si verifica con applicazioni a esecuzione prolungata i cui registri vengono periodicamente ruotati. Anche se si sposta il registro originale (ad es. mv log.txt log.1) E lo si sostituisce immediatamente con un file con lo stesso nome prima che si verifichi qualsiasi registrazione effettiva, se il processo tiene aperto il file, finirà per scrivere log.1(perché potrebbe essere ancora l'inode aperto) o niente.

Un modo comune per gestirlo (lo stesso logger di sistema funziona in questo modo) è implementare un gestore di segnale nel processo che chiuderà e riaprirà i suoi log. Quindi, ogni volta che vuoi spostare o cancellare (cancellando) il registro, invia quel segnale al processo immediatamente dopo.

Ecco una semplice dimostrazione per bash: perdona le mie abilità di shell grezza (ma se hai intenzione di modificarlo per le migliori pratiche, ecc., Assicurati di aver capito prima la funzionalità e di testare la tua revisione prima di modificare):

#!/bin/bash

trap sighandler INT

function sighandler () {
    touch log.txt
    exec &> log.txt
}

echo $BASHPID
exec &> log.txt

count=0;
while [ $count -lt 60 ]; do
    echo "$BASHPID Count is now $count"
    sleep 2
    ((count++))
done          

Inizia questo biforcando in background:

> ./test.sh &
12356

Si noti che segnala il proprio PID al terminale e quindi inizia la registrazione log.txt. Ora hai 2 minuti per giocare. Attendi qualche secondo e prova:

> mv log.txt log.1 && kill -s 2 12356

Semplicemente kill -2 12356può funzionare per voi anche qui. Il segnale 2 è SIGINT (è anche ciò che fa Ctrl-C, quindi potresti provarlo in primo piano e spostare o rimuovere il file di registro da un altro terminale), che trapdovrebbe intercettare. Controllare;

> cat log.1
12356 Count is now 0
12356 Count is now 1
12356 Count is now 2
12356 Count is now 3
12356 Count is now 4
12356 Count is now 5
12356 Count is now 6
12356 Count is now 7
12356 Count is now 8
12356 Count is now 9
12356 Count is now 10
12356 Count is now 11
12356 Count is now 12
12356 Count is now 13
12356 Count is now 14

Ora vediamo se sta ancora scrivendo su a log.txtanche se lo abbiamo spostato:

> cat log.txt
12356 Count is now 15
12356 Count is now 16
12356 Count is now 17
12356 Count is now 18
12356 Count is now 19
12356 Count is now 20
12356 Count is now 21

Si noti che continuava ad andare esattamente da dove si era interrotto. Se non si desidera conservare il record, cancellare semplicemente il registro eliminandolo

> rm -f log.txt && kill -s 2 12356

Dai un'occhiata:

> cat log.txt
12356 Count is now 29
12356 Count is now 30
12356 Count is now 31
12356 Count is now 32
12356 Count is now 33
12356 Count is now 34
12356 Count is now 35
12356 Count is now 36

Ancora in corso.

Sfortunatamente, non puoi farlo in uno script di shell per un sottoprocesso eseguito, perché se è in primo piano, i gestori dei segnali di bash trapsono sospesi e se lo attraversi in background, non puoi riassegnarlo produzione. Cioè, questo è qualcosa che devi implementare nella tua applicazione.

Tuttavia...

Se non riesci a modificare l'applicazione (ad es. Perché non l'hai scritta), ho un'utilità CLI che puoi utilizzare come intermediario. Puoi anche implementare una versione semplice di questo in uno script che funge da pipe per il registro:

#!/bin/bash

trap sighandler INT

function sighandler () {
    touch log.txt
    exec 1> log.txt
}

echo "$0 $BASHPID"
exec 1> log.txt

count=0;
while read; do
    echo $REPLY
done  

Chiamiamo questo pipetrap.sh. Ora abbiamo bisogno di un programma separato con cui provare, imitando l'applicazione che si desidera registrare:

#!/bin/bash

count=0
while [ $count -lt 60 ]; do
    echo "$BASHPID Count is now $count"
    sleep 2
    ((count++))
done           

Quello sarà test.sh:

> (./test.sh | ./pipetrap.sh) &
./pipetrap.sh 15859

Questi sono due processi separati con PID separati. Per cancellare test.shl'output, che viene incanalato attraverso pipetrap.sh:

> rm -f log.txt && kill -s 2 15859

Dai un'occhiata:

>cat log.txt
15858 Count is now 6
15858 Count is now 7
15858 Count is now 8

15858, test.shè ancora in esecuzione e il suo output è in fase di registrazione. In questo caso, non sono necessarie modifiche all'applicazione.


Grazie per le belle spiegazioni. Tuttavia, nel mio caso, non posso modificare l'applicazione per implementare la tua soluzione.
bangnab,

2
Se non è possibile implementare un gestore di segnale nella propria applicazione (perché non è possibile modificarlo periodo), è possibile utilizzare questa tecnica per reindirizzare il registro attraverso una trappola del segnale - vedere le cose dopo "Comunque ..."
goldilocks

Ok, ci proverò e ti farò sapere come è andata.
bangnab,

Finalmente ho un'app CLI scritta in C per questo (mi dispiace che ci sia voluto un po 'più del previsto): cognitivedissonance.ca/cogware/pipelog
goldilocks

6

TL; DR

Apri il tuo file di registro in modalità append :

cmd >> log

Quindi, puoi troncarlo in sicurezza con:

: > log

Dettagli

Con una shell tipo Bourne, ci sono 3 modi principali in cui un file può essere aperto per la scrittura. In modalità sola scrittura ( >), leggi + scrivi ( <>) o aggiungi (e sola scrittura, >>).

Nei primi due, il kernel ricorda la posizione corrente in cui tu (da parte tua, intendo, la descrizione del file aperto , condivisa da tutti i descrittori di file che l'hanno duplicato o ereditato biforcando da quello su cui hai aperto il file) sei nel file.

Quando lo fai:

cmd > log

logè aperto in modalità di sola scrittura dalla shell per lo stdout di cmd.

cmd(il suo processo iniziale generato dalla shell e da tutti i possibili figli) quando si scrive sul loro stdout, scrivere nella posizione corrente del cursore mantenuta dalla descrizione del file aperto che condividono su quel file.

Ad esempio, se cmdinizialmente scrive zzz, la posizione sarà all'offset di byte 4 nel file e la volta successiva cmdo i suoi figli scriveranno nel file, è lì che i dati verranno scritti indipendentemente dal fatto che il file sia cresciuto o ridotto nell'intervallo .

Se il file si è ridotto, ad esempio se è stato troncato con a

: > log

e cmdscrive xx, quelli xxsaranno scritti in offset 4e i primi 3 caratteri saranno sostituiti da caratteri NUL.

$ exec 3> log # open file on fd 3.
$ printf zzz >&3
$ od -c log
0000000   z   z   z
0000003
$ printf aaaa >> log # other open file description -> different cursor
$ od -c log
0000000   z   z   z   a   a   a   a
0000007
$ printf bb >&3 # still write at the original position
$ od -c log
0000000   z   z   z   b   b   a   a
0000007
$ : > log
$ wc log
0 0 0 log
$ printf x >&3
$ od -c log
0000000  \0  \0  \0  \0  \0   x
0000006

Ciò significa che non è possibile troncare un file che è stato aperto in modalità di sola scrittura (ed è lo stesso per read + write ) come in caso contrario, i processi con descrittori di file aperti sul file, lasceranno i caratteri NUL all'inizio del file (quelli, tranne su OS / X, di solito non occupano spazio sul disco, ma diventano file sparsi).

Invece (e noterai che la maggior parte delle applicazioni lo fa quando scrivono nei file di registro), dovresti aprire il file in modalità append :

cmd >> log

o

: > log && cmd >> log

se vuoi iniziare con un file vuoto.

In modalità append, tutte le scritture vengono eseguite alla fine del file, indipendentemente da dove sia stata l'ultima scrittura:

$ exec 4>> log
$ printf aa >&4
$ printf x >> log
$ printf bb >&4
$ od -c log
0000000   a   a   x   b   b
0000005
$ : > log
$ printf cc >&4
$ od -c log
0000000   c   c
0000002

È anche più sicuro che se due processi hanno aperto (in quel modo) il file per errore (come ad esempio se hai avviato due istanze dello stesso demone), il loro output non si sovrascriverà a vicenda.

Nelle versioni recenti di Linux, puoi controllare la posizione corrente e se un descrittore di file è stato aperto in modalità append guardando /proc/<pid>/fdinfo/<fd>:

$ cat /proc/self/fdinfo/4
pos:        2
flags:      0102001

O con:

$ lsof +f G -p "$$" -ad 4
COMMAND  PID USER   FD   TYPE  FILE-FLAG DEVICE SIZE/OFF     NODE NAME
zsh     4870 root    4w   REG 0x8401;0x0 252,18        2 59431479 /home/chazelas/log
~# lsof +f g -p "$$" -ad 4
COMMAND  PID USER   FD   TYPE FILE-FLAG DEVICE SIZE/OFF     NODE NAME
zsh     4870 root    4w   REG   W,AP,LG 252,18        2 59431479 /home/chazelas/log

Tali flag corrispondono ai flag O ..._ passati alla openchiamata di sistema.

$ gcc -E - <<< $'#include <fcntl.h>\nO_APPEND O_WRONLY' | tail -n1
02000 01

( O_APPENDè 0x400 o 02000 ottale)

Quindi la shell >>apre il file con O_WRONLY|O_APPEND(e 0100000 qui è O_LARGEFILE che non è rilevante per questa domanda) mentre >è O_WRONLYsolo (ed <>è O_RDWRsolo).

Se fai un:

sudo lsof -nP +f g | grep ,AP

per cercare i file aperti O_APPEND, troverai la maggior parte dei file di registro attualmente aperti per la scrittura sul tuo sistema.


Perché usi i :(due punti) in : > ?
mvorisek,

1
@Mvorisek, che è per reindirizzare l'output del comando che produce alcun output: :. Senza un comando, il comportamento varia tra le shell.
Stéphane Chazelas,

1

Se sto capendo correttamente, teesembra un approccio ragionevole:

$ ./myapp-that-echoes-the-date-every-second | tee log > /dev/null &
[1] 20519
$ head log
Thu Apr  3 11:29:34 EDT 2014
Thu Apr  3 11:29:35 EDT 2014
Thu Apr  3 11:29:36 EDT 2014
$ > log
$ head log
Thu Apr  3 11:29:40 EDT 2014
Thu Apr  3 11:29:41 EDT 2014
Thu Apr  3 11:29:42 EDT 2014

1

Come soluzione rapida è possibile utilizzare un registro con rotazione (rotazione giornaliera ad esempio):

date=`date +%Y%m%d`
LOGFILE=/home/log$date.log

e reindirizzare la registrazione su di esso ./my_app >> log$date.log


Mi piacerebbe poter ruotare su richiesta. Questo è in realtà un registro che viene prodotto durante un test automatizzato e vorrei cancellarlo prima di eseguire il test.
bangnab,

0

Questo è un problema che è stato a lungo risolto con syslog (in tutte le sue varianti) ma ci sono due strumenti che potrebbero risolvere il tuo particolare problema con il minimo sforzo.

La prima soluzione più portatile ma meno versatile è il logger (un must per qualsiasi toolbox per amministratori). È una semplice utility che copia l'input standard su syslog. (passando il dollaro e rendendo la rotazione dei file il problema di logrotate e syslog)

La seconda soluzione, più elegante ma meno portatile, è syslog-ng che oltre ad accettare messaggi di log dai socket syslog standard può eseguire programmi il cui output viene filtrato attraverso il logger. (Non ho ancora usato questa funzione, ma sembra perfetto per quello che vuoi fare.)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.