Python: модуль регистрации - глобально - PullRequest
47 голосов
/ 01 октября 2011

Мне было интересно, как реализовать глобальный регистратор, который можно использовать везде с вашими настройками:

В настоящее время у меня есть собственный класс логгера:

class customLogger(logging.Logger):
   ...

Класс находится в отдельном файле с некоторыми форматтерами и другим материалом. Регистратор отлично работает самостоятельно.

Я импортирую этот модуль в свой основной файл Python и создаю объект, подобный этому:

self.log = logModule.customLogger(arguments)

Но, очевидно, я не могу получить доступ к этому объекту из других частей моего кода. Я использую неправильный подход? Есть ли лучший способ сделать это?

Ответы [ 4 ]

102 голосов
/ 01 октября 2011

Используйте logging.getLogger(name) для создания именованного глобального регистратора.

main.py

import log
logger = log.setup_custom_logger('root')
logger.debug('main message')

import submodule

log.py

import logging

def setup_custom_logger(name):
    formatter = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(module)s - %(message)s')

    handler = logging.StreamHandler()
    handler.setFormatter(formatter)

    logger = logging.getLogger(name)
    logger.setLevel(logging.DEBUG)
    logger.addHandler(handler)
    return logger

submodule.py

import logging

logger = logging.getLogger('root')
logger.debug('submodule message')

Выход

2011-10-01 20:08:40,049 - DEBUG - main - main message
2011-10-01 20:08:40,050 - DEBUG - submodule - submodule message
39 голосов
/ 05 мая 2017

Поскольку я не нашел удовлетворительного ответа, я хотел бы немного подробнее остановиться на ответе на вопрос, чтобы дать некоторое представление о работе и намерениях библиотеки logging, которая поставляется со стандартом Python. библиотека.

В отличие от подхода OP (оригинальный постер) библиотека четко разделяет интерфейс к регистратору и конфигурацию самого регистратора.

Настройка обработчиков является прерогативой разработчика приложения, использующего вашу библиотеку.

Это означает, что вы должны , а не создать собственный класс регистратора и настроить регистратор внутри этого класса, добавив любую конфигурацию или что-либо еще.

В библиотеке logging представлены четыре компонента: регистраторы , обработчики , фильтры и форматеры .

  • Регистраторы предоставляют интерфейс, который непосредственно использует код приложения.
  • Обработчики отправляют записи журнала (созданные регистраторами) в соответствующее место назначения.
  • Фильтры предоставляют более точную возможность определить, какие записи журнала выводить.
  • Форматтеры определяют формат записей журнала в конечном выводе.

Общая структура проекта выглядит следующим образом:

Project/
|-- .../
|   |-- ...
|
|-- project/
|   |-- package/
|   |   |-- __init__.py
|   |   |-- module.py
|   |   
|   |-- __init__.py
|   |-- project.py
|
|-- ...
|-- ...

Внутри вашего кода (как в module.py ) вы обращаетесь к экземпляру регистратора вашего модуля для регистрации событий на их определенных уровнях.

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

logger = logging.getLogger(__name__)

Специальная переменная __name__ относится к имени вашего модуля и выглядит примерно как project.package.module в зависимости от структуры кода вашего приложения.

module.py (и любой другой класс) может выглядеть примерно так:

import logging
...
log = logging.getLogger(__name__)

class ModuleClass:
    def do_something(self):
        log.debug('do_something() has been called!')

Регистратор в каждом модуле передает любое событие родительскому регистратору, который, в свою очередь, передает информацию своему присоединенному обработчику ! Аналогично структуре пакета / модуля python родительский регистратор определяется пространством имен, используя «точечные имена модулей». Вот почему имеет смысл инициализировать регистратор специальной переменной __name__ (в приведенном выше примере имя соответствует строке "project.package.module" ).

Существует два варианта глобальной настройки регистратора:

  • Создание регистратора в project.py с именем __package__, равным "project" в этом примере и, следовательно, родительским регистратором регистраторов всех подмодули. Необходимо только добавить соответствующий обработчик и форматер к этому регистратору.

  • Настройте регистратор с обработчиком и форматером в исполняемом скрипте (например, main.py ) с именем самого верхнего пакета.

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

Выполнение скрипта, например main.py , может в итоге выглядеть примерно так:

import logging
from project import App

def setup_logger():
    # create logger
    logger = logging.getLogger('project')
    logger.setLevel(logging.DEBUG)

    # create console handler and set level to debug
    ch = logging.StreamHandler()
    ch.setLevel(level)

    # create formatter
    formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(name)s: %(message)s')

    # add formatter to ch
    ch.setFormatter(formatter)

    # add ch to logger
    logger.addHandler(ch)

