Skip to Content
RGPDPartie 14 — Le RGPD dans le code (dev & IA)14.4 — Logs & analytics respectueux

Chapitre 14.4 — Logs & analytics respectueux

⏱️ TL;DR — Deux endroits où la PII fuit sans qu’on le décide vraiment : les logs et l’analytics. Côté logs : on ne journalise pas de données personnelles en clair (e-mail, mot de passe, jeton, corps de requête), on tronque les IP, et on met un middleware de masquage qui assainit automatiquement ce qui part dans les journaux. Côté analytics : on évite les trackers tiers par défaut, on préfère une mesure d’audience respectueuseself-hosted (type Matomo) ou sans cookie (type Plausible) — configurée en privacy by default. Une adresse IP est une donnée personnelle (chapitre 1.1), un log plein d’e-mails est un fichier de données personnelles comme un autre : il doit être minimisé, sécurisé et purgé. On complète ici le chapitre 9.5 côté code.

🎯 Objectifs

  • Bannir la PII des logs et savoir ce qui ne doit jamais y figurer.
  • Tronquer / anonymiser une adresse IP avant de la journaliser.
  • Écrire un middleware de masquage qui assainit automatiquement les logs.
  • Choisir un analytics respectueux (self-hosted / sans cookie) et le configurer privacy by default.

Les logs sont un fichier de données personnelles

Un journal applicatif qui contient login marie.dupont@ecole.fr depuis 88.120.44.12 est un traitement de données personnelles au même titre qu’une table en base : e-mail (identifiant direct) + IP (identifiant indirect, chapitre 1.1). On l’oublie parce que « ce ne sont que des logs » — mais ils sont souvent peu protégés, très dupliqués (agrégateurs, alerting, sauvegardes) et gardés longtemps. C’est un angle mort classique.

Ce qui ne doit jamais apparaître en clair dans un log :

  • Mots de passe, jetons, clés d’API, cookies de session (secrets).
  • E-mails, noms, téléphones, adresses (identifiants directs).
  • Corps de requête / réponse bruts sur les routes qui manipulent de la PII (inscription, profil, paiement).
  • Données sensibles (santé, etc.) — jamais, sous aucune forme.
  • IP complète conservée durablement sans finalité (voir plus bas).
// ❌ MAUVAIS — de la PII et un secret déversés dans les logs logger.info(`login ${email} / mdp ${password} depuis ${req.ip}`) logger.info(`inscription: ${JSON.stringify(req.body)}`) // tout le formulaire en clair // ✅ BON — un identifiant OPAQUE, pas de secret, IP tronquée logger.info(`login ok user=${userId} ip=${anonymiserIp(req.ip)}`) logger.info(`inscription ok user=${userId}`) // le fait, pas le contenu

⚠️ Piège — Le log de debug oublié en production : console.log(req.body) ou logger.debug(user) posé pendant le développement, qui déverse mots de passe, e-mails et jetons dans les journaux à chaque requête. C’est une cause fréquente de violation de données (une fuite des logs = une fuite de PII). Règle : on ne journalise jamais un objet utilisateur ou un corps de requête bruts, même en debug, même « temporairement ».

Tronquer l’IP avant de la journaliser

Une IP est une donnée personnelle, mais on a souvent un besoin légitime et limité (sécurité, détection d’abus, statistiques grossières) qui ne nécessite pas l’adresse complète. La bonne pratique : tronquer — retirer le dernier octet en IPv4, une large part des bits en IPv6 — pour réduire le pouvoir d’identification tout en gardant une utilité approximative (géographie grossière, comptage).

// Tronque une IP pour réduire son pouvoir identifiant avant journalisation. export function anonymiserIp(ip: string | undefined): string { if (!ip) return "inconnue" if (ip.includes(".")) { // IPv4 : on neutralise le dernier octet -> 88.120.44.12 devient 88.120.44.0 const octets = ip.split(".") octets[3] = "0" return octets.join(".") } // IPv6 : on ne garde que le préfixe (les premiers blocs) const blocs = ip.split(":") return blocs.slice(0, 3).join(":") + "::" }

Ce n’est pas de l’anonymisation parfaite (chapitre 1.1), mais une réduction d’exposition proportionnée : si les logs fuient, on n’y trouve pas les adresses exactes. Combine-la avec une durée de conservation courte des logs (purge automatique — chapitre 14.2).

Le middleware de masquage : automatiser l’assainissement

Compter sur la vigilance de chaque développeur pour ne pas logger de PII est illusoire. On automatise : un point de passage unique (formateur de logs, middleware) qui masque les champs sensibles avant écriture. Ainsi, même un log maladroit est assaini.

