Korzystanie z logowania do wielu modułów

257

Mam mały projekt python, który ma następującą strukturę -

Project 
 -- pkg01
   -- test01.py
 -- pkg02
   -- test02.py
 -- logging.conf

Planuję użyć domyślnego modułu rejestrującego do drukowania komunikatów na standardowe wyjście i pliku dziennika. Aby użyć modułu rejestrującego, wymagana jest inicjalizacja -

import logging.config

logging.config.fileConfig('logging.conf')
logger = logging.getLogger('pyApp')

logger.info('testing')

Obecnie wykonuję tę inicjalizację w każdym module przed rozpoczęciem rejestrowania komunikatów. Czy można wykonać tę inicjalizację tylko raz w jednym miejscu, tak aby te same ustawienia były ponownie używane po zalogowaniu się w całym projekcie?

Quest Monger
źródło
3
W odpowiedzi na twój komentarz do mojej odpowiedzi: nie musisz wywoływać fileConfigw każdym module logującym się, chyba że masz if __name__ == '__main__'logikę we wszystkich. odpowiedź prost nie jest dobrą praktyką, jeśli pakiet jest biblioteką, ale może ci się przydać - nie należy konfigurować rejestrowania pakietów bibliotek, oprócz dodawania NullHandler.
Vinay Sajip
1
prost sugeruje, że musimy wywoływać stmts import i logger w każdym module i wywoływać tylko stect fileconfig w module głównym. czy to nie jest podobne do tego, co mówisz?
Quest Monger,
6
prost mówi, że powinieneś umieścić kod konfiguracji logowania package/__init__.py. To zwykle nie jest miejsce, w którym umieszczasz if __name__ == '__main__'kod. Przykład prost wygląda na to, że przy imporcie wywoła kod konfiguracji bezwarunkowo, co nie wydaje mi się właściwe. Zasadniczo rejestrowanie kodu konfiguracyjnego powinno odbywać się w jednym miejscu i nie powinno być efektem ubocznym importu, z wyjątkiem importowania __main__.
Vinay Sajip
masz rację, całkowicie przegapiłem wiersz „# pakiet / __ init__.py” w jego próbce kodu. dzięki za zwrócenie na to uwagi i cierpliwość.
Quest Monger,
1
Co się stanie, jeśli masz wiele if __name__ == '__main__'? (nie jest to wyraźnie wspomniane, jeśli tak jest)
kon psych

Odpowiedzi:

293

Najlepszą praktyką jest, aby w każdym module zdefiniować rejestrator w następujący sposób:

import logging
logger = logging.getLogger(__name__)

u góry modułu, a następnie w innym kodzie w module wykonaj np

logger.debug('My message with %s', 'variable data')

Jeśli chcesz podzielić rejestrowanie w module, użyj np

loggerA = logging.getLogger(__name__ + '.A')
loggerB = logging.getLogger(__name__ + '.B')

i loguj się loggerAi loggerBodpowiednio.

W głównym programie lub programach wykonaj np .:

def main():
    "your program code"

if __name__ == '__main__':
    import logging.config
    logging.config.fileConfig('/path/to/logging.conf')
    main()

lub

def main():
    import logging.config
    logging.config.fileConfig('/path/to/logging.conf')
    # your program code

if __name__ == '__main__':
    main()

Zobacz tutaj, aby zalogować się z wielu modułów, a tutaj, aby zalogować się do konfiguracji kodu, który będzie używany jako moduł biblioteki przez inny kod.

Aktualizacja: Podczas dzwonienia fileConfig()możesz określić, disable_existing_loggers=Falseczy używasz języka Python 2.6 lub nowszego ( więcej informacji znajdziesz w dokumentacji ). Domyślną wartością jest Truekompatybilność wsteczna, co powoduje, że wszystkie istniejące programy rejestrujące są wyłączone, fileConfig()chyba że oni lub ich przodek są wyraźnie nazwani w konfiguracji. Przy ustawionej wartości Falseistniejące rejestratory są pozostawione same sobie. Jeśli używasz Python 2.7 / Python 3.2 lub nowszy, możesz rozważyć dictConfig()API, które jest lepsze niż fileConfig()ponieważ daje większą kontrolę nad konfiguracją.

