Использование входа в 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!!!")

Предостережения:

  1. Вы должны запускать свои файлы как модули, иначе import [your module] не сработает:
    • python -m [your module name].[your filename without .py]
  2. Имя регистратора для точки входа вашей программы будет __main__, но любое решение, использующее __name__ будет эта проблема.
Другие вопросы по тегам