J’ai bien galéré là… alors ce billet est autant pour moi (pour mémoire) que pour être utile à quelqu’un qui aurait les mêmes galères.
La problématique est simple, j’utilise Flask (pour Pydéfis par exemple), et je veux configurer des logs propres. J’aimerais pouvoir choisir l’endroit où loguer (fichier, console, les deux…), le niveau etc..
Tout est prévu dans Flask pour ça… mais j’ai quand même mis beaucoup de temps avant que ça marche correctement, et je suis tombé sur beaucoup de pages Web de personnes qui galéraient alors…
Par défaut l’application créée avec flask
(on l’appelle app
) crée un logger. Il est dans
app.logger
. On peut donc loguer des messages ainsi :
app.logger.debug("Message de débug")
.
C’est parfait, et ça marche sans config.
Les problèmes arrivent si on veut tuner le truc. Voici deux exemples de config. J’ai retenu la première mais la seconde est celle que j’ai pu faire marcher en premier.
Configuration avec logging
Le module logging
permet de configurer les loggers (ici root
) par le biais d’un dictionnaire :
logger_config = {
'version': 1,
'formatters': {
'default': {
'format': '%(asctime)s :: %(levelname)s :: %(threadName)s :: %(module)s :: %(message)s',
}
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'formatter': 'default',
},
'file': {
'class': 'logging.FileHandler',
'filename': "fichier_de_log.log",
'formatter': 'default',
},
},
'root': {
'level': "INFO",
'handlers': ['console', 'file']
}
}
logging.config.dictConfig(logger_config)
J’ai deux handlers, un pour enregistrer dans un fichier, et un pour envoyer sur la console.
le logger configuré est root
, il logue à partir du niveau INFO
et utilise les deux handlers.
Chacun de ces handlers utiliser le même formatter, nommé default
.
On règle le tout avec logging.config.dictConfig(logger_config)
et c’est parti. Il faut que cette
configuration soit faite avant la première utilisation de app.logger
. Sinon app.logger
aura lui même tenté de
créer des handlers, qui ne seront pas forcément ceux que vous voudrez (et par exemple, le format de logs
que vous allez obtenir ne sera pas le bon).
C’est indiqué dans la doc, mais un autre problème (lié à l’encodage) ne m’a pas permis de faire fonctionner ça initialement. Maintenant c’est OK.
Configuration tardive
La configuration peut aussi être faite éventuellement après le premier appel à app.logger
, mais
il faut s’y prendre complètement différemment :
# Réglage du level
app.logger.setLevel("INFO")
# Éventuellement, on vire tous les handlers déjà créés
# peut être automatiquement par Flask
for h in app.logger.handlers:
app.logger.removeHandler(h)
# Et finalement, on ajoute son propre handler (ici j'en ajoute un seul)
handler = FileHandler("fichier_de_log.log")
# On peut régler le niveau du handler indépendamment (on pourrait avoir
# 2 handlers qui logues avec deux niveaux différentes)
handler.setLevel("INFO")
formatter = logging.Formatter('%(asctime)s :: %(levelname)s :: %(threadName)s :: %(message)s')
handler.setFormatter(formatter)
app.logger.addHandler(handler)
# Si on vérifie, il doit y avoir un seul élément dans la liste
print(app.logger.handlers)
# Au passage, on peut utiliser le même handler pour les messages
# qui viennent de werkzeug
logging.getLogger("werkzeug").addHandler(handler)
Locales, encodage etc
Il se trouve que j’ai eu un autre problème, les locales étaient mal
réglées sur le système (la variable LANG
était correcte, mais
les locales n’avaient pas été créées).
Si ça arrive, Python utiliser comme encodage de l’ascii, et si vous loguez des messages avec des accents, ça ne marche plus…
On peut diagnostiquer le problème ainsi :
>>> import locale
>>> locale.getlocale()
(None, None) # <=== ça va pas !!!
Normalement, on devrait avoir :
>>> import locale
>>> locale.getlocale()
("fr_FR", "UTF-8")
On peut forcer l’utilisation d’un encodage particulier dans certains cas (pour FileHandler
par exemple, mais
c’est bien plus tordu pour StreamHandler
)
# Dans le cas de la méthode 1
...
'file': {
'class': 'logging.FileHandler',
'filename': "fichier_de_log.log",
'formatter': 'default',
'encoding': 'utf8',
}
# Dans le cas de la méthode 2
handler = FileHandler("fichier_de_log.log", encoding="utf8")
Ça règle le problème si on logue dans un fichier, mais on peut pas le faire simplement pour la console. Le mieux est de réparer le système pour que Python voit bien que vous voulez de l’UTF8 et pas de l’ASCII.