Как дублировать sys.stdout в файл журнала?
Редактировать: Поскольку кажется, что либо решения нет, либо я делаю что-то настолько нестандартное, что никто не знает - я пересмотрю свой вопрос, чтобы также задать: каков наилучший способ ведения журнала, когда приложение python выполняет много системных вызовов?
В моем приложении есть два режима. В интерактивном режиме я хочу, чтобы все выходные данные выводились на экран, а также в файл журнала, включая выходные данные любых системных вызовов. В режиме демона все выходные данные отправляются в журнал. Режим демона отлично работает с использованием os.dup2()
. Я не могу найти способ "привязать" все выходные данные к журналу в интерактивном режиме, без изменения каждого системного вызова.
Другими словами, я хочу функциональность командной строки 'tee' для любого вывода, генерируемого приложением python, включая вывод системного вызова.
Для уточнения:
Чтобы перенаправить все выходные данные, я делаю что-то вроде этого, и это отлично работает:
# open our log file
so = se = open("%s.log" % self.name, 'w', 0)
# re-open stdout without buffering
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
# redirect stdout and stderr to the log file opened above
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
Самое приятное в этом то, что он не требует специальных вызовов print от остальной части кода. Код также выполняет некоторые команды оболочки, так что приятно не иметь дело с каждым их выводом по отдельности.
Просто я хочу сделать то же самое, за исключением дублирования вместо перенаправления.
Сначала я подумал, что простое изменение dup2
's должно сработать. Почему это не так? Вот мой тест:
import os, sys
### my broken solution:
so = se = open("a.log", 'w', 0)
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
os.dup2(sys.stdout.fileno(), so.fileno())
os.dup2(sys.stderr.fileno(), se.fileno())
###
print("foo bar")
os.spawnve("P_WAIT", "/bin/ls", ["/bin/ls"], {})
os.execve("/bin/ls", ["/bin/ls"], os.environ)
Файл "a.log" должен быть идентичен тому, что отображался на экране.
Переведено автоматически
Ответ 1
У меня была такая же проблема раньше, и я нашел этот фрагмент очень полезным:
class Tee(object):
def __init__(self, name, mode):
self.file = open(name, mode)
self.stdout = sys.stdout
sys.stdout = self
def __del__(self):
sys.stdout = self.stdout
self.file.close()
def write(self, data):
self.file.write(data)
self.stdout.write(data)
def flush(self):
self.file.flush()
откуда: http://mail.python.org/pipermail/python-list/2007-May/438106.html
Ответ 2
print
Инструкция вызовет write()
метод любого объекта, который вы назначите sys.stdout .
Я бы создал небольшой класс для записи в два места одновременно...
import sys
class Logger(object):
def __init__(self):
self.terminal = sys.stdout
self.log = open("log.dat", "a")
def write(self, message):
self.terminal.write(message)
self.log.write(message)
sys.stdout = Logger()
Теперь оператор print
будет одновременно выводиться на экран и добавляться в ваш файл журнала:
# prints "1 2" to <stdout> AND log.dat
print "%d %d" % (1,2)
Очевидно, что это быстро и грязно. Некоторые примечания:
- Вероятно, вам следует параметризовать имя файла журнала.
- Вероятно, вам следует вернуть значение sys.stdout в
<stdout>
, если вы не будете вести журнал в течение всего времени работы программы. - Возможно, вам понадобится возможность записи в несколько файлов журнала одновременно или обрабатывать разные уровни журналов и т.д.
Все это достаточно просто, поэтому мне удобно оставить их в качестве упражнений для читателя. Ключевым моментом здесь является то, что print
просто вызывает "файлоподобный объект", который назначен sys.stdout
.
Ответ 3
Поскольку вам удобно создавать внешние процессы из своего кода, вы могли бы использовать tee
itself . Я не знаю ни одного системного вызова Unix, который делал бы именно то, что делает tee
.
# Note this version was written circa Python 2.6, see below for
# an updated 3.3+-compatible version.
import subprocess, os, sys
# Unbuffer output (this ensures the output is in the correct order)
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
tee = subprocess.Popen(["tee", "log.txt"], stdin=subprocess.PIPE)
os.dup2(tee.stdin.fileno(), sys.stdout.fileno())
os.dup2(tee.stdin.fileno(), sys.stderr.fileno())
print "\nstdout"
print >>sys.stderr, "stderr"
os.spawnve("P_WAIT", "/bin/ls", ["/bin/ls"], {})
os.execve("/bin/ls", ["/bin/ls"], os.environ)
Вы также можете эмулировать tee
с помощью многопроцессорного пакета (или использовать processing, если вы используете Python 2.5 или более ранней версии).
Обновить
Вот версия, совместимая с Python 3.3+:
import subprocess, os, sys
tee = subprocess.Popen(["tee", "log.txt"], stdin=subprocess.PIPE)
# Cause tee's stdin to get a copy of our stdin/stdout (as well as that
# of any child processes we spawn)
os.dup2(tee.stdin.fileno(), sys.stdout.fileno())
os.dup2(tee.stdin.fileno(), sys.stderr.fileno())
# The flush flag is needed to guarantee these lines are written before
# the two spawned /bin/ls processes emit any output
print("\nstdout", flush=True)
print("stderr", file=sys.stderr, flush=True)
# These child processes' stdin/stdout are
os.spawnve("P_WAIT", "/bin/ls", ["/bin/ls"], {})
os.execve("/bin/ls", ["/bin/ls"], os.environ)
Ответ 4
Что вам действительно нужно, так это logging
модуль из стандартной библиотеки. Создайте регистратор и прикрепите два обработчика, один будет выполнять запись в файл, а другой - в stdout или stderr.
Подробнее см. в разделе Ведение журнала по нескольким направлениям