Le traçage distribué devient nécessaire lorsque vous souhaitez corréler plusieurs requêtes provenant d'une seule interaction utilisateur avec votre SaaS. Il devient utile dès que vous synchronisez des systèmes de fournisseurs tiers en utilisant des webhooks ou interagissez avec des API imposant une rate limiting. Cela introduit des tâches distribuées asynchrones dans l'application web.
Pouvez-vous facilement identifier quelle requête a déclenché le webhook ou causé la planification d'une tâche asynchrone ? Et si la cause d'une erreur se trouvait en fait dans une requête passée ? L'instrumentation pour le traçage distribué et la journalisation offre des insights précieux sur le complete flow concernant :
Les services impliqués
La performance globale
L'identification des retards indésirables
La détermination de la cause racine des erreurs inattendues
La validation du comportement attendu
L'impact de la performance sur le comportement
Vous pouvez facilement instrumenter votre application avec Cloud Trace, en particulier lorsque l'application est hébergée sur Google Cloud Platform. Voyons comment le faire en utilisant Cloud Run et Cloud Tasks dans le contexte de l'interaction avec une API tierce imposant une rate limiting. Node.js est utilisé pour cet exemple, mais les concepts sont indépendants du langage.
Les meilleures pratiques de sécurité ne sont pas abordées dans cet article et nous recommandons d'appliquer le principe du moindre privilège lors de la gestion de l'accès à vos services cloud.
La CI/CD et l'IaC liées à la gestion et à l'approvisionnement de ces services cloud sont également omises.
WLors du traitement d'une requête utilisateur, le service de tâches interagit avec une API tierce à taux limité (par exemple, maximum 2 requêtes par seconde). Les interactions sont mises en file d'attente dans Cloud Tasks qui les distribue à un taux acceptable.
Le flux complet est le suivant :
L'utilisateur interagit.
Le service de tâches met en file d'attente dans Cloud Tasks en ciblant l'exécuteur de tâches.
L'exécuteur de tâches exécute l'interaction en consommant l'API tierce.
(à venir) Consultez ici comment déployer et gérer cette infrastructure.
L'objectif sera de corréler la requête originale de l'utilisateur avec la requête réelle qui exécute la tâche.
De plus, nous voulons regrouper les logs associés. Malgré la nature complexe des systèmes distribués, nous pouvons y parvenir avec une configuration minimale.
Notez que Cloud Trace exploite et encourage OpenTelemetry. Il est supposé que vous connaissez déjà ses concepts. Étant open-source, OpenTelemetry permet une instrumentation indépendante du fournisseur pour votre produit SaaS.
traceparent
Cloud Run prend en charge l'en-tête de propagation de contexte de trace standard W3C, traceparent. Cet en-tête est présent sur chaque requête Cloud Run. L'idée clé est de surcharger cet en-tête pour chaque requête dérivant d'une interaction utilisateur.
Vous pouvez bien sûr faire cela que la requête cible directement un autre service sur Cloud Run ou via Cloud Tasks.
const task = await tasksClient.createTask({
parent: tasksClient.queuePath(logging.projectId, location, "queue"),
task: {
httpRequest: {
httpMethod: "POST",
url: `https://task-executor.com/interactions`,
headers: {
traceparent: request.get("traceparent"),
},
},
},
});
Cet exemple utilise le SDK Cloud Logging disponible pour Node.js. Voir Cloud Logging client libraries pour des alternatives.
Pour la deuxième partie de notre objectif, nous pouvons exploiter l'en-tête X-Cloud-Trace-Context. Cet en-tête est présent sur chaque requête Cloud Run. Vous pouvez extraire ce contexte et l'inclure dans votre entrée de log.
Selon votre stack, certaines bibliothèques ajouteront automatiquement le contexte de trace lors du logging.
YVous pourriez également vouloir instrumenter davantage en créant vos propres spans au lieu de les réutiliser. OpenTelemetry fournit également de nombreux outils pour instrumenter automatiquement votre application.
import { Logging } from "@google-cloud/logging";
const logging = new Logging();
await logging.setProjectId();
await logging.setDetectedResource();
app.use((req, res, next) => {
// spec: "X-Cloud-Trace-Context: TRACE_ID/SPAN_ID;o=TRACE_TRUE"
const traceHeader = req.get("X-Cloud-Trace-Context");
const [traceId, spanId] =
traceHeader?.split("/").flatMap((id) => id.split(";")) || [];
const log = logging.logSync("stdout");
log.info(
log.entry(
{
labels: { tt: "42" },
spanId,
trace: `projects/${logging.projectId}/traces/${traceId}`,
},
"correlated log"
)
);
next();
});
Dans le Log Explorer, pour chaque request log, vous avez maintenant vos logs liés imbriqués.
Et dans le Trace Explorer, vous avez tous les logs pour le complete flow.
Code source disponible ici : source code here. source code here
(à venir) Comment corréler les requêtes lorsque des systèmes tiers sont impliqués (par exemple, webhooks)
(à venir) Instrumentation du traçage pour le frontend en utilisant Firebase Hosting