Використання журналу в декількох модулях


257

У мене є невеликий проект python, який має таку структуру -

Project 
 -- pkg01
   -- test01.py
 -- pkg02
   -- test02.py
 -- logging.conf

Я планую використовувати модуль реєстрації за замовчуванням для друку повідомлень до stdout та файлу журналу. Для використання модуля реєстрації потрібна деяка ініціалізація -

import logging.config

logging.config.fileConfig('logging.conf')
logger = logging.getLogger('pyApp')

logger.info('testing')

В даний час я виконую цю ініціалізацію в кожному модулі, перш ніж розпочати журнал повідомлень. Чи можливо виконати цю ініціалізацію лише один раз в одному місці, щоб ті самі налаштування повторно використовувались при вході в весь проект?


3
У відповідь на ваш коментар до моєї відповіді: вам не доведеться дзвонити fileConfigв кожен модуль, який веде журнал, якщо ви не маєте if __name__ == '__main__'логіки в усіх них. відповідь проста не є хорошою практикою, якщо пакет є бібліотекою, хоча це може працювати для вас - не слід налаштовувати вхід у бібліотечні пакети, крім додавання NullHandler.
Vinay Sajip

1
prost мав на увазі, що нам потрібно викликати імпорт та logger stmts у кожному модулі, а лише викликати fileconfig stmt в головному модулі. хіба не схоже на те, що ви говорите?
Квест Монгер

6
prost говорить, що слід поставити код конфігурації журналу package/__init__.py. Це звичайно не місце, куди ви вводите if __name__ == '__main__'код. Крім того, приклад проста виглядає так, що він буде викликати конфігураційний код беззастережно при імпорті, що не відповідає мені. Як правило, реєстраційний конфігураційний код повинен здійснюватися в одному місці і не повинен відбуватися як побічний ефект імпорту, за винятком випадків, коли ви імпортуєте __main__.
Вінай Саджип

ви праві, я повністю пропустив рядок "# package / __ init__.py" у своєму зразку коду. спасибі за те, що за це і ваше терпіння.
Квест Монгер

1
Отже, що станеться, якщо у вас кілька if __name__ == '__main__' ? (прямо не йдеться про це, якщо це так)
kon psych

Відповіді:


293

Найкраща практика - у кожному модулі мати реєстратор, визначений так:

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 у кожному модулі. це буде означати відстеження конфігураційного файлу в кожному модулі, що не виглядає як найкраща практика для мене (уявіть, що при зміні місць розташування модулів / пакетів хаос)
Квест Монгер

4
Якщо ви викликаєте fileConfig після створення реєстратора, то в тому самому або в іншому модулі (наприклад, коли ви створюєте реєстратор у верхній частині файлу), це не працює. Конфігурація журналу застосовується лише до реєстраторів, створених після. Таким чином, такий підхід не працює або не є життєздатним варіантом для декількох модулів. @Quest Monger: Ви завжди можете створити ще один файл, у якому розміщено конфігураційний файл ..;)
Вінсент Кетелаарс

2
@ Oxidator: Не обов’язково - дивіться disable_existing_loggersпрапор, який Trueза замовчуванням, але його можна встановити False.
Vinay Sajip

1
@Vinay Sajip, дякую. Чи є у вас рекомендації для реєстраторів, які працюють в модулях, але поза класами? Оскільки імпорт здійснюється до виклику основної функції, ці журнали вже будуть записані. Гадаю, налаштування вашого реєстратора до того, як весь імпорт в основний модуль є єдиним способом? Потім цей журнал може бути перезаписаний в основному, якщо ви хочете.
Вінсент Кетелаарс

1
Якщо я хочу, щоб усі реєстратори для мого модуля мали рівень журналу, який відрізняється від типового ПОПЕРЕДЖЕННЯ, чи доведеться мені робити це налаштування на кожному модулі? Скажімо, я хочу, щоб усі мої модулі входили в INFO.
Радж

127

