Как одновременно выводить и регистрировать сообщения в Python

Мне нужно что-то вроде того, что делает команда tee в системах на базе Linux, но в чистом коде Python.

Я попробовал использовать этот фрагмент:

import sys

# Define the file name for logging
log_file_name = 'output.log'

# Open the file in append mode
log_file = open(log_file_name, 'a')

# Function to log output to both console and file
def log(message):
    # Write to console
    print(message)
    # Write to file
    log_file.write(message + '\n')
    # Flush the buffer to ensure the message is written immediately
    log_file.flush()

# Redirect stdout and stderr to the log function
sys.stdout.write = log
sys.stderr.write = log


print("Hello world!")

Однако я получаю эту ошибку:

object address  : 0x7f88e6b1c1c0
object refcount : 2
object type     : 0x9e14c0
object type name: RecursionError
object repr     : RecursionError('maximum recursion depth exceeded')
lost sys.stderr

и всякий раз, когда я комментирую оператор печати в функции log, он записывает сообщение в файл журнала, но не выводит сообщение на консоль. Почему это происходит и как я могу это исправить?

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
0
102
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Вот один из способов:

import sys

_print = print

def print(*args, **kwargs):
    _print(*args, **kwargs, file=open('output.log','w'))
    _print(*args, **kwargs)

print("Hello world!")

Объяснение

Функция print() уже имеет аргумент для назначения файла. https://docs.python.org/3/library/functions.html#print

Мы сохранили исходную функцию печати в _print() и вызываем ее из нашей функции печати, чтобы удовлетворить обе наши потребности. Таким образом, вызывающий код не нужно менять.

Ответ принят как подходящий

Что пошло не так

print в Python записывает данное сообщение в стандартный вывод. Причина, по которой вы получаете эту ошибку, заключается в том, что вы вернулись sys.stdout.write к своей log функции, но внутри этой log функции также есть вызов print. Строка print(message) теперь снова вызывает stdout.write, которая является функцией log. Это происходит рекурсивно, пока вы не достигнете максимальной глубины рекурсии, которая по умолчанию в Python равна 1000.

Решение на питоне

Я не думаю, что вам стоит заниматься тем, чем вы сейчас занимаетесь. Перезаписывая sys.stdout.write, вы взламываете свой собственный вход в Python, что вполне может сломать любые используемые вами пакеты или, по крайней мере, сильно запутать всех, с кем вы работаете.

Вероятно, было бы проще просто написать что-то вроде следующего и заменить операторы print на операторы output в вашем коде.

def output(message):
    # To console
    print(message)

    # To file
    with open(log_file_name, 'a') as file:
        file.write(message + '\n')

Обратите внимание, что вы также не закрывали файл после записи в него, что потенциально может привести к повреждению данных. См., например, это объяснение, почему with высказывания — ваш лучший друг!

Но если возможно, использование tee, вероятно, будет самым простым решением.

Обновлено:

Примечание о модуле logging

На самом деле, такая функциональность также возможна с использованием модуля logging из стандартной библиотеки, для более готового к использованию решения. Следующая конфигурация записывает данные как в консоль, так и в файл каждый раз, когда используется регистратор test:

LOGGING = {
    "version": 1,
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
        },
        "file": {
            "class": "logging.FileHandler",
            "filename": "debug.log",
        },
    },
    "loggers": {
        "test": {
            "handlers": ["console", "file"],
        },
    },
}

The Problem:

Проблема возникает из-за того, что оператор печати также выводит данные в sys.stdout, что приводит к рекурсивному вызову функции при обнаружении вывода в потоке stdout.

Solution:

Чтобы решить эту проблему, создается класс LoggingWrapper, сохраняющий исходные потоки stdout и stderr. Этот класс изменяет stdout и stderr для использования оболочки, гарантируя, что данные, выводимые в эти потоки, направляются в исходные потоки, а также регистрируются в файле журнала.

import sys

class LoggingWrapper:
    def __init__(self, original_stream):
        self.original_stream = original_stream

    def write(self, message):
        # Write to the original stream
        self.original_stream.write(message)
        # Write to the log file
        log_file.write(message)
        # Flush the buffer to ensure the message is written immediately
        log_file.flush()

    def flush(self):
        # Ensure the original stream is flushed
        self.original_stream.flush()

# Define the file name for logging
log_file_name = 'output.log'

# Open the file in append mode
log_file = open(log_file_name, 'a')

# Create wrappers for stdout and stderr
stdout_wrapper = LoggingWrapper(sys.stdout)
stderr_wrapper = LoggingWrapper(sys.stderr)

# Redirect stdout and stderr to the wrappers
sys.stdout = stdout_wrapper
sys.stderr = stderr_wrapper

Это решение гарантирует, что сообщения будут записываться в указанный файл журнала, сохраняя при этом исходное поведение stdout и stderr. Это предотвращает ошибки рекурсии, отделяя процесс регистрации от стандартного потока вывода.

Другие вопросы по теме