// lib/log-redaction.ts — masque les champs sensibles avant journalisation const CHAMPS_SENSIBLES = new Set([ "password", "motDePasse", "token", "authorization", "cookie", "email", "telephone", "adresse", "numeroSecu", "amenagement", ]) // Parcourt un objet et remplace la valeur des champs sensibles par "[REDACTED]". export function masquer(donnees: unknown): unknown { if (Array.isArray(donnees)) return donnees.map(masquer) if (donnees && typeof donnees === "object") { const sortie: Record<string, unknown> = {} for (const [cle, valeur] of Object.entries(donnees)) { sortie[cle] = CHAMPS_SENSIBLES.has(cle.toLowerCase()) ? "[REDACTED]" : masquer(valeur) } return sortie } return donnees }

On branche ce masquage dans le logger lui-même, pour qu’il s’applique partout, sans y penser :

// lib/logger.ts — le masquage est appliqué à la source, pas à la charge de l'appelant import pino from "pino" import { masquer } from "./log-redaction" export const logger = pino({ // Beaucoup de bibliothèques de log offrent un mécanisme natif de "redaction" ; // ici on illustre le principe avec un hook qui assainit chaque objet loggé. formatters: { log: (obj) => masquer(obj) as Record<string, unknown>, }, }) // Même si un dev écrit logger.info({ email, password }, "login"), // la sortie contient email:"[REDACTED]" password:"[REDACTED]".

💡 Réflexe — La bonne défense est au niveau du logger, pas de l’appel. Si tu comptes sur « chaque dev pense à masquer », tu as déjà perdu. Configure la redaction une fois, à la source (la plupart des bibliothèques de log — pino, winston — ont une option native), et le masquage devient la valeur par défaut que personne ne peut oublier. C’est du privacy by default appliqué à l’observabilité.

Analytics : respectueux par défaut, trackers tiers par exception

La mesure d’audience est le second robinet à PII. Le réflexe historique — coller un script d’analytics tiers qui dépose des cookies et exfiltre la navigation vers un géant de la pub — est à la fois un problème de consentement (Partie 7) et de transferts (Partie 13). L’alternative moderne existe et se code aussi simplement.

ApprocheCookies ?Données envoyées à un tiersConsentement requis
Tracker publicitaire tiersOuiVers l’éditeur du tracker (souvent hors UE)Oui (bandeau, opt-in)
Analytics self-hosted (type Matomo)ConfigurableRestent chez toiSelon config (exemption possible si bien réglé)
Analytics sans cookie (type Plausible)NonAgrégées, pas d’identifiant individuelSouvent non si mesure d’audience anonyme

Deux stratégies gagnantes :

  • Self-hosted (Matomo installé sur ton infra) : les données ne quittent pas ton périmètre, tu maîtrises la rétention, et une configuration « mesure d’audience » stricte (IP tronquée, pas de cookie de suivi, pas de croisement) peut relever de l’exemption de consentement prévue pour la mesure d’audience.
  • Sans cookie (type Plausible) : pas de cookie, pas d’identifiant individuel persistant, statistiques agrégées. Pas de bandeau à afficher pour ça, et l’expérience utilisateur reste propre.

Le principe transversal : aucun tracker tiers chargé par défaut. Si tu as vraiment besoin d’un traceur non exempté, il ne se déclenche qu’après un consentement explicite — jamais au chargement de la page.

// components/Analytics.tsx — privacy by default : rien de tiers avant consentement export function Analytics({ consentAnalytics }: { consentAnalytics: boolean }) { // Mesure d'audience sans cookie, self-hosted : peut tourner sans consentement // si configurée en mesure d'audience anonyme (IP tronquée, pas de suivi individuel). // Tout traceur NON exempté attend un opt-in explicite. if (!consentAnalytics) return null return <script defer src="/stats/script.js" data-domain="formacampus.fr" /> }

🔒 Côté personne concernée — Un utilisateur qui visite une page de cours ne s’attend pas à ce que sa visite parte chez une régie publicitaire pour alimenter son profil commercial. Une mesure d’audience agrégée et sans cookie lui rend un double service : le site reste rapide (pas dix scripts tiers) et sa navigation ne le suit pas ailleurs sur le web. Le respect de la vie privée, ici, se voit aussi dans l’expérience.

🧭 Sur FormaCampus — Côté logs : un logger pino avec redaction native sur password, token, email, authorization ; les IP tronquées par anonymiserIp ; une rétention des journaux courte, purgée par le cron du chapitre 14.2. Côté audience : Plausible (sans cookie) pour les statistiques générales du site vitrine, Matomo self-hosted en mode mesure d’audience anonyme pour l’usage interne du LMS — zéro tracker publicitaire tiers. Résultat : aucune donnée d’apprenant ne part chez un tiers pour de la pub, et le bandeau cookies reste minimal (voire inutile pour l’audience anonyme).

