Вопрос или проблема
Рассмотрим эту простую программу (abort.py
), которая выводит текст в stdout
и stderr
, а затем завершается с ошибкой (abort()
).
import os
import sys
print("конец stdout")
print("конец stderr", file=sys.stderr)
os.abort()
Когда я запускаю ее в терминале, вывод в stdout
и stderr
производится корректно.
$ python3 abort.py
конец stdout
конец stderr
Программа завершена (дамп ядра)
Однако, если я перенаправлю stdout
и/или stderr
в другую программу (возможно, для целей логирования), это больше не работает.
$ python3 abort.py | cat
конец stderr
$ python3 abort.py |& cat
$ python3 abort.py | tee -a logs
конец stderr
$ python3 abort.py |& tee -a logs
# файл `logs` не изменился
На самом деле, если программа (abort.py
) производит много текста в stdout
и stderr
, только последний раздел теряется с точки зрения принимающей программы на другом конце канала. Я также пробовал запустить программу в bash-скрипте и запустить этот скрипт, но результат тот же.
Почему это происходит и как это исправить?
Фоновая информация
Пример выше явно выдан искусственно. Реальная ситуация заключается в том, что я отлаживал большую программу, которая время от времени завершается с ошибкой (сегментационная ошибка или прерывание) после нескольких дней работы. Она производит много логов как в stdout
, так и в stderr
, но самая важная информация о сбое (такая как трассировка стека) выводится в конце, прямо перед тем, как происходит сбой. У меня настроен логирующий пайплайн в bash-скрипте (с некоторыми tee
и gzip
, например), но я обнаружил, что последний раздел сохраненного лога всегда отсутствует, что довольно раздражает.
Это “проблема” буферизации. Сказав python не использовать буферизацию, мы получаем желаемый результат.
$ python3 -u abort.py |& cat
конец stdout
конец stderr
import os
import sys
print("конец stdout", flush=True)
print("конец stderr", file=sys.stderr, flush=True)
os.abort()
Ответ или решение
Когда вы работаете с программами, использующими пайплайн в Bash, может возникнуть проблема потери данных, когда команда завершает выполнение аварийно или вызывается функция abort()
. Рассмотрим, что происходит в таких ситуациях и как этого избежать.
Проблема буферизации
Когда вы запускаете Python-программу, как в вашем примере с abort.py
, выходные данные (то есть текст, который выводится на стандартный вывод и стандартный вывод ошибок) могут быть буферизованы. Это значит, что данные могут не быть немедленно записаны в конечный вывод, особенно при использовании пайпов. Когда ваша программа завершается аварийно, часто происходит так, что оставшиеся данные в буфере не успевают быть записаны в пайп и тем самым теряются.
В вашем случае:
- Вы наблюдаете, что при запуске программы без пайпа все выходные данные отображаются корректно.
- Однако, когда вы перенаправляете вывод через пайп (
|
), последние вызовыprint()
могут не быть выведены, если программа завершается слишком быстро.
Решение проблемы
Чтобы предотвратить потерю данных, вы можете изменить режим буферизации Python. Вот несколько способов, как это можно сделать:
-
Использование флага -u: Запустите ваш скрипт с помощью флага
-u
, который отключает буферизацию стандартного вывода. Это можно сделать, выполнив команду:python3 -u abort.py |& cat
-
Использование аргумента flush: В вашем коде Python вы можете явно указать, что вывод должен быть сброшен сразу после выполнения функции
print()
. Например:import os import sys print("end stdout", flush=True) print("end stderr", file=sys.stderr, flush=True) os.abort()
Использование аргумента
flush=True
заставляет Python немедленно записать вывод в потоки, избегая потери данных.
Заключение
При разработке и отладке долгоживущих процессов в системах с пайпами важно учитывать особенности буферизации вывода. Оптимизация вывода и правильное управление буферизацией позаботятся о том, что все важные логи и сообщения будут корректно записаны, даже если возникает аварийная ситуация.
Таким образом, путем использования флага -u
или перехвата буферизации с помощью аргумента flush
, вы можете гарантировать, что критически важная информация, такая как трассировка стека, будет сохранена, что особенно полезно при диагностировании сложных ошибок в длительных процессах.