Дублікат журналу при використанні модуля реєстрації Python


105

Я використовую реєстратор python. Наступний мій код:

import os
import time
import datetime
import logging
class Logger :
   def myLogger(self):
      logger = logging.getLogger('ProvisioningPython')
      logger.setLevel(logging.DEBUG)
      now = datetime.datetime.now()
      handler=logging.FileHandler('/root/credentials/Logs/ProvisioningPython'+ now.strftime("%Y-%m-%d") +'.log')
      formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
      handler.setFormatter(formatter)
      logger.addHandler(handler)
      return logger

Проблема в тому, що я отримую кілька записів у файлі журналу для кожного logger.infoдзвінка. Як я можу це вирішити?


Працює для мене. Python 3.2 та Windows XP.
Зуліхін

2
Ви впевнені, що не створюєте кілька екземплярів реєстратора?
Ганді

Так. в іншому файлі я беру новий екземпляр, як ми це робили в проектах Java. Будь ласка, вкажіть, створює це проблема чи ні.
користувач865438

Відповіді:


94

Це logging.getLogger()вже сингл. ( Документація )

Проблема полягає в тому, що кожного разу, коли ви телефонуєте myLogger(), додавання до інстанції іншого обробника, що спричиняє повторювані журнали.

Можливо, щось подібне?

import os
import time
import datetime
import logging

loggers = {}

def myLogger(name):
    global loggers

    if loggers.get(name):
        return loggers.get(name)
    else:
        logger = logging.getLogger(name)
        logger.setLevel(logging.DEBUG)
        now = datetime.datetime.now()
        handler = logging.FileHandler(
            '/root/credentials/Logs/ProvisioningPython' 
            + now.strftime("%Y-%m-%d") 
            + '.log')
        formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        loggers[name] = logger

        return logger

3
Я думаю, ви повинні мати loggers.update (dict ((ім'я, реєстратор))).
акрофобія

чому loggers.update(dict(name=logger))? не loggers[name] = loggerпростіше?
Ryan J McCall

@RyanJMcCall У той час це була умова кодування, яку я використовував. Але переглядаючи код таким, який він є зараз, я бачу, що він порушений. loggers.update(dict(name=logger))створить словник з однією клавішею, яка називається, nameі постійно оновлюватиме цей самий ключ. Я здивований, що ніхто раніше не згадував про це, оскільки цей код досить порушений :) Внесуть необхідні поправки.
Вернер Сміт

Я бачу, @acrophobia ухилявся від цього століттями. Дякую.
Вернер Сміт

хіба не всесвітній loggersсловник із зайвим logging.getLogger? оскільки ви просто хочете уникати додавання додаткових обробників, схоже, що ви віддаєте перевагу відповіді нижче, що перевіряйте на обробників безпосередньо
mway

60

З Python 3.2 ви можете просто перевірити, чи обробники вже є, і якщо так, очистіть їх, перш ніж додавати нові обробники. Це досить зручно при налагодженні, і код включає ініціалізацію вашого реєстратора

if (logger.hasHandlers()):
    logger.handlers.clear()

logger.addHandler(handler)

Гарна відповідь, Thx :))
Гавриель Коен

2
Зауважте, що hasHandlers () поверне true у pytest, де обробник додано до кореневого журналу, навіть якщо ваші локальні / користувацькі обробники ще не додані. У цьому випадку len (logger.handlers) (згідно з відповіддю Гійом) поверне 0 в такому випадку, тому може бути кращим варіантом.
Грант

Це справжнє рішення, що я шукав.
XCanG

45
import datetime
import logging
class Logger :
    def myLogger(self):
       logger=logging.getLogger('ProvisioningPython')
       if not len(logger.handlers):
          logger.setLevel(logging.DEBUG)
          now = datetime.datetime.now()
          handler=logging.FileHandler('/root/credentials/Logs/ProvisioningPython'+ now.strftime("%Y-%m-%d") +'.log')
          formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
          handler.setFormatter(formatter)
          logger.addHandler(handler)
        return logger

