Вопрос-Ответ

How to duplicate sys.stdout to a log file?

Как дублировать 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.

Подробнее см. в разделе Ведение журнала по нескольким направлениям

python