Juil

Qui ne s’est jamais trouvé face à un problème en production, et a dû debugger et résoudre l’erreur en catastrophe ? Qui ne s’est jamais arraché les cheveux lorsque des erreurs silencieuses empêchent le diagnostic et nous font tourner en bourrique ?

C’est d’autant plus vrai dans un contexte microservices. Dans un monolithe « traditionnel », tous les logs sont au même endroit, et il n’y a pas d’ambiguïté quant à où se déroule l’action. Mais avec des microservices, les différentes parties de l’application sont par définition distribuées, potentiellement à plusieurs endroits. Il n’y a rien de pire que d’avoir un crash et de se demander : mais ça concerne quel service ? Quelle fonctionnalité ? Quelle machine ?

Nous allons voir ensemble quelques pistes pour des logs efficaces et des diagnostics facilités.

Améliorer ses logs

Avoir des logs efficaces, c’est crucial pour éviter le mode pompier en prod. Quel que soit le type d’application, ils méritent qu’on s’y attarde, qu’on les réfléchisse et qu’on en prenne soin. Ils doivent être enrichis régulièrement lors des développements, des code-reviews, et leur importance ne doit vraiment pas être sous-estimée : c’est un très bon investissement.

Evidemment, on ne peut pas tout prévoir, et les catastrophes arrivent parfois. C’est une très bonne occasion d’en tirer des leçons lors d’une analyse post-mortem : 

  • Quels logs nous auraient aidés à identifier le problème plus rapidement ? 
  • Quelles informations supplémentaires dans nos logs nous auraient évité des heures de recherche ? 

Cette démarche vous permettra finalement d’économiser du temps, des ressources, et la santé mentale de l’équipe.

Des logs structurés

Un « bon » log doit contenir suffisamment d’information de contexte pour que l’on puisse reconstituer l’action qui l’a généré.

Par opposition au simple message texte, un log dit « structuré » embarque des informations supplémentaires sur le contexte du message. Cela permet d’avoir des logs riches et de simplifier énormément le diagnostic d’une erreur, en réduisant le temps de recherche.

Le message : il doit être descriptif. S’il s’agit d’une exception, on peut mettre le message associé, sans oublier de lier l’objet Exception au log pour accéder aux détails et aux exceptions sous-jacentes.

Le timestamp : l’instant où le log est créé permet d’ordonner les événements les uns par rapport aux autres. A ne pas confondre avec le moment où est émis le log, qui peut être postérieur dans le cas d’un buffer, et qui a moins d’intérêt. (Voir remarque dans l’encadré)

Le niveau de log : il indique la criticité de l’erreur. En général, on retrouve communément les niveaux Verbose, Debug, Information, Error, Fatal ou leurs équivalents. Cela rend possible un filtrage des logs lors du diagnostic, voire de ne sauvegarder que les plus critiques en production (Error et Fatal).

La stacktrace : elle donne la pile d’appel et permet de localiser précisément l’erreur.

Ces informations sont utiles dans tous les types d’applications. Dans un contexte micro-services, d’autres données sont indispensables pour accélérer le diagnostic. 

 

Nom et version du service : on pense parfois au nom du service, mais il faut aussi penser à ajouter sa version, cruciale dans le cas où l’on a plusieurs versions du service déployées en même temps, ou tout simplement pour un diagnostic à postériori. 

Identification de l’instance et de la machine / du container : plusieurs instances d’un même service peuvent être déployées à un instant T pour des raisons de scalabilité. Quelques informations sur l’identification de la machine et de l’instance du service se révèlent très utiles.

Temps d’exécution : Dans certains cas, mesurer les temps d’exécution de certaines parties du code, méthodes d’applications tierces ou d’appels asynchrones peut se révéler très utile également. 

Voilà pour les informations minimales. D’autres données peuvent être incluses en fonction du type d’application : entêtes HTTP, nom d’utilisateur / IP, nom de la fonction ou du module, appels vers des dépendances, …

NB : Certaines de ces informations seront toutefois à prendre avec des pincettes. Dans le cas des applications distribuées sur plusieurs machines, les horloges ne sont pas toujours exactement synchronisées et des écarts de quelques millisecondes voire de quelques secondes peuvent apparaître. Cela peut produire des traces de log un peu inattendues, comme un log d’utilisation d’une ressource avant sa création.