зробив для мене хитрість

за допомогою python 2.7


1
Це працює навіть тоді, коли модуль перезавантажений (що не стосується інших відповідей)
yco

3
Дякую за підказку, BTW, щоб перевірити, чи список порожній чи ні, вам не потрібно використовувати оператор "len", який ви можете безпосередньо використовувати, якщо my_list: ..
rkachach

26

Я вже використовував loggerяк Singleton і перевіряв if not len(logger.handlers), але все ж отримав дублікати : Це був відформатований вихід, за яким слідував неформатований.

Рішення в моєму випадку: logger.propagate = False

Подяка на цю відповідь та документи .


1
Я зрозумів, що подвійний журнал ведеться від RootLogger і мого StreamHandler, але я не зміг вирішити проблему (зберігаючи свій форматтер у StreamHandler), поки це не зробив.
Xander YzWich

10

Вам дзвонять Logger.myLogger()не раз. Збережіть екземпляр реєстратора, який він поверне десь, і повторно використовуйте це .

Також слід враховувати, що якщо ви ввійдете в систему, перш ніж будь-який обробник буде доданий, StreamHandler(sys.stderr)буде створено за замовчуванням .


Насправді я намагаюся отримати доступ до екземпляра реєстратора, який ми використовуємо в java. Але я не знаю, чи потрібно створити екземпляр лише один раз для цілого проекту чи ні.
користувач865438

1
@ user865483: Лише раз. Усі стандартні реєстратори бібліотек є однотонними.
Метт Столяр

5

Це доповнення до відповіді @ rm957377, але з поясненням того, чому це відбувається . Коли ви запускаєте лямбда-функцію в AWS, вони викликають вашу функцію в межах обертового екземпляра, який залишається живим для декількох викликів. Тобто, якщо ви зателефонуєте addHandler()в код своєї функції, він продовжуватиме додавати дублюючі обробники в одиночний журнал щоразу, коли функція запускається. Сингл-журнал зберігається через безліч викликів лямбда-функції.

Щоб вирішити це, ви можете очистити обробники, перш ніж встановити їх за допомогою:

logging.getLogger().handlers.clear()
logging.getLogger().addHandler(...)

Якось у моєму випадку обробники реєстраторів додаються при події під час .info()виклику, яку я не розумію.
Євген

4

Ваш реєстратор повинен працювати як однотонний. Ви не повинні створювати його не один раз. Ось приклад, як це може виглядати:

import os
import time
import datetime
import logging
class Logger :
    logger = None
    def myLogger(self):
        if None == self.logger:
            self.logger=logging.getLogger('ProvisioningPython')
            self.logger.setLevel(logging.DEBUG)
            now = datetime.datetime.now()
            handler=logging.FileHandler('ProvisioningPython'+ now.strftime("%Y-%m-%d") +'.log')
            formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
            handler.setFormatter(formatter)
            self.logger.addHandler(handler)
        return self.logger

s = Logger()
m = s.myLogger()
m2 = s.myLogger()
m.info("Info1")
m2.info("info2")

потім знову, якщо я збираюся взяти інший екземпляр в іншому файлі. Припустимо, у файлі 1 s = Logger () m = s.myLogger () та у файлі 2 s = Logger () Він буде працювати чи ні m2 = s.myLogger ()
user865438

Ще я отримую копію одного журналу кілька разів. У мене тут сумніваються, чи всередині журналу потоку Log надрукується більше одного чи ні. Будь ласка, допоможіть мені в цьому.
користувач865438

1
@ user865438, нам не потрібно турбуватися про те, щоб зробити реалізацію одинарною (Це вже є). Щоб увійти в підмодулі, перейдіть за офіційним посиланням « Журнал готування» . В основному вам потрібно керуватися ієрархією імен, під час іменування реєстраторів, і це піклується про інше.
нараян

4

Реалізація реєстратора - вже сингл.

