Очистка файла без прерывания записи в него

12

У меня есть программа, вывод которой я перенаправить в файл журнала:

./my_app > log

Я хотел бы очистить (то есть пустой) время от времени (по запросу) и пробовал различные вещи, как

cat "" > log

Однако всегда кажется, что исходный канал затем нарушается, и программа больше не перенаправляет свой вывод в файл журнала.

Есть ли способ сделать это?

Обновить

Обратите внимание, что я не могу изменить приложение, производящее вывод. Он просто выплевывает его на стандартный вывод, и я хочу сохранить его в журнале, чтобы я мог проверить его, когда мне это нужно, и очистить его, когда захочу. Однако мне не нужно перезапускать приложение.

bangnab
источник
вот почему вы обычно используете демон регистрации для регистрации событий ...
Kiwy,
@ Kiwy, можешь рассказать, как это решит проблему?
августа
ну, вы обычно используете демон журнала или позволяете вашему приложению обрабатывать журнал, потому что запись чего-либо на вывод и перенаправление его ненадежны. Вы должны взглянуть на syslogdилиlogrotate
Kiwy
2
Работают ли вещи, если вы делаете ./my_app >> log(чтобы принудительно добавить) и cp /dev/null logобрезать это?
Марк Плотник
1
Какое сообщение об ошибке вы получаете? Какое поведение вы видите? «Больше не перенаправляет свой вывод в файл журнала» не очень конкретно. Кроме того, cat "" > logнедопустимая catкоманда, так как файл не вызывается "".
Микель

Ответы:

13

Другая форма этой проблемы возникает с долго работающими приложениями, журналы которых периодически меняются. Даже если вы переместите исходный журнал (например, mv log.txt log.1) и немедленно замените его файлом с тем же именем до того, как произойдет какое-либо фактическое ведение журнала, если процесс удерживает файл открытым, он либо завершит запись log.1(потому что это все еще может быть открытый индекс) или ни к чему.

Распространенный способ справиться с этим (сам системный регистратор работает таким образом) - внедрить обработчик сигналов в процессе, который закроет и снова откроет свои журналы. Затем, когда вы захотите переместить или очистить (удалив) журнал, сразу же отправьте этот сигнал процессу.

Вот простая демонстрация bash - простите мои грубые навыки оболочки (но если вы собираетесь редактировать это для лучших практик и т. Д., Пожалуйста, убедитесь, что вы сначала понимаете функциональность и протестируете свою ревизию перед редактированием):

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

Начните это с разветвления на задний план:

> ./test.sh &
12356

Обратите внимание, что он сообщает свой PID в терминал, а затем начинает входить в систему log.txt. Теперь у вас есть 2 минуты, чтобы поиграть. Подождите несколько секунд и попробуйте:

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

Просто kill -2 12356у вас тоже может получиться. Сигнал 2 - SIGINT (это также то, что делает Ctrl-C, так что вы можете попробовать это на переднем плане и переместить или удалить файл журнала из другого терминала), который trapдолжен перехватить. Проверять;

> 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

Теперь давайте посмотрим, записывает ли он все log.txtеще, хотя мы переместили его:

> 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

Обратите внимание, что он продолжал идти прямо туда, где остановился. Если вы не хотите сохранять запись, просто очистите журнал, удалив его

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

Проверьте:

> 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

Продолжается.

Вы не можете сделать это в сценарии оболочки для исполняемого подпроцесса, к сожалению, потому что, если он находится на переднем плане, собственные обработчики сигналов bash trapприостанавливаются, и если вы разветвляете его в фоновом режиме, вы не можете переназначить его выход. То есть это то, что вы должны реализовать в своем приложении.

Тем не мение...

Если вы не можете изменить приложение (например, потому что вы его не написали), у меня есть утилита CLI, которую вы можете использовать в качестве посредника. Вы также можете реализовать простую версию этого в скрипте, который служит каналом для журнала:

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

Давайте назовем это pipetrap.sh. Теперь нам нужна отдельная программа для тестирования, имитирующая приложение, которое вы хотите зарегистрировать:

#!/bin/bash

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

Это будет test.sh:

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

Это два отдельных процесса с отдельными PID. Чтобы очистить test.shвывод, который проходит через pipetrap.sh:

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

Проверьте:

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

15858,, test.shвсе еще работает, и его выходные данные регистрируются. В этом случае никаких изменений в приложении не требуется.

лютик золотистый
источник
Спасибо за приятные объяснения. Однако в моем случае я не могу изменить приложение для реализации вашего решения.
bangnab
2
Если вы не можете реализовать обработчик сигнала в своем приложении (потому что вы не можете изменить его период), вы можете использовать эту технику для передачи журнала через ловушку сигнала - посмотрите материал после «Однако ...»
goldilocks
Хорошо, я попробую и дам знать, как все прошло.
августа
Наконец-то у меня есть приложение CLI, написанное на C для этого (извините, это заняло немного больше времени, чем первоначально предполагалось): cognitivedissonance.ca/cogware/pipelog
goldilocks
6

