Python: модуль протоколирования - глобально
Эй, мне было интересно, как реализовать глобальный регистратор, который можно использовать везде с вашими собственными настройками:
У меня
class customLogger(logging.Logger):
...
в файле с его форматировщиками и другими материалами.
Регистратор отлично работает сам по себе.
Я импортирую этот модуль в свой файл main.py и создаю такой объект:
self.log = log.customLogger(arguments)
Но, очевидно, я не могу получить доступ к этому объекту из других частей моего кода.
Я использую неправильный подход? Есть ли лучший способ сделать это?
Ответы
Ответ 1
Используйте 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
Ответ 2
Поскольку я не нашел удовлетворительного ответа, я хотел бы немного рассказать об ответе на вопрос, чтобы дать представление о работе и намерениях библиотеки 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__
, которое в этом примере равно "проекту" и, следовательно, является родительским регистратором регистраторов всех подмодулей. Необходимо только добавить к этому регистратору соответствующий обработчик и форматтер.
-
Настройте регистратор с обработчиком и форматированием в исполняемом script (например, main.py) с именем самого верхнего пакета.
При разработке библиотеки, использующей ведение журнала, вы должны позаботиться о том, как библиотека использует ведение журнала - например, имена используемых журналов.
Выполнение script, например 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
предлагает структурированный и модульный подход, который просто необходимо использовать в соответствии с потребностями.
Документация по протоколированию
Ответ 3
Создайте экземпляр customLogger
в вашем модуле журнала и используйте его как одноэлементный - просто используйте импортированный экземпляр, а не класс.
Ответ 4
Вы можете просто передать ему строку с общей подстрокой до первого периода. Части строки, разделенные периодом ( "." ), Могут использоваться для разных классов/модулей/файлов/и т.д. Так же (в частности, часть 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)