Hacer que los registradores de Python envíen todos los mensajes a stdout además del archivo de registro


450

¿Hay alguna manera de hacer que el registro de Python usando el loggingmódulo genere automáticamente cosas para stdout además del archivo de registro donde se supone que deben ir? Por ejemplo, me gustaría que todas las llamadas a logger.warning, logger.critical, logger.errorpara ir a los lugares pensados pero además siempre se va a copiar a stdout. Esto es para evitar duplicar mensajes como:

mylogger.critical("something failed")
print "something failed"

1
Verifique esta respuesta stackoverflow.com/questions/9321741/…
SeF

Respuestas:


635

Todos los resultados de registro son manejados por los manejadores; simplemente agregue un logging.StreamHandler()al registrador raíz.

Aquí hay un ejemplo configurando un controlador de flujo (usando en stdoutlugar del predeterminado stderr) y agregándolo al registrador raíz:

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)

44
Está bien, pero si ya está redirigido a un archivo, ¿cómo puedo imprimirlo stdout?

54
@ user248237: Al agregar un nuevo controlador como se ilustra. Los nuevos controladores no reemplazan a los controladores existentes, también pueden procesar las entradas de registro.
Martijn Pieters

@MartijnPieters ¿hay alguna manera de agregar una cadena a cada declaración de registro impresa?
Prakhar Mohan Srivastava

77
@PrakharMohanSrivastava Supongo que solo puedes agregarlo a la cadena que pasaste logging.Formatter.
A.Wan

3
@ himanshu219: el caso de uso es que tan pronto como comience a agregar varios controladores, generalmente desea diferenciar. DEPURACIÓN a la consola, ADVERTENCIA y hasta un archivo, etc.
Martijn Pieters

505

La forma más sencilla de iniciar sesión en stdout:

import logging
import sys
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)

57
Hm, pero esto no está registrado en un archivo, ¿verdad? La pregunta era cómo iniciar sesión en el archivo y en la consola.
Weidenrinde

Enlace de referencia: Python3 Docs:
Logging.basicConfig

Al menos en Python 3, parece que omitir stream=sys.stdoutaún funciona para iniciar sesión en la consola para mí.
Taylor Edmiston

3
@TaylorEdmiston Sí, pero es la corriente más fuerte AFAIK. Intente redirigir la salida del shell.
Sorin

1
OKAY. Esto no responde a ambos: iniciar sesión en el archivo y en la consola, pero fue agradable encontrar lo que necesitaba en 3 líneas o menos.
Steve3p0

67

Es posible usar múltiples manejadores.

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)

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


44
Maravillosa respuesta, aunque un poco desordenada. Me encanta cómo muestra cómo usar diferentes niveles y formatos para secuencias y archivos. +1, pero +2 en espíritu.
The Unfun Cat

Para mí, esto no funcionó sin el sys.stdoutparámetro ench = logging.StreamHandler()
veuncent

64

Puede crear dos controladores para archivo y stdout y luego crear un registrador con handlersargumento para basicConfig. Podría ser útil si tiene la misma salida log_level y formato para ambos manejadores:

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')

32

La forma más sencilla de iniciar sesión en el archivo y stderr:

import logging

logging.basicConfig(filename="logfile.txt")
stderrLogger=logging.StreamHandler()
stderrLogger.setFormatter(logging.Formatter(logging.BASIC_FORMAT))
logging.getLogger().addHandler(stderrLogger)

Esto no muestra las etiquetas INFO, DEPURACIÓN y ERROR antes del mensaje de registro en la consola. Muestra esas etiquetas en el archivo. ¿Alguna idea para mostrar también las etiquetas en la consola?
JahMyst

1
Gracias, @JahMyst, agregué el formateador. Desafortunadamente, ya no es tan corto, pero sigue siendo la forma más simple. :-)
Weidenrinde

12

Aquí hay una solución basada en el logging.config.dictConfigmétodo poderoso pero poco documentado . En lugar de enviar cada mensaje de registro a stdout, envía mensajes con nivel de registro ERRORy superior a stderry todo lo demás a stdout. Esto puede ser útil si otras partes del sistema están escuchando stderro 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)

tuvo que cambiar el nombre del registrador a una cadena vacía para obtener realmente el registrador raíz. De lo contrario, muy útil, gracias!
Newtopian

8

Como nadie ha compartido un lindo dos líneas, compartiré el mío:

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

2

Aquí hay un ejemplo extremadamente 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 salida mostrará "test msg" en stdout y también en el archivo.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.