повідомлення журналу, що з’являються двічі за допомогою програми Python Logging


100

Я використовую журнал Python, і чомусь усі мої повідомлення з’являються двічі.

У мене є модуль для налаштування журналу:

# BUG: It's outputting logging messages twice - not sure why - it's not the propagate setting.
def configure_logging(self, logging_file):
    self.logger = logging.getLogger("my_logger")
    self.logger.setLevel(logging.DEBUG)
    self.logger.propagate = 0
    # Format for our loglines
    formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
    # Setup console logging
    ch = logging.StreamHandler()
    ch.setLevel(logging.DEBUG)
    ch.setFormatter(formatter)
    self.logger.addHandler(ch)
    # Setup file logging as well
    fh = logging.FileHandler(LOG_FILENAME)
    fh.setLevel(logging.DEBUG)
    fh.setFormatter(formatter)
    self.logger.addHandler(fh)

Пізніше я закликаю цей метод для налаштування журналу:

if __name__ == '__main__':
    tom = Boy()
    tom.configure_logging(LOG_FILENAME)
    tom.buy_ham()

І тоді в скажімо, модуль buy_ham, я закликаю:

self.logger.info('Successfully able to write to %s' % path)

І чомусь усі повідомлення з’являються двічі. Я прокоментував один із обробників потоків, все одно те саме. Трохи дивний, не впевнений, чому це відбувається ... хаха. Якщо припустити, що я пропустив щось очевидне.

Ура, Вікторе


1
Ви впевнені, що вас configure_logging()не викликають двічі (наприклад, від конструктора)? Чи створений лише один екземпляр Boy ()?
Jacek Konieczny

1
Використання self.logger.handlers = [ch]натомість вирішило б цю проблему, хоча найкраще просто переконатися, що ви не запускаєте цей код двічі, наприклад, використовуючи if not self.loggerна початку.
Ninjakannon

Відповіді:


133

Ви дзвоните configure_loggingдвічі (можливо, у __init__методі Boy): getLoggerповерне той самий об'єкт, але addHandlerне перевіряє, чи схожий обробник уже доданий до реєстратора.

Спробуйте відстежити дзвінки до цього методу та усунути один із них. Або встановити прапор, logging_initializedініційований Falseу __init__методі Boyта змінити, configure_loggingщоб нічого не робити, якщо logging_initializedє True, і встановити його Trueпісля того, як ви ініціалізували реєстратор.

Якщо ваша програма створює кілька Boyекземплярів, вам доведеться змінити спосіб виконання глобальної configure_loggingфункції, додаючи обробники, і Boy.configure_loggingметод лише ініціалізує self.loggerатрибут.

Інший спосіб вирішити це - перевірити атрибут handlers вашого реєстратора:

logger = logging.getLogger('my_logger')
if not logger.handlers:
    # create the handlers and call logger.addHandler(logging_handler)

1
Так, ви мали рацію - нерозумно мені. Я назвав це init , а також явно в інших місцях. Лол. Спасибі =).
victorhooi

Дякую. Ваше рішення сьогодні врятувало мене.
ForeverLearner

1
У моєму випадку вони з’являлися 6 разів. Я підозрював, що тому що я оголосив однотипний реєстратор у 6 класах
oop

5
Я хотів би поділитися тут своїм досвідом: для розробленого я Flask-програми, повідомлення журналу з'являлися БІЛЬШЕ, ніж два рази. Я б сказав, що вони збільшувались у файлі журналу через те, що при завантаженні програми та модулів loggerвикористовувана змінна була не тією, що інстанцірована в одному з моїх класів, а loggerзміною, присутнім у кеші Python3 , і обробник додається кожні 60 сек AppScheduler, який я налаштував. Отже, це if not logger.handlersдосить розумний спосіб уникнути подібного явища. Дякую за рішення, товаришу :)!
ivanleoncz

2
Я бачу цю проблему в моєму додатку Flask. Це рішення вирішило проблему з повідомленнями журналів, створеними в основній програмі для колби, але мої програми викликують функції модуля бібліотеки, і ці повідомлення з цієї бібліотеки все ще отримують реєстрацію кілька разів. Я не знаю, як це виправити.
Кас

24