Vinay Sajip
źródło
21
jeśli spojrzysz na mój przykład, już robię to, co sugerujesz powyżej. moje pytanie brzmiało: jak scentralizować tę inicjalizację rejestrowania, tak że nie muszę powtarzać tych 3 instrukcji. również w twoim przykładzie pominąłeś plik „logowanie.config.fileConfig („ logowanie.conf ”)” stmt. ten stmt jest właściwie główną przyczyną mojej troski. widzisz, jeśli zainicjowałem program rejestrujący w każdym module, musiałbym wpisać ten stmt w każdym module. oznaczałoby to śledzenie ścieżki pliku conf w każdym module, co nie wydaje mi się najlepszą praktyką (wyobraź sobie spustoszenie przy zmianie lokalizacji modułów / pakietów).
Quest Monger,
4
Jeśli wywołasz fileConfig po utworzeniu programu rejestrującego, to w tym samym lub w innym module (np. Po utworzeniu programu rejestrującego u góry pliku) nie zadziała. Konfiguracja rejestrowania dotyczy tylko rejestratorów utworzonych później. To podejście nie działa lub nie jest wykonalną opcją dla wielu modułów. @Quest Monger: Zawsze możesz utworzyć kolejny plik, który przechowuje lokalizację pliku konfiguracyjnego ..;)
Vincent Ketelaars
2
@Oxidator: Niekoniecznie - zobacz disable_existing_loggersflagę, która jest Truedomyślnie, ale można ją ustawić na False.
Vinay Sajip,
1
@Vayay Sajip, dziękuję. Czy masz zalecenia dotyczące programów rejestrujących, które działają w modułach, ale także poza klasami? Ponieważ importowanie odbywa się przed wywołaniem funkcji głównej, dzienniki te zostaną już zarejestrowane. Wydaje mi się, że skonfigurowanie rejestratora, zanim wszystkie importy do modułu głównego będą jedynym sposobem? Ten rejestrator może zostać zastąpiony w głównym, jeśli chcesz.
Vincent Ketelaars,
1
Jeśli chcę, aby wszystkie moduły rejestrujące specyficzne dla modułu miały poziom rejestrowania inny niż domyślny OSTRZEŻENIE, czy muszę wprowadzić to ustawienie w każdym module? Powiedz, że chcę, aby wszystkie moje moduły logowały się w INFO.
Raj,
127

W rzeczywistości każdy program rejestrujący jest dzieckiem programu rejestrującego pakiet nadrzędny (tzn. package.subpackage.moduleDziedziczy konfigurację package.subpackage), więc wystarczy tylko skonfigurować program rejestrujący root. Można to osiągnąć za pomocą logging.config.fileConfig(własnej konfiguracji programów rejestrujących ) lub logging.basicConfig(ustawia program rejestrujący root) Skonfiguruj logowanie do modułu wejściowego ( __main__.pylub cokolwiek, co chcesz uruchomić, na przykład main_script.py. __init__.pyDziała również)

using basicConfig:

# package/__main__.py
import logging
import sys

logging.basicConfig(stream=sys.stdout, level=logging.INFO)

using fileConfig:

# package/__main__.py
import logging
import logging.config

logging.config.fileConfig('logging.conf')

a następnie utwórz każdy rejestrator za pomocą:

# package/submodule.py
# or
# package/subpackage/submodule.py
import logging
log = logging.getLogger(__name__)

log.info("Hello logging!")

Aby uzyskać więcej informacji, zobacz Zaawansowany samouczek rejestrowania .

Stan Prokop
źródło
15
jest to zdecydowanie najprostsze rozwiązanie problemu, nie wspominając o tym, że ujawnia i wykorzystuje relacje rodzic-dziecko między modułami, czego ja jako noob nie byłem świadomy. danke.
Quest Monger,
masz rację. i jak zauważył vinay w swoim poście, twoje rozwiązanie jest słuszne, o ile nie ma go w module init .py. Twoje rozwiązanie działało, kiedy zastosowałem je do modułu głównego (punktu wejścia).
Quest Monger,
2
właściwie o wiele bardziej trafna odpowiedź, ponieważ pytanie dotyczy oddzielnych modułów.
Jan Sila
1
Głupie pytanie może: jeśli nie ma programu rejestrującego __main__.py(np. Czy chcę użyć modułu w skrypcie, który nie ma programu rejestrującego) logging.getLogger(__name__)nadal będzie logować się w module, czy spowoduje wyjątek?
Bill
1
Wreszcie. Miałem działający rejestrator, ale nie działał w systemie Windows dla równoległych uruchomień z joblib. Myślę, że to ręczne dostosowanie systemu - coś innego jest nie tak z Parallel. Ale na pewno działa! Dzięki
B Furtado,
17

