Zduplikowane dane wyjściowe dziennika podczas korzystania z modułu rejestrowania w języku Python

106

Używam rejestratora Pythona. Oto mój kod:

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

Problem polega na tym, że podczas każdego logger.infopołączenia otrzymuję wiele wpisów w pliku dziennika . Jak mogę to rozwiązać?

user865438
źródło
Pracuje dla mnie. Python 3.2 i Windows XP.
Zuljin
2
Czy na pewno nie tworzysz wielu instancji programu rejestrującego?
Gandi
Tak. w innym pliku biorę nową instancję, tak jak robiliśmy to w projektach Java. Proszę określić, czy to stwarza problem, czy nie.
user865438

Odpowiedzi:

95

To logging.getLogger()już jest singleton. ( Dokumentacja )

Problem polega na tym, że za każdym razem, gdy dzwonisz myLogger(), dodaje on kolejną procedurę obsługi do instancji, co powoduje zduplikowane dzienniki.

Może coś takiego?

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
Werner Smit
źródło
3
Myślę, że zamiast tego powinieneś mieć loggers.update (dict ((name, logger))).
akrofobia
dlaczego loggers.update(dict(name=logger))? nie jest loggers[name] = loggerprostsze?
Ryan J McCall
@RyanJMcCall W tamtym czasie stosowałem konwencję kodowania. Ale po przejrzeniu kodu, jaki jest teraz, widzę, że jest uszkodzony. loggers.update(dict(name=logger))utworzy słownik z jednym wywołanym kluczem namei będzie stale aktualizować ten sam klucz. Dziwię się, że nikt wcześniej o tym nie wspomniał, ponieważ ten kod jest dość zepsuty :) Wprowadzi wymagane poprawki.
Werner Smit
Widzę, że @acrophobia wymykała się temu wieki temu. Dzięki.
Werner Smit,
czy loggerssłownik globalny nie jest zbędny logging.getLogger? ponieważ naprawdę chcesz po prostu uniknąć dodawania dodatkowych
programów
62

Od Pythona 3.2 możesz po prostu sprawdzić, czy programy obsługi są już obecne, a jeśli tak, wyczyść je przed dodaniem nowych programów obsługi. Jest to bardzo wygodne podczas debugowania, a kod obejmuje inicjalizację programu rejestrującego

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

