Освобождение файла, не разрушая канал, пишущий в него

./application.bin | sed '$!G' >> /tmp/Log
12
03.04.2014, 15:36
5 ответов

Другая форма этой проблемы возникает с долго работающими приложениями, журналы которых периодически меняются. Даже если вы переместите исходный журнал (например, 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, поэтому вы можете попробовать это на переднем плане и переместить или удалить файл журнала с другого терминала), который должен перехватить ловушка . Чтобы проверить:

> 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 s) приостанавливаются, и если вы его разветвите в фоновый режим, вы не можете переназначить его вывод. То есть это то, что вы должны реализовать в своем приложении.

Однако ...

Если вы не можете изменить приложение (например, потому что вы его не писали), у меня есть утилита 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 , все еще работает, и его вывод регистрируется.В этом случае никаких изменений в приложении не требуется.

13
27.01.2020, 19:55

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

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

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

0
27.01.2020, 19:55

TL; DR

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

cmd >> log

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

: > log

Подробности

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

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

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

cmd > 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 в начале файла (они, за исключением 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

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

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

$ 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_APPEND - 0x400 или восьмеричное 02000)

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

Если вы выполните:

sudo lsof -nP +f g | grep ,AP

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

6
27.01.2020, 19:55

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

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

и перенаправить запись в него ./ my_app >> log $ date.log

1
27.01.2020, 19:55

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

$ ./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
27.01.2020, 19:55

Теги

Похожие вопросы