Je me demandais comment implémenter un enregistreur global qui pourrait être utilisé partout avec vos propres paramètres:
J'ai actuellement une classe d'enregistreur personnalisée:
class customLogger(logging.Logger):
...
La classe est dans un fichier séparé avec quelques formateurs et autres trucs. L'enregistreur fonctionne parfaitement seul.
J'importe ce module dans mon fichier principal python et je crée un objet comme celui-ci:
self.log = logModule.customLogger(arguments)
Mais évidemment, je ne peux pas accéder à cet objet à partir d'autres parties de mon code. Suis-je en train d'utiliser une mauvaise approche? Y a-t-il une meilleure manière de faire cela?
Utilisez logging.getLogger(name)
pour créer un enregistreur global nommé.
main.py
import log
logger = log.setup_custom_logger('root')
logger.debug('main message')
import submodule
log.py
import logging
def setup_custom_logger(name):
formatter = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(module)s - %(message)s')
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logger = logging.getLogger(name)
logger.setLevel(logging.DEBUG)
logger.addHandler(handler)
return logger
submodule.py
import logging
logger = logging.getLogger('root')
logger.debug('submodule message')
Sortie
2011-10-01 20:08:40,049 - DEBUG - main - main message
2011-10-01 20:08:40,050 - DEBUG - submodule - submodule message
Comme je n'ai pas trouvé de réponse satisfaisante, je voudrais développer un peu la réponse à la question afin de donner un aperçu du fonctionnement et des intentions de la bibliothèque logging
, fournie avec le standard de Python. bibliothèque.
Contrairement à l'approche de l'OP (affiche originale), la bibliothèque sépare clairement l'interface avec l'enregistreur et la configuration de l'enregistreur lui-même.
La configuration des gestionnaires est la prérogative du développeur d'application qui utilise votre bibliothèque.
Cela signifie que vous devez not créer une classe d'enregistreur personnalisée et configurer l'enregistreur à l'intérieur de cette classe en ajoutant une configuration ou autre.
La bibliothèque logging
introduit quatre composants: loggers, handlers, filters, et formers.
- Les enregistreurs exposent l'interface que le code d'application utilise directement.
- Les gestionnaires envoient les enregistrements de journal (créés par les enregistreurs) à la destination appropriée.
- Les filtres offrent une fonction plus fine pour déterminer les enregistrements de journaux à afficher.
- Les formateurs spécifient la disposition des enregistrements de journal dans la sortie finale.
Une structure de projet commune ressemble à ceci:
Project/
|-- .../
| |-- ...
|
|-- project/
| |-- package/
| | |-- __init__.py
| | |-- module.py
| |
| |-- __init__.py
| |-- project.py
|
|-- ...
|-- ...
Dans votre code (comme dans module.py ), vous vous référez à l'instance de l'enregistreur de votre module pour enregistrer les événements à leurs niveaux spécifiques.
Une bonne convention à utiliser pour nommer les enregistreurs consiste à utiliser un enregistreur de niveau module, dans chaque module qui utilise la journalisation, nommé comme suit:
logger = logging.getLogger(__name__)
La variable spéciale __name__
Fait référence au nom de votre module et ressemble à project.package.module
En fonction de la structure de code de votre application.
module.py (et toute autre classe) pourrait essentiellement ressembler à ceci:
import logging
...
log = logging.getLogger(__name__)
class ModuleClass:
def do_something(self):
log.debug('do_something() has been called!')
L'enregistreur de chaque module propage tout événement à l'enregistreur parent qui, en retour, transmet les informations à son attaché gestionnaire! De manière similaire à la structure de module/module python, l'enregistreur parent est déterminé par l'espace de noms à l'aide de "noms de module en pointillés". C'est pourquoi il est logique d'initialiser l'enregistreur avec le __name__
variable (dans l'exemple ci-dessus nom correspond à la chaîne "project.package.module").
Il existe deux options pour configurer l'enregistreur globalement:
Instanciez un enregistreur dans project.py avec le nom __package__
Qui est égal à "project" dans cet exemple et est donc l'enregistreur parent des enregistreurs de tous les sous-modules. Il est seulement nécessaire d'ajouter un gestionnaire et un formateur appropriés à this logger.
Configurez un enregistreur avec un gestionnaire et un formateur dans le script d'exécution (comme main.py ) avec le nom du package le plus haut.
Lors du développement d'une bibliothèque qui utilise la journalisation, vous devez prendre soin de documenter la façon dont la bibliothèque utilise la journalisation - par exemple, les noms des enregistreurs utilisés.
Le script d'exécution, comme main.py par exemple, pourrait finalement ressembler à ceci:
import logging
from project import App
def setup_logger():
# create logger
logger = logging.getLogger('project')
logger.setLevel(logging.DEBUG)
# create console handler and set level to debug
ch = logging.StreamHandler()
ch.setLevel(level)
# create formatter
formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(name)s: %(message)s')
# add formatter to ch
ch.setFormatter(formatter)
# add ch to logger
logger.addHandler(ch)
if __== '__main__' and __package__ is None:
setup_logger()
app = App()
app.do_some_funny_stuff()
L'appel de méthode log.setLevel(...)
spécifie le message de journal le moins grave qu'un enregistreur manipulera mais pas nécessairement en sortie! Cela signifie simplement que le message est transmis au gestionnaire tant que le niveau de gravité du message est supérieur (ou égal) à celui qui est défini. Mais le gestionnaire est responsable de manipulation le message du journal (en l'imprimant ou en le stockant par exemple).
La bibliothèque logging
propose donc une approche structurée et modulaire qui ne demande qu'à être exploitée selon ses besoins.
Créez une instance de customLogger
dans votre module de journal et utilisez-la comme singleton - utilisez simplement l'instance importée, plutôt que la classe.
Vous pouvez simplement lui passer une chaîne avec une sous-chaîne commune avant la première période. Les parties de la chaîne séparées par le point (".") Peuvent être utilisées pour différentes classes/modules/fichiers/etc. Comme cela (en particulier la partie logger = logging.getLogger(loggerName)
):
def getLogger(name, logdir=LOGDIR_DEFAULT, level=logging.DEBUG, logformat=FORMAT):
base = os.path.basename(__file__)
loggerName = "%s.%s" % (base, name)
logFileName = os.path.join(logdir, "%s.log" % loggerName)
logger = logging.getLogger(loggerName)
logger.setLevel(level)
i = 0
while os.path.exists(logFileName) and not os.access(logFileName, os.R_OK | os.W_OK):
i += 1
logFileName = "%s.%s.log" % (logFileName.replace(".log", ""), str(i).zfill((len(str(i)) + 1)))
try:
#fh = logging.FileHandler(logFileName)
fh = RotatingFileHandler(filename=logFileName, mode="a", maxBytes=1310720, backupCount=50)
except IOError, exc:
errOut = "Unable to create/open log file \"%s\"." % logFileName
if exc.errno is 13: # Permission denied exception
errOut = "ERROR ** Permission Denied ** - %s" % errOut
Elif exc.errno is 2: # No such directory
errOut = "ERROR ** No such directory \"%s\"** - %s" % (os.path.split(logFileName)[0], errOut)
Elif exc.errno is 24: # Too many open files
errOut = "ERROR ** Too many open files ** - Check open file descriptors in /proc/<PID>/fd/ (PID: %s)" % os.getpid()
else:
errOut = "Unhandled Exception ** %s ** - %s" % (str(exc), errOut)
raise LogException(errOut)
else:
formatter = logging.Formatter(logformat)
fh.setLevel(level)
fh.setFormatter(formatter)
logger.addHandler(fh)
return logger
class MainThread:
def __init__(self, cfgdefaults, configdir, pidfile, logdir, test=False):
self.logdir = logdir
logLevel = logging.DEBUG
logPrefix = "MainThread_TEST" if self.test else "MainThread"
try:
self.logger = getLogger(logPrefix, self.logdir, logLevel, FORMAT)
except LogException, exc:
sys.stderr.write("%s\n" % exc)
sys.stderr.flush()
os._exit(0)
else:
self.logger.debug("-------------------- MainThread created. Starting __init__() --------------------")
def run(self):
self.logger.debug("Initializing ReportThreads..")
for (group, cfg) in self.config.items():
self.logger.debug(" ------------------------------ GROUP '%s' CONFIG ------------------------------ " % group)
for k2, v2 in cfg.items():
self.logger.debug("%s <==> %s: %s" % (group, k2, v2))
try:
rt = ReportThread(self, group, cfg, self.logdir, self.test)
except LogException, exc:
sys.stderr.write("%s\n" % exc)
sys.stderr.flush()
self.logger.exception("Exception when creating ReportThread (%s)" % group)
logging.shutdown()
os._exit(1)
else:
self.threads.append(rt)
self.logger.debug("Threads initialized.. \"%s\"" % ", ".join([t.name for t in self.threads]))
for t in self.threads:
t.Start()
if not self.test:
self.loop()
class ReportThread:
def __init__(self, mainThread, name, config, logdir, test):
self.mainThread = mainThread
self.name = name
logLevel = logging.DEBUG
self.logger = getLogger("MainThread%s.ReportThread_%s" % ("_TEST" if self.test else "", self.name), logdir, logLevel, FORMAT)
self.logger.info("init database...")
self.initDB()
# etc....
if __== "__main__":
# .....
MainThread(cfgdefaults=options.cfgdefaults, configdir=options.configdir, pidfile=options.pidfile, logdir=options.logdir, test=options.test)