Les scripts Python sont le ciment qui permet à de nombreuses applications et à leur infrastructure de fonctionner, mais lorsque l'un de vos scripts génère une exception, vous ne le saurez peut-être pas immédiatement, sauf si vous disposez d'un emplacement central pour agréger les erreurs. C'est là que l'ajout de Sentrycan a résolu ce problème de journalisation des erreurs distribuées.
Dans ce didacticiel, nous verrons comment ajouter rapidement Sentry à un script Python nouveau ou existant pour signaler les erreurs dans un emplacement centralisé pour un débogage ultérieur.
Configuration de l'environnement de développement
Assurez-vous que Python 3 est installé. À l'heure actuelle, Python 3.8.3 est la dernière version de Python.
Au cours de ce tutoriel, nous allons également utiliser :
- une instance Sentry hébergée sur sentry.io, à laquelle nous aurons besoin d'un compte pour accéder
- la bibliothèque d'assistance Sentry Python pour envoyer des données d'exception à notre instance Sentry
Installez les bibliothèques de code ci-dessus dans un nouvel environnement virtuel Python à l'aide des commandes suivantes :
python -m venv sentryscript source sentryscript/bin/activate pip install sentry-sdk>=0.14.4
Notre environnement de développement est maintenant prêt et nous pouvons écrire du code qui lèvera des exceptions pour montrer comment utiliser Sentry.
Notez que tout le code de ce didacticiel se trouve dans le référentiel blog-code-examplesGit sur GitHub sous le répertoire python-script-sentry.
Un exemple de script pour charger des modules Python
Nous commencerons par écrire un petit script utile qui imprime les noms de tous les modules d'un package Python, puis nous y ajouterons Sentry lorsqu'il deviendra évident que la capture d'exceptions serait un ajout utile.
Créez un nouveau fichier nommé module_loader.py
et écrivez-y les lignes de code suivantes pour nous permettre de l'exécuter facilement sur la ligne de commande.
import argparse def import_submodules(package): return {} if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("package") args = parser.parse_args() package_to_load = args.package results = import_submodules(package_to_load) for r in results: print(str(r))
Le code ci-dessus prend un argument lorsque le script est invoqué à partir de la ligne de commande et utilise la valeur comme entrée dans le stubimport_submodules
fonction qui contiendra le code pour parcourir l'arborescence des modules dans le package.
Ensuite, ajoutez les lignes de code en surbrillance suivantes pour utiliser importlib
etpkgutil
pour importer récursivement des modules depuis le paquet s'il en trouve un qui correspond au nom envoyé en tant que package
arguments.
import argparse import importlib import pkgutil def import_submodules(package): """Import all submodules of a module, recursively, including subpackages. :param package: package (name or actual module) :type package: str | module :rtype: dict[str, types.ModuleType] """ if isinstance(package, str): package = importlib.import_module(package) results = {} for loader, name, is_pkg in pkgutil.walk_packages(package.__path__): full_name = package.__name__ + '.' + name try: results[full_name] = importlib.import_module(full_name) if is_pkg: results.update(import_submodules(full_name)) except ModuleNotFoundError as mnfe: print("module not found: {}".format(full_name)) except Exception as general_exception: print(general_exception) return results if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("package") args = parser.parse_args() package_to_load = args.package results = import_submodules(package_to_load) for r in results: print(str(r))
Le nouveau code ci-dessus parcourt tous les packages avec le walk_package
fonction dans le pkgutil
module de bibliothèque standard et essaie de l'importer en utilisant le import_module
sur le nom du package plus le package sous forme de chaîne. Si le résultat est réussi, la fonction s'appellera récursivement pour importer des sous-modules dans le package importé. Si un module n'est pas trouvé, ou si un autre problème survient, des exceptions sont détectées afin que le script n'échoue pas mais puisse continuer à traiter les modules potentiels.
Testez le script complet pour voir ce qu'il affiche avec un package arbitraire sur la ligne de commande :
python module_loader.py importlib
L'exemple ci-dessus génère la sortie :
importlib._bootstrap importlib._bootstrap_external importlib.abc importlib.machinery importlib.resources importlib.util
Essayer d'inspecter un paquet qui n'est pas installé donnera une erreur. Utilisez le script avec un package qui n'est pas installé dans votre environnement actuel.
python module_loader.py flask
La commande ci-dessus produit la trace suivante en raison d'un ModuleNotFoundError
attendu .
Traceback (most recent call last): File "module_loader.py", line 35, in <module> results = import_submodules(package_to_load) File "module_loader.py", line 14, in import_submodules package = importlib.import_module(package) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/importlib/__init__.py", line 127, in import_module return _bootstrap._gcd_import(name[level:], package, level) File "<frozen importlib._bootstrap>", line 1006, in _gcd_import File "<frozen importlib._bootstrap>", line 983, in _find_and_load File "<frozen importlib._bootstrap>", line 965, in _find_and_load_unlocked ModuleNotFoundError: No module named 'flask'
Si vous installez Flask dans votre environnement actuel, le module est trouvé et l'application parcourt la liste des modules et sous-modules.
Notre exemple de script est utilisable mais que se passe-t-il si nous exécutons ce code ou quelque chose de similaire sur un ou plusieurs serveurs que nous ne vérifions pas souvent ? C'est là qu'il serait utile d'avoir un moyen d'agréger une ou plusieurs sorties d'exception de scripts en un seul endroit. Sentry peut nous aider à atteindre cet objectif.
Ajout de rapports d'exception avec Sentry
Sentry peut soit être auto-hébergé, soit être utilisé comme service cloud via Sentry.io. Dans ce didacticiel, nous utiliserons la version hébergée dans le cloud car elle est plus rapide que la configuration de votre propre serveur et gratuite pour les petits projets.
Allez sur la page d'accueil de Sentry.io.
Connectez-vous à votre compte ou créez un nouveau compte gratuit. Vous serez sur le tableau de bord du compte principal après vous être connecté ou avoir terminé le processus d'inscription Sentry.
Il n'y a pas encore d'erreurs enregistrées sur le tableau de bord de notre compte, ce qui est normal car nous n'avons pas encore connecté notre compte au Pythonscript.
Vous souhaiterez créer un nouveau projet Sentry uniquement pour cette application. Cliquez donc sur "Projets" dans la barre latérale gauche pour accéder à la page Projets.
Sur la page Projets, cliquez sur le bouton "Créer un projet" dans le coin supérieur droit de la page.
Sélectionnez Python, donnez un nom à votre nouveau projet, puis appuyez sur le bouton "Créer un projet". Notre nouveau projet est prêt à être intégré à notre script Python.
Nous avons besoin de l'identifiant unique de notre compte et de notre projet pour autoriser notre code Python à envoyer des erreurs à cette instance Sentry. Le moyen le plus simple d'obtenir ce dont nous avons besoin est d'accéder à la page de documentation de démarrage de Python et de faire défiler jusqu'à la section "Configurer le SDK".
Copiez le paramètre de chaîne pour le init
et définissez-la comme une variable d'environnement plutôt que de l'exposer directement dans le code de votre application.
export SENTRY_DSN='https://yourkeygoeshere.ingest.sentry.io/project-number'
Assurez-vous de remplacer "votreclévaici" par votre propre identifiant unique et "numéro de projet" par l'ID qui correspond au projet que vous venez de créer.
Vérifiez que le SENTRY_DSN
est défini correctement dans votre shell en utilisant le echo
commande :
echo $SENTRY_DSN
Modifiez l'application pour envoyer des informations d'exception à Sentry maintenant que nous avons notre identifiant unique. Ouvrir module_loader.py
à nouveau et mettez à jour les lignes de code en surbrillance suivantes.
import argparse import importlib import os import pkgutil import sentry_sdk from sentry_sdk import capture_exception # find on https://docs.sentry.io/error-reporting/quickstart/?platform=python sentry_sdk.init(dsn=os.getenv('SENTRY_DSN')) def import_submodules(package): """Import all submodules of a module, recursively, including subpackages. :param package: package (name or actual module) :type package: str | module :rtype: dict[str, types.ModuleType] """ if isinstance(package, str): package = importlib.import_module(package) results = {} for loader, name, is_pkg in pkgutil.walk_packages(package.__path__): full_name = package.__name__ + '.' + name try: results[full_name] = importlib.import_module(full_name) if is_pkg: results.update(import_submodules(full_name)) except ModuleNotFoundError as mnfe: print("module not found: {}".format(full_name)) capture_exception(mnfe) except Exception as general_exception: print(general_exception) capture_exception(general_exception) return results if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("package") args = parser.parse_args() package_to_load = args.package results = import_submodules(package_to_load) for r in results: print(str(r))
Ces nouvelles lignes de code importent theSentry Python SDK et os
bibliothèque (pour lire les variables d'environnement système). L'application initialise alors le Sentry SDK avec la chaîne trouvée dans le SENTRY_DSN
variables d'environnement. En bas dans le import_submodules
la fonction appelle alors le capture_exception
Fonction SDK chaque fois qu'un ModuleNotFoundException
est levée ou une autre exception qui serait attrapée dans le plus large Exception
seau.
Maintenant que notre code est en place, testons la nouvelle intégration de Sentry.
Tester le script et afficher les exceptions
Le moyen le plus simple de tester si le code Sentry fonctionne ou non est d'essayer d'importer un module qui n'existe pas. Disons que vous faites une faute de frappe dans votre commande et essayez d'exécuter le script sur importliba
au lieu de importlib
(peut-être parce que vous utilisez un affreux clavier "papillon" Macbook Pro au lieu d'un clavier durable). Essayez-le et voyez ce qui se passe :
python module_loader.py importliba
Le script s'exécutera et se terminera mais il y aura des erreurs car ce module n'existe pas. Grâce à notre nouveau code, nous pouvons visualiser les erreurs dans Sentry.
Vérifiez le tableau de bord Sentry pour voir l'erreur.
Nous pouvons également cliquer sur l'erreur pour en savoir plus sur ce qui s'est passé.
Vous pouvez également recevoir des rapports par e-mail sur les erreurs qui se produisent afin que vous n'ayez pas à rester toujours connecté au tableau de bord.
Avec tout cela configuré, nous avons maintenant une excellente base pour étendre le script et créer une meilleure gestion des erreurs avec Sentry à mesure que notre application Python devient plus complexe.
Quelle est la prochaine ?
Nous venons de créer un exemple de script qui génère tous les modules et sous-modules d'un package, puis nous y avons ajouté Sentry afin qu'il signale toute exception à notre instance hébergée centrale.
Il ne s'agit que d'une simple introduction à Sentry. Vous voudrez donc lire l'un des articles suivants pour en savoir plus :
- Documentation Python Sentry
- Comment utiliser Sentry avec Flask
- Intégration de Sentry dans les files d'attente de tâches Celery
Vous pouvez également avoir une idée de ce qu'il faut coder ensuite dans votre projet Python en lisant la page de table des matières Full Stack Python.
Des questions? Contactez-moi via Twitter@fullstackpythonor @mattmakai. Je suis également sur GitHub avec le nom d'utilisateur mattmakai.
Quelque chose ne va pas avec ce message ? La source de cette page sur GitHuband soumet une pull request.