Использование входа в Python в нескольких модулях
У меня есть небольшой проект Python, который имеет следующую структуру -
Project
-- pkg01
-- test01.py
-- pkg02
-- test02.py
-- logging.conf
Я планирую использовать модуль регистрации по умолчанию для печати сообщений на стандартный вывод и файл журнала. Чтобы использовать модуль регистрации, требуется некоторая инициализация -
import logging.config
logging.config.fileConfig('logging.conf')
logger = logging.getLogger('pyApp')
logger.info('testing')
В настоящее время я выполняю эту инициализацию в каждом модуле, прежде чем начать регистрировать сообщения. Можно ли выполнить эту инициализацию только один раз в одном месте, чтобы одни и те же настройки использовались повторно при ведении журнала по всему проекту?
12 ответов
В каждом модуле рекомендуется использовать регистратор, определяемый следующим образом:
import logging
logger = logging.getLogger(__name__)
в верхней части модуля, а затем в другом коде в модуле, например,
logger.debug('My message with %s', 'variable data')
Если вам нужно подразделить действия по регистрации внутри модуля, используйте, например,
loggerA = logging.getLogger(__name__ + '.A')
loggerB = logging.getLogger(__name__ + '.B')
и войти в loggerA
а также loggerB
по мере необходимости.
В вашей основной программе или программах, например:
def main():
"your program code"
if __name__ == '__main__':
import logging.config
logging.config.fileConfig('/path/to/logging.conf')
main()
или же
def main():
import logging.config
logging.config.fileConfig('/path/to/logging.conf')
# your program code
if __name__ == '__main__':
main()
Смотрите здесь для регистрации из нескольких модулей, и здесь для регистрации конфигурации для кода, который будет использоваться в качестве модуля библиотеки другим кодом.
Обновление: при звонке fileConfig()
Вы можете указать disable_existing_loggers=False
если вы используете Python 2.6 или более поздней версии (см. документацию для получения дополнительной информации). Значением по умолчанию является True
для обратной совместимости, которая приводит к отключению всех существующих регистраторов fileConfig()
если они или их предок не указаны явно в конфигурации. Со значением, установленным на False
существующие логгеры остаются одни. Если вы используете Python 2.7/Python 3.2 или новее, вы можете рассмотреть dictConfig()
API, который лучше, чем fileConfig()
поскольку это дает больше контроля над конфигурацией.
Фактически каждый регистратор является дочерним по отношению к регистратору пакетов родителя (т.е. package.subpackage.module
наследует конфигурацию от package.subpackage)
, так что все, что вам нужно сделать, это просто настроить корневой логгер. Это может быть достигнуто путем logging.config.fileConfig
(ваш собственный конфиг для логгеров) или logging.basicConfig
(устанавливает корневой логгер). Настройка входа в ваш входной модуль (__main__.py
или что вы хотите запустить, например main_script.py
, __init__.py
тоже работает)
используя basicConfig:
# package/__main__.py
import logging
import sys
logging.basicConfig(stream=sys.stdout, level=logging.INFO)
используя fileConfig:
# package/__main__.py
import logging
import logging.config
logging.config.fileConfig('logging.conf')
и затем создайте каждый регистратор, используя:
# package/submodule.py
# or
# package/subpackage/submodule.py
import logging
log = logging.getLogger(__name__)
log.info("Hello logging!")
Для получения дополнительной информации см. Advanced Logging Tutorial.
Простым способом использования одного экземпляра библиотеки журналов в нескольких модулях для меня было следующее решение:
base_logger.py
import logging
logger = logging
logger.basicConfig(format='%(asctime)s - %(message)s', level=logging.INFO)
Другие файлы
from base_logger import logger
if __name__ == '__main__':
logger.info("This is an info message")
Я всегда делаю это, как показано ниже.
Используйте один файл python для настройки моего журнала как одноэлементного шаблона с именем 'log_conf.py
'
#-*-coding:utf-8-*-
import logging.config
def singleton(cls):
instances = {}
def get_instance():
if cls not in instances:
instances[cls] = cls()
return instances[cls]
return get_instance()
@singleton
class Logger():
def __init__(self):
logging.config.fileConfig('logging.conf')
self.logr = logging.getLogger('root')
В другом модуле просто импортируйте конфиг.
from log_conf import Logger
Logger.logger.info("Hello World")
Это единый шаблон для регистрации, просто и эффективно.
Я хотел бы добавить свое решение (основанное на поваренной книге журналов и других статьях и предложениях из этого потока. Однако мне потребовалось немало времени, чтобы понять, почему оно не сразу работает так, как я ожидал. Поэтому я создал небольшой тестовый проект, чтобы узнать, как работает ведение журнала.
Поскольку я понял это, я хотел поделиться своим решением, возможно, оно может кому-то помочь.
Я знаю, что часть моего кода может быть не лучшей практикой, но я все еще учусь. Я оставил
print()
функции там, как я их использовал, в то время как ведение журнала работало не так, как ожидалось. Они удалены в другом моем приложении. Также я приветствую любые отзывы о любых частях кода или структуры.
Структура проекта my_log_test (клонированная / упрощенная из другого проекта, над которым я работаю)
my_log_test
├── __init__.py
├── __main__.py
├── daemon.py
├── common
│ ├── my_logger.py
├── pkg1
│ ├── __init__.py
│ └── mod1.py
└── pkg2
├── __init__.py
└── mod2.py
Требования
В комбинации, которую я использую, есть несколько других вещей, которые я не видел явно упомянутыми:
- основной модуль вызывается
- Я хочу иметь возможность вызывать модули и по отдельности во время разработки / тестирования
- На этом этапе я не хотел использовать
basicConfig()
илиFileConfig()
но сохраните это как в поваренной книге журнала
По сути, это означает, что мне нужно инициализировать корневой регистратор в (всегда) и в модулях и
mod2.py
(только при прямом звонке).
Чтобы упростить эту инициализацию в нескольких модулях, я создал
my_logger.py
что делает, что описано в кулинарной книге.
Мои ошибки
Раньше моя ошибка в этом модуле заключалась в том, что я запускал регистратор с помощью
logger = logging.getLogger(__name__)
(модуль журнала) вместо использования
logger = logging.getLogger()
(чтобы получить корневой логгер).
Первая проблема заключалась в том, что при вызове из пространства имен регистратора было установлено значение
my_log_test.common.my_logger
. Регистратор модуля в
mod1.py
с несоответствующим пространством имен
my_log_test.pkg1.mod1
следовательно, не мог подключиться к другому регистратору, и я не увидел бы вывода журнала из mod1.
Вторая «проблема» заключалась в том, что моя основная программа находится в
daemon.py
а не в. Но, в конце концов, это не проблема для меня, но это добавило путаницы в пространство имен.
Рабочий раствор
Это из поваренной книги, но в отдельном модуле. Я также добавил
logger_cleanup
функцию, которую я могу вызвать из демона, чтобы удалить журналы старше x дней.
## my_logger.py
from datetime import datetime
import time
import os
## Init logging start
import logging
import logging.handlers
def logger_init():
print("print in my_logger.logger_init()")
print("print my_logger.py __name__: " +__name__)
path = "log/"
filename = "my_log_test.log"
## get logger
#logger = logging.getLogger(__name__) ## this was my mistake, to init a module logger here
logger = logging.getLogger() ## root logger
logger.setLevel(logging.INFO)
# File handler
logfilename = datetime.now().strftime("%Y%m%d_%H%M%S") + f"_{filename}"
file = logging.handlers.TimedRotatingFileHandler(f"{path}{logfilename}", when="midnight", interval=1)
#fileformat = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s")
fileformat = logging.Formatter("%(asctime)s [%(levelname)s]: %(name)s: %(message)s")
file.setLevel(logging.INFO)
file.setFormatter(fileformat)
# Stream handler
stream = logging.StreamHandler()
#streamformat = logging.Formatter("%(asctime)s [%(levelname)s:%(module)s] %(message)s")
streamformat = logging.Formatter("%(asctime)s [%(levelname)s]: %(name)s: %(message)s")
stream.setLevel(logging.INFO)
stream.setFormatter(streamformat)
# Adding all handlers to the logs
logger.addHandler(file)
logger.addHandler(stream)
def logger_cleanup(path, days_to_keep):
lclogger = logging.getLogger(__name__)
logpath = f"{path}"
now = time.time()
for filename in os.listdir(logpath):
filestamp = os.stat(os.path.join(logpath, filename)).st_mtime
filecompare = now - days_to_keep * 86400
if filestamp < filecompare:
lclogger.info("Delete old log " + filename)
try:
os.remove(os.path.join(logpath, filename))
except Exception as e:
lclogger.exception(e)
continue
для запуска deamon.py (через
__main__.py
) использовать
python3 -m my_log_test
## __main__.py
from my_log_test import daemon
if __name__ == '__main__':
print("print in __main__.py")
daemon.run()
для запуска deamon.py (напрямую) используйте
python3 -m my_log_test.daemon
## daemon.py
from datetime import datetime
import time
import logging
import my_log_test.pkg1.mod1 as mod1
import my_log_test.pkg2.mod2 as mod2
## init ROOT logger from my_logger.logger_init()
from my_log_test.common.my_logger import logger_init
logger_init() ## init root logger
logger = logging.getLogger(__name__) ## module logger
def run():
print("print in daemon.run()")
print("print daemon.py __name__: " +__name__)
logger.info("Start daemon")
loop_count = 1
while True:
logger.info(f"loop_count: {loop_count}")
logger.info("do stuff from pkg1")
mod1.do1()
logger.info("finished stuff from pkg1")
logger.info("do stuff from pkg2")
mod2.do2()
logger.info("finished stuff from pkg2")
logger.info("Waiting a bit...")
time.sleep(30)
if __name__ == '__main__':
try:
print("print in daemon.py if __name__ == '__main__'")
logger.info("running daemon.py as main")
run()
except KeyboardInterrupt as e:
logger.info("Program aborted by user")
except Exception as e:
logger.info(e)
Чтобы запустить mod1.py (напрямую), используйте
python3 -m my_log_test.pkg1.mod1
## mod1.py
import logging
# mod1_logger = logging.getLogger(__name__)
mod1_logger = logging.getLogger("my_log_test.daemon.pkg1.mod1") ## for testing, namespace set manually
def do1():
print("print in mod1.do1()")
print("print mod1.py __name__: " +__name__)
mod1_logger.info("Doing someting in pkg1.do1()")
if __name__ == '__main__':
## Also enable this pkg to be run directly while in development with
## python3 -m my_log_test.pkg1.mod1
## init root logger
from my_log_test.common.my_logger import logger_init
logger_init() ## init root logger
print("print in mod1.py if __name__ == '__main__'")
mod1_logger.info("Running mod1.py as main")
do1()
Для запуска mod2.py (напрямую) используйте
python3 -m my_log_test.pkg2.mod2
## mod2.py
import logging
logger = logging.getLogger(__name__)
def do2():
print("print in pkg2.do2()")
print("print mod2.py __name__: " +__name__) # setting namespace through __name__
logger.info("Doing someting in pkg2.do2()")
if __name__ == '__main__':
## Also enable this pkg to be run directly while in development with
## python3 -m my_log_test.pkg2.mod2
## init root logger
from my_log_test.common.my_logger import logger_init
logger_init() ## init root logger
print("print in mod2.py if __name__ == '__main__'")
logger.info("Running mod2.py as main")
do2()
Счастлив, если это поможет. Мы рады получить обратную связь!
Бросив в другое решение.
В основном модуле моего модуля у меня есть что-то вроде:
import logging
def get_module_logger(mod_name):
logger = logging.getLogger(mod_name)
handler = logging.StreamHandler()
formatter = logging.Formatter(
'%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)
return logger
Затем в каждом классе мне нужен регистратор, я делаю:
from [modname] import get_module_logger
logger = get_module_logger(__name__)
Когда пропущены журналы, вы можете различать их источник по модулю, из которого они пришли.
Некоторые из этих ответов предполагают, что в верхней части модуля вы делаете
import logging
logger = logging.getLogger(__name__)
Насколько я понимаю, это считается очень плохой практикой. Причина в том, что файл конфигурации отключит все существующие регистраторы по умолчанию. Например
#my_module
import logging
logger = logging.getLogger(__name__)
def foo():
logger.info('Hi, foo')
class Bar(object):
def bar(self):
logger.info('Hi, bar')
И в вашем основном модуле:
#main
import logging
# load my module - this now configures the logger
import my_module
# This will now disable the logger in my module by default, [see the docs][1]
logging.config.fileConfig('logging.ini')
my_module.foo()
bar = my_module.Bar()
bar.bar()
Теперь журнал, указанный в logging.ini, будет пустым, так как существующий регистратор был отключен вызовом fileconfig.
Хотя это, безусловно, можно обойти (disable_existing_Loggers=False), реально многие клиенты вашей библиотеки не будут знать об этом и не будут получать ваши журналы. Сделайте это проще для ваших клиентов, всегда вызывая logging.getLogger локально. Наконечник шляпы: я узнал об этом поведении от Веб-сайта Виктора Линя.
Поэтому хорошей практикой является всегда вызывать logging.getLogger локально. Например
#my_module
import logging
logger = logging.getLogger(__name__)
def foo():
logging.getLogger(__name__).info('Hi, foo')
class Bar(object):
def bar(self):
logging.getLogger(__name__).info('Hi, bar')
Кроме того, если вы используете fileconfig в своем основном файле, установите disable_existing_loggers=False, на тот случай, если ваши разработчики библиотеки используют экземпляры логгера уровня модуля.
Вы также можете придумать что-то вроде этого!
def get_logger(name=None):
default = "__app__"
formatter = logging.Formatter('%(levelname)s: %(asctime)s %(funcName)s(%(lineno)d) -- %(message)s',
datefmt='%Y-%m-%d %H:%M:%S')
log_map = {"__app__": "app.log", "__basic_log__": "file1.log", "__advance_log__": "file2.log"}
if name:
logger = logging.getLogger(name)
else:
logger = logging.getLogger(default)
fh = logging.FileHandler(log_map[name])
fh.setFormatter(formatter)
logger.addHandler(fh)
logger.setLevel(logging.DEBUG)
return logger
Теперь вы можете использовать несколько регистраторов в одном модуле и в рамках всего проекта, если вышеперечисленное определено в отдельном модуле и импортировано в другие модули, для которых требуется ведение журнала.
a=get_logger("__app___")
b=get_logger("__basic_log__")
a.info("Starting logging!")
b.debug("Debug Mode")
Решение @ Ярки выглядело лучше. Я хотел бы добавить еще к этому -
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances.keys():
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class LoggerManager(object):
__metaclass__ = Singleton
_loggers = {}
def __init__(self, *args, **kwargs):
pass
@staticmethod
def getLogger(name=None):
if not name:
logging.basicConfig()
return logging.getLogger()
elif name not in LoggerManager._loggers.keys():
logging.basicConfig()
LoggerManager._loggers[name] = logging.getLogger(str(name))
return LoggerManager._loggers[name]
log=LoggerManager().getLogger("Hello")
log.setLevel(level=logging.DEBUG)
Таким образом, LoggerManager может быть подключен ко всему приложению. Надеюсь, что это имеет смысл и ценность.
Лучшей практикой было бы создать модуль отдельно, который имеет только один метод, задача которого - передать обработчик регистратора вызывающему методу. Сохраните этот файл как m_logger.py
import logger, logging
def getlogger():
# logger
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
# create console handler and set level to debug
#ch = logging.StreamHandler()
ch = logging.FileHandler(r'log.txt')
ch.setLevel(logging.DEBUG)
# create formatter
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
# add formatter to ch
ch.setFormatter(formatter)
# add ch to logger
logger.addHandler(ch)
return logger
Теперь вызывайте метод getlogger() всякий раз, когда требуется обработчик логгера.
from m_logger import getlogger
logger = getlogger()
logger.info('My mssg')
Есть несколько ответов. я получил похожее, но другое решение, которое имеет смысл для меня, может быть, оно будет иметь смысл и для вас. Моя основная цель состояла в том, чтобы иметь возможность передавать журналы обработчикам по их уровню (журналы уровня отладки на консоль, предупреждения и выше в файлы):
from flask import Flask
import logging
from logging.handlers import RotatingFileHandler
app = Flask(__name__)
# make default logger output everything to the console
logging.basicConfig(level=logging.DEBUG)
rotating_file_handler = RotatingFileHandler(filename="logs.log")
rotating_file_handler.setLevel(logging.INFO)
app.logger.addHandler(rotating_file_handler)
создал хороший утилитный файл с именем logger.py:
import logging
def get_logger(name):
return logging.getLogger("flask.app." + name)
the flask.app - это жестко закодированное значение в колбе. регистратор приложений всегда начинается с flask.app в качестве имени модуля.
Теперь в каждом модуле я могу использовать его в следующем режиме:
from logger import get_logger
logger = get_logger(__name__)
logger.info("new log")
Это создаст новый журнал для "app.flask.MODULE_NAME" с минимальными усилиями.
Новичок в python, поэтому я не знаю, целесообразно ли это, но он отлично работает, чтобы не переписывать шаблон.
Ваш проект должен иметь init.py, чтобы его можно было загрузить как модуль.
# Put this in your module's __init__.py
import logging.config
import sys
# I used this dictionary test, you would put:
# logging.config.fileConfig('logging.conf')
# The "" entry in loggers is the root logger, tutorials always
# use "root" but I can't get that to work
logging.config.dictConfig({
"version": 1,
"formatters": {
"default": {
"format": "%(asctime)s %(levelname)s %(name)s %(message)s"
},
},
"handlers": {
"console": {
"level": 'DEBUG',
"class": "logging.StreamHandler",
"stream": "ext://sys.stdout"
}
},
"loggers": {
"": {
"level": "DEBUG",
"handlers": ["console"]
}
}
})
def logger():
# Get the name from the caller of this function
return logging.getLogger(sys._getframe(1).f_globals['__name__'])
sys._getframe(1)
предложение исходит отсюда
Затем, чтобы использовать ваш логгер в любом другом файле:
from [your module name here] import logger
logger().debug("FOOOOOOOOO!!!")
Предостережения:
- Вы должны запускать свои файлы как модули, иначе
import [your module]
не сработает:python -m [your module name].[your filename without .py]
- Имя регистратора для точки входа вашей программы будет
__main__
, но любое решение, использующее__name__
будет эта проблема.