Насправді кожен реєстратор є дочірнім батьківським реєстратором пакетів (тобто 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!")

Для отримання додаткової інформації див Посібник з розширеного ведення журналу .


15
це, безумовно, найпростіше рішення проблеми, не кажучи вже про це, це відкриває і використовує взаємозв'язок між батьком і дитиною між модулями, чого я не знав. данке.
Квест Монгер

ти правий. і як зазначив Vinay у своєму дописі, ваше рішення є правильним, якщо його немає в модулі init .py. ваше рішення спрацювало, коли я застосував його до основного модуля (точка входу).
Квест Монгер

2
насправді набагато більш відповідна відповідь, оскільки питання стосується окремих модулів.
Ян Сіла

1
Можливо, глупе запитання: якщо немає реєстратора __main__.py(наприклад, якщо я хочу використовувати модуль у скрипті, який не має реєстратора), logging.getLogger(__name__)все-таки буде проводитись якийсь журнал в модулі чи це призведе до виключення?
Білл

1
Нарешті. У мене був робочий реєстратор, але він не вдався в Windows для паралельних запусків з joblib. Я здогадуюсь, що це ручне налаштування системи - щось інше не в паралелі з паралельним. Але, це, безумовно, працює! Спасибі
Б Фуртадо

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")

Це однотонний зразок для реєстрації, просто та ефективно.


1
дякую за деталізацію одинарного малюнка. Я планував реалізувати це, але тоді рішення @prost набагато простіше і ідеально відповідає моїм потребам. Я вважаю, що ваше рішення є корисним - це більші проекти, які мають декілька пунктів входу (крім основних). данке.
Квест Монгер

46
Це марно. Кореневий реєстратор - уже сингтон. Просто використовуйте logging.info замість Logger.logr.info.
Под

9

Кілька з цих відповідей свідчать про те, що ви працюєте у верхній частині модуля

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.

Хоча це, безумовно, можливо обійти (ones_existing_Loggers = Неправдиво), реально багато клієнтів вашої бібліотеки не знають про цю поведінку і не отримуватимуть ваші журнали. Полегшіть своїм клієнтам, завжди телефонуючи 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 в основному, встановіть invalid_existing_loggers = False, на випадок, якщо дизайнери вашої бібліотеки використовують екземпляри реєстратора модулів.


Ти не можеш logging.config.fileConfig('logging.ini')раніше бігати import my_module? Як пропонується у цій відповіді .
lucid_dreamer

Не впевнений - але, безумовно, також вважатиметься поганою практикою поєднання імпорту та виконуваного коду таким чином. Ви також не хочете, щоб ваші клієнти мали змогу перевіряти, чи потрібно їм налаштовувати журнал перед тим, як імпортувати, особливо коли є тривіальна альтернатива! Уявіть, якби це зробила широко використовувана бібліотека на зразок запитів ....!
phil_20686

"Не впевнений - але, безумовно, також вважатиметься поганою практикою поєднання імпорту та виконуваного коду таким чином". - чому?
lucid_dreamer

Мені не надто зрозуміло, чому це погано. І я не повністю розумію твій приклад. Чи можете ви розмістити конфігурацію для цього прикладу і показати деяке використання?
lucid_dreamer

1
Ви, здається, суперечили офіційним документам : "Гарною умовою для використання при іменуванні реєстраторів є використання реєстратора на рівні модулів, у кожному модулі, який використовує журнал, іменований таким чином: logger = logging.getLogger(__name__)"
iron9,

8

Простим способом використання одного примірника реєстрації бібліотеки в декількох модулях для мене було наступне рішення:

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__)

Коли журнали пропущені, ви можете розмежувати їх джерело за модулем, з якого вони походили.


Що означає "головний мої модулі"? І "Тоді в кожному класі мені потрібен лісоруб, я роблю:"? Чи можете ви надати зразок, який називається_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")

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 може підключатися до всієї програми. Сподіваюся, це має сенс і цінність.


11
Модуль реєстрації вже має справу з однотонними. logging.getLogger ("Привіт") отримає однаковий реєстратор для всіх ваших модулів.
Под

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)

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__, матиме цю проблему.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.