Якщо ви бачите цю проблему і не додаєте обробник двічі, дивіться відповідь abarnert тут

З документів :

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

Отже, якщо ви хочете користувальницький обробник на "тесті", і ви не хочете, щоб його повідомлення також надходили до кореневого обробника, відповідь проста: вимкніть його розповсюдження прапор:

logger.propagate = Неправильно


1
Це найкраща відповідь. Він не відповідав призначенню плаката (логічна помилка кодування), але в більшості випадків це повинно бути так.
Артем

Браво. ЦЕ є фактичною причиною дублікатів (для найбільш загальних випадків).
Містер

У цьому і була проблема з моїм кодом. Дуже дякую.
суворо

Найкраща відповідь коли-небудь. Дякую!
Foivos Ts

8

Обробник додається щоразу, коли ви телефонуєте ззовні. Спробуйте видалити обробник після закінчення роботи:

self.logger.removeHandler(ch)

1
Я використовував logger.handlers.pop() у python 2.7, робить трюк
radtek


4

У моєму випадку я б налаштовувався logger.propagate = Falseна запобігання подвійного друку.

Знизу код, якщо ви видалите, logger.propagate = Falseви побачите подвійний друк.

import logging
from typing import Optional

_logger: Optional[logging.Logger] = None

def get_logger() -> logging.Logger:
    global _logger
    if _logger is None:
        raise RuntimeError('get_logger call made before logger was setup!')
    return _logger

def set_logger(name:str, level=logging.DEBUG) -> None:
    global _logger
    if _logger is not None:
        raise RuntimeError('_logger is already setup!')
    _logger = logging.getLogger(name)
    _logger.handlers.clear()
    _logger.setLevel(level)
    ch = logging.StreamHandler()
    ch.setLevel(level)
    # warnings.filterwarnings("ignore", "(Possibly )?corrupt EXIF data", UserWarning)
    ch.setFormatter(_get_formatter())
    _logger.addHandler(ch)
    _logger.propagate = False # otherwise root logger prints things again


def _get_formatter() -> logging.Formatter:
    return logging.Formatter(
        '[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s')

Це питання, яке у мене є. Дякую
q0987

Чудова відповідь; додавання logger.propagate = Falseбуло рішенням для запобігання подвійного входу у програму Flask, розміщену офіціанткою, під час входу в app.loggerекземпляр Flask .
bluebinary

1

Дзвінок на logging.debug()дзвінки, logging.basicConfig()якщо не встановлено кореневих обробників. Це сталося для мене в рамках тестування, де я не міг контролювати порядок запуску тестових справ. Мій код ініціалізації встановлював другий. За замовчуванням використовується реєстрація.BASIC_FORMAT, яку я не хотів.


Я думаю, що саме це відбувається для мене. Як ви запобігаєте автоматичному створенню консольних реєстраторів?
Роберт

@Robert стосується того, щоб переконатися, що ви ініціалізовані з потрібним реєстратором, перед першим викликом реєстрації. Тестування рамок може затьмарити це, але повинен бути спосіб це зробити. Крім того, якщо ви багатопроцесорні, вам потрібно робити те саме з кожним процесом.
JimB

1

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

logging.warning("look out)"

...
ch = logging.StreamHandler(sys.stdout)
root = logging.getLogger()
root.addHandler(ch)

root.info("hello")

Я отримаю щось на зразок (ігнорування параметрів формату)

look out
hello
hello

і все було написано в stdout двічі. Я вважаю, що це тому, що перший виклик logging.warningстворює новий обробник автоматично, а потім явно додав ще один обробник. Проблема усунулася, коли я видалив випадковий перший logging.warningдзвінок.


0

У мене виникла дивна ситуація, коли журнали консолей були подвоєні, але мої файли не були. Після тонни копання я зрозумів це.

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

Моє рішення для цього полягало в тому, щоб зареєструвати мій консольний реєстратор на кореневому рівні:

rootLogger = logging.getLogger()  # note no text passed in--that's how we grab the root logger
if not rootLogger.handlers:
        ch = logging.StreamHandler()
        ch.setLevel(logging.INFO)
        ch.setFormatter(logging.Formatter('%(process)s|%(levelname)s] %(message)s'))
        rootLogger.addHandler(ch)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.