configuration de l'enregistreur pour se connecter au fichier et imprimer sur la sortie standard

353

J'utilise le module de journalisation de Python pour consigner certaines chaînes de débogage dans un fichier qui fonctionne plutôt bien. Maintenant, en plus, j'aimerais utiliser ce module pour imprimer également les chaînes sur stdout. Comment puis-je faire cela? Pour enregistrer mes chaînes dans un fichier, j'utilise le code suivant:

import logging
import logging.handlers
logger = logging.getLogger("")
logger.setLevel(logging.DEBUG)
handler = logging.handlers.RotatingFileHandler(
    LOGFILE, maxBytes=(1048576*5), backupCount=7
)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)

puis appeler une fonction d'enregistrement comme

logger.debug("I am written to the file")

Merci pour votre aide ici!

stdcerr
la source

Réponses:

451

Obtenez simplement une poignée sur l'enregistreur racine et ajoutez le StreamHandler. L' StreamHandlerécrit à stderr. Je ne sais pas si vous avez vraiment besoin de stdout sur stderr, mais c'est ce que j'utilise lorsque je configure l'enregistreur Python et j'ajoute également le FileHandler. Ensuite, tous mes journaux vont aux deux endroits (ce qui ressemble à ce que vous voulez).

import logging
logging.getLogger().addHandler(logging.StreamHandler())

Si vous souhaitez générer une sortie au stdoutlieu de stderr, il vous suffit de le spécifier au StreamHandlerconstructeur.

import sys
# ...
logging.getLogger().addHandler(logging.StreamHandler(sys.stdout))

Vous pouvez également y ajouter un Formatterafin que toutes vos lignes de journal aient un en-tête commun.

c'est à dire:

import logging
logFormatter = logging.Formatter("%(asctime)s [%(threadName)-12.12s] [%(levelname)-5.5s]  %(message)s")
rootLogger = logging.getLogger()

fileHandler = logging.FileHandler("{0}/{1}.log".format(logPath, fileName))
fileHandler.setFormatter(logFormatter)
rootLogger.addHandler(fileHandler)

consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(logFormatter)
rootLogger.addHandler(consoleHandler)

Imprime au format:

2012-12-05 16:58:26,618 [MainThread  ] [INFO ]  my message
Waterboy
la source
19
Vous pouvez également simplement initialiser le StreamHandleravec sys.stdout, puis il se connectera à cela au lieu de stderr.
Silas Ray
1
@ sr2222 logger.addHandler (sys.stdout) me donne NameError: le nom 'sys' n'est pas défini
stdcerr
21
Eh bien oui ... vous devez d' import sysabord. Et réellement initialiser le gestionnaire, c'estconsoleHandler = logging.StreamHandler(sys.stdout)
Silas Ray
15
Parce que comme je l'ai déjà dit, ce n'est pas comme ça que vous le faites. Créez le HANDLER avec sys.stdout, puis attachez le gestionnaire à l'enregistreur.
Silas Ray
6
N'oubliez pas rootLogger.setLevel(logging.DEBUG)si vous essayez de voir des messages d'information ou de débogage
storm_m2138
247

logging.basicConfig()peut prendre un argument mot-clé handlersdepuis Python 3.3, ce qui simplifie beaucoup la configuration de la journalisation, en particulier lors de la configuration de plusieurs gestionnaires avec le même formateur:

handlers- S'il est spécifié, cela devrait être un itérable de gestionnaires déjà créés à ajouter à l'enregistreur racine. Tous les gestionnaires qui n'ont pas encore un ensemble de formateurs se verront attribuer le formateur par défaut créé dans cette fonction.

L'ensemble de la configuration peut donc se faire avec un seul appel comme celui-ci:

import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[
        logging.FileHandler("debug.log"),
        logging.StreamHandler()
    ]
)

(Ou avec import sys+ StreamHandler(sys.stdout)par les exigences de la question d'origine - la valeur par défaut pour StreamHandler est d'écrire sur stderr. Regardez les attributs LogRecord si vous souhaitez personnaliser le format du journal et ajouter des choses comme le nom de fichier / la ligne, les informations sur le fil, etc.)

La configuration ci-dessus doit être effectuée une seule fois vers le début du script. Vous pouvez utiliser la journalisation de tous les autres endroits de la base de code plus tard, comme ceci:

logging.info('Useful message')
logging.error('Something bad happened')
...

Remarque: Si cela ne fonctionne pas, quelqu'un d'autre a probablement déjà initialisé le système d'enregistrement différemment. Les commentaires suggèrent de faire logging.root.handlers = []avant l'appel à basicConfig().

