Quoi logger
Le plus dur en intégrant Recalled, ce n'est pas l'appel SDK, c'est de décider ce qui mérite d'être loggué. Trop peu, ton audit trail ne sert à rien. Trop, tu fais exploser ton quota, tu ralentis ton app, et tu noies le signal dans le bruit.
Cette page est le guide opinion. Suis ces règles et tu auras un audit log propre, utile, conforme, sans trop y penser.
Les 3 règles
1. Logue les changements d'état, pas les lectures. Un user qui consulte son dashboard 50 fois par jour, ce n'est pas un audit event. Un user qui change son adresse email une fois, oui. Si l'action ne mute rien dans ton système ou dans le compte de l'utilisateur, ça n'a pas sa place dans Recalled.
2. Logue les actions à conséquences, pas le bruit technique. Un health check raté, un cache miss, une réponse 304, un purge CDN : ce ne sont pas des audit events. Ça va dans ton APM. Recalled, c'est pour les actions dont un humain ou un auditeur se souciera dans 6 mois.
3. Logue ce qui raconte une histoire. Imagine quelqu'un dans 6 mois qui demande "qui a supprimé cette facture et pourquoi". La réponse doit venir d'un seul event : l'acteur, la cible, la raison s'il y en a une, l'IP, le moment. Si ton entrée de log ne permet pas de reconstruire l'histoire, elle est incomplète.
Arbre de décision
Quand tu t'apprêtes à ajouter un client.events.create(), demande-toi :
- Est-ce que ça change un état dans notre système ? Non → ne logue pas.
- L'équipe légale, support ou sécu pourrait-elle un jour demander "qui a fait ça et quand" ? Non → ne logue pas.
- Le volume est-il raisonnable ? (moins de 10 events par user actif par session) Oui → logue. Non → considère du sampling ou de l'agrégation côté client.
Si tu réponds oui à 1 et 2 et que le volume tient, logue. Sinon, skip.
Catalogue par catégorie
Liste standard de ce que la plupart des SaaS B2B et B2C devraient logger. Choisis les catégories qui s'appliquent à ton produit, copie les noms d'actions, adapte à ton domaine. Chaque onglet contient les events à logger, ceux à skipper, et un indice metadata.
Authentification
Toute action qui change qui est connecté ou comment.
- →
user.signed_up(création de compte) - →
user.logged_in(auth réussie, toute méthode) - →
user.logged_out(logout explicite, y compris révocation de session) - →
user.login_failed(mauvais mot de passe, token expiré, IP bloquée). Critique pour la sécu. - →
user.password_changed - →
user.password_reset_requestedetuser.password_reset_completed(deux events distincts) - →
user.email_changed - →
user.two_factor_enabledetuser.two_factor_disabled - →
user.session_revoked(force-logout admin, se déconnecter de tous les appareils) - →
magic_link.sentetmagic_link.consumed - →
oauth.linkedetoauth.unlinked(Google, GitHub, etc.)
- ×Page views ou user a navigué vers /dashboard
- ×Refresh de token (toutes les 5 min, c'est du bruit, logue plutôt les révocations)
- ×Health checks d'auth
- ×Vérifications CSRF réussies
auth_provider (email, google, magic_link), ip, user_agent, mfa_used (true/false), success (pour les échecs, inclus la raison : bad_password, account_locked, mfa_required).
Autorisation
Tout ce qui change qui peut faire quoi.
- →
member.invited,member.joined,member.removed - →
member.role_changed(toujours inclure avant/après en metadata) - →
team.created,team.deleted - →
permission.granted,permission.revoked - →
api_key.created,api_key.revoked. Ne pas logger chaque utilisation, garderlast_used_atsur la clé suffit. - →
sharing.granted,sharing.revoked(quand un user partage une ressource avec un autre user ou en externe) - →
ownership.transferred(surtout en B2B)
- ×Les vérifications de permissions (chaque requête API en fait, c'est du domaine APM)
- ×Les accès en lecture seule s'ils auto-expirent rapidement
- ×Les étapes internes de calcul RBAC
role_before, role_after, scope, granted_by, expires_at.
Cycle de vie
Le cœur de l'audit logging. Chaque objet métier que tes users créent, modifient, suppriment, partagent, déplacent.
- →<objet>.created (
invoice.created,project.created,document.created, etc.) - →<objet>.updated (avec un
changed_fieldsen metadata, pas le diff complet s'il est gros) - →<objet>.deleted (toujours, soft-delete et hard-delete)
- →<objet>.archived et <objet>.restored
- →<objet>.published, <objet>.unpublished
- →<objet>.duplicated (par qui, source dans targets)
- →<objet>.moved (changement de dossier, transfert d'ownership, etc.)
- ×Les auto-saves et drafts si ton app sauvegarde toutes les 10 secondes. Logue le save explicite à la place.
- ×Les lectures, ouvertures, vues (ça va dans le product analytics, pas l'audit)
- ×Les jobs internes de dénormalisation qui touchent la même ligne
changed_fields (tableau de noms de champs), reason (si le user en a fourni une), source (manual, api, import, automation).
Argent
Chaque changement d'état monétaire. Sois exhaustif ici, les comptables et ton toi-du-futur te remercieront.
- →
subscription.created,subscription.updated,subscription.canceled - →
subscription.plan_changed(avec from et to slugs en metadata) - →
invoice.created,invoice.paid,invoice.failed,invoice.refunded - →
payment.succeeded,payment.failed(inclus la raison du refus) - →
refund.issued,refund.completed - →
coupon.applied,coupon.expired - →
payment_method.added,payment_method.removed,payment_method.set_default
- ×Le ping webhook Stripe lui-même (logue plutôt l'event que représente le webhook)
- ×Les vérifications de conversion de devise
- ×Les pré-validations de paiement qui n'ont mené à aucune tentative de débit
amount_cents, currency, stripe_payment_intent_id, provider (stripe, paddle, etc.), reason pour les refunds.
Actions admin
Tout ce qu'un membre de ton équipe interne fait au nom d'un user. Toujours logger ça, sans exception. C'est ce que les auditeurs regardent en premier.
- →
admin.impersonation_started,admin.impersonation_ended - →
admin.user_unlocked,admin.user_locked - →
admin.feature_toggled(par user ou globalement) - →
admin.data_overridden(quand le support corrige manuellement une valeur) - →
admin.support_intervention(catch-all pour les corrections ponctuelles) - →N'importe quoi qui passe par ton back-office interne et mute des données client
admin_id, reason (toujours exiger une raison dans l'UI back-office), ticket_id si tu link à ton helpdesk.
Exports & imports
Données qui sortent ou entrent en masse.
- →
export.started,export.completed,export.failed - →
import.started,import.completed,import.failed - →
bulk_delete.requested,bulk_delete.completed - →
gdpr.access_requestetgdpr.erasure_request
format (csv, json), row_count, destination pour les exports, source pour les imports, size_bytes.
Intégrations
Les events qui impliquent des tiers.
- →
integration.connected,integration.disconnected - →
webhook.created,webhook.updated,webhook.deleted - →
webhook.delivery_failed(après que les retries soient épuisés, pas chaque retry) - →
oauth.token_refreshedseulement si ça représente un event significatif (forced re-auth), sinon skip
- ×Chaque livraison de webhook réussie
- ×Les health checks contre les intégrations
Sécurité
Ce dont ton équipe sécu a besoin de visibilité.
- →
security.brute_force_detected(après le seuil) - →
security.suspicious_login(nouveau pays, nouveau device, etc.) - →
security.rate_limit_exceeded(uniquement si ça persiste, pas chaque hit) - →
security.csp_violation_reported - →
security.api_key_leaked(si tu as un pipeline de détection)
- ×Chaque requête rate-limitée individuellement
- ×Chaque violation CSP (sample ou agrège par heure)
Système & jobs
Utile pour le debug ops, mais attention à la cardinalité.
- →
cron.<nom>.completedetcron.<nom>.failed(un par run, pas par item traité) - →
migration.appliedetmigration.rolled_back - →
backup.created,backup.restored
- ×Chaque itération de boucle, chaque ligne traitée par un batch job
- ×Les heartbeats, les liveness probes
- ×Les déplacements internes de queue
Toute action qui change qui est connecté ou comment.
- →
user.signed_up(création de compte) - →
user.logged_in(auth réussie, toute méthode) - →
user.logged_out(logout explicite, y compris révocation de session) - →
user.login_failed(mauvais mot de passe, token expiré, IP bloquée). Critique pour la sécu. - →
user.password_changed - →
user.password_reset_requestedetuser.password_reset_completed(deux events distincts) - →
user.email_changed - →
user.two_factor_enabledetuser.two_factor_disabled - →
user.session_revoked(force-logout admin, se déconnecter de tous les appareils) - →
magic_link.sentetmagic_link.consumed - →
oauth.linkedetoauth.unlinked(Google, GitHub, etc.)
- ×Page views ou user a navigué vers /dashboard
- ×Refresh de token (toutes les 5 min, c'est du bruit, logue plutôt les révocations)
- ×Health checks d'auth
- ×Vérifications CSRF réussies
auth_provider (email, google, magic_link), ip, user_agent, mfa_used (true/false), success (pour les échecs, inclus la raison : bad_password, account_locked, mfa_required).
Conventions de nommage
Suis ce schéma partout : <domaine>.<sujet>.<verbe_passé> ou <domaine>.<verbe_passé> quand il n'y a pas de sujet distinct.
Règles :
- Tout en minuscules, séparé par points, snake_case si nécessaire à l'intérieur d'un segment.
- Verbes au passé :
.createdpas.create,.updatedpas.update,.deletedpas.delete. - Domaine d'abord, sujet ensuite, verbe à la fin :
invoice.line_item.addedplutôt queadded.invoice.line_item. - Sois cohérent : choisis un verbe et tiens-toi-y. Ne mélange pas
.deletedet.removedpour le même domaine. Ne mélange pas.failedet.errored. - Stable dans le temps : les noms d'actions finissent dans tes dashboards, retention rules et filtres de notifications. Les renommer plus tard casse l'historique.
Verbes standards à utiliser, classés par fréquence :
| Verbe | À utiliser pour |
|---|---|
.created | Nouvelle ressource créée |
.updated | Ressource existante modifiée |
.deleted | Ressource supprimée (soft ou hard) |
.archived / .restored | Changements d'état soft |
.published / .unpublished | Toggles de visibilité |
.shared / .unshared | Octrois et révocations d'accès |
.signed_up / .logged_in / .logged_out | Cycle d'auth |
.invited / .joined / .removed | Membership |
.started / .completed / .failed | Actions async longues |
.requested / .granted / .revoked | Flows de permissions |
Schéma metadata
metadata est un objet JSON libre. Garde-le petit (quelques centaines d'octets, c'est très bien), structuré, et utile dans 6 mois. Les patterns ci-dessous marchent pour la plupart des catégories.
À toujours inclure quand pertinent
source: d'où vient l'action. Valeurs :web,mobile,api,admin_panel,automation,import,webhook.reason: raison textuelle libre si le user ou l'admin en a fourni une (metadata.reason: "duplicate").request_id: ton propre id de corrélation pour remonter à tes logs.result:successoufailure. Default àsuccesspour pouvoir filtrer les échecs plus tard.
Pour les updates
{
"metadata": {
"changed_fields": ["status", "due_date"],
"before": { "status": "draft" },
"after": { "status": "paid" }
}
}Inclus uniquement les champs modifiés, pas l'objet complet. Si le diff est énorme, garde juste changed_fields et skip before/after.
Pour l'argent
Toujours sérialiser les montants en entiers de cents (ou plus petite unité de la devise), jamais en floats :
{
"metadata": {
"amount_cents": 4200,
"currency": "EUR",
"stripe_payment_intent_id": "pi_..."
}
}Pour les échecs
{
"metadata": {
"result": "failure",
"reason": "card_declined",
"code": "insufficient_funds"
}
}Antipatterns
Ne mets jamais ça dans metadata :
- Secrets en clair, mots de passe, tokens complets, numéros de carte complets. Si ça ne devrait pas être dans tes logs, ça ne devrait pas être dans Recalled.
- Corps de documents complets, fichiers, blobs, images. Recalled n'est pas un store de documents. Mets l'id de la ressource dans
targetset laisse le consommateur fetch le contenu s'il en a besoin. - PII dont tu n'as pas besoin. Si tu logues une action sur un user, tu as déjà
actor.id. Tu n'as pas aussi besoin de son adresse complète, son téléphone, etc. Moins de PII = moins d'exposition RGPD. - Données de debug serveur. Stack traces, requêtes SQL, IDs internes qui ne veulent rien dire pour un humain auditeur. Envoie ça dans ton APM.
- Quoi que ce soit de plus de ~2 KB. Une grosse
metadataralentit les list queries et inflate ta facture de stockage.
Volume
Recalled facture au nombre d'events par mois. Le bon ratio pour la plupart des SaaS B2B :
- 1 à 5 events par user actif par session (login, quelques actions, logout)
- 0 event quand un user lit juste
- Quelques-uns par jour pour les actions système (cron, billing, backups)
Si tu te retrouves à logger plus de 10 events par user actif par jour, tu over-logges probablement des lectures ou tu captures du bruit technique. Relis les 3 règles en haut.
Exemple complet
Un SaaS B2B fictif de facturation. Voici la liste complète des events que son équipe pousserait dans Recalled. Note comme ça reste focalisé.
# Auth (5)
user.signed_up
user.logged_in
user.logged_out
user.login_failed
user.password_changed
# Équipe (4)
member.invited
member.joined
member.removed
member.role_changed
# Factures, l'objet métier central (6)
invoice.created
invoice.updated
invoice.sent
invoice.paid
invoice.refunded
invoice.deleted
# Clients (3)
customer.created
customer.updated
customer.deleted
# Facturation pour le SaaS lui-même (4)
subscription.created
subscription.plan_changed
subscription.canceled
payment_method.added
# Admin (2)
admin.impersonation_started
admin.impersonation_ended
# Exports (2)
export.started
export.completedSoit 26 actions distinctes, largement assez pour un audit trail propre. Ta vraie liste sera de taille similaire.
Étape suivante
Une fois ta liste prête, file dans Events API et commence à envoyer. Le tool list_actions_summary du dashboard te montre en temps réel quelles actions tu logues le plus, pour repérer du sur-logging ou des trous.