Bilan du parc : log.log, max(key=) et code de sortie
Journaliser chaque serveur au niveau qui correspond à son état avec log.log, trouver le pire état du parc avec max et key, et en déduire le code de sortie du programme : 0, 1 ou 2.
Mis à jour le
À recouper avec le cours. Cette leçon a été écrite à partir du plan du chapitre, avant relecture avec les supports. En cas de doute, le cours fait foi.
Pourquoi on voit ça
Ton outil a vérifié chaque serveur et obtenu un état pour chacun. Il reste à rendre le verdict, pour deux publics :
- un humain, qui lit les messages et veut voir tout de suite ce qui va mal ;
- un programme (cron, pipeline CI, supervision), qui ne lit pas tes messages. Il ne regarde qu’une chose : le code de sortie.
Cette leçon assemble trois petites pièces : journaliser selon la gravité, trouver le pire état, et en faire un code de sortie. C’est la fin du chapitre, et ce qui rend ton outil utilisable dans une chaîne automatisée.
Journaliser selon la gravité : log.log
Le chapitre 3 a présenté logging et ses niveaux. Chaque niveau est en réalité un nombre :
| Niveau | Valeur | Méthode habituelle |
|---|---|---|
logging.DEBUG |
10 | log.debug(...) |
logging.INFO |
20 | log.info(...) |
logging.WARNING |
30 | log.warning(...) |
logging.ERROR |
40 | log.error(...) |
Dans le fil rouge, un serveur a trois états possibles : ok, degraded (il répond, mais mal) ou down (en panne). On veut un message INFO pour ok, WARNING pour degraded, ERROR pour down.
Première idée : un if / elif / else avec trois appels différents. Ça marche, mais c’est long. Mieux : log.log(niveau, message), qui prend le niveau en argument. Le niveau devient une donnée, qu’on range dans un dictionnaire.
import logging
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
log = logging.getLogger("fleetcheck")
NIVEAUX = {
"ok": logging.INFO,
"degraded": logging.WARNING,
"down": logging.ERROR,
}
resultats = [
{"name": "web-1", "status": "ok"},
{"name": "db-1", "status": "down"},
{"name": "cache-1", "status": "degraded"},
]
for r in resultats:
log.log(NIVEAUX[r["status"]], "%s : %s", r["name"], r["status"])
INFO web-1 : ok
ERROR db-1 : down
WARNING cache-1 : degraded
log.log(logging.ERROR, "...") fait exactement la même chose que log.error("..."). Les %s du message sont remplis, dans l’ordre, par les arguments suivants.
Le seuil filtre toujours
Le seuil de basicConfig s’applique comme d’habitude. Sans aucune configuration, il vaut WARNING : la ligne de web-1 disparaît, et seuls les serveurs à problème s’affichent.
db-1 : down
cache-1 : degraded
C’est tout l’intérêt de choisir le bon niveau : en usage normal, on ne voit que ce qui va mal ; avec -v, on voit tout.
Trouver le pire état : max avec key
Le bilan du parc tient en une question : quel est le pire état rencontré ? Tu connais max sur des nombres. Sur des chaînes, il a un piège :
etats = ["ok", "down", "degraded"]
print(max(etats))
ok
max compare les chaînes par ordre alphabétique, et « ok » vient après « down ». Il ne sait pas que down est plus grave. Il faut le lui dire, avec un dictionnaire qui donne une note de gravité à chaque état, et le paramètre key.
GRAVITE = {"ok": 0, "degraded": 1, "down": 2}
etats = ["ok", "down", "degraded"]
pire = max(etats, key=lambda e: GRAVITE[e])
print(pire)
down
key reçoit une fonction. max l’appelle sur chaque élément, compare les notes obtenues, puis renvoie l’élément qui a la meilleure note. Il renvoie "down", pas 2. C’est le même key que celui de sorted, vu au chapitre 1.
Ça marche aussi sur la liste de dictionnaires :
pire = max(resultats, key=lambda r: GRAVITE[r["status"]])
print(pire["name"], pire["status"])
db-1 down
Ici pire est le dictionnaire entier du serveur le plus mal en point.
Le code de sortie : 0, 1 ou 2
Quand un programme se termine, il laisse un nombre au shell. echo $? l’affiche. La convention du cours :
| Code | Sens | Exemples |
|---|---|---|
| 0 | tout va bien | tous les serveurs sont ok |
| 1 | l’outil a tourné et a détecté un problème | un serveur degraded ou down |
| 2 | erreur d’utilisation ou de configuration | inventaire introuvable, argument invalide, variable obligatoire absente |
La différence entre 1 et 2 est le point à ne pas rater :
- 1 : l’outil a fait son travail. C’est le parc qui a un problème.
- 2 : l’outil n’a pas pu faire son travail. C’est la façon de le lancer qu’il faut corriger.
argparserenvoie déjà 2 tout seul quand un argument est mauvais.
Un pipeline peut ainsi réagir différemment : alerter l’équipe d’astreinte pour un 1, prévenir celui qui a écrit la configuration pour un 2.
Du pire état au code
def code_sortie(etats):
pire = max(etats, key=lambda e: GRAVITE[e])
if pire == "ok":
return 0
return 1
print(code_sortie(["ok", "ok"]))
print(code_sortie(["ok", "degraded"]))
print(code_sortie(["down", "ok"]))
0
1
1
Un seul serveur dégradé suffit à faire passer le code à 1.
sys.exit : rendre le code au shell
sys.exit(n) termine le programme immédiatement avec le code n. Rien de ce qui suit n’est exécuté.
import sys
print("début")
sys.exit(1)
print("fin")
début
Puis, dans le shell :
echo $?
1
La forme habituelle : main() renvoie le code avec return, et une seule ligne, tout en bas, le transmet au shell.
import sys
def main():
etats = ["ok", "degraded", "ok"]
pire = max(etats, key=lambda e: GRAVITE[e])
print("pire état :", pire)
if pire == "ok":
return 0
return 1
if __name__ == "__main__":
sys.exit(main())
pire état : degraded
Le code de sortie est 1. Un programme qui se termine sans appeler sys.exit, et sans exception, renvoie 0.
À retenir
log.log(niveau, message, ...)journalise à un niveau passé en argument : on le lit dans un dictionnaire état → niveau.- Les niveaux sont des nombres : DEBUG 10, INFO 20, WARNING 30, ERROR 40. Sans configuration, le seuil est WARNING.
max(etats)sanskeycompare par ordre alphabétique : il renvoie"ok".max(etats, key=lambda e: GRAVITE[e])compare les notes, mais renvoie l’élément.- Code de sortie : 0 tout va bien, 1 problème détecté dans le parc, 2 erreur d’utilisation ou de configuration.
sys.exit(n)arrête le programme tout de suite avec le coden; forme habituelle :sys.exit(main()).
Entraînement
12 questions, dont 4 à compléter. Tu réponds, on passe à la suivante. La note arrive à la fin.
Ta note
Cartes · 7
Réviser les cartes ici
Pourquoi