TL; DR

Откройте файл журнала в режиме добавления :

cmd >> log

Затем вы можете безопасно обрезать его:

: > log

Детали

В борновоподобной оболочке существует 3 основных способа открытия файла для записи. В режиме только для записи ( >), чтение + запись ( <>) или добавление (и только запись >>).

В первых двух ядро ​​запоминает текущую позицию, в которой вы (под вами, я имею в виду, описание открытого файла , разделяемое всеми дескрипторами файлов, которые продублировали или унаследовали его путем разветвления от того, на котором вы открыли файл), находитесь в файл.

Когда вы делаете:

cmd > log

logоткрыт в режиме « только для записи» оболочкой для вывода cmd.

cmd(его начальный процесс порожден оболочкой и всеми возможными дочерними элементами) при записи в их стандартный вывод записывать в текущей позиции курсора, содержащейся в открытом описании файла, которое они разделяют в этом файле.

Например, при cmdпервоначальной записи zzzпозиция будет иметь байтовое смещение 4 в файле, а в следующий раз cmdили когда его дочерние элементы будут записывать в файл, именно туда будут записываться данные независимо от того, вырос ли файл или сократился в интервале ,

Если файл сжался, например, если он был усечен с

: > log

и cmdпишет xx, те xxбудут записаны со смещением 4, и первые 3 символа будут заменены на 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

Это означает, что вы не можете усечь файл, который был открыт в режиме только для записи (и то же самое для чтения + записи ), как если бы вы это делали, процессы, у которых в файле были открыты файловые дескрипторы, оставят NUL-символы в начале file (те, за исключением OS / X, обычно не занимают место на диске, хотя они становятся разреженными файлами).

Вместо этого (и вы заметите, что большинство приложений делают это, когда они пишут в файлы журналов), вы должны открыть файл в режиме добавления :

cmd >> log

или

: > log && cmd >> log

если вы хотите начать с пустого файла.

В режиме добавления все записи производятся в конце файла, независимо от того, где была последняя запись:

$ 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

Также безопаснее, если два процесса по ошибке открыли (таким образом) файл (например, если вы запустили два экземпляра одного и того же демона), их выходные данные не будут перезаписывать друг друга.

В последних версиях Linux вы можете проверить текущую позицию и был ли дескриптор файла открыт в режиме добавления , посмотрев /proc/<pid>/fdinfo/<fd>:

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

Или с:

$ 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

Эти флаги соответствуют флагам O ..._, передаваемым openсистемному вызову.

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

( O_APPEND0x400 или восьмеричное 02000)

Так оболочечному >>открывает файл с O_WRONLY|O_APPEND(и 0100000 здесь O_LARGEFILE , которая не имеет отношения к этому вопросу) в то время как >это O_WRONLYтолько (и <>это O_RDWRтолько).

Если вы делаете:

sudo lsof -nP +f g | grep ,AP

для поиска файлов, открытых с помощью O_APPEND, вы найдете большинство файлов журналов, открытых на данный момент для записи в вашей системе.

Стефан Шазелас
источник
Почему вы используете :(двоеточие) в : > ?
Мворисек
1
@Mvorisek, это перенаправить вывод команды , которая не производит никакого вывода: :. Без команды поведение меняется между оболочками.
Стефан Шазелас
1

Если я правильно понимаю, teeкажется разумным подход:

$ ./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

В качестве быстрого решения вы можете использовать бревно с ротацией (например, ежедневная ротация):

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

и перенаправить логирование к нему ./my_app >> log$date.log

Чарльз Нахель
источник
Я хотел бы иметь возможность вращаться по требованию. На самом деле это журнал, который создается во время автоматического теста, и я хотел бы очистить его перед запуском теста.
августа
0

Эта проблема давно решается с помощью системного журнала (во всех его вариантах), но есть два инструмента, которые решат вашу конкретную проблему с минимальными усилиями.

Первое, более портативное, но менее универсальное решение - это регистратор (необходим для всех инструментов администратора). Это простая утилита, которая копирует стандартный ввод в системный журнал. (обойдя проблему и сделав ротацию файлов проблемой logrotate и syslog)

Второе, более элегантное, но менее портативное решение - это syslog-ng, которое помимо приема сообщений журнала из стандартных сокетов syslog может выполнять программы, выходные данные которых фильтруются через регистратор. (Я еще не использовал эту функцию, но она выглядит идеально для того, что вы хотите сделать.)

hildred
источник