Fazendo com que os registradores Python produzam todas as mensagens para stdout, além do arquivo de log

450

Existe uma maneira de fazer o log do Python usando o loggingmódulo gerar automaticamente coisas para o stdout , além do arquivo de log para onde elas devem ir? Por exemplo, eu gostaria de todas as chamadas para logger.warning, logger.critical, logger.errorpara ir para seus lugares destinados mas, além disso sempre ser copiado para stdout. Isso evita a duplicação de mensagens como:

mylogger.critical("something failed")
print "something failed"
Ben
fonte
1
Por favor, verifique esta resposta stackoverflow.com/questions/9321741/…
SeF

Respostas:

635

Toda saída de log é manipulada pelos manipuladores; basta adicionar um logging.StreamHandler()ao registrador raiz.

Aqui está um exemplo de configuração de um manipulador de fluxo (usando em stdoutvez do padrão stderr) e adicionando-o ao criador de logs raiz:

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
fonte
4
Tudo bem, mas se já estiver redirecionado para um arquivo, como posso imprimi-lo stdout?
54
@ user248237: Adicionando um novo manipulador conforme ilustrado. Os novos manipuladores não substituem os manipuladores existentes, eles também processam as entradas de log.
Martijn Pieters
@MartijnPieters, existe uma maneira de adicionar uma string a cada instrução de log impressa?
Prakhar Mohan Srivastava
7
@PrakharMohanSrivastava Eu acho que você pode simplesmente adicioná-lo à string passada logging.Formatter.
A.Wan
3
@ himanshu219: o caso de uso é que, assim que você começa a adicionar vários manipuladores, geralmente deseja se diferenciar. DEBUG para o console, WARNING e até um arquivo etc.
Martijn Pieters
505

A maneira mais simples de fazer logon no stdout:

import logging
import sys
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
Eyal
fonte
57
Hum, mas isso não está registrado em um arquivo, certo? A questão era como fazer o log no arquivo e no console.
Weidenrinde
Pelo menos no Python 3, parece que a omissão stream=sys.stdoutainda funciona para o logon no console para mim.
Taylor Edmiston
3
@TaylorEdmiston Sim, mas é o AFAIK do fluxo stderr. Tente redirecionar a saída do shell.
Sorin
1
ESTÁ BEM. Isso não responde a ambos: registrar no arquivo e no console, mas foi bom encontrar o que eu precisava em três linhas ou menos.
Steve3p0
67

É possível usando vários manipuladores.

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
fonte
4
Resposta maravilhosa, embora um pouco confusa. Adoro como você mostra como usar diferentes níveis e formatos para fluxos e arquivos. +1, mas +2 em espírito.
The Unfun Cat
Para mim isso não funciona sem o sys.stdoutparâmetroch = logging.StreamHandler()
veuncent
64

Você pode criar dois manipuladores para file e stdout e, em seguida, criar um logger com handlersargumento para basicConfig. Pode ser útil se você tiver o mesmo log_level e formato de saída para os dois manipuladores:

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
fonte
32

A maneira mais simples de registrar em arquivo e em stderr:

import logging

logging.basicConfig(filename="logfile.txt")
stderrLogger=logging.StreamHandler()
stderrLogger.setFormatter(logging.Formatter(logging.BASIC_FORMAT))
logging.getLogger().addHandler(stderrLogger)
Weidenrinde
fonte
Isso não mostra os rótulos INFO, DEBUG e ERRO antes da mensagem de log no console. Ele mostra esses rótulos no arquivo. Alguma idéia para mostrar também os rótulos no console?
precisa saber é o seguinte
1
Obrigado, @JahMyst, adicionei o Formatador. Infelizmente, não é mais tão curto, mas ainda é a maneira mais simples. :-)
Weidenrinde 22/01
12

Aqui está uma solução baseada no logging.config.dictConfigmétodo poderoso, mas mal documentado . Em vez de enviar todas as mensagens de log para stdout, ele envia mensagens com nível de log ERRORe superior a stderre tudo o mais para stdout. Isso pode ser útil se outras partes do sistema estiverem ouvindo 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
fonte
teve que renomear o criador de logs para uma string vazia para realmente obter o criador de logs raiz. Caso contrário, muito útil, obrigado!
Newtopian
8

Como ninguém compartilhou uma linha dupla elegante, compartilharei a minha:

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

Aqui está um exemplo extremamente simples:

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

A saída mostrará "test msg" no stdout e também no arquivo.

Kiki Jewell
fonte