documentation
Kohinos v3 Doc générée le commit 8d60b1904

Observabilité — logs, métriques, traces, alerting

On ne peut pas opérer une infra monétaire à l'aveugle : chaque écart de centime doit laisser une trace consultable, datée et signée. Mais on ne va pas non plus déléguer les logs d'une caisse coopérative à un SaaS américain. Cette page définit l'observabilité Kohinos : 100 % self-host, sobre, focalisée sur le signal financier — utilisable sur un Raspberry Pi 5 sans transpirer.

Les trois piliers

PilierOutil retenuRôleCoût
Logs Monolog → fichiers var/log/ + (option) Loki self-host Journal des événements applicatifs et financiers 0 € — disque local + Loki sur Pi
Métriques Prometheus + Grafana self-host Séries temporelles (CPU, RAM, solde global, latence) 0 € — ~150 Mo RAM sur Pi 5
Traces Optionnel — Tideways self-host pour profiling ponctuel Spans de requêtes lentes, hotspots SQL 0 € — activé à la demande seulement

Les trois piliers sont indépendants : aucun n'est obligatoire pour démarrer. L'installation minimum (logs sur disque + Uptime Kuma) est déjà conforme. Le reste s'ajoute quand le besoin apparaît.

Channels Monolog dédiés

La configuration config/packages/monolog.yaml et config/packages/prod/monolog.yaml segmente les logs par channel métier. Cela permet de filtrer un incident SEPA sans noyer la console sous les logs Doctrine.

ChannelRôleFichierNiveau prod
appGénéral applicatif, fallbackvar/log/prod.logwarning
webhookHelloAsso, PayZen — critiquevar/log/webhook.loginfo
sepaMandats, prélèvements, fichiers pain.008var/log/sepa.loginfo
ssaCalculs et redistribution SSAvar/log/ssa.loginfo
securityAuth, voters, échecs login, brute-forcevar/log/security.lognotice
backupDumps, chiffrement, restauration (cf. backup-restore)var/log/backup.loginfo
cronCommandes planifiées (déjà configuré, voir cron-prod.log)var/log/cron-prod.logdebug
emailEnvois symfony/mailer, bouncesvar/log/email.loginfo
auditModifications BDD significatives par adminvar/log/audit.loginfo

Déclaration côté YAML :

monolog:
    channels: ['app', 'webhook', 'sepa', 'ssa', 'security',
              'backup', 'cron', 'email', 'audit']

Côté service, injection par MonologBundle nommée :

public function __construct(
    #[Autowire(service: 'monolog.logger.sepa')]
    private readonly LoggerInterface $sepaLogger,
) {}

Rotation & rétention

Les logs prod tournent via logrotate : rotation quotidienne, compression à J+1, suppression à J+90 — sauf les channels financiers et audit qui sont archivés chiffrés (rétention 10 ans, cf. obligations comptables).

# /etc/logrotate.d/kohinos
/var/www/kohinos/var/log/*.log {
    daily
    rotate 90
    compress
    delaycompress
    missingok
    notifempty
    create 0640 www-data www-data
    sharedscripts
    postrotate
        # php-fpm garde des handles ouverts — reload
        systemctl reload php8.4-fpm > /dev/null 2>&1 || true
    endscript
}

# Logs financiers : archivés à part, chiffrés, 10 ans
/var/www/kohinos/var/log/sepa.log
/var/www/kohinos/var/log/ssa.log
/var/www/kohinos/var/log/audit.log
{
    daily
    rotate 3650
    compress
    create 0600 www-data www-data
    postrotate
        # Chiffrement age vers stockage déporté self-host
        find /var/www/kohinos/var/log -name "*.log.1.gz" \
            -exec age -r "$(cat /etc/kohinos/audit.age.pub)" {} -o {}.age \;
    endscript
}
⚠ Logs financiers = pièces comptables

Les channels sepa, ssa et audit font foi en cas d'audit ou de litige adhérent. Ne jamais les purger avant 10 ans, ne jamais les éditer à la main. Le chiffrement age + clé hors-machine garantit l'intégrité.

GlitchTip — error tracking self-host

Pour centraliser les exceptions PHP sans dépendance cloud, on utilise GlitchTip : compatible avec le SDK Sentry, donc la bibliothèque PHP sentry/sentry-symfony fonctionne telle quelle. Tourne sur un Raspberry Pi 5 (Postgres + Redis + container Docker, ~400 Mo RAM).

# .env.local — DSN GlitchTip self-host
SENTRY_DSN=https://abc123@glitchtip.kohinos-infra.local/1
MONOLOG_HANDLERS_SENTRY_DSN=${SENTRY_DSN}

# config/packages/sentry.yaml — scrub avant envoi
sentry:
    dsn: '%env(SENTRY_DSN)%'
    options:
        before_send: '@App\Monitoring\SentryScrubber'
        send_default_pii: false
        environment: '%kernel.environment%'

Le SentryScrubber retire obligatoirement avant transmission : IBAN, email, nom/prénom adhérent, tokens, valeurs $_POST sensibles. Aucune donnée nominative ne doit quitter la machine, même vers une instance GlitchTip de confiance.

⚠ Pas de Sentry cloud — politique souveraineté

.claude/rules/global.md et .claude/rules/mcp-policy.md proscrivent les SaaS US dans le path critique. GlitchTip self-host couvre ~95 % des cas d'usage Sentry (groupes, releases, alertes) sans dépendance externe. Pas besoin de plus pour Kohinos.

Prometheus + Grafana self-host

Exporters système

Métriques applicatives custom

Une commande Symfony pousse les métriques métier vers un pushgateway Prometheus toutes les 5 minutes :

# Cron toutes les 5 minutes
*/5 * * * * www-data cd /var/www/kohinos && \
    php bin/console app:metrics --push --env=prod

