Перенаправление sys.stdout на запись в python

Итак, сейчас у нас много скриптов python, и мы пытаемся их консолидировать, исправлять и увольнять. Одна из вещей, которую мы пытаемся сделать, заключается в том, чтобы все sys.stdout/sys.stderr вошли в модуль регистрации python.

Теперь самое главное, мы хотим напечатать следующее:

[<ERROR LEVEL>] | <TIME> | <WHERE> | <MSG>

Теперь все sys.stdout/sys.stderr msgs в значительной степени во всех сообщениях об ошибках python находятся в формате [LEVEL] - MSG, все они написаны с использованием sys.stdout/sys.stderr. Я могу разобрать штраф, в моей sys.stdout-оболочке и в обертке sys.stderr. Затем вызовите соответствующий уровень ведения журнала, в зависимости от анализируемого ввода.

Итак, в основном у нас есть пакет под названием foo и подпакет под названием log. В __init__.py мы определяем следующее:

def initLogging(default_level = logging.INFO, stdout_wrapper = None, \
                stderr_wrapper = None):
    """
        Initialize the default logging sub system
    """
    root_logger = logging.getLogger('')
    strm_out = logging.StreamHandler(sys.__stdout__)
    strm_out.setFormatter(logging.Formatter(DEFAULT_LOG_TIME_FORMAT, \
                                            DEFAULT_LOG_TIME_FORMAT))
    root_logger.setLevel(default_level)
    root_logger.addHandler(strm_out)

    console_logger = logging.getLogger(LOGGER_CONSOLE)
    strm_out = logging.StreamHandler(sys.__stdout__)
    #strm_out.setFormatter(logging.Formatter(DEFAULT_LOG_MSG_FORMAT, \
    #                                        DEFAULT_LOG_TIME_FORMAT))
    console_logger.setLevel(logging.INFO)
    console_logger.addHandler(strm_out)

    if stdout_wrapper:
        sys.stdout = stdout_wrapper
    if stderr_wrapper:
        sys.stderr = stderr_wrapper


def cleanMsg(msg, is_stderr = False):
    logy = logging.getLogger('MSG')
    msg = msg.rstrip('\n').lstrip('\n')
    p_level = r'^(\s+)?\[(?P<LEVEL>\w+)\](\s+)?(?P<MSG>.*)$'
    m = re.match(p_level, msg)
    if m:
        msg = m.group('MSG')
        if m.group('LEVEL') in ('WARNING'):
            logy.warning(msg)
            return
        elif m.group('LEVEL') in ('ERROR'):
            logy.error(msg)
            return
    if is_stderr:
        logy.error(msg)
    else:
        logy.info(msg)

class StdOutWrapper:
    """
        Call wrapper for stdout
    """
    def write(self, s):
        cleanMsg(s, False)

class StdErrWrapper:
    """
        Call wrapper for stderr
    """
    def write(self, s):
        cleanMsg(s, True)

Теперь мы будем называть это в одном из наших скриптов, например:

import foo.log
foo.log.initLogging(20, foo.log.StdOutWrapper(), foo.log.StdErrWrapper())
sys.stdout.write('[ERROR] Foobar blew')

который будет преобразован в сообщение журнала ошибок. Как:

[ERROR] | 20090610 083215 | __init__.py | Foobar Blew

Теперь проблема в том, когда мы это делаем. Модуль, в котором было зарегистрировано сообщение об ошибке, теперь находится __init__ (соответствует файлу foo.log.__init__.py), который побеждает цели.

Я попытался сделать deepCopy/shallowCopy объектов stderr/stdout, но это ничего не делает, он все еще говорит модулю, что сообщение произошло в __init__.py. Как я могу это сделать, чтобы этого не случилось?

Ответ 1

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

Чтобы найти ваш вызывающий фрейм, вы можете использовать модуль проверки:

import inspect
f = inspect.currentframe(N)

будет искать N кадров и вернуть вам указатель на фрейм. т.е. ваш непосредственный вызывающий объект является текущим (1), но вам может потребоваться перейти в другой фрейм, если это метод stdout.write. Когда у вас есть вызывающий фрейм, вы можете получить исполняемый объект кода и посмотреть имя файла и функции, связанные с ним. например:

code = f.f_code
caller = '%s:%s' % (code.co_filename, code.co_name)

Вам также может понадобиться поместить некоторый код для обработки вызова кода, отличного от python, в вас (т.е. функции C или встроенные функции), поскольку для них могут отсутствовать объекты f_code.

В качестве альтернативы, следуя mikej answer, вы можете использовать тот же подход в пользовательском классе Logger, наследующем от logging.Logger, который отменяет findCaller для перемещения по нескольким кадрам, а скорее чем один.

Ответ 2

Я думаю, проблема в том, что ваши фактические сообщения журнала теперь создаются вызовами logy.error и logy.info в cleanMsg, поэтому этот метод является источником сообщений журнала, и вы видите это как __init__.py

Если вы посмотрите в источнике Python lib/logging/__init__.py, вы увидите метод, называемый findCaller, который используется модулем ведения журнала для вызова вызывающего пользователя запроса ведения журнала.
Возможно, вы можете переопределить это на своем объекте ведения журнала, чтобы настроить поведение?