Exemple de log structuré

Corréler les événements

Tous ces éléments permettent d’identifier avec précision l’événement, le service et la machine concernés. Mais dans une application microservices, un événement n’est jamais seul. Il y a toute une chaîne d’exécution derrière une action utilisateur, passant de service en service, d’événement en événement, pour produire un résultat final. Alors comment lier les événements les uns aux autres, et à l’action initiale ?

La solution est de définir un ID pour chaque requête ou action faite sur l’application, et le transmettre à chaque service prenant en charge un élément de la requête. Cet ID de corrélation sera ajouté aux metadatas des logs de l’application, et permettra de tracer l’historique d’une requête, au fil des services, un peu comme une callstack. 

On pourra alors générer une sorte de diagramme temporel de tous les événements liés à une action, et les agréger, permettant entre autres de diagnostiquer les possibles sources de latence entre les services, et de reproduire l’erreur plus facilement.

Tous les logs issus de ces services feront référence au correlationID 42

Centraliser les logs

Toutes ces metadatas vont nous permettre d’ordonner et filtrer efficacement nos logs. Encore faut-il tout avoir au même endroit ! Si les logs d’un service sont dans un fichier texte sur un serveur et ceux d’un autre service sur ApplicationInsights sur Azure, on va avoir du mal à avoir une vision claire de ce qui se passe.

Il y a plusieurs solutions : soit on agit au niveau de chaque service pour envoyer tout au même endroit dès le départ ; soit on utilise un agrégateur qui va chercher ici et là les logs dont il a besoin, comme le fait Log Analytics via divers collecteurs qui vont récupérer les logs machine ou application.

Une fois que tout est centralisé, on peut visualiser les logs, au départ bien abstraits, sur un diagramme, générer des graphes, des dashboards, faire des recherches avancées, et même créer des alertes pour identifier les cas suspects, par exemple définir un temps de réponse maximum.

Quelques outils pour une fonctionnalité transversale

Paradoxalement, la télémétrie est aussi une fonctionnalité secondaire, au sens où elle ne fait pas partie du business de l’application et qu’elle doit rester en dehors de son chemin critique, pour ne pas le gêner.

L’enjeu est donc d’avoir des logs riches, efficaces, tout en ne pénalisant pas les performances. Il y a plusieurs pistes pour ça, exploitées par la plupart des librairies de logs : 

  • Bufferisation des logs : l’envoi groupé permet d’économiser les appels et donc de privilégier l’exécution des fonctionnalités premières du service.
  • Envoi asynchrone : les logs sont envoyés à leur destination de manière asynchrone, afin de ne pas affecter le chemin critique de l’application.

Le choix de la librairie de logs doit tenir compte de ces possibles optimisations. Nous allons voir quelques solutions, en particulier pour une application .NET.

