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
| Pilier | Outil retenu | Rôle | Coû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.
| Channel | Rôle | Fichier | Niveau prod |
|---|---|---|---|
app | Général applicatif, fallback | var/log/prod.log | warning |
webhook | HelloAsso, PayZen — critique | var/log/webhook.log | info |
sepa | Mandats, prélèvements, fichiers pain.008 | var/log/sepa.log | info |
ssa | Calculs et redistribution SSA | var/log/ssa.log | info |
security | Auth, voters, échecs login, brute-force | var/log/security.log | notice |
backup | Dumps, chiffrement, restauration (cf. backup-restore) | var/log/backup.log | info |
cron | Commandes planifiées (déjà configuré, voir cron-prod.log) | var/log/cron-prod.log | debug |
email | Envois symfony/mailer, bounces | var/log/email.log | info |
audit | Modifications BDD significatives par admin | var/log/audit.log | info |
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
}
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.
.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
node_exporter— CPU, RAM, disque, réseau de l'hôte.mariadb_exporter— connexions, requêtes lentes, taille des tables.nginx_exporter(ouapache_exporter) — requêtes, latence, codes HTTP.php-fpm_exporter— pool, processus actifs, file d'attente.
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étrique | Type | Source |
|---|---|---|
kohinos_global_balance_emlc | gauge | Somme des comptes adhérents |
kohinos_active_members_total | gauge | User avec cotisation à jour |
kohinos_ssa_last_run_timestamp | gauge | Dernière exécution kohinos:ssa:execute |
kohinos_sepa_last_run_timestamp | gauge | Dernière exécution kohinos:sepa:process-monthly |
kohinos_pending_webhooks | gauge | PendingPaymentData en attente |
kohinos_sepa_mandates_pending | gauge | Mandats non validés trésorier |
kohinos_account_check_failures | counter | Compteur d'échecs accounts:check |
Dashboards Grafana
- Pi system — température CPU, charge, swap, espace disque, débit réseau.
- App perf — latence HTTP par route, taux d'erreur 5xx, requêtes Doctrine lentes, mémoire php-fpm.
- Financial overview — solde global, évolution adhérents actifs, prochaines
échéances SEPA, dernières exécutions SSA, ratio anomalies
accounts:check.
Les dashboards JSON sont versionnés dans infra/grafana/ (provisionnés
automatiquement au démarrage Grafana).
Alerting
Trois canaux complémentaires, tous self-host :
- Uptime Kuma — pings HTTP réguliers sur
/health, latence, certificat TLS. Notification email trésorier sur incident. - Alertmanager (associé à Prometheus) — règles seuils sur les métriques ci-dessus, déduplication, escalade.
- Mail direct via
cron_mailMonolog (déjà en place, voirconfig/packages/prod/monolog.yaml) : tout cron qui échoue déclenche un mail.
Règles dures — alertes critiques
Ces sept règles doivent toujours exister et déclencher une notification immédiate trésorier+technique :
| Règle | Condition | Sévérité |
|---|---|---|
| Backup KO depuis 24h | time() - kohinos_backup_last_success > 86400 | critical |
| Solde négatif détecté | kohinos_account_check_failures > 0 | critical |
| Webhook idempotence cassée | Doublon helloasso_payment_id détecté | critical |
| SSA mensuelle non exécutée le 15 | day_of_month == 15 AND ssa_last_run < début_mois | critical |
| SEPA mensuelle non générée le J-15 | Pas de fichier pain.008 à 15 jours du prélèvement | critical |
| CSP violations soudaines | Spike csp_report > 10 / min | warning |
| 5xx soutenu | Taux 5xx > 1 % sur 5 min | critical |
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.).
| Colonne | Description |
|---|---|
actor_id | Admin ayant effectué l'action |
action | Code action (ex. sepa.mandate.validate) |
entity_type · entity_id | Cible de l'action |
before_value · after_value | JSON, valeurs scrubbées des PII |
occurred_at | Timestamp UTC |
ip_hash | IP 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é
- Pas de PII dans les logs applicatifs : scrub
obligatoire de tout IBAN, email, nom complet, n° de téléphone avant
écriture. Un
ProcessorMonolog s'en charge globalement (App\Monolog\PiiScrubProcessor). - IP adhérent : 6 mois maximum côté
securityet 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.yamlabsent). Activation ponctuelle pour debug → désactivation immédiate après.
Comment debug un incident
- Reproduire en staging si possible — ne jamais investiguer sur la prod si on peut éviter.
- Tailler le channel pertinent :
tail -f var/log/sepa.log,tail -f var/log/webhook.log… selon la nature de l'incident. - Cross-référencer avec Grafana : une latence anormale ? Un spike de 5xx ? Une métrique financière qui dérive ?
- Symfony Profiler en dev/staging pour rejouer la requête incriminée et inspecter SQL / cache / events.
- Tideways ponctuel si la latence est inexplicable (profiling de prod activé le temps d'un échantillon, désactivé ensuite).
- 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 :
| Composant | RAM | CPU (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.
- Monitoring & incidents — vue opérationnelle existante
- Sauvegardes & restauration — pilier intégrité
- Sécurité serveur — hardening hôte
- Runbooks incidents — procédures à suivre