Observer votre application Symfony en toute simplicité avec OpenTelemetry (partie 1)
Publié le 13 janvier 2025
Dans cette série d'articles, nous allons voir comment rendre facilement observable une application Symfony, grâce à quelques lignes de code et un peu de configuration. Nous construirons de zéro ou presque notre application de démo, dont vous pouvez d’ores et déjà trouver le code sur ce repo si vous souhaitez suivre et expérimenter tout en lisant !
L’observation, pourquoi ?
Faisons simple, et allons voir ce que Wikipédia dit de l’observabilité. En résumant, il s’agit de la capacité à recueillir des informations sur le fonctionnement d’un système (ici, un programme informatique) selon les données qu’il peut émettre.
L’existence de telles informations et données est cruciale pour faire face à ce qu’on pourrait appeler les “inconnues inconnues”. Prenons un exemple : nous créons une application quelconque. Pour faire bien les choses, chaque centimètre de code est couvert par des tests qui vont définir son comportement. Satisfaits, nous la mettons en production en estimant que tout est prévu. On peut déjà imaginer des scénarios que les tests peuvent difficilement couvrir : que se passe-t-il par exemple si la connexion à notre base de données saute temporairement ?
Ce type de problème est une “inconnue connue”, on peut l’avoir déjà rencontré, ou l’imaginer et prévoir en avance des solutions. Mais qu’en est-il de ce qu’on a jamais rencontré encore ?
Il faut s’imaginer que si l’une de ces “inconnues inconnues” se produit, on ne sera probablement pas à même de la déboguer immédiatement, surtout en production. C’est pourquoi il est indispensable, pour assurer la pérennité de notre application, de faire en sorte qu’elle nous transmette suffisamment d’informations pour répondre à la question “Qu’est ce qui a pu se passer ?”.
Bien évidemment, il est impossible de tout observer sans dépenser un temps considérable, et il faut également tâcher d’émettre ces données sans compromettre la performance et la lisibilité du code existant. C’est pourquoi de nombreux outils existent, tels Blackfire ou Sentry, qui permettent une intégration facilitée de cette volonté d’observabilité.
Nous allons parler aujourd’hui d’un ensemble de ces outils : OpenTelemetry
OpenTelemetry
OpenTelemetry est un projet de la Cloud Native Computing Foundation, issu de la fusion de deux anciens projets : OpenTracing et OpenCensus. C’est un framework à plusieurs composants qui vise à créer, exporter et gérer des données de télémétrie. Il fournit ainsi des spécifications et protocoles sur le formatage de ces données, mais également des SDK dans de nombreux langages pour les générer et les manipuler.
Parmi les avantages par rapport à d’autres produits existants, on peut noter l’aspect Open Source des outils et SDK fournis, ainsi que la volonté de maîtriser à 100% les données générées et éviter le vendor lock-in. Pour en apprendre plus sur ce qui est disponible dans le projet OpenTelemetry, n’hésitez pas à faire un tour sur leur documentation !
Pour notre part, nous allons ici nous attarder sur quelques concepts propres qui nous aideront pour la suite, à savoir les types de données.
OpenTelemetry s’intéresse à plusieurs types de signaux, qui sont différentes façons de mesurer l’activité de notre système. Actuellement, quatre signaux sont supportés, et d’autres sont en cours d’ajout. Nous allons nous pencher sur les trois types les plus répandus, et dont le support est par conséquent plus important : les traces, les logs et les metrics.
Il s’agit du passage, dans un contexte web, d’une requête à travers notre application. Elles sont composées d’une ou plusieurs unités de travail nommées Spans, potentiellement imbriquées.
Une Span correspond alors à un passage spécifique d’une partie du code et va fournir des informations sur celle-ci : timestamps de début et de fin d'exécution de ce morceau de code, status (OK, Error), divers attributs informatifs (par exemple, le nom de la fonction ou de la classe liée à la Span en cours), etc.
Voici un exemple de Span fourni dans leur documentation :
{
"name": "/v1/sys/health",
"context": {
"trace_id": "7bba9f33312b3dbb8b2c2c62bb7abe2d",
"span_id": "086e83747d0e381e"
},
"parent_id": "",
"start_time": "2021-10-22 16:04:01.209458162 +0000 UTC",
"end_time": "2021-10-22 16:04:01.209514132 +0000 UTC",
"status_code": "STATUS_CODE_OK",
"status_message": "",
"attributes": {
"net.transport": "IP.TCP",
"net.peer.ip": "172.17.0.1",
"net.peer.port": "51820",
"net.host.ip": "10.177.2.152",
"net.host.port": "26040",
"http.method": "GET",
"http.target": "/v1/sys/health",
"http.server_name": "mortar-gateway",
"http.route": "/v1/sys/health",
"http.user_agent": "Consul Health Check",
"http.scheme": "http",
"http.host": "10.177.2.152:26040",
"http.flavor": "1.1"
},
"events": [
{
"name": "",
"message": "OK",
"timestamp": "2021-10-22 16:04:01.209512872 +0000 UTC"
}
]
}
On y retrouve divers éléments, comme le endpoint appelé, la méthode HTTP, le fait que cette route ait répondu avec un status code OK …
Ceci dit, vu comme ça, ce n’est pas forcément très parlant. Mais en tant qu’utilisateurs et utilisatrices de Symfony, vous avez probablement déjà utilisé son profiler pendant votre développement. Et celui-ci fournit des traces dans son onglet “Performances” ! Voyez par exemple :
On y retrouve la Trace correspondant à la requête effectuée vers https://localhost, et les diverses Spans qui la composent. On peut ainsi voir que cette requête a passé 0.4ms dans le RouterListener, puis 2.7ms dans le controller correspondant, etc.
Un log correspond à l’enregistrement horodaté d’un événement se produisant dans notre application. On pourrait par exemple souhaiter enregistrer “À telle heure, un mail de confirmation de commande a bien été envoyé”, ou “À telle heure, l’utilisateur toto@example.com n’a pas réussi à s’authentifier”. Ces Logs peuvent être structurés en JSON, ou suivre des formats définis (CLF, ELF), ou encore n’avoir aucune structure précise, mais cela les rend alors plus difficiles à parser en production pour les trier ou les filtrer selon les informations qu’ils contiennent.
Vous êtes certainement familier·e avec le concept de Logs, qui est depuis longtemps bien intégré dans de nombreux écosystèmes informatiques, et nous verrons un peu plus loin comment intégrer OpenTelemetry avec notre bibliothèque de logging préférée, Monolog.
Le troisième type de signaux que nous allons explorer est les Metrics. Ce sont tout simplement des mesures capturées durant l’exécution. Elles sont en général associées à un timestamp et à d’autres métadonnées, et sont souvent de bons indicateurs de la disponibilité et des performances. On pourra par exemple émettre une Metric qui indiquera combien de mémoire est consommée par un morceau de code, et si en l’observant on s’aperçoit qu’il est trop gourmand, le retravailler pour l’optimiser.
Maintenant que nous avons fait un tour d’OpenTelemetry et d’une bonne partie de ses concepts, et si nous créions notre application à observer ? Rendez-vous dans notre deuxième volet !