logger.addHandler(handler)
rm957377
źródło
Dobra odpowiedź, dzięki :))
Gavriel Cohen
3
Zwróć uwagę, że funkcja hasHandlers () zwróci wartość true w pytest, gdzie procedura obsługi została dodana do głównego programu rejestrującego, nawet jeśli lokalne / niestandardowe procedury obsługi nie zostały jeszcze dodane. Len (logger.handlers) (zgodnie z odpowiedzią Guillaume'a) zwróci w tym przypadku 0, więc może być lepszą opcją.
Grant
To jest prawdziwe rozwiązanie, którego szukałem.
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

zrobił dla mnie sztuczkę

za pomocą Pythona 2.7

Guillaume Cisco
źródło
1
Działa to nawet po ponownym załadowaniu modułu (co nie ma miejsca w przypadku innych odpowiedzi)
yco
3
Dzięki za podpowiedź, przy okazji żeby sprawdzić czy lista jest pusta czy nie nie musisz używać operatora "len" którego możesz bezpośrednio użyć if my_list: ..
rkachach
27

Użyłem już loggerjako singletona i sprawdziłem if not len(logger.handlers), ale nadal otrzymałem duplikaty : było to sformatowane wyjście, a następnie niesformatowane.

Rozwiązanie w moim przypadku: logger.propagate = False

Podziękowania za tę odpowiedź i dokumenty .

Pan B.
źródło
1
Dowiedziałem się, że zdublowane rejestrowanie pochodziło z RootLogger i mojego StreamHandler, ale nie mogłem rozwiązać problemu (utrzymując mój formater w StreamHandler), dopóki tego nie zrobiłem.
Xander YzWich
10

Dzwonisz Logger.myLogger()więcej niż raz. Zapisz gdzieś zwracaną instancję rejestrującą i użyj jej ponownie .

Pamiętaj również, że jeśli zalogujesz się przed dodaniem jakiegokolwiek modułu obsługi, StreamHandler(sys.stderr)zostanie utworzony domyślny .

Matt Joiner
źródło
Właściwie próbuję uzyskać dostęp do instancji rejestrującej, której używamy w java, ale nie wiem, czy trzeba utworzyć instancję tylko raz dla całego projektu, czy nie.
user865438
1
@ user865483: Tylko raz. Wszystkie standardowe rejestratory bibliotek są singletonami.
Matt Joiner,
5

Implementacja loggera jest już singletonem.

Wielokrotne wywołania loggera.getLogger („someLogger”) zwracają odwołanie do tego samego obiektu rejestrującego. Dotyczy to nie tylko tego samego modułu, ale także między modułami, o ile jest to w tym samym procesie interpretera języka Python. Dotyczy to odniesień do tego samego obiektu; dodatkowo, kod aplikacji może definiować i konfigurować nadrzędny rejestrator w jednym module i tworzyć (ale nie konfigurować) podrzędny rejestrator w oddzielnym module, a wszystkie wywołania logera do dziecka będą przekazywane do rodzica. Oto główny moduł

Źródło - korzystanie z logowania w wielu modułach

Tak więc powinieneś to wykorzystać -

Załóżmy, że stworzyliśmy i skonfigurowaliśmy rejestrator o nazwie „main_logger” w głównym module (który po prostu konfiguruje rejestrator, nic nie zwraca).

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

Teraz w module podrzędnym, jeśli utworzymy rejestrator podrzędny zgodnie z hierarchią nazewnictwa „main_logger.sub_module_logger” , nie musimy go konfigurować w module podrzędnym. Wystarczy utworzyć rejestrator zgodnie z hierarchią nazewnictwa.

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

I nie doda również zduplikowanej obsługi.

Zobacz to pytanie, aby uzyskać bardziej szczegółową odpowiedź.

narayan
źródło
1
redefinicja handlerów po getLogger wydaje się działać dla mnie: logger = logging.getLogger('my_logger') ; logger.handlers = [logger.handlers[0], ]
radtek
5

To jest dodatek do odpowiedzi @ rm957377, ale z wyjaśnieniem, dlaczego tak się dzieje . Kiedy uruchamiasz funkcję lambda w AWS, wywołują twoją funkcję z wewnątrz instancji opakowującej, która pozostaje aktywna dla wielu wywołań. Oznacza to, że jeśli wywołasz addHandler()w kodzie funkcji, będzie ona nadal dodawać zduplikowane procedury obsługi do singletonu rejestrowania za każdym razem, gdy funkcja zostanie uruchomiona. Singleton rejestrowania utrzymuje się przez wiele wywołań funkcji lambda.

Aby rozwiązać ten problem, możesz wyczyścić programy obsługi przed ich ustawieniem za pomocą:

logging.getLogger().handlers.clear()
logging.getLogger().addHandler(...)
Chad Befus
źródło
Jakoś w moim przypadku programy obsługi rejestratora są dodawane w zdarzeniu na .info()wezwanie, którego nie rozumiem.
Evgeny
4

Twój rejestrator powinien działać jako singleton. Nie powinieneś go tworzyć więcej niż raz. Oto przykład, jak to może wyglądać:

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")
Zuljin
źródło
to znowu, jeśli zamierzam wziąć inną instancję w innym pliku. Załóżmy, że w pliku 1 s = Logger () m = s.myLogger () iw pliku 2 s = Logger () Będzie działać lub nie m2 = s.myLogger ()
user865438
Mimo to kilka razy otrzymuję kopię tego samego dziennika. Mam tutaj wątpliwości, czy w wątku Log wypisuje więcej niż jeden, czy nie, pomóż mi w tym.
user865438
1
@ user865438, nie musimy się martwić, że implementacja będzie singletonem (już jest). Aby zalogować się do podmodułów, kliknij oficjalny link do książki kucharskiej logowania . Zasadniczo musisz przestrzegać hierarchii nazewnictwa podczas nadawania nazw rejestratorom, a on zajmie się resztą.
narayan
2

Podwójne (lub potrójne lub ... - w zależności od liczby przeładowań) wyjście loggera może się również zdarzyć, gdy przeładujesz moduł przez importlib.reload(z tego samego powodu, co wyjaśniono w zaakceptowanej odpowiedzi). Dodaję tę odpowiedź tylko dla przyszłego odniesienia, ponieważ zajęło mi trochę czasu, aby dowiedzieć się, dlaczego mój wynik jest podwójny (potrójny).

rkuska
źródło
1

Jedno proste obejście jest podobne

logger.handlers[:] = [handler]

W ten sposób unika się dołączania nowego modułu obsługi do bazowej listy „programów obsługi”.

aihex
źródło
1

Podsumowując, w większości przypadków, gdy tak się dzieje, wystarczy wywołać logger.getLogger () tylko raz na moduł. Jeśli masz wiele zajęć, tak jak ja, mogę to tak nazwać:

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')

Oba będą miały własną pełną nazwę pakietu i metodę, w której są rejestrowane.

Harlin
źródło
0

Możesz uzyskać listę wszystkich programów obsługi dla konkretnego rejestratora, więc możesz zrobić coś takiego

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)

W powyższym przykładzie sprawdzamy, czy handler dla określonego pliku jest już podpięty do loggera, ale mając dostęp do listy wszystkich handlerów możesz zdecydować, które kryteria powinieneś dodać, czy nie.

Najbardziej poszukiwany
źródło
0

Miałem ten problem dzisiaj. Ponieważ moje funkcje były @staticmethod, powyższe sugestie zostały rozwiązane za pomocą random ().

Wygląda jak:

import random

logger = logging.getLogger('ProvisioningPython.{}'.format(random.random()))
Pacman
źródło
-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
Avinash Kumar
źródło
Dodaj wyjaśnienie, aby ta odpowiedź była bardziej wartościowa przy długotrwałym użytkowaniu.
Aminah Nuraini