Faire en sorte que les enregistreurs Python envoient tous les messages à stdout en plus du fichier journal

450

Existe-t-il un moyen de faire en sorte que la journalisation Python en utilisant le loggingmodule génère automatiquement des éléments vers stdout en plus du fichier journal où ils sont censés aller? Par exemple, je voudrais que tous les appels logger.warning, logger.critical, logger.errord'aller à leurs lieux destinés , mais en plus être copiés toujours stdout. C'est pour éviter de dupliquer des messages comme:

mylogger.critical("something failed")
print "something failed"
Ben
la source
1
Veuillez vérifier cette réponse stackoverflow.com/questions/9321741/…
SeF

Réponses:

635

Toutes les sorties de journalisation sont gérées par les gestionnaires; il suffit d'ajouter un logging.StreamHandler()à l'enregistreur racine.

Voici un exemple de configuration d'un gestionnaire de flux (en utilisant stdoutau lieu de la valeur par défaut stderr) et en l'ajoutant à l'enregistreur racine:

import logging
import sys

root = logging.getLogger()
root.setLevel(logging.DEBUG)

handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
root.addHandler(handler)
Martijn Pieters
la source
4
C'est bien, mais s'il est déjà redirigé vers un fichier, comment puis-je l'imprimer stdouten plus?
54
@ user248237: en ajoutant un nouveau gestionnaire comme illustré. Les nouveaux gestionnaires ne remplacent pas les gestionnaires existants, ils peuvent également traiter les entrées du journal.
Martijn Pieters
@MartijnPieters existe-t-il un moyen d'ajouter une chaîne à chaque instruction de journal imprimée?
Prakhar Mohan Srivastava
7
@PrakharMohanSrivastava Je suppose que vous pouvez simplement l'ajouter à la chaîne passée logging.Formatter.
A.Wan
3
@ himanshu219: le cas d'utilisation est que dès que vous commencez à ajouter plusieurs gestionnaires, vous voulez généralement vous différencier. DÉBOGAGE à la console, AVERTISSEMENT et jusqu'à un fichier, etc.
Martijn Pieters
505

La façon la plus simple de se connecter à stdout:

import logging
import sys
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
Eyal
la source
57
Hm, mais ce n'est pas enregistré dans un fichier, non? La question était de savoir comment se connecter au fichier et à la console.
Weidenrinde
Lien de référence: Python3 Docs: Logging.basicConfig
Czechnology
Dans Python 3 au moins, il semble que l'omission stream=sys.stdoutfonctionne toujours pour la connexion à la console pour moi.
Taylor Edmiston
3
@TaylorEdmiston Oui, mais c'est le flux stderr AFAIK. Essayez de rediriger la sortie du shell.
Sorin
1
D'ACCORD. Cela ne répond pas aux deux: se connecter au fichier et à la console, mais c'était agréable de trouver ce dont j'avais besoin en 3 lignes ou moins.
Steve3p0
67

Il est possible d'utiliser plusieurs gestionnaires.

import logging
import auxiliary_module

# create logger with 'spam_application'
log = logging.getLogger('spam_application')
log.setLevel(logging.DEBUG)

# create formatter and add it to the handlers
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# create file handler which logs even debug messages
fh = logging.FileHandler('spam.log')
fh.setLevel(logging.DEBUG)
fh.setFormatter(formatter)
log.addHandler(fh)

# create console handler with a higher log level
ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)
ch.setFormatter(formatter)
log.addHandler(ch)

log.info('creating an instance of auxiliary_module.Auxiliary')
a = auxiliary_module.Auxiliary()
log.info('created an instance of auxiliary_module.Auxiliary')

log.info('calling auxiliary_module.Auxiliary.do_something')
a.do_something()
log.info('finished auxiliary_module.Auxiliary.do_something')

log.info('calling auxiliary_module.some_function()')
auxiliary_module.some_function()
log.info('done with auxiliary_module.some_function()')

# remember to close the handlers
for handler in log.handlers:
    handler.close()
    log.removeFilter(handler)

Veuillez consulter: https://docs.python.org/2/howto/logging-cookbook.html

Alok Singh Mahor
la source
4
Merveilleuse réponse, quoique un peu désordonnée. Aimez la façon dont vous montrez comment utiliser différents niveaux et formats pour les flux et les fichiers. +1, mais +2 en esprit.
The Unfun Cat
Pour moi, cela ne fonctionnait pas sans le sys.stdoutparamètre inch = logging.StreamHandler()
veuncent
64

