Повторяющийся вывод журнала при использовании модуля ведения журнала 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вызова. Как я могу это решить?

user865438
источник
Работает для меня. 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)
rm957377
источник
Хороший ответ, спасибо :))
Гавриэль Коэн
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()))
Pacman
источник
-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
Авинаш Кумар
источник
Пожалуйста, добавьте пояснения, чтобы сделать этот ответ более полезным для долгосрочного использования.
Амина Нураини