Métriques exposées :

MétriqueTypeSource
kohinos_global_balance_emlcgaugeSomme des comptes adhérents
kohinos_active_members_totalgaugeUser avec cotisation à jour
kohinos_ssa_last_run_timestampgaugeDernière exécution kohinos:ssa:execute
kohinos_sepa_last_run_timestampgaugeDernière exécution kohinos:sepa:process-monthly
kohinos_pending_webhooksgaugePendingPaymentData en attente
kohinos_sepa_mandates_pendinggaugeMandats non validés trésorier
kohinos_account_check_failurescounterCompteur d'échecs accounts:check

Dashboards Grafana

Les dashboards JSON sont versionnés dans infra/grafana/ (provisionnés automatiquement au démarrage Grafana).

Alerting

Trois canaux complémentaires, tous self-host :

Règles dures — alertes critiques

Ces sept règles doivent toujours exister et déclencher une notification immédiate trésorier+technique :

RègleConditionSévérité
Backup KO depuis 24htime() - kohinos_backup_last_success > 86400critical
Solde négatif détectékohinos_account_check_failures > 0critical
Webhook idempotence casséeDoublon helloasso_payment_id détectécritical
SSA mensuelle non exécutée le 15day_of_month == 15 AND ssa_last_run < début_moiscritical
SEPA mensuelle non générée le J-15Pas de fichier pain.008 à 15 jours du prélèvementcritical
CSP violations soudainesSpike csp_report > 10 / minwarning
5xx soutenuTaux 5xx > 1 % sur 5 mincritical

Extrait infra/prometheus/alerts.yml :

groups:
  - name: kohinos-finance
    rules:
      - alert: KohinosBackupStale
        expr: time() - kohinos_backup_last_success_timestamp > 86400
        for: 5m
        labels: { severity: critical }
        annotations:
          summary: "Aucun backup Kohinos réussi depuis 24h"
          runbook: "https://docs.kohinos.local/devops/runbooks.html#backup-stale"

      - alert: KohinosSepaNotGenerated
        expr: (day_of_month() >= 15) and (kohinos_sepa_last_run_timestamp < (time() - 86400 * 20))
        for: 1h
        labels: { severity: critical }
        annotations:
          summary: "Fichier SEPA mensuel non généré à J-15"

Audit log applicatif

Au-delà des logs fichiers, certaines modifications BDD sont tracées dans une entité dédiée AuditLog persistée en base. Cible : les actions sensibles côté admin (validation mandat SEPA, modification taux SSA, suppression flux, changement de rôle, etc.).

ColonneDescription
actor_idAdmin ayant effectué l'action
actionCode action (ex. sepa.mandate.validate)
entity_type · entity_idCible de l'action
before_value · after_valueJSON, valeurs scrubbées des PII
occurred_atTimestamp UTC
ip_hashIP de l'admin hashée (pas en clair)

Rétention : 6 ans (obligation comptable). Consultation via Sonata Admin AuditLogAdmin — lecture seule, pas d'édition possible (l'entité elle-même refuse UPDATE/DELETE au niveau Doctrine listener).

RGPD vs observabilité

⛔ Données personnelles dans les logs
  • Pas de PII dans les logs applicatifs : scrub obligatoire de tout IBAN, email, nom complet, n° de téléphone avant écriture. Un Processor Monolog s'en charge globalement (App\Monolog\PiiScrubProcessor).
  • IP adhérent : 6 mois maximum côté security et nginx access log, ensuite anonymisées (dernier octet à zéro).
  • Tokens / secrets : JAMAIS dans les logs, même en DEBUG. Les en-têtes Authorization, cookies session, clés API HelloAsso/PayZen sont automatiquement filtrés.
  • Profiler Symfony : désactivé en prod (cf. config/packages/prod/web_profiler.yaml absent). Activation ponctuelle pour debug → désactivation immédiate après.

Comment debug un incident

  1. Reproduire en staging si possible — ne jamais investiguer sur la prod si on peut éviter.
  2. Tailler le channel pertinent : tail -f var/log/sepa.log, tail -f var/log/webhook.log… selon la nature de l'incident.
  3. Cross-référencer avec Grafana : une latence anormale ? Un spike de 5xx ? Une métrique financière qui dérive ?
  4. Symfony Profiler en dev/staging pour rejouer la requête incriminée et inspecter SQL / cache / events.
  5. Tideways ponctuel si la latence est inexplicable (profiling de prod activé le temps d'un échantillon, désactivé ensuite).
  6. Si financier : passer directement au runbook approprié — voir runbooks DevOps. Ne pas improviser sur un incident monétaire.

Coût

Tout cet outillage est self-host : 0 € supplémentaire si un serveur (Pi 5 ou VPS) est déjà en place pour Kohinos. Empreinte mesurée sur un Pi 5 8 Go avec ~500 adhérents :

ComposantRAMCPU (moyenne)Disque
Monolog (déjà inclus)négligeable~0 %~50 Mo/mois
Prometheus~80 Mo~1 %~200 Mo/mois
Grafana~60 Mo~0,5 %~30 Mo
GlitchTip (Postgres + Redis)~400 Mo~2 %~150 Mo/mois
Uptime Kuma~50 Mo~0,5 %négligeable
Total observabilité~600 Mo~5 %~450 Mo/mois

Largement supportable par un Pi 5 8 Go. Sur un Pi 4 4 Go saturé, on commence par Monolog seul + Uptime Kuma, et on déporte Prometheus/Grafana sur une seconde machine.

Voir aussi