Comment créer des logs d'application SaaS

Pragmatic Nerdz
Nov 28, 2023 - 5 Minutes

Cet article présente une approche systématique pour générer les logs applicatifs d'applications SaaS. Cet publication fait suite à l'article qui présente les principes d'architecture Twelve Factor App, et se concentre sur le le 11ème facteur:

Les logs applicatifs doivent être considérés comme un journal d'évènements.

Pourquoi la journalisation est importante?

La journalisation est un mécanisme permettant de suivre le comportement d'exécution d'une application, principalement à des fins de débogage/traçage. Dans presque toutes les applications considérablement complexes/vitales, la tenue d’un journal d’application approprié est indispensable ; par conséquent, tout développeur doit être familier avec le paradigme de la journalisation.

Comment ça marche?

Lorsque un client passe une commande en utilisant cette l'application mobile illustrée ci-dessus:

  1. Le client va cliquer sur le bouton dans son app mobile pour soumettre sa commande
  2. L'app va envoyer une requête au service User pour obtenir les informations du client.
  3. Le service User va retourner les informations du client, et enregistrer dans son journal user.log un événement associé à cette requête.
  4. L'app en soumettre la commande au service Order
  5. Le service Order va traiter la commande et enregistrer dans son journal order.log un événement associé à ce traitement.
  6. L'app va enregistrer dans son journal app.log un événement associé à cette interaction.

Chacune des interactions des usagers laisse des traces dans le journal de chaque nœud du système.

Quelles informations à inclure dans le journal?

Voici la liste des informations que tout événement d'un journal d'application devrait inclure:

  • datetime: La date et l'heure pour comprendre quand l'événement est survenu. Il est aussi important de standardiser l'affichage en utilisant le format ISO 8601.
  • level: La priorité de l'événement. Les frameworks de journalisation standard tels que Log4J définissent les priorités TRACE, DEBUG, INFO, WARNING, ERROR.
  • message:  Une message convivial pour les humains qui décrit l'événement.
  • correlation_id: Il s'agit d'un identificateur qui permet de corréler les événements associés à une interactions donnée. Cet identificateur est souvent appelé aussi span_id ou trace_id
  • device_id: il s'agit de l'identificateur de l'appareil qui a initié l'événement. Cette information permet de d'identifier les événements d'un appareil donné.
  • client_id: Il s'agit de l'identificateur du service ou de l'application qui a envoyé la requête associée à l'événement. 
  • tenant_id: Pour les applications mutualisées (multi-tenant), le tenant_id identifie l'organisation associée à l'événement.
  • build_id: La version du logiciel qui a produit l'événement.
  • hostname: Le nom de la machine ou l'événement a été généré.
  • error_code: En cas d'erreur, le code d'erreur
  • exception: En cas d'erreur causée par une exception, le nom de la classe de l'exception. Ex: java.lang.NulPointerException.
  • exeption_message: Le message de l'exception qui a causé l'erreur.
  • exception_stacktrace: La trace complète de l'exception qui a causé l'erreur.
  • http_endpoint: le nom de l'opération du web-service qui produit l'événement. Ex: /v1/users
  • http_method: La méthode HTTP du service web qui produit l'événement (GETPUT, POST, DELETE, OPTIONS)
  • http_status: Le statut d'appel HTTP du service web. Vous pouvez vous référer à cet article pour en savoir plus sur les statut HTTP.
  • latency_millis:  Le temps de traitement de l'opération en millisecondes.
  • Les paramètres d'entrée, ce qui permet de comprendre les intrants associées à l'événement.
  • Les paramètres de sorties, ce qui permet de comprendre le extrants associé à l'événement

Quel format pour le journal?

Le journal d'événements doit être construit pour être:

  • Analysés, traités et stockés par des machines
  • Lus, compris et utilisé par les humains pour leur permettre de diagnostiquer les problèmes éventuels.

Les formats de choix pour un journal d'événements:

