[PYTHON] Machen wir uns keine Gedanken mehr über die Protokollierung !!

1. Geschichte

Bitte beenden Sie das Drucken und importieren Sie die Protokollierung für die Protokollausgabe - Qiita

Ich las, während ich dachte: "Nun, ich verstehe." Aber gleichzeitig dachte ich so.

** Wie schreibt man Protokollierung? ** ** ** ** Selbst kostenlos kann ich mich nicht gut in Python anmelden und es braucht Zeit, aber ich kann nicht loggen !! **

Also habe ich beschlossen, diesen Artikel zu schreiben. Der Zweck ist wie folgt.

――Ich möchte mein eigenes Verständnis teilen, das ich als Ergebnis eines verzweifelten Loggers gefunden habe ――Verschwenden Sie keine Zeit mit der Protokollierung, wenn alle Apps mit Python erstellen

2. Warum sollte ich nicht die Protokollierung verwenden? ??

Dies ist die Ansicht aus dem Lesen der offiziellen Dokumente und des obigen Qiita-Artikels.

Die Protokollierung hat eine hierarchische Struktur.

└── rootLogger
    ├── scanLogger
    │    ├── scanLogger.txt
    │    ├── scanLogger.html
    │    └── scanLogger.pdf
    └── anotherLogger

Es hat (oder wird erstellt) mehrere untergeordnete Logger mit einem Logger namens rootLogger als übergeordnetem Protokoll.

Zum Beispiel ...

Der Logger für jedes dieser Pakete und Apps heißt "rootLogger".

bad_sample.py


import logging

logging.info("Taking log...")

Dies ist gleichbedeutend mit dem direkten Spielen mit "rootLogger". Hören wir also auf. Es ähnelt der Idee, mit Instanz zu spielen, anstatt mit Klasse zu spielen.

3. Beispiel protokollieren

Selbst wenn Sie sich das offizielle Dokument oder das Beispiel ansehen, wissen Sie nicht, wie Sie es schreiben sollen ... Schweiß Wie viel feste Formulierung und wie viel kann ich selbst ändern? (Bereich, den Sie selbst benennen können)

Deshalb werde ich die Konfiguration vorstellen, deren Betrieb bestätigt wurde. Wenn Sie ein Problem haben, kopieren Sie es bitte und verwenden Sie es.

So viel wie möglich werde ich einen Kommentar in die Kommentare aufnehmen, damit Sie ihn einfach anpassen können.

3-1. (Muster.1) Verwenden Sie die Konfigurationsdatei

3-1-1. Ausführungsumgebung

** Ich schreibe die Protokollierungskonfigurationsdatei mit toml. ** ** ** Sie können auf das Toml-Paket mit pip install toml verweisen.

Die Notationsmethode in yaml lautet hier.

terminal


$ tree
.
├── app
│   ├── logger
│   │   ├── __init__.py
│   │   └── logger.py
│   ├── logging.config.toml
│   └── main.py
└── log
    ├── application.log
    └── error.log

$ cd app/
$ python app.py  # app/Ich schreibe ein Beispiel unter der Annahme, dass dieser Befehl im Verzeichnis ausgeführt wird

3-1-2. Datei

