Регистратор файлов / модулей в Python

У меня есть код Python, в который мне нужно добавить логирование.

Я всегда отдавал предпочтение красивым, большим макросам на языке C, таким как «DEBUG ()», «ERROR ()» и т. Д. Для журналирования. Я чувствую, что это упрощает чтение кода (без объектов), когда точки трассировки визуально отличаются от реального кода.

Я также хотел бы иметь возможность устанавливать уровни ведения журнала на уровне модуля.

Как я могу создать модуль под названием «журнал», который может это делать (используя модуль журналирования библиотеки Python std)?

E.g.:

Файл: main.py

# This imports LOG_MODULE_NAME, DEBUG, WARN, etc
from log import *
import my_module

LOG_MODULE_NAME("main")

log.set_level("main", log.LVL_DEBUG)
log.set_level("my_module", log.LVL_WARN)

if __name__ == "__main__":
    foo = my_module.myFunc(2)

DEBUG("Exiting main.py")

Файл: my_module.py

from log import *

LOG_MODULE_NAME("my_module")


def myFunc(x):
    DEBUG("Entering function")
    if x != 1:
         WARN("I thought it would be 1")
    DEBUG("Exiting function")
    return x+1

Я ожидал, что результат будет выглядеть примерно так:

[WARN:my_module - my_module.py:9] I thought it would be 1
[DEBUG:main - main.py:11] Exiting main.py

person BobIsNotMyName    schedule 17.07.2013    source источник
comment
возможный дубликат Элегантная настройка ведения журнала Python в Django   -  person dnozay    schedule 18.07.2013
comment
@dnozay Это не так. OP пытается создать функции, которые охватывают Logger.   -  person 2rs2ts    schedule 18.07.2013
comment
Если вы читаете вопрос, который я связал, нам не нужен n-й вопрос о том, как настроить ведение журнала, когда задействованы разные модули.   -  person dnozay    schedule 18.07.2013
comment
Пожалуйста, отредактируйте свой вопрос. В настоящее время ваш текущий вопрос касается того, как написать целый модуль. Вы пробовали написать этот модуль? На каких частях вы застряли? Используете модуль ведения журнала Python? Получение имени модуля, из которого вызывается функция? Настраиваете форматтер, как вы указали?   -  person Mark Hildreth    schedule 18.07.2013
comment
Я застрял на том, чтобы получить функцию (DEBUG, WARN и т. Д.), Которая должна иметь поведение, зависящее от файла / модуля, из которого она вызывается. Переосмыслив это, я думаю, я мог бы задать этот вопрос более простым способом (который не имеет ничего общего с ведением журнала). Я не могу просто сослаться на _ name_ в log.py, потому что это будет относиться к модулю журнала. Кроме того, я хотел бы иметь возможность регистрировать несколько файлов как один модуль. В любом случае, прочитав ответ 2rs2ts, я думаю, что просто использовать глобальное имя - это правильный способ сделать это. Я не уверен насчет влияния x import *.   -  person BobIsNotMyName    schedule 18.07.2013
comment
@BobIsNotMyName import дает вам доступ к коду внутри модуля. Очевидно, это не означает, что пространства имен будут конфликтовать. В любом случае вам не нужно и не нужно делать модуль, который будет обертывать logging.   -  person 2rs2ts    schedule 18.07.2013


Ответы (4)


Если вы хотите, чтобы имя регистратора указывало на модуль, в котором он использовался, вы можете использовать _ 1_ и передайте __name__ в качестве (необязательного) аргумента, как описано в здесь.

Если вы хотите использовать имена вроде DEBUG(), сделайте что-нибудь подобное в каждом своих файлах ...

LOG_MODULE_NAME = logging.getLogger(__name__)

def DEBUG(msg):
    global LOG_MODULE_NAME
    LOG_MODULE_NAME.debug(msg)

Я не понимаю, как глобальные пространства имен на самом деле работают в Python ... в этом ответе говорится

у каждого модуля есть свое собственное «глобальное» пространство имен.

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

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

DEBUG:my_module:Entering function
WARN:my_module:I thought it would be 1
DEBUG:my_module:Exiting function
DEBUG:root:Exiting main.py

Недостаточно интуитивно? Вам нужен модуль inpsect, который даст вам много информации о программе во время ее работы. Это, например, даст вам текущий номер строки.

Ваше замечание о «настройке уровней журнала на уровне модуля» заставляет меня думать, что вам нужно что-то вроде _ 8_. Вы можете попытаться раздавить это вот так:

LOG_MODULE_NAME = logging.getLogger(__name__)
MODULE_LOG_LEVEL = log.LVL_WARN

