Zmuszenie rejestratorów Pythona do wysyłania wszystkich komunikatów na standardowe wyjście oprócz pliku dziennika

450

Czy istnieje sposób, aby logowanie w Pythonie za pomocą loggingmodułu automatycznie wyświetlało rzeczy na standardowe wyjście oprócz pliku dziennika, do którego powinny się udać? Na przykład, chciałbym wszystkich połączeń do logger.warning, logger.critical, logger.erroraby przejść do zamierzonych miejscach, ale w dodatku zawsze być kopiowane do stdout. Ma to na celu uniknięcie powielania wiadomości, takich jak:

mylogger.critical("something failed")
print "something failed"
Ben
źródło
1
Sprawdź tę odpowiedź stackoverflow.com/questions/9321741/...
SeF

Odpowiedzi:

635

Wszystkie dane wyjściowe rejestrowania są obsługiwane przez procedury obsługi; po prostu dodaj a logging.StreamHandler()do głównego loggera.

Oto przykład konfigurowania modułu obsługi strumienia (użycie stdoutzamiast domyślnego stderr) i dodawanie go do głównego programu rejestrującego:

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
źródło
4
W porządku, ale jeśli jest już przekierowany do pliku, jak mogę go dodatkowo wydrukować stdout?
54
@ user248237: Dodając nowy moduł obsługi zgodnie z ilustracją. Nowe moduły obsługi nie zastępują istniejących modułów obsługi, mogą także przetwarzać wpisy dziennika.
Martijn Pieters
@MartijnPieters czy istnieje sposób, aby dodać ciąg do każdej wydrukowanej instrukcji dziennika?
Prakhar Mohan Srivastava
7
@PrakharMohanSrivastava Myślę, że możesz po prostu dodać go do przekazanego ciągu logging.Formatter.
A.Wan
3
@ himanshu219: przypadek użycia jest taki, że jak tylko zaczniesz dodawać wiele programów obsługi, zwykle chcesz rozróżnić. DEBUGOWANIE do konsoli, OSTRZEŻENIE i do pliku itp.
Martijn Pieters
505

Najprostszy sposób na zalogowanie się na standardowe wyjście:

import logging
import sys
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
Eyal
źródło
57
Hm, ale to nie jest zalogowane do pliku, prawda? Pytanie dotyczyło sposobu logowania do pliku i do konsoli.
Weidenrinde
Przynajmniej w Pythonie 3 wygląda na to, że pominięcie stream=sys.stdoutnadal działa w przypadku logowania do konsoli.
Taylor Edmiston,
3
@TaylorEdmiston Tak, ale to strumień AFAIK stderr. Spróbuj przekierować wyjście z powłoki.
Sorin
1
OK. To nie odpowiada na oba: logowanie do pliku i na konsolę, ale miło było znaleźć to, czego potrzebowałem w 3 liniach lub mniej.
Steve3p0
67

Jest to możliwe przy użyciu wielu programów obsługi.

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)

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

Alok Singh Mahor
źródło
4
Cudowna odpowiedź, choć trochę niechlujna. Uwielbiam to, jak pokazujesz, jak używać różnych poziomów i formatów dla strumieni i plików. +1, ale +2 w duchu.
The Unfun Cat
Dla mnie to nie działa bez sys.stdoutparametru wch = logging.StreamHandler()
veuncent
64

Możesz utworzyć dwie procedury obsługi plików i standardowego wyjścia, a następnie utworzyć jeden program rejestrujący z handlersargumentem do basicConfig. Może to być przydatne, jeśli masz ten sam poziom log_level i format wyjściowy dla obu programów obsługi:

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
źródło
32

Najprostszy sposób na zalogowanie się do pliku i do stderr:

import logging

logging.basicConfig(filename="logfile.txt")
stderrLogger=logging.StreamHandler()
stderrLogger.setFormatter(logging.Formatter(logging.BASIC_FORMAT))
logging.getLogger().addHandler(stderrLogger)
Weidenrinde
źródło
Nie wyświetla etykiet INFO, DEBUG i ERROR przed komunikatem logowania w konsoli. To pokazuje te etykiety w pliku. Jakieś pomysły, aby pokazać etykiety w konsoli?
JahMyst
1
Dzięki, @JahMyst, dodałem formatyzator. Niestety nie jest już tak krótki, ale wciąż najprostszy sposób. :-)
Weidenrinde
12

Oto rozwiązanie oparte na potężnej, ale słabo udokumentowanej logging.config.dictConfigmetodzie . Zamiast wysyłać każdy komunikat dziennika do stdout, wysyła wiadomości o poziomie dziennika wyższym ERRORi do stderrwszystkich innych elementów stdout. Może to być przydatne, jeśli inne części systemu słuchają stderrlub 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
źródło
musiał zmienić nazwę rejestratora na pusty ciąg, aby faktycznie uzyskać rejestrator główny. W przeciwnym razie bardzo pomocne, dzięki!
Newtopian
8

Ponieważ nikt nie udostępnił zgrabnego linera, podzielę się własnym:

logging.basicConfig(filename='logs.log', level=logging.DEBUG, format="%(asctime)s:%(levelname)s: %(message)s")
logging.getLogger().addHandler(logging.StreamHandler())
Lexander
źródło
2

Oto niezwykle prosty przykład:

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

Dane wyjściowe pokażą „test msg” na standardowym wyjściu, a także w pliku.

Kiki Jewell
źródło