перенаправление sys.stdout для регистрации в Python - PullRequest
11 голосов
/ 10 июня 2009

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

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

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

Теперь все сообщения sys.stdout / sys.stderr почти во всех сообщениях об ошибках 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. Как я могу сделать так, чтобы этого не случилось?

Ответы [ 2 ]

6 голосов
/ 10 июня 2009

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

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

import inspect
f = inspect.currentframe(N)

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

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

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

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

2 голосов
/ 10 июня 2009

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...