Как завершить команду Linux tee, не убивая приложение, от которого она получает

19

У меня есть скрипт bash, который работает, пока машина Linux включена. Я начинаю это, как показано ниже:

( /mnt/apps/start.sh 2>&1 | tee /tmp/nginx/debug_log.log ) &

После этого я вижу команду tee в выводе ps, как показано ниже:

$ ps | grep tee
  418 root       0:02 tee /tmp/nginx/debug_log.log
3557 root       0:00 grep tee

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

monitor_debug_log_size() {
                ## Monitor the file size of the debug log to make sure it does not get too big
                while true; do
                                cecho r "CHECKING DEBUG LOG SIZE... "
                                debugLogSizeBytes=$(stat -c%s "/tmp/nginx/debug_log.log")
                                cecho r "DEBUG LOG SIZE: $debugLogSizeBytes"
                                if [ $((debugLogSizeBytes)) -gt 100000 ]; then
                                                cecho r "DEBUG LOG HAS GROWN TO LARGE... "
                                                sleep 3
                                                #rm -rf /tmp/nginx/debug_log.log 1>/dev/null 2>/dev/null
                                                kill -9 `pgrep -f tee`
                                fi
                                sleep 30
                done
}

К моему удивлению, команда kill tee также уничтожает экземпляр start.sh. Почему это? Как я могу завершить команду tee, но мой start.sh продолжает работать? Благодарю.

PhilBot
источник

Ответы:

34

Когда teeзавершается, команда подачи будет продолжать работать, пока не попытается записать больше вывода. Затем он получит SIGPIPE (13 на большинстве систем) за попытку записи в канал без читателей.

Если вы измените свой скрипт, чтобы перехватить SIGPIPE и предпринять какое-то соответствующее действие (например, прекратить запись выходных данных), тогда вы сможете продолжить его после того, как tee будет завершен.


А еще лучше, а не убивать tee вообще, использовать logrotateс copytruncateопцией для простоты.

Цитировать logrotate(8):

copytruncate

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

Wildcard
источник
9
Вы также хотели бы использовать tee -aдля teeоткрытия файла в режиме добавления, в противном случае tee продолжит запись в файл с тем же смещением после его усечения (и в системах, которые не поддерживают разреженные файлы, как в macOS, которые будут перераспределить часть файла, ведущую к этой позиции, занимая вдвое больше дискового пространства).
Стефан Шазелас
4
Другим вариантом будет передача по logger -sканалу для syslog, чтобы позаботиться о регистрации ( -sтакже распечатать на stderr).
Стефан Шазелас
1
+1 за logrotate. Отличная программа
Дмитрий Кудрявцев
2
Или в системе, использующей systemd и journald, systemd-cat вместо logger. Тогда вы получите много выходной фильтрации и вращения бесплатно.
Zan Lynx
3

Объясняя «почему»

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

Делая это лучше: вращайте внутри своего регистратора

Рассмотрим следующее, которое вообще не используется tee, в качестве наглядного примера:

#!/usr/bin/env bash

file=${1:-debug.log}                     # filename as 1st argument
max_size=${2:-100000}                    # max size as 2nd argument
size=$(stat --format=%s -- "$file") || exit  # Use GNU stat to retrieve size
exec >>"$file"                           # Open file for append

while IFS= read -r line; do              # read a line from stdin
  size=$(( size + ${#line} + 1 ))        # add line's length + 1 to our counter
  if (( size > max_size )); then         # and if it exceeds our maximum...
    mv -- "$file" "$file.old"            # ...rename the file away...
    exec >"$file"                        # ...and reopen a new file as stdout
    size=0                               # ...resetting our size counter
  fi
  printf '%s\n' "$line"                  # regardless, append to our current stdout
done

Если запустить как:

/mnt/apps/start.sh 2>&1 | above-script /tmp/nginx/debug_log

... это начнется с добавления /tmp/nginx/debug_log, переименования файла, /tmp/nginx/debug_log.oldкогда присутствует более 100 КБ содержимого. Поскольку сам регистратор выполняет ротацию, в нем нет ни разорванного канала, ни ошибки, ни окна потери данных, когда происходит ротация - каждая строка будет записана в тот или иной файл.

Конечно, реализация этого в native bash неэффективна, но вышеприведенный пример является иллюстративным. Существует множество доступных программ, которые реализуют вышеуказанную логику для вас. Рассмотреть возможность:

  • svlogd, сервисный регистратор из набора Runit.
  • s6-log, активно поддерживаемая альтернатива из набора Сканет.
  • multilog от DJB Daemontools, дедушки этого семейства инструментов контроля и мониторинга процессов.
Чарльз Даффи
источник