1. Le format clé/valeur

date_time="06/Nov/2014:19:10:38 +0000" 
level=INFO 
build_id=v1.14.5 
device_id=8ec8ee57-139b-454e-b7be-9d3dd089d17f 
correlation_id=f346310c-accf-4abc-9b69-c81d855c7139 
client_id=user_service 
tenant_id=555 
http_method=GET 
http_endoint="/news/53f8d72920ba2744fe873ebc.html"
http_status=404 
message="User created"

2. Le format JSON

{
  "date_time": "2023-11-24T22:24:17Z",
  "level":  "INFO",
  "build_id": "v1.14.5",
  "device_id": "8ec8ee57-139b-454e-b7be-9d3dd089d17f",
  "correlation_id": "f346310c-accf-4abc-9b69-c81d855c7139",
  "client_id=user_service",
  "tenant_id": 555 ,
  "http_method": "GET",
  "http_endoint": "/news/53f8d72920ba2744fe873ebc.html",
  "http_status": 404,
  "message": "User created"
}

Exemple

Receive my Stories your e-mail inbox as soon as I publish them.
Subscribe to my Blog

Revenons a l'exemple d'envoie de commande pour comprendre comment la journalisation est faite à travers les différents nœuds du système.

1. L'usager soumet sa commande

2. L'app mobile va:

  1. Générer un correlation_id unique pour identifier cette interaction. Il est recommandé d'utiliser les UUIDs offert par tous les langages de programmation
  2. Récupérer l'identifiant de l'appareil (device_id). 
  3. Récupérer l'identifiant de l'app (client_id). L'identifiant de l'app est une constante  définie par le développeur de l'application (Ex: amz_app).
  4. Envoyer la requête GET /users/555 au service User, en incluant dans l'entête de la requête les correlation_id, device_id et client_id.

3. Le service User va:

  1. Rechercher les informations de l'usager 555.
  2. Enregistrer dans user.log l'événements associé à la requête, en incluant: les correlation_iddevice_id et client_id qu'il a extrait de l'entête de la requête; l'intrant user_id
  3. Retourner à l'app les informations de l'usager.

4. L'app mobile va envoyer la requête POST /orders au service Order, en incluant dans l'entête de la requête les correlation_iddevice_id et client_id.

5. Le service Order va 

  1. Créer la commande
  2. Enregistrer dans order.log l'événement associé à la requête, en incluant: les correlation_iddevice_id et client_id qu'il a extrait de l'entête de la requête; les intrants user_idproduct_id et quantity; l'extrant order_id.
  3. Retourner à l'app les informations de la commande créée

6. L'app va enregistrer dans app.log l'événement associé a cette interaction.

Autres considérations

Comment récupérer l'identifiant de l'appareil?

  • Dans le cas d'app mobile, les plateformes Android et iOS offrent des API pour accéder aux informations de l'appareil, dont son identifiant.
  • Dans le cas d'app web, il faut installer un cookie  dans le navigateur contenant un UUID, et l'utiliser le comme device_id.

Et si le service appelle un autre en aval?

Dans le cas ou un service Order doit appeler Delivery pour initier la livraison de la commande,  il faut que Order:

  • Transfère les device_id et correlation_id dans l'entête de la requête vers Delivery.
  • Envoie comme client_id l'identificateur du service Order.

L'agrégation des journaux

A cause du fait que chaque nœud du système a son propre journal, il devient très vite fastidieux de naviguer à travers le différents journaux. Il faut donc considérer intégrer dans votre architecture un système de gestion des journaux d'événements qui va fusionner tous les journaux dans une seule base de données, et offrir un interface pour accéder aux événements. Des plateformes telles que New Relic, Datadog, Splunk etc. offrent de telles fonctionnalités.


Si vous avez aimé l'article, montrez votre soutien avec un ❤️ et abonnez vous a mon blog! Votre engagement m’inspire!

12 Factor App
Logging
Journalisation