configuração do logger para registrar em arquivo e imprimir em stdout

353

Estou usando o módulo de log do Python para registrar algumas seqüências de depuração em um arquivo que funciona muito bem. Agora, além disso, eu gostaria de usar este módulo para também imprimir as strings no stdout. Como eu faço isso? Para registrar minhas seqüências de caracteres em um arquivo, use o seguinte 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)

e chame uma função de logger como

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

Obrigado por alguma ajuda aqui!

stdcerr
fonte

Respostas:

451

Basta obter um identificador para o logger raiz e adicionar o StreamHandler. O StreamHandlergrava stderr. Não tenho certeza se você realmente precisa de stdout sobre stderr, mas é isso que eu uso quando configuro o logger Python e também adiciono o FileHandler. Então todos os meus logs vão para os dois lugares (o que parece que você deseja).

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

Se você deseja enviar para, em stdoutvez de stderr, basta especificá-lo para o StreamHandlerconstrutor.

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

Você também pode adicionar um Formatterpara que todas as suas linhas de log tenham um cabeçalho comum.

ou seja:

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 no formato de:

2012-12-05 16:58:26,618 [MainThread  ] [INFO ]  my message
Waterboy
fonte
19
Você também pode apenas inicializar o StreamHandlercom sys.stdoute, em seguida, ele registrará isso em vez de stderr.
Silas raio
11
@ sr2222 logger.addHandler (sys.stdout) me fornece NameError: o nome 'sys' não está definido
stdcerr
21
Bem, sim ... você precisa import sysprimeiro. E, na verdade inicializar o manipulador, ou sejaconsoleHandler = logging.StreamHandler(sys.stdout)
Silas Ray
15
Porque como eu já disse, não é assim que você faz. Crie o HANDLER com sys.stdout e, em seguida, conecte o manipulador ao criador de logs.
Silas raio
6
Não se esqueça do rootLogger.setLevel(logging.DEBUG)que você está tentando ver informações de depuração ou mensagens
storm_m2138
247

logging.basicConfig()pode usar um argumento de palavra-chave handlersdesde o Python 3.3, o que simplifica muito a configuração do registro, especialmente ao configurar vários manipuladores com o mesmo formatador:

handlers- Se especificado, deve ser uma iteração de manipuladores já criados para adicionar ao registrador raiz. Qualquer manipulador que ainda não possua um conjunto de formatadores receberá o formatador padrão criado nesta função.

Toda a configuração pode, portanto, ser feita com uma única chamada como esta:

import logging

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

(Ou com import sys+ de StreamHandler(sys.stdout)acordo com os requisitos da pergunta original - o padrão para o StreamHandler é gravar no stderr. Observe os atributos LogRecord se você quiser personalizar o formato do log e adicionar itens como nome do arquivo / linha, informações do encadeamento etc.)

A configuração acima precisa ser feita apenas uma vez perto do início do script. Você pode usar o log de todos os outros lugares na base de código posteriormente, desta forma:

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

Nota: Se não funcionar, provavelmente alguém já inicializou o sistema de registro de maneira diferente. Comentários sugerem fazer logging.root.handlers = []antes da chamada para basicConfig().

Yirkha
fonte
5
não se esqueça de definir level = logging.INFO ou o nível desejado também #
Andy Matteson
5
Definição para FileHandler: logging.FileHandler(filename, mode='a', encoding=None, delay=False). Isso significa que, quando você quiser fazer login na mesma pasta, poderá usar FileHandler("mylog.log"). Se você deseja substituir o log toda vez, defina "w" como o segundo argumento.
user136036
7
Eu tentei isso, mas o arquivo de saída está vazio, embora o console esteja dando a saída .. Alguma sugestão ..?
Ramesh-X
4
@ Ramesh-X, isso me deixou louco também. basta fazer logging.root.handlers = []antes da chamada basicConfig, dê uma olhada na função - é irritante.
ihadanny
70

Adicionar um StreamHandler sem argumentos vai para stderr em vez de stdout. Se algum outro processo tiver uma dependência do dump do stdout (por exemplo, ao escrever um plug-in NRPE), certifique-se de especificar o stdout explicitamente ou poderá haver problemas inesperados.

Aqui está um exemplo rápido reutilizando os valores assumidos e LOGFILE da pergunta:

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
fonte
Estou tentando isso.
precisa
19

Execute basicConfigcom stream=sys.stdouto argumento anterior à configuração de qualquer outro manipulador ou registre qualquer mensagem ou adicione manualmente um StreamHandlerque envia mensagens para stdout ao registrador raiz (ou qualquer outro registrador desejado, nesse sentido).

Silas Ray
fonte
5

Depois de ter usado o código de Waterboy repetidamente em vários pacotes Python, finalmente o converti em um pequeno pacote Python autônomo, que você pode encontrar aqui:

https://github.com/acschaefer/duallog

O código está bem documentado e fácil de usar. Basta baixar o .pyarquivo e incluí-lo no seu projeto ou instalar o pacote inteiro via pip install duallog.

Lexxer
fonte
Por alguma razão não está sendo registrada no nem arquivo de console (está vazio)
JackTheKnife
5

Registrando em stdoute rotating filecom diferentes níveis e 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
fonte
2

Aqui está uma solução completa e bem organizada, com base na resposta de Waterboy e em várias outras fontes. Ele suporta o log no console e no arquivo de log, permite diferentes configurações no nível do log, fornece saída colorida e é facilmente configurável (também disponível 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
fonte
-4

Para 2.7, tente o seguinte:

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