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"
Ben
fuente
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)
Martijn Pieters
fuente
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)
Eyal
fuente
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

Alok Singh Mahor
fuente
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')
Anton Protopopov
fuente
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)
Weidenrinde
fuente
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)
Elias Strehle
fuente
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())
Lexander
fuente
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.

Kiki Jewell
fuente