Повторяющийся вывод журнала при использовании модуля ведения журнала Python


106

Я использую регистратор 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.
— Zuljin

2
Вы уверены, что не создаете несколько экземпляров регистратора?
— Gandi

Да. в другом файле я беру новый экземпляр, как мы это делали в проектах Java. Пожалуйста, уточните, создает ли это проблему или нет.
— user865438

Ответы:


95

Это 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 ((name, logger))).
— акрофобия

почему loggers.update(dict(name=logger))? не loggers[name] = loggerпроще?
— Ryan J McCall

@RyanJMcCall В то время я использовал это соглашение о кодировании. Но, просмотрев код в его нынешнем виде, я вижу, что он сломан. loggers.update(dict(name=logger))создаст словарь с одним вызываемым ключом nameи будет постоянно обновлять этот же ключ. Я удивлен, что никто не упомянул об этом раньше, так как этот код сильно сломан :) Внесу необходимые поправки.
— Вернер Смит

Я вижу, что @acrophobia ускользнула от этого много лет назад. Спасибо.
— Вернер Смит

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

62

Начиная с Python 3.2, вы можете просто проверить, присутствуют ли уже обработчики, и если да, очистить их перед добавлением новых обработчиков. Это довольно удобно при отладке, и код включает инициализацию вашего регистратора.

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

logger.addHandler(handler)

Хороший ответ, спасибо :))
— Гавриэль Коэн

3
Обратите внимание, что 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
Спасибо за совет. Кстати, чтобы проверить, пуст ли список, вам не нужно использовать оператор "len", который вы можете использовать напрямую, если my_list: ..
— rkachach 01

27

Я уже использовал в loggerкачестве синглтона и проверил if not len(logger.handlers), но все же получил дубликаты : это был форматированный вывод, за которым следовал неформатированный.

Решение в моем случае: logger.propagate = False

Кредиты на этот ответ и документы .


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

10

Вы звоните Logger.myLogger()не один раз. Храните экземпляр регистратора он возвращает где - то и повторное использование , что .

Также имейте в виду, что если вы войдете в систему до добавления любого обработчика, StreamHandler(sys.stderr)будет создано значение по умолчанию .


На самом деле я пытаюсь получить доступ к экземпляру регистратора, который мы используем в java, но я не знаю, нужно ли создавать экземпляр только один раз для всего проекта или нет.
— user865438

1
@ user865483: Один раз. Все стандартные библиотечные регистраторы являются одиночными.
— Мэтт Джойнер,

5

Реализация логгера уже одноэлементная.

Несколько вызовов 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

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 печатает больше одного или нет. Пожалуйста, помогите мне в этом.
— user865438

1
@ user865438, нам не нужно беспокоиться о том, чтобы сделать реализацию одноэлементной (она уже есть). Для входа в подмодули перейдите по официальной ссылке Logging Cookbook . По сути, вам нужно следовать иерархии именования при именовании регистраторов, а он позаботится обо всем остальном.
— narayan

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, вышеуказанные предложения были разрешены с помощью random ().

Что-то вроде:

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.