configuración del registrador para iniciar sesión en el archivo e imprimir en stdout

353

Estoy usando el módulo de registro de Python para registrar algunas cadenas de depuración en un archivo que funciona bastante bien. Ahora, además, me gustaría usar este módulo para imprimir también las cadenas en stdout. ¿Cómo hago esto? Para registrar mis cadenas en un archivo, uso el siguiente código:

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)

y luego llamar a una función de registrador como

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

Gracias por un poco de ayuda aquí!

stdcerr
fuente

Respuestas:

451

Simplemente obtenga un identificador para el registrador raíz y agregue el StreamHandler. El StreamHandlerescribe a stderr. No estoy seguro de si realmente necesita stdout sobre stderr, pero esto es lo que uso cuando configuro el registrador Python y también agrego el FileHandler. Luego, todos mis registros van a ambos lugares (que es lo que parece que quieres).

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

Si desea generar en stdoutlugar de stderr, solo debe especificarlo al StreamHandlerconstructor.

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

También puede agregarle una Formatterpara que todas sus líneas de registro tengan un encabezado común.

es decir:

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 en el formato de:

2012-12-05 16:58:26,618 [MainThread  ] [INFO ]  my message
Chico de agua
fuente
19
También podría simplemente inicializar StreamHandlercon sys.stdout, y luego se registrará en eso en lugar de stderr.
Silas Ray
1
@ sr2222 logger.addHandler (sys.stdout) me da NameError: el nombre 'sys' no está definido
stdcerr 05 de
21
Bueno, sí ... tienes que import sysprimero. Y en realidad inicializar el controlador, es decirconsoleHandler = logging.StreamHandler(sys.stdout)
Silas Ray
15
Porque como ya dije, no es así como lo haces. Cree el MANEJADOR con sys.stdout, luego conecte el controlador al registrador.
Silas Ray
66
No olvides rootLogger.setLevel(logging.DEBUG)si estás intentando ver información o mensajes de depuración
storm_m2138
247

logging.basicConfig()puede tomar un argumento de palabra clave handlersdesde Python 3.3, lo que simplifica mucho la configuración de registro, especialmente cuando se configuran múltiples controladores con el mismo formateador:

handlers- Si se especifica, este debe ser un iterador de controladores ya creados para agregar al registrador raíz. A los controladores que aún no tengan un conjunto de formateadores se les asignará el formateador predeterminado creado en esta función.

Por lo tanto, toda la configuración se puede hacer con una sola llamada como esta:

import logging

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

(O con los requisitos de import sys+ StreamHandler(sys.stdout)según la pregunta original: el valor predeterminado para StreamHandler es escribir en stderr. Mire los atributos de LogRecord si desea personalizar el formato de registro y agregar elementos como nombre de archivo / línea, información de hilo, etc.)

La configuración anterior solo debe realizarse una vez cerca del comienzo del script. Puede usar el registro desde todos los otros lugares en la base de código más adelante de esta manera:

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

Nota: Si no funciona, es probable que otra persona ya haya inicializado el sistema de registro de manera diferente. Los comentarios sugieren hacer logging.root.handlers = []antes de la llamada a basicConfig().

Yirkha
fuente
55
no olvide establecer level = logging.INFO o el nivel deseado también
Andy Matteson
55
Definición de FileHandler: logging.FileHandler(filename, mode='a', encoding=None, delay=False). Esto significa que cuando solo desea iniciar sesión en la misma carpeta, simplemente puede usar FileHandler("mylog.log"). Si desea sobrescribir el registro cada vez, establezca "w" como segundo argumento.
user136036
77
Intenté esto, pero el archivo de salida está vacío aunque la consola está dando la salida ... ¿Alguna sugerencia ...?
Ramesh-X
44
@ Ramesh-X, esto también me volvió loco. simplemente haga logging.root.handlers = []antes de la llamada a basicConfig, eche un vistazo a la función: es molesto.
ihadanny
70

Agregar un StreamHandler sin argumentos va a stderr en lugar de stdout. Si algún otro proceso depende del volcado stdout (es decir, al escribir un complemento NRPE), asegúrese de especificar stdout explícitamente o podría encontrarse con algunos problemas inesperados.

Aquí hay un ejemplo rápido que reutiliza los valores asumidos y el LOGFILE de la pregunta:

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
fuente
Estoy intentando esto.
Ajay Kumar
19

Ya sea correr basicConfigcon stream=sys.stdoutcomo argumento antes de establecer cualquier otro manipuladores o ingresar cualquier mensaje, o manualmente añadir StreamHandlerque los mensajes empuja a la salida estándar al registrador de la raíz (o cualquier otro registro prefieres, para el caso).

Rayo Silas
fuente
5

Después de haber usado el código de Waterboy una y otra vez en múltiples paquetes de Python, finalmente lo convertí en un pequeño paquete de Python independiente, que puedes encontrar aquí:

https://github.com/acschaefer/duallog

El código está bien documentado y es fácil de usar. Simplemente descargue el .pyarchivo e inclúyalo en su proyecto, o instale todo el paquete a través de pip install duallog.

Lexxer
fuente
Por alguna razón no se registra en la consola ni el archivo (está vacío)
JackTheKnife
5

Inicio de sesión en stdouty rotating filecon diferentes niveles y formatos:

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
fuente
2

Aquí hay una solución completa y bien envuelta basada en la respuesta de Waterboy y varias otras fuentes. Admite el registro tanto en la consola como en el archivo de registro, permite diferentes configuraciones de nivel de registro, proporciona resultados coloreados y es fácilmente configurable (también disponible como 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
fuente
-4

Para 2.7, intente lo siguiente:

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