if __name__ == '__main__' and __package__ is None:
     setup_logger()
     app = App()
     app.do_some_funny_stuff()

Вызов метода log.setLevel(...) указывает сообщение журнала самой низкой серьезности, которое регистратор будет обрабатывать , но не обязательно выводить! Это просто означает, что сообщение передается в обработчик, если уровень серьезности сообщения выше (или равен) установленному уровню. Но обработчик отвечает за обработку сообщения журнала (например, распечатывая или сохраняя его).

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

Ведение документации

9 голосов
/ 01 октября 2011

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

3 голосов
/ 01 октября 2011

Вы можете просто передать ему строку с общей подстрокой перед первым периодом. Части строки, разделенные точкой ("."), Могут использоваться для разных классов / модулей / файлов / и т. Д. Примерно так (в частности, часть logger = logging.getLogger(loggerName)):

def getLogger(name, logdir=LOGDIR_DEFAULT, level=logging.DEBUG, logformat=FORMAT):
    base = os.path.basename(__file__)
    loggerName = "%s.%s" % (base, name)
    logFileName = os.path.join(logdir, "%s.log" % loggerName)
    logger = logging.getLogger(loggerName)
    logger.setLevel(level)
    i = 0
    while os.path.exists(logFileName) and not os.access(logFileName, os.R_OK | os.W_OK):
        i += 1
        logFileName = "%s.%s.log" % (logFileName.replace(".log", ""), str(i).zfill((len(str(i)) + 1)))
    try:
        #fh = logging.FileHandler(logFileName)
        fh = RotatingFileHandler(filename=logFileName, mode="a", maxBytes=1310720, backupCount=50)
    except IOError, exc:
        errOut = "Unable to create/open log file \"%s\"." % logFileName
        if exc.errno is 13: # Permission denied exception
            errOut = "ERROR ** Permission Denied ** - %s" % errOut
        elif exc.errno is 2: # No such directory
            errOut = "ERROR ** No such directory \"%s\"** - %s" % (os.path.split(logFileName)[0], errOut)
        elif exc.errno is 24: # Too many open files
            errOut = "ERROR ** Too many open files ** - Check open file descriptors in /proc/<PID>/fd/ (PID: %s)" % os.getpid()
        else:
            errOut = "Unhandled Exception ** %s ** - %s" % (str(exc), errOut)
        raise LogException(errOut)
    else:
        formatter = logging.Formatter(logformat)
        fh.setLevel(level)
        fh.setFormatter(formatter)
        logger.addHandler(fh)
    return logger

class MainThread:
    def __init__(self, cfgdefaults, configdir, pidfile, logdir, test=False):
        self.logdir = logdir
        logLevel = logging.DEBUG
        logPrefix = "MainThread_TEST" if self.test else "MainThread"
        try:
            self.logger = getLogger(logPrefix, self.logdir, logLevel, FORMAT)
        except LogException, exc:
            sys.stderr.write("%s\n" % exc)
            sys.stderr.flush()
            os._exit(0)
        else:
            self.logger.debug("-------------------- MainThread created.  Starting __init__() --------------------")

    def run(self):
        self.logger.debug("Initializing ReportThreads..")
        for (group, cfg) in self.config.items():
            self.logger.debug(" ------------------------------ GROUP '%s' CONFIG ------------------------------     " % group)
            for k2, v2 in cfg.items():
                self.logger.debug("%s <==> %s: %s" % (group, k2, v2))
            try:
                rt = ReportThread(self, group, cfg, self.logdir, self.test)
            except LogException, exc:
                sys.stderr.write("%s\n" % exc)
                sys.stderr.flush()
                self.logger.exception("Exception when creating ReportThread (%s)" % group)
                logging.shutdown()
                os._exit(1)
            else:
                self.threads.append(rt)
        self.logger.debug("Threads initialized.. \"%s\"" % ", ".join([t.name for t in self.threads]))
        for t in self.threads:
            t.Start()
        if not self.test:
            self.loop()


class ReportThread:
    def __init__(self, mainThread, name, config, logdir, test):
        self.mainThread = mainThread
        self.name = name
        logLevel = logging.DEBUG
        self.logger = getLogger("MainThread%s.ReportThread_%s" % ("_TEST" if self.test else "", self.name), logdir, logLevel, FORMAT)
        self.logger.info("init database...")
        self.initDB()
        # etc....

if __name__ == "__main__":
    # .....
    MainThread(cfgdefaults=options.cfgdefaults, configdir=options.configdir, pidfile=options.pidfile, logdir=options.logdir, test=options.test)
...