Использование входа в несколько модулей

257

У меня есть небольшой проект 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')

В настоящее время я выполняю эту инициализацию в каждом модуле, прежде чем начать регистрировать сообщения. Можно ли выполнить эту инициализацию только один раз в одном месте, чтобы одни и те же настройки использовались повторно при ведении журнала по всему проекту?

Квест Монгер
источник
3
В ответ на ваш комментарий к моему ответу: вам не нужно вызывать fileConfigкаждый модуль, который ведет журналирование, если у вас нет if __name__ == '__main__'логики во всех из них. Ответ prost не является хорошей практикой, если пакет является библиотекой, хотя он может работать для вас - не следует настраивать ведение журнала в пакетах библиотек, кроме добавления a NullHandler.
Vinay Sajip
1
Прост подразумевал, что нам нужно вызывать stmts import и logger в каждом модуле, и вызывать только stmt fileconfig в главном модуле. разве это не похоже на то, что вы говорите?
Квест Монгер
6
Прост говорит, что вы должны поместить код конфигурации регистрации package/__init__.py. Обычно это не то место, куда вы помещаете if __name__ == '__main__'код. Кроме того, пример Prost выглядит так, как будто он безоговорочно вызывает код конфигурации при импорте, что мне не подходит. Как правило, регистрация кода конфигурации должна выполняться в одном месте и не должна являться побочным эффектом импорта, за исключением случаев, когда вы импортируете __main__.
Vinay Sajip
Вы правы, я полностью пропустил строку "# package / __ init__.py" в его примере кода. спасибо за это и ваше терпение.
Квест Монгер
1
Так что же произойдет, если у вас есть несколько if __name__ == '__main__'? (это не упоминается явно в вопросе, если это так)
Кон Псих

Ответы:

294

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

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()тот, который дает больший контроль над конфигурацией.

Винай Саджип
источник
21
если вы посмотрите на мой пример, я уже делаю то, что вы предлагаете выше. Мой вопрос заключался в том, как мне централизовать эту инициализацию регистрации, чтобы мне не пришлось повторять эти 3 утверждения. Кроме того, в вашем примере вы пропустили stmt 'logging.config.fileConfig (' logging.conf ')'. это stmt на самом деле является основной причиной моей озабоченности. вы видите, если бы я запускал регистратор в каждом модуле, мне пришлось бы вводить этот stmt в каждом модуле. это означало бы отслеживание пути к файлу conf в каждом модуле, что для меня не является лучшей практикой (представьте себе хаос при изменении расположения модулей / пакетов).
Квест Монгер
4
Если вы вызываете fileConfig после создания регистратора, в том же самом или в другом модуле (например, когда вы создаете регистратор в верхней части файла), не работает. Конфигурация регистрации применяется только к регистраторам, созданным после. Таким образом, этот подход не работает или не подходит для нескольких модулей. @Quest Monger: Вы всегда можете создать другой файл, в котором будет храниться расположение файла конфигурации ..;)
Винсент Кетелаарс
2
@Oxidator: Не обязательно - смотрите disable_existing_loggersфлаг, который Trueпо умолчанию установлен, но может быть установлен в False.
Vinay Sajip
1
@ Винай Саджип, спасибо. У вас есть рекомендации для регистраторов, которые работают в модулях, но также и вне классов? Поскольку импорт выполняется до вызова основной функции, эти журналы будут уже зарегистрированы. Я полагаю, настройка вашего регистратора до всех импортов в основном модуле - единственный способ? Этот логгер может быть перезаписан в main, если хотите.
Винсент Кетелаарс
1
Если я хочу, чтобы все мои логгеры, специфичные для моего модуля, имели уровень логирования, отличный от значения по умолчанию ПРЕДУПРЕЖДЕНИЕ, нужно ли будет устанавливать этот параметр на каждом модуле? Скажем, я хочу, чтобы все мои модули регистрировались на INFO.
Радж
128

