Как скопировать sys.stdout в файл журнала? - PullRequest
137 голосов
/ 06 марта 2009

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

Самое приятное в этом то, что он не требует специальных вызовов для печати из остальной части кода. В коде также выполняются некоторые команды оболочки, поэтому хорошо не иметь дело с каждым их выводом в отдельности.

Просто я хочу сделать то же самое, за исключением дублирования вместо перенаправления.

Сначала подумал, я подумал, что простое изменение dup2 должно работать. Почему не так? Вот мой тест:

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" должен совпадать с тем, что отображалось на экране.

Ответы [ 16 ]

128 голосов
/ 06 марта 2009

У меня уже была такая же проблема, и я нашел этот фрагмент очень полезным:

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

71 голосов
/ 06 марта 2009

Оператор 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.

64 голосов
/ 06 марта 2009

То, что вы действительно хотите, это logging модуль из стандартной библиотеки. Создайте регистратор и подключите два обработчика, один будет записывать в файл, а другой - в stdout или stderr.

См. Вход в несколько пунктов назначения для получения подробной информации

49 голосов
/ 16 марта 2009

Поскольку вы можете создавать внешние процессы из своего кода, вы можете использовать tee. Я не знаю ни одного системного вызова 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, используя пакет multiprocessing (или использовать обработка , если вы используете 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)
16 голосов
/ 14 мая 2013

Вот еще одно решение, которое является более общим, чем другие - оно поддерживает разделение вывода (записанного в sys.stdout) на любое количество файловоподобных объектов. Там нет требования, что __stdout__ сама включена.

import sys

class multifile(object):
    def __init__(self, files):
        self._files = files
    def __getattr__(self, attr, *args):
        return self._wrap(attr, *args)
    def _wrap(self, attr, *args):
        def g(*a, **kw):
            for f in self._files:
                res = getattr(f, attr, *args)(*a, **kw)
            return res
        return g

# for a tee-like behavior, use like this:
sys.stdout = multifile([ sys.stdout, open('myfile.txt', 'w') ])

# all these forms work:
print 'abc'
print >>sys.stdout, 'line2'
sys.stdout.write('line3\n')

ПРИМЕЧАНИЕ. Это подтверждение концепции. Реализация здесь не завершена, так как она охватывает только методы файловоподобных объектов (например, write), опуская значения members / properties / setattr и т. Д. Однако для большинства человек в его нынешнем виде.

Что мне в нем нравится, кроме его общности, так это то, что он чистый в том смысле, что не делает прямых вызовов write, flush, os.dup2 и т. Д.

12 голосов
/ 07 февраля 2010

Как описано в другом месте, возможно, лучшее решение - использовать модуль регистрации напрямую:

import logging

logging.basicConfig(level=logging.DEBUG, filename='mylog.log')
logging.info('this should to write to the log file')

Однако в некоторых (редких) случаях вы действительно хотите перенаправить стандартный вывод. У меня была такая ситуация, когда я расширял команду django runserver, которая использует print: я не хотел взламывать источник django, но нуждался в операторах print, чтобы перейти в файл.

Это способ перенаправить stdout и stderr из оболочки с помощью модуля регистрации:

import logging, sys

class LogFile(object):
    """File-like object to log text using the `logging` module."""

    def __init__(self, name=None):
        self.logger = logging.getLogger(name)

    def write(self, msg, level=logging.INFO):
        self.logger.log(level, msg)

    def flush(self):
        for handler in self.logger.handlers:
            handler.flush()

logging.basicConfig(level=logging.DEBUG, filename='mylog.log')

# Redirect stdout and stderr
sys.stdout = LogFile('stdout')
sys.stderr = LogFile('stderr')

print 'this should to write to the log file'

Эту реализацию LogFile следует использовать только в том случае, если вы действительно не можете использовать модуль ведения журнала напрямую.

11 голосов
/ 06 августа 2010

Я написал tee() реализацию в Python, которая должна работать в большинстве случаев, и она также работает в Windows.

https://github.com/pycontribs/tendo

Кроме того, вы можете использовать его в сочетании с logging модулем из Python, если хотите.

9 голосов
/ 15 марта 2009

