сообщения журнала появляются дважды с 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

Ответы:

135

Вы вызываете 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. Это решение устранило проблему для сообщений журнала, сгенерированных в основном приложении Flask, но мои приложения работают в модуле библиотеки, и эти сообщения из этой библиотеки по-прежнему регистрируются несколько раз. Я не знаю, как это исправить.
Cas
27

Если вы столкнулись с этой проблемой и не добавляете обработчик дважды, см. Ответ abarnert здесь.

Из документов :

Примечание. Если вы прикрепите обработчик к регистратору и одному или нескольким его предкам, он может генерировать одну и ту же запись несколько раз. В общем, вам не нужно прикреплять обработчик более чем к одному регистратору - если вы просто присоедините его к соответствующему регистратору, который находится на самом высоком уровне в иерархии регистраторов, тогда он увидит все события, зарегистрированные всеми дочерними регистраторами, при условии, что они распространяются оставлено значение True. Распространенный сценарий - присоединить обработчики только к корневому регистратору, а все остальное позаботится распространению.

Итак, если вам нужен специальный обработчик для «test», и вы не хотите, чтобы его сообщения также передавались корневому обработчику, ответ прост: отключите его propagateфлаг:

logger.propagate = False
сумасшедший еж
источник
1
Это лучший ответ. Это не соответствовало цели плаката (логическая ошибка в коде), но в большинстве случаев так и должно быть.
Артем
Браво. ЭТО и есть настоящая причина дубликатов (для самых общих случаев).
Mr. Duhart
Это была проблема и с моим кодом. Большое спасибо.
суровый
Лучший ответ на свете. Спасибо!
Foivos Ts,
Почему это не может быть по умолчанию. Я строю все свои регистраторы под "root". + структура каталогов, так что я могу легко контролировать все из «корневого» регистратора
MortenB
8

Обработчик добавляется каждый раз, когда вы звоните извне. Попробуйте удалить обработчик после завершения работы:

self.logger.removeHandler(ch)
Маюх Рой
источник
1
Я использовал logger.handlers.pop() в Python 2.7, помогает
Radtek
7

Я новичок в Python, но, похоже, это сработало для меня (Python 2.7)

while logger.handlers:
     logger.handlers.pop()
Грегори Понто
источник
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, размещенное в Waitress, при входе в app.loggerэкземпляр Flask .
bluebinary
1

Обращение к logging.debug()вызовам, logging.basicConfig()если не установлены корневые обработчики. Это происходило со мной в тестовой среде, где я не мог контролировать порядок запуска тестовых примеров. Мой код инициализации устанавливал второй. По умолчанию используется logging.BASIC_FORMAT, который мне не нужен.

JimB
источник
Я думаю, что это то, что со мной происходит. Как предотвратить автоматическое создание логгеров консоли?
Роберт
@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

и все было дважды записано на стандартный вывод. Я считаю, что это потому, что первый вызов 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)
Роберт
источник