Фактически каждый регистратор является дочерним по отношению к родительскому регистратору пакетов (то есть 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 .

Стан Прокоп
источник
15
это, безусловно, самое простое решение проблемы, не говоря уже о том, что оно раскрывает и использует отношения родитель-потомок между модулями, о чем я, как новичок, не знал. Danke.
Квест Монгер
Ты прав. и, как указал Виней в своем посте, ваше решение верно, если его нет в модуле init .py. Ваше решение сработало, когда я применил его к основному модулю (точка входа).
Квест Монгер
2
на самом деле гораздо более актуальный ответ, поскольку вопрос касается отдельных модулей.
Ян Сила
1
Возможно, тупой вопрос: если нет входящего в систему регистратора __main__.py(например, если я хочу использовать модуль в сценарии, в котором нет регистратора), все logging.getLogger(__name__)равно будет выполнено какое-либо вход в модуль, или это вызовет исключение?
Билл
1
В заключение. У меня был рабочий регистратор, но он не удался в Windows для параллельного запуска с joblib. Я предполагаю, что это ручная настройка системы - что-то еще не так с Parallel. Но это, безусловно, работает! Спасибо
B Фуртадо
17

Я всегда делаю это, как показано ниже.

Используйте один файл 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.logr.info("Hello World")

Это единый шаблон для регистрации, просто и эффективно.

Yarkee
источник
1
спасибо за детализацию шаблона синглтона. Я планировал реализовать это, но тогда решение @prost намного проще и идеально подходит для моих нужд. Однако я считаю, что ваше решение полезно для больших проектов, которые имеют несколько точек входа (кроме основной). Danke.
Квест Монгер
46
Это бесполезно. Корневой регистратор уже синглтон. Просто используйте logging.info вместо Logger.logr.info.
Pod
9

Некоторые из этих ответов предполагают, что в верхней части модуля вы делаете

import logging
logger = logging.getLogger(__name__)

Насколько я понимаю, это считается очень плохой практикой . Причина в том, что файл config отключит все существующие регистраторы по умолчанию. Например

#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, на тот случай, если ваши разработчики библиотеки используют экземпляры логгера уровня модуля.

phil_20686
источник
Ты не можешь бежать logging.config.fileConfig('logging.ini')раньше import my_module? Как предлагается в этом ответе .
lucid_dreamer
Не уверен - но было бы определенно плохой практикой смешивать импорт и исполняемый код таким образом. Вы также не хотите, чтобы ваши клиенты проверяли, нужно ли им настраивать ведение журналов перед импортом, особенно когда есть тривиальная альтернатива! Представьте, если бы широко используемая библиотека, например запросы, сделала это ....!
phil_20686
«Не уверен - но было бы определенно плохой практикой смешивать импорт и исполняемый код таким образом». - Зачем?
lucid_dreamer
Я не слишком понимаю, почему это плохо. И я не совсем понимаю ваш пример. Можете ли вы опубликовать свой конфиг для этого примера и показать его использование?
lucid_dreamer
1
Похоже, вы противоречите официальным документам : «Хорошим соглашением, которое следует использовать при именовании регистраторов, является использование регистратора уровня модуля, в каждом модуле, который использует журналирование, который называется следующим образом: logger = logging.getLogger(__name__)»
iron9
9

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

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")
Алекс Джолиг
источник
7

Бросив в другое решение.

В init .py моего модуля у меня есть что-то вроде:

# mymodule/__init__.py
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

Затем в каждом модуле мне нужен регистратор, я делаю:

# mymodule/foo.py
from [modname] import get_module_logger
logger = get_module_logger(__name__)

Когда пропущены журналы, вы можете различать их источник по модулю, из которого они пришли.

Томми
источник
Что означает «главная инициализация моего модуля»? И «Тогда в каждом классе мне нужен регистратор, я делаю:»? Можете ли вы предоставить пример с названием named_module.py и пример его использования в качестве импорта из модуля caller_module.py? Смотрите этот ответ для вдохновения в формате, о котором я спрашиваю. Не пытаясь быть покровительственным. Я пытаюсь понять ваш ответ, и я знаю, если бы вы написали это так.
lucid_dreamer
1
@lucid_dreamer Я уточнил.
Томми,
4

Вы также можете придумать что-то вроде этого!

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")
deeshank
источник
4

Решение @ Ярки казалось лучше. Я хотел бы добавить еще к этому -

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 может быть подключен ко всему приложению. Надеюсь, что это имеет смысл и ценность.

deeshank
источник
11
Модуль регистрации уже имеет дело с синглетонами. logging.getLogger («Hello») получит один и тот же регистратор для всех ваших модулей.
Pod
2

Есть несколько ответов. я получил похожее, но другое решение, которое имеет смысл для меня, может быть, оно будет иметь смысл и для вас. Моя главная цель состояла в том, чтобы иметь возможность передавать журналы обработчикам по их уровню (журналы уровня отладки на консоль, предупреждения и выше в файлы):

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" с минимальными усилиями.

Бен Ицхаки
источник
2

Лучше всего было бы создать модуль отдельно, у которого есть только один метод, задача которого состоит в том, чтобы предоставить обработчик логгера вызывающему методу. Сохраните этот файл как 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')
Мусам Сингх
источник
1
Это хорошо, если у вас нет дополнительных параметров. Но если, скажем, у вас есть --debugопция в приложении и вы хотите установить уровень ведения журнала во всех регистраторах в вашем приложении на основе этого параметра ...
The Godfather
@TheGodfather Да, это трудно достичь с помощью этой методологии. Что мы можем сделать в этой ситуации, так это создать класс, для которого при создании объекта в качестве параметра будет использоваться форматер и будет иметь аналогичную функцию для возврата обработчика логгера. Каково ваше мнение по этому поводу?
Мусам Сингх
Да, я сделал похожую вещь, сделал get_logger(level=logging.INFO)для возврата какой-то синглтон, поэтому, когда он вызывается первый раз из основного приложения, он инициализирует регистратор и обработчики с надлежащим уровнем, а затем возвращает тот же loggerобъект всем другим методам.
Крестный отец
0

Новичок в 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__будет эта проблема.
npjohns
источник