📚 Le texte — Les cookies et traceurs relèvent de la directive ePrivacy (2002/58/CE, transposée à l’article 82 de la loi Informatique et Libertés) et des lignes directrices CNIL 2020 : consentement requis sauf pour les traceurs strictement nécessaires et la mesure d’audience correctement configurée (exemptée sous conditions). L’IP est une donnée personnelle (jurisprudence Breyer, chapitre 1.1). Le fond « pas de PII dans les journaux, journaux sécurisés et purgés » prolonge l’article 32 (sécurité) et la minimisation (art. 5) — détaillé côté sécurité au chapitre 9.5.

✏️ Exercices

Exercice 1 — Nettoie ces logs. Une revue de code révèle ces trois lignes en production. Corrige chacune : (a) logger.info("login", { email, password }) ; (b) logger.error("paiement échoué", { body: req.body }) ; (c) logger.info("visite depuis " + req.ip).

✅ Solution

(a) Secret + PII : ne jamais logger le password (ni l’e-mail en clair). Remplacer par un identifiant opaque : logger.info("login ok", { user: userId }) — et compter sur la redaction du logger comme filet. (b) Le corps de requête brut d’un paiement contient de la PII (et peut-être des fragments de données de paiement) : logger le fait et un identifiant de transaction, pas le body : logger.error("paiement échoué", { txId, code }). (c) L’IP complète : la tronquer avant journalisation — logger.info("visite", { ip: anonymiserIp(req.ip) }) — et prévoir une rétention courte des logs.

Exercice 2 — Choisis ta mesure d’audience. FormaCampus veut mesurer les pages les plus vues de son site vitrine, sans afficher un gros bandeau cookies ni envoyer de données à une régie. Quelles options respectent ces contraintes, et à quelle condition peut-on éviter le bandeau de consentement ?

✅ Solution

Deux options : un analytics sans cookie (type Plausible) ou un analytics self-hosted (type Matomo) configuré en mesure d’audience anonyme. On peut se passer du bandeau de consentement si la mesure relève de l’exemption prévue pour la mesure d’audience : pas de cookie de suivi (ou traceur strictement limité), IP tronquée, statistiques agrégées, pas de croisement avec d’autres traitements ni de partage à des tiers, finalité limitée à la mesure. Dès qu’on sort de ce cadre (suivi individuel, cookie publicitaire, partage tiers), le consentement explicite redevient obligatoire. Dans tous les cas : aucun tracker tiers chargé par défaut.

🧠 Quiz de révision

1. Pourquoi les logs sont-ils un traitement de données personnelles ?

Parce qu’ils contiennent souvent des identifiants directs (e-mail, nom) et indirects (IP). Un journal plein d’e-mails est un fichier de données personnelles comme un autre — souvent peu protégé, très dupliqué et gardé longtemps. Il doit donc être minimisé, sécurisé et purgé.

2. Que ne doit-on jamais journaliser en clair ?

Les secrets (mots de passe, jetons, clés, cookies de session), les identifiants directs (e-mail, nom, téléphone), les corps de requête bruts des routes manipulant de la PII, les données sensibles (santé), et l’IP complète conservée durablement sans finalité. On préfère un identifiant opaque et une IP tronquée.

3. Comment anonymiser une IP dans les logs, et est-ce suffisant ?

En la tronquant : neutraliser le dernier octet en IPv4, ne garder qu’un préfixe en IPv6. Ce n’est pas une anonymisation parfaite mais une réduction d’exposition proportionnée ; à combiner avec une durée de conservation courte des logs (purge auto).

4. Pourquoi masquer la PII au niveau du logger plutôt qu’à chaque appel ?

Parce que compter sur la vigilance de chaque développeur est illusoire : un console.log oublié suffit à déverser des secrets. En configurant la redaction une fois, à la source (option native de pino/winston), le masquage devient la valeur par défaut — du privacy by default appliqué à l’observabilité.

5. Qu’est-ce qu’un analytics « respectueux » et quand évite-t-il le bandeau ?

Une mesure d’audience self-hosted (type Matomo) ou sans cookie (type Plausible), sans tracker publicitaire tiers. Elle peut relever de l’exemption de consentement de la mesure d’audience si : pas de cookie de suivi, IP tronquée, données agrégées, pas de croisement ni de partage tiers, finalité limitée à la mesure. Sinon, consentement explicite requis, et aucun tracker tiers par défaut.


Chapitre suivant : RGPD & IA — base légale, minimisation des prompts, le fournisseur de LLM comme sous-traitant, la prudence sur les mineurs, et l’articulation avec l’AI Act.

Last updated on