Loguer (C#)

NLog est une librairie .NET opensource (sous licence BSD) permettant d’envoyer des logs vers une ou plusieurs destinations (targets) fournies par la communauté, ou à développer soi-même. Certaines gèrent la bufferisation, l’envoi asynchrone, le retry et des scenarios « de secours » en cas d’indisponibilité de la destination.
La configuration est possible avec un fichier de configuration, ou simplement au démarrage de l’application, dans le code. Elle est d’ailleurs altérable à la volée, sans redémarrer l’application.
La librairie gère les logs structurés depuis la version 4.5 (2018) en ajoutant des informations via des templates de message.

https://nlog-project.org/

Serilog offre des fonctionnalités similaires à celles de Nlog, sous licence Apache 2.0. Le projet est plus jeune (2013) et gère nativement les logs structurés et sérialisés vers une liste de sinks aussi fournie que celle des targets de NLog.
L’enrichissement des logs est plus poussé et permet d’ajouter des informations de contexte, que ce soit au cas par cas via des templates de message, sur certaines zones ciblées de code (module, classe ou simple bloc using) voire à l’échelle de tout un service. Des packages d’enrichissement peuvent être utilisés, certains ajoutent automatiquement les informations sur l’environnement, le processus, le thread, etc, et un autre pour la prise en charge la corrélation des événements (tracing).
La configuration du logger se fait très simplement au démarrage de l’application via une API Fluent. Il est toutefois possible d’utiliser des packages additionnels lorsque les fichiers de configuration sont préférables. La source des logs elle-même est configurable et permet de faire passer par Serilog les logs d’autres librairies, très pratique en cas de code legacy ou de migration.
A noter que son log interne (SelfLog), qui permet entre autres de diagnostiquer les problèmes de configuration du logger, peut se révéler assez pratique.

https://serilog.net/

Tracer

OpenTelemetry est un ensemble d’outils opensource designés pour produire des logs, des métriques et autres données de télémétrie, disponible dans un large ensemble de langages et s’intégrant avec les librairies et frameworks les plus populaires. Il est capable de produire une trace, composée d’un arbre de span liés entre eux et comprenant les informations concernant les différentes parties de l’application traversées par la requête. On peut les enrichir avec des attributs, couples clé-valeur, et des événements avec un timestamp, sur lesquels on pourra grouper, filtrer ou trier lors de l’analyse.

https://opentelemetry.io/

Application Insights est une fonctionnalité d’Azure Monitor intégrant des outils d’analyse des applications. Il peut être utilisé pour les logs applicatifs (ou comme destination des librairies vues plus haut) mais propose également des fonctionnalités automatiques de tracing, et surveillance des performances (compteurs de performance, dépendances externes, services d’arrière-plan, diagnostics d’hébergement …). Il est disponible pour un large éventail de plateformes, hébergées on premise, sur le cloud ou de manière hybride.
Pour créer une trace, AI s’appuie sur un operation_Id représentant la requête ou l’action mère, elle-même composée de requêtes plus petites identifiées par un request.id propre et référençant l’operation_Id. Chaque appel externe (http, base de données, storage, etc) est traité un peu comme une sous requête avec un dependency.id. Un arbre d’appels se forme entre action mère, requêtes, dépendances et permet de visualiser les logs grâce à un langage de requête avancé.

https://docs.microsoft.com/fr-fr/azure/azure-monitor/app/app-insights-overview

Centraliser

Azure Monitor est un outil Azure permettant de collecter des données de sources diverses. Elles sont ensuite stockées dans un espace de travail Log Analytics, qui permet de les requêter et les analyser. Ces requêtes seront utiles pour lever des alertes quand un seuil est dépassé (envoi d’email ou sms, réaction sur d’autres produits Azure pour scaler par exemple), ou créer des graphiques pour identifier des tendances de manière proactive sur un dashboard personnalisable.
Les données récupérées sont aussi exportables vers d’autres outils (par exemple PowerBI ou Azure EventHub) et récupérables via l’API REST, le Cmdlet Powershell, ou Azure CLI.

https://docs.microsoft.com/fr-fr/azure/azure-monitor/overview

Loggly est une autre solution d’agrégation cloud à l’offre assez similaire à celle d’Azure. Ses dashboards permettent d’identifier les risques de manière proactive et de lever des alertes facilement sur divers outils de communication d’entreprise (Slack, Teams, etc). Sa fonction de recherche permet de requêter très facilement les données, là où Azure Monitor demande l’apprentissage (bien que rapide) du langage de requête. Il s’intègre avec de nombreuses sources de données ainsi qu’avec Jira et Github.

https://www.loggly.com/

Conclusion

Nous avons vu que la qualité des logs d’une application est primordiale. Ils doivent être revus et améliorés régulièrement et à tout moment de la vie de l’application. Les diverses données de contexte ajoutées permettent d’identifier plus précisément et plus rapidement l’origine d’un bug, d’autant plus quand les logs des différents services sont liés entre eux. Enfin, avoir tous les logs systèmes, applicatifs au même endroit est un sérieux atout lors de l’analyse.
Une question reste cependant ouverte : nous n’avons pas abordé le choix de la responsabilité de l’aiguillage des logs. Dans un contexte microservice, les services restent relativement indépendants les uns des autres. Cette responsabilité doit-elle leur revenir, déléguée à la couche de centralisation, ou à un service externe ?

 

Carole CHEVALIER

Related Posts

Leave A Comment