[PYTHON] Arrêtons de nous soucier de la journalisation !!

1. Histoire

Veuillez arrêter l'impression et importer la journalisation pour la sortie du journal - Qiita

Je lisais en pensant: "Eh bien, je vois." Mais en même temps, je pensais comme ça.

** Alors, comment écrivez-vous la journalisation? ** ** ** Même gratuitement, je ne suis pas doué pour me connecter en Python et cela prend du temps, mais je ne peux pas faire de logger !! **

J'ai donc décidé d'écrire cet article. Le but est le suivant.

――Je veux partager ma propre compréhension que j'ai trouvée à la suite de la confrontation désespérée d'un bûcheron

2. Après tout, pourquoi ne pas utiliser la journalisation! ??

C'est le point de vue de la lecture du Document officiel et de l'article Qiita ci-dessus.

la journalisation a une structure hiérarchique.

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

Il a (ou est créé) plusieurs enregistreurs enfants avec un enregistreur appelé rootLogger comme parent.

Par exemple ...

L'enregistreur de chacun de ces packages et applications est appelé rootLogger.

bad_sample.py


import logging

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

Cela revient à jouer directement avec rootLogger, alors arrêtons. C'est similaire à l'idée de jouer avec l'instance au lieu de jouer avec la classe.

3. Exemple de journalisation

Même si vous regardez le document officiel ou l'échantillon, vous ne savez pas comment l'écrire ... Combien de mots fixes et combien puis-je changer par moi-même? (Gamme que vous pouvez nommer vous-même)

C'est pourquoi je présenterai la configuration dont le fonctionnement a été confirmé. Si vous rencontrez un problème, veuillez le copier et l'utiliser.

Autant que possible, je vais inclure un commentaire dans les commentaires afin que vous puissiez facilement le personnaliser.

3-1. (Pattern.1) Utiliser le fichier de configuration

3-1-1. Environnement d'exécution

** J'écris le fichier de configuration de journalisation avec toml. ** ** Vous pouvez vous référer au paquet toml avec pip install toml.

La méthode de notation en yaml est ici.

terminal


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

$ cd app/
$ python app.py  # app/J'écris un exemple en supposant que cette commande est exécutée dans le répertoire

3-1-2. Fichier

Mettez également l 'exemple de code Github.

main.py


#L'enregistreur précédent est un répertoire. L'enregistreur à l'arrière est un fichier.
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 est configuré pour afficher les journaux sur l'écran et le fichier de la console.

toml:logging.config.toml


version = 1
disable_existing_loggers = false  #Ne désactivez pas les enregistreurs pour les autres modules

[formatters]
    [formatters.basic]
    #Définissez le format du journal. Les détails de l'écriture seront décrits plus tard
    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]
    #Définissez la sortie de la console ici
    class = "logging.StreamHandler"  #Libellé fixe
    level = "DEBUG"                  #le niveau de journalisation est votre choix
    formatter = "basic"              #Sélectionnez le format de journal décrit dans les formateurs
    stream = "ext://sys.stdout"      #Libellé fixe

    [handlers.file]
    class = "logging.handlers.TimedRotatingFileHandler"  #Libellé fixe. Les détails de TimeRotatingFileHandler seront décrits plus loin. Il y a des notes
    formatter = "basic"
    filename = "../log/application.log"                  #Si spécifié par le chemin relatif$Écrire à partir d'un répertoire qui exécute python
    when = 'D'                                           #log Une unité de synchronisation pour changer de fichier. ré= day。
    interval = 1                                         #Le jour étant sélectionné ci-dessus, un nouveau fichier est créé chaque jour.
    backupCount = 31                                     #Le jour étant sélectionné ci-dessus, 31 jours de fichiers journaux sont conservés

    [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 est le nom du logger appelé depuis python. Nommé arbitrairement
    level = "INFO"
    handlers = [
        "console",
        "file",
        "error",
    ]                   #Définir les gestionnaires définis ci-dessus à utiliser
    propagate = false

[root]
level = "INFO"
handlers = [
    "console",
    "file",
    "error"
]                       #Réglage de l'enregistreur racine parent de l'enregistreur. Je souhaite également conserver le rootLogger dans la console et le fichier

logger/__init__.py


from . import logger

logger.init_logger('logging.config.toml', 'app_name')
#Premier argument: configfile =Le nom du fichier de configuration. Chemin relatif ou chemin absolu du répertoire exécuté par la commande python
#Deuxième argument: loggername =Le nom de l'enregistreur défini dans le fichier de configuration

logger/logger.py


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

CONFIGFILE = "logging.config.toml"    #Traitez comme une variable globale. La valeur par défaut est celle illustrée à gauche. Tout
LOGGERNAME = "root"                   #Traitez comme une variable globale. La valeur par défaut est root. Tout

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) Utilisation de Python dict