Кілька викликів до logging.getLogger ('someLogger') повертають посилання на той самий об’єкт реєстратора. Це стосується не лише одного і того ж модуля, але і для всіх модулів, якщо це знаходиться в одному і тому ж процесі інтерпретації Python. Це справедливо для посилань на один і той же об'єкт; крім того, код програми може визначати та конфігурувати батьківський реєстратор в одному модулі та створювати (але не конфігурувати) дочірній реєстратор в окремому модулі, і всі виклики реєстратора до дитини передаватимуться до батьківського. Ось основний модуль

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

Таким чином, ви повинні використовувати це -

Припустимо, ми створили та налаштували реєстратор під назвою 'main_logger' у головному модулі (який просто налаштовує реєстратор, нічого не повертає).

# get the logger instance
logger = logging.getLogger("main_logger")
# configuration follows
...

Тепер у підмодулі, якщо ми створимо дочірній реєстратор за ієрархією імен 'main_logger.sub_module_logger' , нам не потрібно налаштовувати його в підмодулі. Достатньо лише створення реєстратора за ієрархією імен.

# get the logger instance
logger = logging.getLogger("main_logger.sub_module_logger")
# no configuration needed
# it inherits the configuration from the parent logger
...

І він також не додасть обробник дублікатів.

Дивіться це запитання для більш детальної відповіді.


1
переглядання оброблювачів після getLogger, здається, працює для мене: logger = logging.getLogger('my_logger') ; logger.handlers = [logger.handlers[0], ]
radtek

2

Подвійний (або потрійний або ..- на основі кількості перезавантажень) вихід журналу може також статися при перезавантаженні модуля через importlib.reload(з тієї ж причини, що пояснено у прийнятій відповіді). Я додаю цю відповідь лише для подальшої довідки, оскільки мені знадобилося деякий час, щоб зрозуміти, чому мій результат дублюється (потрійний).


1

Одне просте вирішення подібне

logger.handlers[:] = [handler]

Таким чином ви уникаєте додавання нового обробника до нижнього списку "обробники".


1

Підсумок у більшості випадків, коли це трапляється, потрібно викликати logger.getLogger () лише один раз на модуль. Якщо у вас є кілька класів, як у мене, я можу назвати це так:

LOGGER = logger.getLogger(__name__)

class MyClass1:
    log = LOGGER
    def __init__(self):
        self.log.debug('class 1 initialized')

class MyClass2:
    log = LOGGER
    def __init__(self):
        self.log.debug('class 2 initialized')

Обидва тоді матимуть своє власне повне ім’я пакета та спосіб, де вони входили в систему.


0

Ви можете отримати список усіх обробників для конкретного реєстратора, тому ви можете зробити щось подібне

logger = logging.getLogger(logger_name)
handler_installed = False
for handler in logger:
    # Here your condition to check for handler presence
    if isinstance(handler, logging.FileHandler) and handler.baseFilename == log_filename:
        handler_installed = True
        break

if not handler_installed:
    logger.addHandler(your_handler)

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


0

Ця проблема була сьогодні. Оскільки мої функції були @staticmethod, наведені вище пропозиції були вирішені випадковим чином ().

Дивлячись щось на зразок:

import random

logger = logging.getLogger('ProvisioningPython.{}'.format(random.random()))

-1
from logging.handlers import RotatingFileHandler
import logging
import datetime

# stores all the existing loggers
loggers = {}

def get_logger(name):

    # if a logger exists, return that logger, else create a new one
    global loggers
    if name in loggers.keys():
        return loggers[name]
    else:
        logger = logging.getLogger(name)
        logger.setLevel(logging.DEBUG)
        now = datetime.datetime.now()
        handler = logging.FileHandler(
            'path_of_your_log_file' 
            + now.strftime("%Y-%m-%d") 
            + '.log')
        formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        loggers.update(dict(name=logger))
        return logger

Будь ласка, додайте пояснення, щоб зробити цю відповідь ціннішою для тривалого використання.
Аміна Нурайні
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.