def DEBUG(msg):
    if MODULE_LOG_LEVEL = log.LVL_DEBUG:
        global LOG_MODULE_NAME
        LOG_MODULE_NAME.debug(msg)

Однако у меня недостаточно опыта работы с модулем logging, чтобы рассказать вам, как можно динамически изменять уровни журналов каждого модуля.

person 2rs2ts    schedule 17.07.2013
comment
Я бы добавил, что модуль ведения журнала дает вам возможность настроить вывод журнала в соответствии с тем, что вы просили выше. - person zzzirk; 18.07.2013
comment
Первоначально я разместил ссылку на logging учебник, но затем я понял, что OP уже упомянул модуль, поэтому я удалил его. - person 2rs2ts; 18.07.2013
comment
Спасибо. Да, я много читал о модуле ведения журнала, прежде чем опубликовать это. Я просто хотел знать, как заставить DEBUG () иметь поведение на основе файла, из которого он был вызван (или, более конкретно, на основе строки, переданной в LOG_MODULE_NAME () в указанном файле - как в моем примере кода). В любом случае, способ обработки глобальных переменных в Python мне все еще не на 100% ясен, но я думаю, что это правильный путь. - person BobIsNotMyName; 18.07.2013
comment
@BobIsNotMyName Вы также можете использовать препроцессор. ;) - person 2rs2ts; 18.07.2013
comment
@ 2rs2ts: Использование global для привязки области видимости модуля достаточно плохо, но import внутри условного выражения просто пугает. Пожалуйста, посмотрите мой ответ, чтобы узнать о более питоническом подходе. - person msw; 18.07.2013
comment
@msw Я нигде об этом не писал, но у вас есть точка зрения на global. - person 2rs2ts; 18.07.2013
comment
Нет, вы не написали импорт в условном выражении; мои извинения. - person msw; 18.07.2013
comment
@ 2rs2ts, ваше решение, похоже, испортило номера строк, можете ли вы создать оператор, как в вопросе: [DEBUG:main - main.py:11] Exiting main.py? - person dnozay; 19.07.2013
comment
@dnozay Что вы имеете в виду, когда говорите о неправильных номерах строк? Если вам нужны номера строк, убедитесь, что format (например, в logging.basicConfig()) включает _ 3_ атрибут. - person 2rs2ts; 19.07.2013
comment
@dnozay Номер строки - это номер позиции вызова Logger.debug() или других подобных вызовов регистрации. Прочтите документацию: белье%(lineno)d Номер строки исходного текста, в которой ведется журнал звонок был оформлен (при наличии). Это ожидаемое поведение. - person 2rs2ts; 19.07.2013
comment
Я знаю; именно поэтому этот ответ не решает вопрос; Ответ @msw лучше в этом отношении, где номер строки - это то, где выдается вызов DEBUG(). - person dnozay; 19.07.2013
comment
@dnozay Я вижу, как это подразумевалось в вопросе OP. Лично я думал, что ответ MSW был лучше. - person 2rs2ts; 19.07.2013

Эти ответы, похоже, пропускают очень простую идею о том, что функции являются первоклассными объектами, которые позволяют:

def a_function(n):
    pass

MY_HAPPY_NAME = a_function
MY_HAPPY_NAME(15) # which is equivalent to a_function(15)

Кроме, я рекомендую вам не делать этого. соглашения о кодировании PEP8 широко используются, потому что жизнь слишком коротка, чтобы заставить меня читать идентификаторы в COBOLy так, как вам нравится их писать.

В некоторых других ответах также используется оператор global, который почти всегда не нужен в Python. Если вы создаете экземпляры регистратора на уровне модуля, тогда

import logging

log = logging.getLogger(__name__)

def some_method():
    ...
    log.debug(...)

- это вполне работоспособный и лаконичный способ использования переменной log на уровне модуля. Вы даже могли бы сделать

log = logging.getLogger(__name__)
DEBUG = log.debug

def some_method():
    ...
    DEBUG(...)

но я оставляю за собой право называть это уродливым.

person msw    schedule 18.07.2013
comment
Я совершенно забыл о том, как можно воспользоваться преимуществами работы функций в Python. Очень-очень хороший звонок. Я согласен, что делать то, что делает OP, - ужасная идея, но, эй, он спросил. - person 2rs2ts; 18.07.2013
comment
Может быть, это плохая идея ... Но я считаю, что код выглядит гораздо более загроможденным, когда повсюду переплетаются всевозможные строки log.debug, log.warn и т. Д. Визуально трудно отличить инструментарий / трассировку от реального полезного кода. OTOH, DEBUG, WARN и т. Д. Иногда действительно упрощают чтение кода (инструментарий начинает выглядеть как комментирование). - person BobIsNotMyName; 18.07.2013
comment
@BobIsNotMyName Однако ваш подход исключает возможность наличия нескольких регистраторов с разными конфигурациями. Если вы не добавите регистратор в качестве аргумента к своим макросам, в этом случае будет менее ясно, чем вызов метода, что вы делаете. - person 2rs2ts; 18.07.2013
comment
@msw, честно говоря, единственная разница между вашим ответом и стандартным способом - DEBUG = log.debug. - person dnozay; 18.07.2013