Yirkha
la source
5
n'oubliez pas de définir level = logging.INFO ou le niveau souhaité également
Andy Matteson
5
Définition pour FileHandler: logging.FileHandler(filename, mode='a', encoding=None, delay=False). Cela signifie que lorsque vous souhaitez simplement vous connecter au même dossier, vous pouvez simplement l'utiliser FileHandler("mylog.log"). Si vous souhaitez écraser le journal à chaque fois, définissez "w" comme deuxième argument.
user136036
7
J'ai essayé cela, mais le fichier de sortie est vide bien que la console donne la sortie .. Des suggestions ..?
Ramesh-X
4
@ Ramesh-X, cela m'a rendu fou aussi. faites juste logging.root.handlers = []avant l'appel à basicConfig, jetez un oeil à la fonction - c'est ennuyeux.
ihadanny
70

L'ajout d'un StreamHandler sans arguments va à stderr au lieu de stdout. Si un autre processus dépend du vidage stdout (c'est-à-dire lors de l'écriture d'un plugin NRPE), assurez-vous de spécifier explicitement stdout ou vous pourriez rencontrer des problèmes inattendus.

Voici un exemple rapide de réutilisation des valeurs supposées et LOGFILE de la question:

import logging
from logging.handlers import RotatingFileHandler
from logging import handlers
import sys

log = logging.getLogger('')
log.setLevel(logging.DEBUG)
format = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")

ch = logging.StreamHandler(sys.stdout)
ch.setFormatter(format)
log.addHandler(ch)

fh = handlers.RotatingFileHandler(LOGFILE, maxBytes=(1048576*5), backupCount=7)
fh.setFormatter(format)
log.addHandler(fh)
Hazok
la source
J'essaye ça.
Ajay Kumar
19

Exécutez basicConfigavec stream=sys.stdoutcomme argument avant de configurer d'autres gestionnaires ou de consigner tous les messages, ou ajoutez manuellement un StreamHandlerqui pousse les messages vers la sortie standard vers l'enregistreur racine (ou tout autre enregistreur souhaité, d'ailleurs).

Silas Ray
la source
5

Après avoir utilisé le code de Waterboy à plusieurs reprises dans plusieurs packages Python, je l'ai finalement converti en un petit package Python autonome, que vous pouvez trouver ici:

https://github.com/acschaefer/duallog

Le code est bien documenté et facile à utiliser. Téléchargez simplement le .pyfichier et incluez-le dans votre projet, ou installez l'ensemble du package via pip install duallog.

Lexxer
la source
Pour une raison quelconque, il n'est pas connecté à la console, ni aucun fichier (est vide)
JackTheKnife
5

Connexion à stdoutet rotating fileavec différents niveaux et formats:

import logging
import logging.handlers
import sys

if __name__ == "__main__":

    # Change root logger level from WARNING (default) to NOTSET in order for all messages to be delegated.
    logging.getLogger().setLevel(logging.NOTSET)

    # Add stdout handler, with level INFO
    console = logging.StreamHandler(sys.stdout)
    console.setLevel(logging.INFO)
    formater = logging.Formatter('%(name)-13s: %(levelname)-8s %(message)s')
    console.setFormatter(formater)
    logging.getLogger().addHandler(console)

    # Add file rotating handler, with level DEBUG
    rotatingHandler = logging.handlers.RotatingFileHandler(filename='rotating.log', maxBytes=1000, backupCount=5)
    rotatingHandler.setLevel(logging.DEBUG)
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    rotatingHandler.setFormatter(formatter)
    logging.getLogger().addHandler(rotatingHandler)

    log = logging.getLogger("app." + __name__)

    log.debug('Debug message, should only appear in the file.')
    log.info('Info message, should appear in file and stdout.')
    log.warning('Warning message, should appear in file and stdout.')
    log.error('Error message, should appear in file and stdout.')
Andrej Debenjak
la source
2

Voici une solution complète et bien emballée basée sur la réponse de Waterboy et diverses autres sources. Il prend en charge la journalisation à la fois sur la console et le fichier journal, permet différents paramètres de niveau de journal, fournit une sortie colorisée et est facilement configurable (également disponible sous forme de Gist ):

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# -------------------------------------------------------------------------------
#                                                                               -
#  Python dual-logging setup (console and log file),                            -
#  supporting different log levels and colorized output                         -
#                                                                               -
#  Created by Fonic <https://github.com/fonic>                                  -
#  Date: 04/05/20                                                               -
#                                                                               -
#  Based on:                                                                    -
#  https://stackoverflow.com/a/13733863/1976617                                 -
#  https://uran198.github.io/en/python/2016/07/12/colorful-python-logging.html  -
#  https://en.wikipedia.org/wiki/ANSI_escape_code#Colors                        -
#                                                                               -
# -------------------------------------------------------------------------------

# Imports
import os
import sys
import logging

# Logging formatter supporting colored output
class LogFormatter(logging.Formatter):

    COLOR_CODES = {
        logging.CRITICAL: "\033[1;35m", # bright/bold magenta
        logging.ERROR:    "\033[1;31m", # bright/bold red
        logging.WARNING:  "\033[1;33m", # bright/bold yellow
        logging.INFO:     "\033[0;37m", # white / light gray
        logging.DEBUG:    "\033[1;30m"  # bright/bold black / dark gray
    }

    RESET_CODE = "\033[0m"

    def __init__(self, color, *args, **kwargs):
        super(LogFormatter, self).__init__(*args, **kwargs)
        self.color = color

    def format(self, record, *args, **kwargs):
        if (self.color == True and record.levelno in self.COLOR_CODES):
            record.color_on  = self.COLOR_CODES[record.levelno]
            record.color_off = self.RESET_CODE
        else:
            record.color_on  = ""
            record.color_off = ""
        return super(LogFormatter, self).format(record, *args, **kwargs)

# Setup logging
def setup_logging(console_log_output, console_log_level, console_log_color, logfile_file, logfile_log_level, logfile_log_color, log_line_template):

    # Create logger
    # For simplicity, we use the root logger, i.e. call 'logging.getLogger()'
    # without name argument. This way we can simply use module methods for
    # for logging throughout the script. An alternative would be exporting
    # the logger, i.e. 'global logger; logger = logging.getLogger("<name>")'
    logger = logging.getLogger()

    # Set global log level to 'debug' (required for handler levels to work)
    logger.setLevel(logging.DEBUG)

    # Create console handler
    console_log_output = console_log_output.lower()
    if (console_log_output == "stdout"):
        console_log_output = sys.stdout
    elif (console_log_output == "stderr"):
        console_log_output = sys.stderr
    else:
        print("Failed to set console output: invalid output: '%s'" % console_log_output)
        return False
    console_handler = logging.StreamHandler(console_log_output)

    # Set console log level
    try:
        console_handler.setLevel(console_log_level.upper()) # only accepts uppercase level names
    except:
        print("Failed to set console log level: invalid level: '%s'" % console_log_level)
        return False

    # Create and set formatter, add console handler to logger
    console_formatter = LogFormatter(fmt=log_line_template, color=console_log_color)
    console_handler.setFormatter(console_formatter)
    logger.addHandler(console_handler)

    # Create log file handler
    try:
        logfile_handler = logging.FileHandler(logfile_file)
    except Exception as exception:
        print("Failed to set up log file: %s" % str(exception))
        return False

    # Set log file log level
    try:
        logfile_handler.setLevel(logfile_log_level.upper()) # only accepts uppercase level names
    except:
        print("Failed to set log file log level: invalid level: '%s'" % logfile_log_level)
        return False

    # Create and set formatter, add log file handler to logger
    logfile_formatter = LogFormatter(fmt=log_line_template, color=logfile_log_color)
    logfile_handler.setFormatter(logfile_formatter)
    logger.addHandler(logfile_handler)

    # Success
    return True

# Main function
def main():

    # Setup logging
    script_name = os.path.splitext(os.path.basename(sys.argv[0]))[0]
    if (not setup_logging(console_log_output="stdout", console_log_level="warning", console_log_color=True,
                        logfile_file=script_name + ".log", logfile_log_level="debug", logfile_log_color=False,
                        log_line_template="%(color_on)s[%(created)d] [%(threadName)s] [%(levelname)-8s] %(message)s%(color_off)s")):
        print("Failed to setup logging, aborting.")
        return 1

    # Log some messages
    logging.debug("Debug message")
    logging.info("Info message")
    logging.warning("Warning message")
    logging.error("Error message")
    logging.critical("Critical message")

# Call main function
if (__name__ == "__main__"):
    sys.exit(main())
Maxxim
la source
-4

Pour 2.7, essayez ce qui suit:

fh = logging.handlers.RotatingFileHandler(LOGFILE, maxBytes=(1048576*5), backupCount=7)
JonM
la source