Zawsze robię to jak poniżej.

Użyj pojedynczego pliku Pythona, aby skonfigurować mój dziennik jako wzorzec singletonu o nazwie „ log_conf.py

#-*-coding:utf-8-*-

import logging.config

def singleton(cls):
    instances = {}
    def get_instance():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    return get_instance()

@singleton
class Logger():
    def __init__(self):
        logging.config.fileConfig('logging.conf')
        self.logr = logging.getLogger('root')

W innym module wystarczy zaimportować konfigurację.

from log_conf import Logger

Logger.logr.info("Hello World")

Jest to singletonowy wzór do rejestrowania, prosty i wydajny.

Yarkee
źródło
1
dzięki za wyszczególnienie wzoru singletonu. planowałem to wdrożyć, ale wtedy rozwiązanie @prost jest znacznie prostsze i idealnie odpowiada moim potrzebom. Widzę jednak, że Twoje rozwiązanie jest przydatne w większych projektach, które mają wiele punktów wejścia (innych niż główne). danke.
Quest Monger,
46
To jest bezużyteczne. Rejestrator root jest już singletonem. Wystarczy użyć logowania.info zamiast Logger.logr.info.
Pod
9

Kilka z tych odpowiedzi sugeruje, że u góry modułu robisz

import logging
logger = logging.getLogger(__name__)

Rozumiem, że jest to uważane za bardzo złą praktykę . Powodem jest to, że konfiguracja pliku domyślnie wyłączy wszystkie istniejące programy rejestrujące. Na przykład

#my_module
import logging

logger = logging.getLogger(__name__)

def foo():
    logger.info('Hi, foo')

class Bar(object):
    def bar(self):
        logger.info('Hi, bar')

A w głównym module:

#main
import logging

# load my module - this now configures the logger
import my_module

# This will now disable the logger in my module by default, [see the docs][1] 
logging.config.fileConfig('logging.ini')

my_module.foo()
bar = my_module.Bar()
bar.bar()

Teraz dziennik określony w logowaniu.ini będzie pusty, ponieważ istniejący rejestrator został wyłączony przez wywołanie fileconfig.

Chociaż z pewnością można to obejść (disable_existing_Loggers = False), realistycznie wielu klientów twojej biblioteki nie będzie wiedziało o tym zachowaniu i nie otrzyma twoich logów. Ułatw swoim klientom, zawsze wywołując lokalnie logowanie.getLogger. Hat Tip: Dowiedziałem się o tym zachowaniu ze strony Victora Lin .

Dlatego dobrą praktyką jest zawsze lokalne wywoływanie rejestrowania.getLogger. Na przykład

#my_module
import logging

logger = logging.getLogger(__name__)

def foo():
    logging.getLogger(__name__).info('Hi, foo')

class Bar(object):
    def bar(self):
        logging.getLogger(__name__).info('Hi, bar')    

Ponadto, jeśli używasz fileconfig w głównym, ustaw disable_existing_loggers = False, na wypadek, gdyby projektanci bibliotek używali instancji modułu rejestrującego na poziomie modułu.

phil_20686
źródło
Nie umiesz biegać logging.config.fileConfig('logging.ini')wcześniej import my_module? Jak sugerowano w tej odpowiedzi .
lucid_dreamer
Nie jestem pewien - ale na pewno uważane byłoby również za złe mieszanie importu i kodu wykonywalnego w ten sposób. Nie chcesz też, aby Twoi klienci musieli sprawdzać, czy muszą skonfigurować rejestrowanie przed importowaniem, szczególnie gdy istnieje trywialna alternatywa! Wyobraź sobie, że zrobiły to powszechnie używane biblioteki, takie jak żądania ...!
phil_20686,
„Nie jestem pewien - ale zdecydowanie uważane byłoby również za złe łączenie importu i kodu wykonywalnego w ten sposób”. - czemu?
lucid_dreamer
Nie jestem zbyt jasny, dlaczego to jest złe. I nie do końca rozumiem twój przykład. Czy możesz opublikować konfigurację dla tego przykładu i pokazać jej użycie?
lucid_dreamer
1
Wydaje się, że zaprzeczasz oficjalnym dokumentom : „Dobrą konwencją, którą należy stosować podczas nazywania rejestratorów, jest używanie rejestratora na poziomie modułu, w każdym module używającym rejestrowania, o następującej nazwie: logger = logging.getLogger(__name__)
iron9
8