Чтобы расширить понятие @ 2rs2ts, вы можете просто регистрироваться по модулям, как показано в Python регистрация HOWTO.

Хорошее соглашение, которое следует использовать при именовании средств ведения журнала, - использовать средство ведения журнала на уровне модуля в каждом модуле, который использует ведение журнала, с следующим названием:

logger = logging.getLogger(__name__) 

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

Сделайте первые 2 строки всех ваших файлов так:

import logging
logger = logging.getLogger(__name__) 

Тогда у вас есть ведение журнала для каждого модуля, которое легко настраивается.

Вы можете войти на разные уровни через:

logger.debug("I just saw this %s" , var )
logger.info("I just saw this %s" , var )
logger.warn("I just saw this %s" , var )

Обратите внимание, что я передал переменную шаблона строки как *arg, а не %, чтобы немедленно создать шаблон строки. Если ваш регистратор настроен на более высокий уровень, чем уровень, на котором вы входите, вы экономите циклы ЦП. В logging документации есть несколько подобных приемов.

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

person Jonathan Vanasco    schedule 17.07.2013

Более чистое решение можно найти по этому другому вопросу. Более чистый способ настроить ведение журнала для библиотеки - использовать эти несколько строк в каждом вашем модуле.

import logging
logging.getLogger(__name__).addHandler(logging.NullHandler())

Ваш основной скрипт (например, мой script.py) отвечает за:

  • выполнение конфигурации ведения журнала, например с помощью logging.basicConfig() или _ 4_
  • выбор Formatter для использования в событиях журнала.
  • выбор logging.handlers для отправки события в нужное место (например, удаленное ведение журнала, консоль, файл ...)

Возьмем ваш пример:

def myFunc(x):
    DEBUG("Entering function")
    if x != 1:
         WARN("I thought it would be 1")
    DEBUG("Exiting function")
    return x+1

и ваш ожидаемый результат:

[WARN:my_module - my_module.py:9] I thought it would be 1
[DEBUG:main - main.py:11] Exiting main.py

Как мы выясним, что DEBUG("Existing main.py") произошло main.py:11? Очевидный ответ: вы не можете, если вы не проверите фрейм и не выясните, кто звонит. Если вы хотите определить функцию DEBUG() в модуле log.py, вы не получите правильных записей. Функции очень отличаются от макросов, вам нужно определить функцию, и нет встроенной замены, поэтому вы больше не находитесь в одном кадре.

Просто для удовольствия я написал решение, используя Самоанализ с inspect модулем (примечание: не запускайте это как производственный код), но на самом деле, чтобы ответить на вопросы, «где была вызвана эта функция и в какой строке?» нужно проделать некоторую работу.

  • bar.py

    # file: bar.py
    # this is just a module to show DEBUG works when used in a different module
    from log import DEBUG
    
    def baz():
        DEBUG('oh hai from baz')
    
  • log.py

    # file: log.py
    import inspect
    import logging
    
    def DEBUG(msg, *args):
        curframe = inspect.currentframe()
        try:
            # http://docs.python.org/2/library/inspect.html#inspect.getouterframes
            _, filename, _, code, _, _ = inspect.getouterframes(curframe, 2)[1]
            logging.debug('[%s:%s] '+msg, filename, code, *args)
        finally:
            del curframe
    
  • основной скрипт script.py

    # file: script.py
    # responsible for the logging setup.
    import logging
    from log import DEBUG
    from bar import baz
    
    def foo():
        DEBUG('oh hai')
    
    if __name__ == '__main__':
        logging.basicConfig()
        logging.root.setLevel(logging.DEBUG)
        foo()
        baz()
    

Очевидная проблема с моим решением заключается в том, что мы используем только регистратор root:

$ python script.py 
DEBUG:root:[script.py:foo] oh hai
DEBUG:root:[/Users/dnozay/Desktop/bar.py:baz] oh hai from baz

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

person dnozay    schedule 17.07.2013
comment
@msw, ответ на этот вопрос в том виде, в котором он был задан, будет способствовать распространению недобросовестных практик. даже ответ @ 2rs2ts изменился, чтобы продвигать передовой опыт; logging.getLogger(__name__) - лучший способ. очевидный ответ на этот вопрос - не делай этого! пожалуйста, следуйте инструкциям. - person dnozay; 18.07.2013