(Ах, просто перечитайте свой вопрос и увидите, что это не совсем применимо.)

Вот пример программы, которая использует модуль регистрации Python . Этот модуль регистрации был во всех версиях с 2.3. В этом примере регистрация настраивается с помощью параметров командной строки.

В тихом режиме он будет регистрировать только файл, в обычном режиме - как файл, так и консоль.

import os
import sys
import logging
from optparse import OptionParser

def initialize_logging(options):
    """ Log information based upon users options"""

    logger = logging.getLogger('project')
    formatter = logging.Formatter('%(asctime)s %(levelname)s\t%(message)s')
    level = logging.__dict__.get(options.loglevel.upper(),logging.DEBUG)
    logger.setLevel(level)

    # Output logging information to screen
    if not options.quiet:
        hdlr = logging.StreamHandler(sys.stderr)
        hdlr.setFormatter(formatter)
        logger.addHandler(hdlr)

    # Output logging information to file
    logfile = os.path.join(options.logdir, "project.log")
    if options.clean and os.path.isfile(logfile):
        os.remove(logfile)
    hdlr2 = logging.FileHandler(logfile)
    hdlr2.setFormatter(formatter)
    logger.addHandler(hdlr2)

    return logger

def main(argv=None):
    if argv is None:
        argv = sys.argv[1:]

    # Setup command line options
    parser = OptionParser("usage: %prog [options]")
    parser.add_option("-l", "--logdir", dest="logdir", default=".", help="log DIRECTORY (default ./)")
    parser.add_option("-v", "--loglevel", dest="loglevel", default="debug", help="logging level (debug, info, error)")
    parser.add_option("-q", "--quiet", action="store_true", dest="quiet", help="do not log to console")
    parser.add_option("-c", "--clean", dest="clean", action="store_true", default=False, help="remove old log file")

    # Process command line options
    (options, args) = parser.parse_args(argv)

    # Setup logger format and output locations
    logger = initialize_logging(options)

    # Examples
    logger.error("This is an error message.")
    logger.info("This is an info message.")
    logger.debug("This is a debug message.")

if __name__ == "__main__":
    sys.exit(main())
8 голосов
/ 30 июля 2013

Для ответа Джона Т: https://stackoverflow.com/a/616686/395687

Я добавил __enter__ и __exit__ методы, чтобы использовать его в качестве менеджера контекста с ключевым словом with, которое дает этот код

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 __enter__(self):
        pass

    def __exit__(self, _type, _value, _traceback):
        pass

Затем его можно использовать как

with Tee('outfile.log', 'w'):
    print('I am written to both stdout and outfile.log')
5 голосов
/ 05 июля 2014

Я знаю, что на этот вопрос неоднократно отвечали, но для этого я взял основной ответ из ответа Джона Т * и изменил его так, чтобы он содержал предложенный сброс и следовал за связанной версией. Я также добавил вход и выход, как упомянуто в ответе cladmi для использования с оператором with. Кроме того, в документации упоминается очистка файлов с использованием os.fsync(), поэтому я также добавил это. Я не знаю, нужно ли вам действительно , но оно там.

import sys, os

class Logger(object):
    "Lumberjack class - duplicates sys.stdout to a log file and it's okay"
    #source: https://stackoverflow.com/q/616645
    def __init__(self, filename="Red.Wood", mode="a", buff=0):
        self.stdout = sys.stdout
        self.file = open(filename, mode, buff)
        sys.stdout = self

    def __del__(self):
        self.close()

    def __enter__(self):
        pass

    def __exit__(self, *args):
        self.close()

    def write(self, message):
        self.stdout.write(message)
        self.file.write(message)

    def flush(self):
        self.stdout.flush()
        self.file.flush()
        os.fsync(self.file.fileno())

    def close(self):
        if self.stdout != None:
            sys.stdout = self.stdout
            self.stdout = None

        if self.file != None:
            self.file.close()
            self.file = None

Вы можете использовать его

with Logger('My_best_girlie_by_my.side'):
    print("we'd sing sing sing")

или

Log=Logger('Sleeps_all.night')
print('works all day')
Log.close()
...