Vous pouvez créer deux gestionnaires pour file et stdout, puis créer un enregistreur avec l' handlersargument to basicConfig. Cela peut être utile si vous avez le même niveau de journalisation et le même format de sortie pour les deux gestionnaires:

import logging
import sys

file_handler = logging.FileHandler(filename='tmp.log')
stdout_handler = logging.StreamHandler(sys.stdout)
handlers = [file_handler, stdout_handler]

logging.basicConfig(
    level=logging.DEBUG, 
    format='[%(asctime)s] {%(filename)s:%(lineno)d} %(levelname)s - %(message)s',
    handlers=handlers
)

logger = logging.getLogger('LOGGER_NAME')
Anton Protopopov
la source
32

Le moyen le plus simple de se connecter au fichier et à stderr:

import logging

logging.basicConfig(filename="logfile.txt")
stderrLogger=logging.StreamHandler()
stderrLogger.setFormatter(logging.Formatter(logging.BASIC_FORMAT))
logging.getLogger().addHandler(stderrLogger)
Weidenrinde
la source
Cela n'affiche pas les étiquettes INFO, DEBUG et ERROR avant le message de journalisation dans la console. Il montre ces étiquettes dans le fichier. Avez-vous des idées pour afficher également les étiquettes dans la console?
JahMyst
1
Merci, @JahMyst, j'ai ajouté le formateur. Malheureusement, ce n'est plus si court, mais c'est toujours le moyen le plus simple. :-)
Weidenrinde
12

Voici une solution basée sur la logging.config.dictConfigméthode puissante mais peu documentée . Au lieu d'envoyer chaque message de journal à stdout, il envoie des messages de niveau journal ERRORet supérieur à stderret tout le reste à stdout. Cela peut être utile si d'autres parties du système écoutent stderrou stdout.

import logging
import logging.config
import sys

class _ExcludeErrorsFilter(logging.Filter):
    def filter(self, record):
        """Filters out log messages with log level ERROR (numeric value: 40) or higher."""
        return record.levelno < 40


config = {
    'version': 1,
    'filters': {
        'exclude_errors': {
            '()': _ExcludeErrorsFilter
        }
    },
    'formatters': {
        # Modify log message format here or replace with your custom formatter class
        'my_formatter': {
            'format': '(%(process)d) %(asctime)s %(name)s (line %(lineno)s) | %(levelname)s %(message)s'
        }
    },
    'handlers': {
        'console_stderr': {
            # Sends log messages with log level ERROR or higher to stderr
            'class': 'logging.StreamHandler',
            'level': 'ERROR',
            'formatter': 'my_formatter',
            'stream': sys.stderr
        },
        'console_stdout': {
            # Sends log messages with log level lower than ERROR to stdout
            'class': 'logging.StreamHandler',
            'level': 'DEBUG',
            'formatter': 'my_formatter',
            'filters': ['exclude_errors'],
            'stream': sys.stdout
        },
        'file': {
            # Sends all log messages to a file
            'class': 'logging.FileHandler',
            'level': 'DEBUG',
            'formatter': 'my_formatter',
            'filename': 'my.log',
            'encoding': 'utf8'
        }
    },
    'root': {
        # In general, this should be kept at 'NOTSET'.
        # Otherwise it would interfere with the log levels set for each handler.
        'level': 'NOTSET',
        'handlers': ['console_stderr', 'console_stdout', 'file']
    },
}

logging.config.dictConfig(config)
Elias Strehle
la source
a dû renommer l'enregistreur en une chaîne vide pour obtenir l'enregistreur racine. Sinon, très utile, merci!
Newtopian
8

Puisque personne n'a partagé une doublure soignée, je partagerai la mienne:

logging.basicConfig(filename='logs.log', level=logging.DEBUG, format="%(asctime)s:%(levelname)s: %(message)s")
logging.getLogger().addHandler(logging.StreamHandler())
Lexander
la source
2

Voici un exemple extrêmement simple:

import logging
l = logging.getLogger("test")

# Add a file logger
f = logging.FileHandler("test.log")
l.addHandler(f)

# Add a stream logger
s = logging.StreamHandler()
l.addHandler(s)

# Send a test message to both -- critical will always log
l.critical("test msg")

La sortie affichera "test msg" sur stdout et également dans le fichier.

Kiki Jewell
la source