J'ai une application Flask) qui fonctionne bien et génère une erreur occasionnelle, qui est visible lorsqu'elle est exécutée avec debug=True
:
if __== '__main__':
app.run(debug=True)
Je reçois des messages d'erreur utiles tels que:
Traceback (most recent call last):
File "./main.py", line 871, in index_route
KeyError: 'stateIIIII'
Je souhaite recevoir des messages d'erreur similaires à ceux enregistrés dans un fichier lorsque j'exécute l'application en production (à l'aide de Lighttpd + fastcgi).
Après avoir examiné diverses questions sur StackOverflow ( http://flask.pocoo.org/docs/errorhandling/ , http://docs.python.org/2/library/logging.html , etc.); la liste de diffusion Flask; et quelques blogs, il semble qu’il n’existe aucun moyen simple d’envoyer tous les messages d'erreur géniaux dans un fichier - je dois utiliser le Python module de journalisation pour personnaliser les choses, alors j’ai eu le code suivant.
En haut de mon dossier de candidature, j'ai plusieurs importations suivies de:
app = Flask(__name__)
if app.debug is not True:
import logging
from logging.handlers import RotatingFileHandler
file_handler = RotatingFileHandler('python.log', maxBytes=1024 * 1024 * 100, backupCount=20)
file_handler.setLevel(logging.ERROR)
app.logger.setLevel(logging.ERROR)
app.logger.addHandler(file_handler)
J'ai ensuite mis le code de chaque route dans une instruction try/except et utiliser traceback pour déterminer la ligne d'où provient l'erreur et imprimer un message d'erreur Nice:
def some_route():
try:
# code for route in here (including a return statement)
except:
exc_type, exc_value, exc_traceback = sys.exc_info()
app.logger.error(traceback.print_exception(exc_type, exc_value, exc_traceback, limit=2))
return render_template('error.html')
Et puis, à la fin du fichier, je supprime la déclaration debug=True
. Bien que je ne pense pas avoir besoin de le faire, l'application est exécutée par un serveur fastcgi (?) Lorsqu'elle est exécutée en production. Les deux dernières lignes de mon code d'application ressemblent à ceci:
if __== '__main__':
app.run()
Je me bats pour que cela fonctionne. Je pense que le mieux que j'ai pu faire est de ne sauvegarder qu'un seul message du journal des erreurs dans le fichier en utilisant (app.logger.error('test message')
), mais ce message ne sera imprimé que. Une tentative de consigner une autre erreur directement après celle-ci est simplement ignorée.
Je ne sais pas pourquoi ça ne marche pas mais je peux dire comment je fais.
Tout d'abord, vous n'avez pas besoin de définir le niveau de app.logger. Supprimez donc cette ligne app.logger.setLevel()
.
Vous souhaitez enregistrer une exception et retourner une page d'erreur pour chaque vue. Écrire ce code partout représente beaucoup de travail. Flask fournit une méthode pour ce faire. Définissez une méthode de traitement des erreurs comme celle-ci.
@app.errorhandler(500)
def internal_error(exception):
app.logger.error(exception)
return render_template('500.html'), 500
Chaque fois qu'une vue lève une exception, cette méthode sera appelée et l'exception sera passée comme argument. Python fournit une méthode d’exception permettant d’enregistrer une trace complète de l’exception.
Comme cela gère toutes les exceptions, vous n'avez même pas besoin de mettre du code dans try/except block. Toutefois, si vous souhaitez faire quelque chose avant d'appeler le gestionnaire d'erreurs (par exemple, une session ou une transaction d'annulation), procédez comme suit:
try:
#code
except:
#code
raise
Si vous souhaitez que la date et l'heure soient ajoutées à chaque entrée de votre fichier journal, vous pouvez utiliser le code suivant (à la place du code similaire présenté dans la question).
if app.debug is not True:
import logging
from logging.handlers import RotatingFileHandler
file_handler = RotatingFileHandler('python.log', maxBytes=1024 * 1024 * 100, backupCount=20)
file_handler.setLevel(logging.ERROR)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
file_handler.setFormatter(formatter)
app.logger.addHandler(file_handler)
Pour ceux qui liront cela plus tard.
Je pense qu'il est préférable d’insérer des informations plus utiles dans les messages d’erreur. URL, adresse IP du client, agent d’utilisateur, etc. Flask enregistre les exceptions en interne (dans app.debug==False
mode) avec Flask.log_exception
une fonction. Donc, au lieu de journaliser manuellement les choses dans @app.errorhandler
Je fais quelque chose comme ça:
class MoarFlask(Flask):
def log_exception(self, exc_info):
"""...description omitted..."""
self.logger.error(
"""
Request: {method} {path}
IP: {ip}
User: {user}
Agent: {agent_platform} | {agent_browser} {agent_browser_version}
Raw Agent: {agent}
""".format(
method = request.method,
path = request.path,
ip = request.remote_addr,
agent_platform = request.user_agent.platform,
agent_browser = request.user_agent.browser,
agent_browser_version = request.user_agent.version,
agent = request.user_agent.string,
user=user
), exc_info=exc_info
)
Ensuite, au moment de la configuration, liez FileHandler
à app.logger
et continuez. Je n'utilise pas StreamHandler
, car de nombreux serveurs (par exemple, uWSGI) aiment le polluer avec leurs propres messages propriétaires-verbeux-inutiles-non-désactivables.
N'ayez pas peur d'étendre Flask. Vous serez obligé de le faire tôt ou tard;)
Je ne suis pas un spécialiste du module logging
, mais en ce qui concerne mon expérience + quelques années sur Python + + Flask, vous pouvez disposer d'une bonne configuration de journalisation, en tenant compte de quelques observations. :
au début de chaque fonction (route), créez un objet timestamp , afin d'enregistrer l'heure exacte à laquelle la demande a été effectuée, indépendamment du lieu réussi ou pas
utiliser @ app.after_request , pour enregistrer chaque demande réussie
utiliser @ app.errorhandler , pour enregistrer les erreurs générales et les traçages
Voici un exemple qui illustre cette idée:
#/usr/bin/python3
""" Demonstration of logging feature for a Flask App. """
from logging.handlers import RotatingFileHandler
from flask import Flask, request, jsonify
from time import strftime
__author__ = "@ivanleoncz"
import logging
import traceback
app = Flask(__name__)
@app.route("/")
@app.route("/index")
def get_index():
""" Function for / and /index routes. """
return "Welcome to Flask! "
@app.route("/data")
def get_data():
""" Function for /data route. """
data = {
"Name":"Ivan Leon",
"Occupation":"Software Developer",
"Technologies":"[Python, Flask, JavaScript, Java, SQL]"
}
return jsonify(data)
@app.route("/error")
def get_nothing():
""" Route for intentional error. """
return foobar # intentional non-existent variable
@app.after_request
def after_request(response):
""" Logging after every request. """
# This avoids the duplication of registry in the log,
# since that 500 is already logged via @app.errorhandler.
if response.status_code != 500:
ts = strftime('[%Y-%b-%d %H:%M]')
logger.error('%s %s %s %s %s %s',
ts,
request.remote_addr,
request.method,
request.scheme,
request.full_path,
response.status)
return response
@app.errorhandler(Exception)
def exceptions(e):
""" Logging after every Exception. """
ts = strftime('[%Y-%b-%d %H:%M]')
tb = traceback.format_exc()
logger.error('%s %s %s %s %s 5xx INTERNAL SERVER ERROR\n%s',
ts,
request.remote_addr,
request.method,
request.scheme,
request.full_path,
tb)
return "Internal Server Error", 500
if __== '__main__':
handler = RotatingFileHandler('app.log', maxBytes=10000, backupCount=3)
logger = logging.getLogger(__name__)
logger.setLevel(logging.ERROR)
logger.addHandler(handler)
app.run(Host="127.0.0.1",port=8000)
Pour plus d’informations sur logrotate et les journaux sur stdout et fichier en même temps: this Gist
Si vous utilisez gunicorn pour exécuter votre Flask app, vous pouvez enregistrer toutes les exceptions Flask dans les journaux de Gunicorn en ajoutant les gestionnaires d’erreur gunicorn à la Flask enregistreur:
Dans module/__init__.py
:
@app.before_first_request
def setup_logging():
if not app.debug:
import logging
gunicorn_logger = logging.getLogger('gunicorn.error')
for handler in gunicorn_logger.handlers:
app.logger.addHandler(handler)