C'est plus flexible que la journalisation dans le fichier de configuration. Dans le fichier Config, le répertoire parent est «../», ce qui le rend plus dépendant du système d'exploitation. Si vous le définissez avec python dict, vous pouvez le spécifier avec ʻos.path.pardir`.

3-2-1. Environnement d'exécution

terminal


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

$ cd app/
$ python main.py

3-2-2. Fichier

Mettez également l 'exemple de code Github. main.py est [main.py ci-dessus](https://qiita.com/uANDi/items/9a2b980262bc43455f2e#3-1-2-%E3%83%95%E3%82%A1%E3%82 Identique à% A4% E3% 83% AB). De plus, le contenu de «CONFIG» est le même que celui de «logging.config.toml» ci-dessus.

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. Résultat de l'exécution

Le format de sortie est le même pour le motif 1 et le motif 2. [app_name] est le nom de l'enregistreur.

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. Un petit commentaire

3-4-1. TimedRotatingFileHandler

Document officiel TimedRotatingFileHandler "Ce qui est défini sur ʻinterval et when`" Pouvez-vous le faire? »Est écrit!

Sort le journal dans un nouveau fichier à l'heure définie. Dans l'exemple ci-dessus, `` D', c'est-à-dire que le fichier est réécrit quotidiennement. Les anciens fichiers journaux ont une date surfix dans le nom du fichier, comme ʻapplication.log.2019-12-21.

**Mise en garde! ** ** TimedRotatingFileHandler ne fonctionnera pas pour les applications où app.py ne démarre pas toute la journée. Cela fonctionne bien pour les applications Web comme Django et Flask, mais peut ne pas fonctionner correctement avec cron.

3-4-2. Format

Une liste de chaque format peut être trouvée dans l'attribut LogRecord de la documentation officielle (https://docs.python.org/en/3/library/logging.html#id2). Organisons les articles dont vous avez besoin!

4. Résumé (points à retenir)

J'ai présenté deux types de procédure de journalisation! Regardons chacun en arrière! (Si vous pensez que c'est parfait, sautez-le !!)

À propos, Takeaways signifie «point, point». à emporter = À emporter → À emporter avec la présentation d'aujourd'hui! C'est une telle nuance.

4-1. Pourquoi la journalisation des importations n'est pas bonne

Un certain nombre d'enregistreurs ont été créés avec rootLogger comme parent. Une image comme rootLogger → Classe, enregistreur → instance.

logging.info () va directement jouer avec rootLogger, alors créons votre propre logger.

4-2. Exemple de fichier

Alors comment le faites-vous concrètement?

4-3. Précautions

--Si vous ne définissez pas disable_existing_loggers = False, l'enregistreur de gunicorn sera cassé. --Lors de la création d'un fichier de configuration

5. Enfin

Je ne pense pas que ce soit la bonne façon d'écrire la journalisation. Vous pouvez également ajouter un gestionnaire à votre fichier python avec ʻaddHandler`. Nous vous serions reconnaissants de bien vouloir nous apprendre comment configurer la journalisation recommandée.

Recommended Posts

Arrêtons de nous soucier de la journalisation !!
Arrêtons sudo!
En pensant à la journalisation, alors