Prostym sposobem użycia jednej instancji biblioteki rejestrowania w wielu modułach było dla mnie następujące rozwiązanie:

base_logger.py

import logging

logger = logging
logger.basicConfig(format='%(asctime)s - %(message)s', level=logging.INFO)

Inne pliki

from base_logger import logger

if __name__ == '__main__':
    logger.info("This is an info message")
Alex Jolig
źródło
7

Rzucanie w innym rozwiązaniu.

W moim module init .py mam coś takiego:

# mymodule/__init__.py
import logging

def get_module_logger(mod_name):
  logger = logging.getLogger(mod_name)
  handler = logging.StreamHandler()
  formatter = logging.Formatter(
        '%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
  handler.setFormatter(formatter)
  logger.addHandler(handler)
  logger.setLevel(logging.DEBUG)
  return logger

Następnie w każdym module potrzebuję rejestratora:

# mymodule/foo.py
from [modname] import get_module_logger
logger = get_module_logger(__name__)

Gdy brakuje dzienników, możesz odróżnić ich źródło od modułu, z którego pochodzą.

Tommy
źródło
Co oznacza „główna inicjalizacja mojego modułu”? I „W takim razie potrzebuję rejestratora:”? Czy możesz podać próbkę o nazwie_module.py i przykład jej użycia jako importu z modułu moduł_wywołania.py? Zobacz tę odpowiedź, aby uzyskać inspirację formatu, o który pytam. Nie próbować patronować. Próbuję zrozumieć twoją odpowiedź i wiem, że tak, gdybyś napisał to w ten sposób.
lucid_dreamer
1
@lucid_dreamer ja wyjaśniłem.
Tommy
4

Możesz również wymyślić coś takiego!

def get_logger(name=None):
    default = "__app__"
    formatter = logging.Formatter('%(levelname)s: %(asctime)s %(funcName)s(%(lineno)d) -- %(message)s',
                              datefmt='%Y-%m-%d %H:%M:%S')
    log_map = {"__app__": "app.log", "__basic_log__": "file1.log", "__advance_log__": "file2.log"}
    if name:
        logger = logging.getLogger(name)
    else:
        logger = logging.getLogger(default)
    fh = logging.FileHandler(log_map[name])
    fh.setFormatter(formatter)
    logger.addHandler(fh)
    logger.setLevel(logging.DEBUG)
    return logger

Teraz możesz używać wielu rejestratorów w tym samym module i całym projekcie, jeśli powyższe jest zdefiniowane w osobnym module i zaimportowane w innych modułach, jeśli wymagane jest rejestrowanie.

a=get_logger("__app___")
b=get_logger("__basic_log__")
a.info("Starting logging!")
b.debug("Debug Mode")
deeshank
źródło
4

@ Rozwiązanie Yarkee wydawało się lepsze. Chciałbym coś jeszcze dodać -

class Singleton(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances.keys():
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]


class LoggerManager(object):
    __metaclass__ = Singleton

    _loggers = {}

    def __init__(self, *args, **kwargs):
        pass

    @staticmethod
    def getLogger(name=None):
        if not name:
            logging.basicConfig()
            return logging.getLogger()
        elif name not in LoggerManager._loggers.keys():
            logging.basicConfig()
            LoggerManager._loggers[name] = logging.getLogger(str(name))
        return LoggerManager._loggers[name]    


log=LoggerManager().getLogger("Hello")
log.setLevel(level=logging.DEBUG)

LoggerManager może być podłączany do całej aplikacji. Mam nadzieję, że to ma sens i wartość.

deeshank
źródło
11
Moduł logowania już obsługuje singletony. Logowanie.getLogger („Cześć”) otrzyma ten sam rejestrator we wszystkich modułach.
Pod
2

Istnieje kilka odpowiedzi. Skończyło się na podobnym, ale innym rozwiązaniu, które ma dla mnie sens, być może ma to również sens dla ciebie. Moim głównym celem była możliwość przekazywania dzienników do programów obsługi według ich poziomu (dzienniki poziomu debugowania do konsoli, ostrzeżenia i wyżej do plików):

from flask import Flask
import logging
from logging.handlers import RotatingFileHandler

app = Flask(__name__)

# make default logger output everything to the console
logging.basicConfig(level=logging.DEBUG)

rotating_file_handler = RotatingFileHandler(filename="logs.log")
rotating_file_handler.setLevel(logging.INFO)

app.logger.addHandler(rotating_file_handler)

utworzył ładny plik util o nazwie logger.py:

import logging

def get_logger(name):
    return logging.getLogger("flask.app." + name)

flask.app jest zakodowaną wartością w kolbie. rejestrator aplikacji zawsze zaczyna się od flask.app jako nazwy modułu.

teraz w każdym module mogę go używać w następującym trybie:

from logger import get_logger
logger = get_logger(__name__)

logger.info("new log")

Spowoduje to utworzenie nowego dziennika dla „app.flask.MODULE_NAME” przy minimalnym wysiłku.

Ben Yitzhaki
źródło
2

Najlepszą praktyką byłoby utworzenie modułu osobno, który ma tylko jedną metodę, której zadaniem jest przekazanie funkcji wywołującej program rejestrujący do metody wywołującej. Zapisz ten plik jako m_logger.py

import logger, logging

def getlogger():
    # logger
    logger = logging.getLogger(__name__)
    logger.setLevel(logging.DEBUG)
    # create console handler and set level to debug
    #ch = logging.StreamHandler()
    ch = logging.FileHandler(r'log.txt')
    ch.setLevel(logging.DEBUG)
    # create formatter
    formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
    # add formatter to ch
    ch.setFormatter(formatter)
    # add ch to logger
    logger.addHandler(ch)
    return logger

Teraz wywołuj metodę getlogger (), ilekroć potrzebna jest obsługa programu rejestrującego.

from m_logger import getlogger
logger = getlogger()
logger.info('My mssg')
Mousam Singh
źródło
1
Jest to dobre, jeśli nie masz żadnych dodatkowych parametrów. Ale jeśli, powiedzmy, masz --debugopcję w aplikacji i chcesz ustawić poziom rejestrowania we wszystkich rejestratorach w aplikacji na podstawie tego parametru ...
The Godfather
@TheGodfather Tak, jest to trudne do osiągnięcia dzięki tej metodologii. W tej sytuacji możemy stworzyć klasę, dla której formater przyjmowałby parametr jako parametr podczas tworzenia obiektu i miałby podobną funkcję zwracającą moduł obsługi programu rejestrującego. Jakie są twoje poglądy na ten temat?
Mousam Singh
Tak, zrobiłem coś podobnego, get_logger(level=logging.INFO)aby zwrócić jakiś singleton, więc kiedy wywołano go po raz pierwszy z głównej aplikacji, inicjuje program rejestrujący i moduły obsługi z odpowiednim poziomem, a następnie zwraca ten sam loggerobiekt do wszystkich innych metod.
The Godfather
0

Nowy w Pythonie, więc nie wiem, czy jest to wskazane, ale działa świetnie, aby nie przepisywać płyty głównej.

Twój projekt musi mieć plik inicjujący .py, aby można go było załadować jako moduł

# Put this in your module's __init__.py
import logging.config
import sys

# I used this dictionary test, you would put:
# logging.config.fileConfig('logging.conf')
# The "" entry in loggers is the root logger, tutorials always 
# use "root" but I can't get that to work
logging.config.dictConfig({
    "version": 1,
    "formatters": {
        "default": {
            "format": "%(asctime)s %(levelname)s %(name)s %(message)s"
        },
    },
    "handlers": {
        "console": {
            "level": 'DEBUG',
            "class": "logging.StreamHandler",
            "stream": "ext://sys.stdout"
        }
    },
    "loggers": {
        "": {
            "level": "DEBUG",
            "handlers": ["console"]
        }
    }
})

def logger():
    # Get the name from the caller of this function
    return logging.getLogger(sys._getframe(1).f_globals['__name__'])

sys._getframe(1) sugestia pochodzi stąd

Następnie, aby użyć rejestratora w dowolnym innym pliku:

from [your module name here] import logger

logger().debug("FOOOOOOOOO!!!")

Ostrzeżenia:

  1. Musisz uruchomić swoje pliki jako moduły, w przeciwnym razie import [your module]nie będzie działać:
    • python -m [your module name].[your filename without .py]
  2. Nazwa programu rejestrującego dla punktu wejścia twojego programu będzie __main__, ale w każdym używającym rozwiązaniu __name__będzie występować ten problem.
npjohns
źródło