Geben Sie auch den [Github-Beispielcode] ein (https://github.com/hiphopku/QiitaLoggerToml).

main.py


#Der vorherige Logger ist ein Verzeichnis. Der Logger auf der Rückseite ist eine Datei.
from logger.logger import get_logger

logger = get_logger()

def main():
    logger.info("start = main()")
    # do something 
    logger.error("oops! something wrong!!")
    # do something 
    logger.info("end = main()")

if __name__ == "__main__":
    main()

logging.config.toml ist so eingestellt, dass Protokolle auf dem Konsolenbildschirm und in der Datei ausgegeben werden.

toml:logging.config.toml


version = 1
disable_existing_loggers = false  #Deaktivieren Sie keine Logger für andere Module

[formatters]
    [formatters.basic]
    #Legen Sie das Protokollformat fest. Details zum Schreiben werden später beschrieben
    format = "%(asctime)s  [%(name)s][%(levelname)s]  %(message)s  (%(filename)s:%(module)s:%(funcName)s:%(lineno)d)" 
    datefmt = "%Y-%m-%d %H:%M:%S"

[handlers]
    [handlers.console]
    #Stellen Sie hier die Konsolenausgabe ein
    class = "logging.StreamHandler"  #Feste Formulierung
    level = "DEBUG"                  #Protokollstufe ist Ihre Wahl
    formatter = "basic"              #Wählen Sie das unter Formatierer beschriebene Protokollformat aus
    stream = "ext://sys.stdout"      #Feste Formulierung

    [handlers.file]
    class = "logging.handlers.TimedRotatingFileHandler"  #Feste Formulierung. Details zu TimeRotatingFileHandler werden später beschrieben. Es gibt Notizen
    formatter = "basic"
    filename = "../log/application.log"                  #Wenn durch relativen Pfad angegeben$Schreiben Sie aus einem Verzeichnis, in dem Python ausgeführt wird
    when = 'D'                                           #log Eine Zeiteinheit zum Wechseln von Dateien. D.= day。
    interval = 1                                         #Da oben Tag ausgewählt ist, wird jeden Tag eine neue Datei erstellt.
    backupCount = 31                                     #Da oben der Tag ausgewählt ist, bleiben Protokolldateien im Wert von 31 Tagen erhalten

    [handlers.error]
    class = "logging.handlers.TimedRotatingFileHandler"
    level = "ERROR"
    formatter = "basic"
    filename = "../log/error.log"
    when = 'D'
    interval = 1
    backupCount = 31

[loggers]
    [loggers.app_name]  # app_name ist der Name des von Python aufgerufenen Loggers. Beliebig benannt
    level = "INFO"
    handlers = [
        "console",
        "file",
        "error",
    ]                   #Legen Sie fest, welcher der oben festgelegten Handler verwendet werden soll
    propagate = false

[root]
level = "INFO"
handlers = [
    "console",
    "file",
    "error"
]                       #Einstellung des übergeordneten Stammloggers des Loggers. Ich möchte auch den rootLogger in Konsole und Datei behalten

logger/__init__.py


from . import logger

logger.init_logger('logging.config.toml', 'app_name')
#Erstes Argument: configfile =Der Name der Konfigurationsdatei. Relativer Pfad oder absoluter Pfad des Verzeichnisses, das vom Python-Befehl ausgeführt wird
#Zweites Argument: loggername =Der Name des in der Konfigurationsdatei festgelegten Loggers

logger/logger.py


import os
import toml
from logging import getLogger
from logging.config import dictConfig

CONFIGFILE = "logging.config.toml"    #Als globale Variable behandeln. Der Standardwert ist wie links gezeigt. Irgendein
LOGGERNAME = "root"                   #Als globale Variable behandeln. Der Standardwert ist root. Irgendein

def init_logger(configfile, loggername):
    global CONFIGFILE 
    global LOGGERNAME
    CONFIGFILE = configfile
    LOGGERNAME = loggername
    dictConfig(toml.load(CONFIGFILE))

def get_logger():
    return getLogger(LOGGERNAME)

3-2. (Pattern.2) Verwenden von Python dict

Dies ist flexibler als die Anmeldung in der Konfigurationsdatei. In der Konfigurationsdatei lautet das übergeordnete Verzeichnis "../", wodurch es stärker vom Betriebssystem abhängig ist. Wenn Sie es mit Python Dict festlegen, können Sie es mit "os.path.pardir" angeben.

3-2-1. Ausführungsumgebung

terminal


$ tree
.
├── app
│   ├── logger
│   │   ├── __init__.py
│   │   └── logger.py
│   └── main.py
└── log
    ├── application.log
    └── error.log

$ cd app/
$ python main.py

3-2-2. Datei

Geben Sie auch den [Github-Beispielcode] ein (https://github.com/hiphopku/QiitaLoggerPydict). main.py ist [main.py oben](https://qiita.com/uANDi/items/9a2b980262bc43455f2e#3-1-2-%E3%83%95%E3%82%A1%E3%82 Gleich wie% A4% E3% 83% AB). Außerdem ist der Inhalt von "CONFIG" der gleiche wie der von "logging.config.toml".

logger/__init__.py


from . import logger

logger.init_logger()

logger/logger.py


import os
from logging import getLogger
from logging.config import dictConfig

APP_NAME = os.getenv('APP_NAME', default='app_name')
CONFIG = {
    'version': 1, 
    'disable_existing_loggers': False,
    'formatters': {
        'basic': {
            'format': '%(asctime)s  [%(name)s][%(levelname)s]  %(message)s  (%(module)s:%(filename)s:%(funcName)s:%(lineno)d)', 
            'datefmt': '%Y-%m-%d %H:%M:%S'
            }
        },
        'handlers': {
            'console': {
                'class': 'logging.StreamHandler', 
                'level': 'DEBUG', 
                'formatter': 'basic', 
                'stream': 'ext://sys.stdout'
            }, 
            'file': {
                'class': 'logging.handlers.TimedRotatingFileHandler', 
                'formatter': 'basic', 
                'filename': os.path.join(os.path.pardir, 'log', 'application.log'), 
                'when': 'D', 
                'interval': 1, 
                'backupCount': 31
            }, 
            'error': {
                'class': 'logging.handlers.TimedRotatingFileHandler', 
                'level': 'ERROR', 
                'formatter': 'basic', 
                'filename': os.path.join(os.path.pardir, 'log', 'error.log'), 
                'when': 'D', 
                'interval': 1, 
                'backupCount': 31
            }
        }, 
        'loggers': {
            APP_NAME: {
                'level': 'INFO', 
                'handlers': [
                    'console', 
                    'file', 
                    'error'
                ], 
                'propagate': False
            }
        }, 
        'root': {
            'level': 'INFO', 
            'handlers': [
                'console', 
                'file', 
                'error'
            ] 
        }
    }

def init_logger():
    dictConfig(CONFIG)

def get_logger():
    return getLogger(APP_NAME)

3-3. Ausführungsergebnis

Das Ausgabeformat ist für Muster 1 und Muster 2 gleich. [app_name] ist der Loggername.

terminal


$ cd app/
$ python main.py
2019-12-21 15:43:28  [app_name][INFO]  start = main()  (main:main.py:main:8)
2019-12-21 15:43:28  [app_name][ERROR]  oops! something wrong!!  (main:main.py:main:10)
2019-12-21 15:43:28  [app_name][INFO]  end = main()  (main:main.py:main:12)

$ cat ../log/application.log
2019-12-21 15:43:28  [app_name][INFO]  start = main()  (main:main.py:main:8)
2019-12-21 15:43:28  [app_name][ERROR]  oops! something wrong!!  (main:main.py:main:10)
2019-12-21 15:43:28  [app_name][INFO]  end = main()  (main:main.py:main:12)

$ cat ../log/error.log
2019-12-21 15:43:28  [app_name][ERROR]  oops! something wrong!!  (main:main.py:main:10)

3-4. Ein kleiner Kommentar

3-4-1. TimedRotatingFileHandler

In Official Document TimedRotatingFileHandler wird "Was wird in" Intervall "und" Wann "festgelegt? Schaffst du das? "Ist geschrieben!

Das Ausgabeprotokoll wird zur festgelegten Zeit in einer neuen Datei protokolliert. Im obigen Beispiel "D" wird die Datei täglich neu geschrieben. Frühere Protokolldateien haben das Datums-Surfix im Dateinamen, z. B. "application.log.2019-12-21".

**Hinweis! ** ** ** TimedRotatingFileHandler funktioniert nicht für Anwendungen, bei denen app.py nicht den ganzen Tag startet. Es funktioniert gut für Web-Apps wie Django und Flask, funktioniert aber möglicherweise nicht richtig mit cron.

3-4-2. Format

Eine Liste der einzelnen Formate finden Sie im Attribut "Official Documentation LogRecord" (https://docs.python.org/ja/3/library/logging.html#id2). Lassen Sie uns die Artikel arrangieren, die Sie brauchen!

4. Zusammenfassung (Imbiss)

Ich habe zwei Arten der Protokollierung vorgestellt! Lassen Sie uns auf jeden zurückblicken! (Wenn Sie denken, dass es perfekt ist, überspringen Sie es !!)

Imbiss bedeutet übrigens "Punkt, Punkt". take away = Take away → Nehmen Sie dies mit der heutigen Präsentation weg! Es ist so eine Nuance.

4-1. Warum "Import Logging" schlecht ist

Eine Reihe von Protokollierern wurde mit rootLogger als übergeordnetem Element erstellt. Ein Bild wie rootLogger → Klasse, Logger → Instanz.

logging.info () wird direkt mit rootLogger in Konflikt geraten, also erstellen wir Ihren eigenen Logger.

4-2. Beispieldatei

Wie macht man es dann konkret?

--Lesen Sie die Protokollierungseinstellungen aus der Konfigurationsdatei

4-3 Vorsichtsmaßnahmen

5. Schließlich

Ich denke nicht, dass dies der richtige Weg ist, um Protokollierung zu schreiben. Sie können Ihrer Python-Datei auch mit addHandler einen Handler hinzufügen. Wir wären Ihnen dankbar, wenn Sie uns das Einrichten Ihrer empfohlenen Protokollierung beibringen könnten.

Recommended Posts

Machen wir uns keine Gedanken mehr über die Protokollierung !!
Lass uns aufhören sudo!
Denken Sie also an die Protokollierung