Logger-Konfiguration zum Protokollieren in einer Datei und Drucken in Standardausgabe

353

Ich verwende das Protokollierungsmodul von Python, um einige Debug-Zeichenfolgen in einer Datei zu protokollieren, die ziemlich gut funktioniert. Außerdem möchte ich dieses Modul jetzt verwenden, um auch die Zeichenfolgen in stdout auszudrucken. Wie mache ich das? Um meine Zeichenfolgen in einer Datei zu protokollieren, verwende ich folgenden Code:

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)

und rufen Sie dann eine Logger-Funktion wie auf

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

Vielen Dank für etwas Hilfe hier!

stdcerr
quelle

Antworten:

451

Holen Sie sich einfach ein Handle zum Root-Logger und fügen Sie das hinzu StreamHandler. Das StreamHandlerschreibt an stderr. Ich bin mir nicht sicher, ob Sie wirklich stdout über stderr benötigen, aber dies ist, was ich verwende, wenn ich den Python-Logger einrichte und auch das hinzufüge FileHandler. Dann gehen alle meine Protokolle an beide Orte (so hört es sich an, wie Sie es wollen).

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

Wenn Sie stdoutanstelle von ausgeben möchten stderr, müssen Sie dies nur dem StreamHandlerKonstruktor mitteilen.

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

Sie können auch ein hinzufügen Formatter, damit alle Ihre Protokollzeilen einen gemeinsamen Header haben.

dh:

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)

Druckt im Format:

2012-12-05 16:58:26,618 [MainThread  ] [INFO ]  my message
Wasserjunge
quelle
19
Sie könnten auch initialisieren nur das StreamHandlermit sys.stdout, und dann wird es statt stderr dass loggen sein .
Silas Ray
1
@ sr2222 logger.addHandler (sys.stdout) gibt mir NameError: Name 'sys' ist nicht definiert
stdcerr
21
Nun ja ... du musst import syszuerst. Und tatsächlich initialisieren Sie den Handler, dhconsoleHandler = logging.StreamHandler(sys.stdout)
Silas Ray
15
Denn wie ich schon sagte, machst du das nicht so. Erstellen Sie den HANDLER mit sys.stdout und hängen Sie den Handler an den Logger an.
Silas Ray
6
Vergessen Sie nicht, rootLogger.setLevel(logging.DEBUG)wenn Sie versuchen, Informationen zu sehen oder Nachrichten zu debuggen
Storm_M2138
247

logging.basicConfig()kann handlersseit Python 3.3 ein Schlüsselwortargument annehmen , was die Einrichtung der Protokollierung erheblich vereinfacht, insbesondere wenn mehrere Handler mit demselben Formatierer eingerichtet werden:

handlers- Wenn angegeben, sollte dies eine Iteration bereits erstellter Handler sein, die dem Root-Logger hinzugefügt werden sollen. Allen Handlern, für die noch kein Formatierungssatz festgelegt ist, wird der in dieser Funktion erstellte Standardformatierer zugewiesen.

Das gesamte Setup kann daher mit einem einzigen Aufruf wie folgt durchgeführt werden:

import logging

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

(Oder mit import sys+ StreamHandler(sys.stdout)gemäß den Anforderungen der ursprünglichen Frage - der Standard für StreamHandler ist das Schreiben in stderr. Sehen Sie sich die LogRecord-Attribute an, wenn Sie das Protokollformat anpassen und Dinge wie Dateiname / Zeile, Thread-Informationen usw. hinzufügen möchten.)

Das obige Setup muss nur einmal am Anfang des Skripts durchgeführt werden. Sie können die Protokollierung von allen anderen Stellen in der Codebasis später wie folgt verwenden:

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

Hinweis: Wenn es nicht funktioniert, hat wahrscheinlich bereits jemand anderes das Protokollierungssystem anders initialisiert. Kommentare empfehlen, logging.root.handlers = []vor dem Anruf zu tun basicConfig().

Yirkha
quelle
5
Vergessen Sie nicht, level = logging.INFO oder das gewünschte Level festzulegen
Andy Matteson
5
Definition für FileHandler: logging.FileHandler(filename, mode='a', encoding=None, delay=False). Dies bedeutet, dass Sie nur verwenden können, wenn Sie sich nur im selben Ordner anmelden möchten FileHandler("mylog.log"). Wenn Sie das Protokoll jedes Mal überschreiben möchten, setzen Sie "w" als zweites Argument.
user136036
7
Ich habe es versucht, aber die Ausgabedatei ist leer, obwohl die Konsole die Ausgabe ausgibt. Irgendwelche Vorschläge ..?
Ramesh-X
4
@ Ramesh-X, das hat mich auch verrückt gemacht. Schauen Sie sich logging.root.handlers = []vor dem Aufruf von basicConfigdie Funktion an - es ist ärgerlich.
Ihadanny
70

Das Hinzufügen eines StreamHandlers ohne Argumente erfolgt an stderr anstelle von stdout. Wenn ein anderer Prozess vom stdout-Speicherauszug abhängig ist (dh beim Schreiben eines NRPE-Plugins), müssen Sie stdout explizit angeben, da sonst unerwartete Probleme auftreten können.

Hier ist ein kurzes Beispiel für die Wiederverwendung der angenommenen Werte und LOGFILE aus der Frage:

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
quelle
Ich versuche das.
Ajay Kumar
19

Entweder laufen basicConfigmit stream=sys.stdoutvor als Argument dem Einrichten eines Handler oder alle Nachrichten Anmeldung oder manuell hinzufügen ein , StreamHandlerdass Schübe Nachrichten an stdout an die Wurzel Logger (oder einem anderen Logger Sie wollen, für diese Angelegenheit).

Silas Ray
quelle
5

Nachdem ich Waterboys Code in mehreren Python-Paketen immer wieder verwendet habe, habe ich ihn schließlich in ein winziges eigenständiges Python-Paket umgewandelt, das Sie hier finden:

https://github.com/acschaefer/duallog

Der Code ist gut dokumentiert und einfach zu bedienen. Laden Sie einfach die .pyDatei herunter und fügen Sie sie in Ihr Projekt ein oder installieren Sie das gesamte Paket über pip install duallog.

Lexxer
quelle
Aus irgendeinem Grund wird weder in der Konsole noch in der Datei protokolliert (ist leer)
JackTheKnife
5

Protokollierung in stdoutund rotating filemit verschiedenen Ebenen und Formaten:

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
quelle
2

Hier ist eine vollständige, gut verpackte Lösung, die auf Waterboys Antwort und verschiedenen anderen Quellen basiert . Es unterstützt die Protokollierung sowohl in der Konsole als auch in der Protokolldatei, ermöglicht unterschiedliche Einstellungen für die Protokollebene, bietet eine farbige Ausgabe und ist einfach zu konfigurieren (auch als Gist verfügbar ):

#!/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
quelle
-4

Versuchen Sie für 2.7 Folgendes:

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