## Objectif Remonter les erreurs **backend** Symfony vers **GlitchTip** (SDK Sentry), **prod uniquement**, **inerte sans `SENTRY_DSN`**. Transport réseau via **Tailscale** sur le host de prod (infra, hors repo). Frontend hors périmètre. ## Contenu - `sentry/sentry-symfony:^5.10` (+ `symfony.lock` recipe) - `config/bundles.php` → `SentryBundle ['prod' => true]` - `config/packages/sentry.yaml` (nouveau) : DSN runtime, release `%app.version%`, 4xx ignorés, pas de tracing, handler Monolog ERROR+ - `config/packages/monolog.yaml` : handler `sentry` en `when@prod` - `.env` : bloc `SENTRY_DSN` documenté (vide → inerte) - `doc/error-tracking.md` (runbook Tailscale) + section `CLAUDE.md` - Spec + plan sous `docs/superpowers/` ## Vérifications - Prod `cache:clear` OK, service `Sentry\Monolog\Handler` chargé - **267/267 tests verts**, dev/test inchangés (bundle non chargé hors prod) - Aucun changement `frontend/` / `.gitea/` / `deploy/docker/` - Revue multi-agents : **READY TO MERGE** (aucun Critical/Important) ## Activation prod (hors code, cf. `doc/error-tracking.md`) 1. Tailscale sur l'hôte GlitchTip **et** sur le VPS OVH (prod) 2. Créer le projet `sirh-api` dans GlitchTip → récupérer le DSN 3. `SENTRY_DSN=http://<clé>@<IP-tailnet>:<port>/<id>` dans l'env_file serveur + redéploiement 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed-on: #37 Co-authored-by: matthieu <matthieu@yuno.malio.fr> Co-committed-by: matthieu <matthieu@yuno.malio.fr>
9.7 KiB
Error tracking backend SIRH → GlitchTip (via Tailscale)
Date : 2026-06-28 Périmètre : backend Symfony uniquement, prod only, transport Tailscale. Référence pattern : projet Lesstime (
config/packages/sentry.yaml,README.md§ Error tracking).
1. Contexte & contrainte
GlitchTip (instance auto-hébergée MALIO, compatible SDK Sentry) vit sur le réseau interne,
bloqué par Sophos, sur le domaine interne logs.malio-dev.fr (DNS local, CA auto-signée).
SIRH tourne sur un VPS OVH (Internet public) → le container PHP ne peut pas joindre l'interne.
Décision : on monte un tunnel Tailscale sur le host de prod OVH. Le container PHP atteint
GlitchTip par le tailnet. Backend seulement pour l'instant (les erreurs front partent du
navigateur RH, hors périmètre — pourra être ajouté plus tard via un proxy nginx /ingest).
Flux retenu :
| Flux | Source | Chemin vers GlitchTip |
|---|---|---|
| Backend Symfony | container PHP sur le VPS OVH | → host Tailscale → tailnet → GlitchTip ✅ |
| Frontend SPA | navigateur RH | hors périmètre (pas de SDK front) |
2. Principes
- Prod only : le bundle n'est enregistré que pour
prod. En dev/test : zéro impact. - Inerte sans DSN : si
SENTRY_DSNest vide/absent, le SDK ne fait rien (no-op). - Runtime DSN : le DSN est lu à l'exécution depuis l'
env_filedu serveur, jamais baké dans l'image (pas de secret dans le repo ni dans l'image Docker). - Pas d'APM/tracing (
traces_sample_rate: 0) : on ne remonte que les erreurs. - Bruit filtré : 4xx HTTP (404/405/AccessDenied) ignorés ; channels
event/doctrine/ deprecation/cronexclus du handler Monolog.
3. Changements de code (repo SIRH)
3.1 Dépendance
make shell # ou docker exec dans le container php
composer require sentry/sentry-symfony:^5.10
Met à jour composer.json + composer.lock. Version identique à Lesstime (stack PHP 8.4 /
Symfony 8 commune).
3.2 config/bundles.php
Ajouter l'enregistrement prod-only :
use Sentry\SentryBundle\SentryBundle;
// ...
SentryBundle::class => ['prod' => true],
composer requireajoute généralement la ligne['all' => true]via Flex — la corriger en['prod' => true].
3.3 config/packages/sentry.yaml (nouveau fichier)
# Error tracking → GlitchTip (compatible SDK Sentry).
# Actif uniquement en prod (bundle enregistré prod-only dans bundles.php).
# Si SENTRY_DSN est vide/non défini, le SDK est inerte (rien n'est envoyé).
when@prod:
parameters:
env(SENTRY_DSN): ''
sentry:
dsn: '%env(SENTRY_DSN)%'
# On capture les erreurs fatales PHP via le handler, mais on DÉSACTIVE le listener
# kernel pour éviter les doublons avec le handler Monolog (les exceptions du kernel
# sont déjà logguées par Symfony → remontées via Monolog).
register_error_listener: false
register_error_handler: true
options:
environment: '%env(APP_ENV)%'
release: '%app.version%'
traces_sample_rate: 0.0
ignore_exceptions:
- Symfony\Component\HttpKernel\Exception\NotFoundHttpException
- Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException
- Symfony\Component\Security\Core\Exception\AccessDeniedException
# Handler Monolog → Sentry : remonte les logs niveau ERROR+ comme Issues GlitchTip.
services:
Sentry\Monolog\Handler:
arguments:
$hub: '@Sentry\State\HubInterface'
$level: !php/const Monolog\Level::Error
$bubble: true
release: '%app.version%'réutiliseconfig/version.yaml(app.version, ex.0.1.127).
3.4 config/packages/monolog.yaml
Dans le bloc when@prod.monolog.handlers, ajouter :
# Remonte les logs ERROR+ vers GlitchTip en tant qu'Issues (service défini dans
# sentry.yaml). Envoi immédiat, indépendamment des handlers fichier.
sentry:
type: service
id: Sentry\Monolog\Handler
channels: ["!event", "!doctrine", "!deprecation", "!cron"]
Les autres handlers (
main,cron,deprecation) restent inchangés.
3.5 .env (+ .env.example si présent)
Bloc documenté (valeur réelle injectée côté serveur uniquement) :
###> sentry/sentry-symfony ###
# Error tracking backend → GlitchTip (projet "sirh-api"). Prod only, vide => inerte.
# À définir dans l'env_file du serveur, PAS ici. Format :
# SENTRY_DSN=http://<clé>@<host-ou-IP-tailnet>:<port>/<id-projet>
# SENTRY_DSN=
###< sentry/sentry-symfony ###
3.6 CI / Dockerfile
Aucun changement requis pour le backend : le DSN est runtime (env_file). La CI
(.gitea/workflows/build-docker.yml) ne build/push que l'image — rien à toucher.
CA TLS (conditionnel) — voir §4.4 : nécessaire uniquement si le DSN cible l'HTTPS interne
logs.malio-dev.fr. Si on tape l'endpoint HTTP GlitchTip via le tailnet (recommandé), pas de
modif Dockerfile.
4. Runbook infra (hors repo) — toutes les étapes & commandes
4.1 Installer Tailscale sur le host de prod OVH
# Sur le serveur OVH (Debian/Ubuntu), en root/sudo :
curl -fsSL https://tailscale.com/install.sh | sh
# Jointure du tailnet (ouvre une URL d'auth, ou utiliser une auth key headless) :
sudo tailscale up
# --- headless (CI/scripté) :
# sudo tailscale up --authkey tskey-auth-XXXXXXXXXXXX
# Vérifier l'état et récupérer l'IP tailnet du serveur :
tailscale status
tailscale ip -4
Si GlitchTip est sur une autre machine du tailnet : noter son IP tailnet (
100.x.y.z) ou son nom MagicDNS. Si GlitchTip est derrière un subnet router (LAN interne non tailnet) : ajouter--accept-routesautailscale up, et s'assurer qu'un subnet router annonce le sous-réseau.
4.2 Vérifier la connectivité host → GlitchTip via le tailnet
# Depuis le host OVH :
tailscale ping <glitchtip-tailnet-name-ou-IP>
curl -sS -o /dev/null -w "%{http_code}\n" http://<glitchtip-IP-tailnet>:<port>/_health/ # → 200 attendu
4.3 Rendre le tailnet joignable depuis le container PHP
Le container PHP est sur le réseau bridge Docker, pas directement sur le tailnet. Deux options :
Option A — Host Tailscale + IP tailnet dans le DSN (recommandé, simple).
L'egress du container est masqueradé par le host, qui route 100.x.y.z via tailscale0.
→ Pointer SENTRY_DSN directement sur l'IP tailnet de GlitchTip (pas MagicDNS, que le
container ne résout pas). Optionnellement figer le nom via extra_hosts dans le compose :
# docker-compose.yml (serveur)
extra_hosts:
- "glitchtip.tailnet:100.x.y.z"
Prérequis : IP forwarding actif sur le host (net.ipv4.ip_forward=1, déjà posé par l'install
Tailscale).
Option B — Sidecar Tailscale (robuste, si A ne route pas).
Service tailscale/tailscale dans le compose, et le container app en
network_mode: service:tailscale → l'app partage l'interface tailnet (MagicDNS dispo).
À retenir seulement si l'option A ne fonctionne pas.
4.4 (Conditionnel) CA racine MALIO — uniquement si DSN = HTTPS interne
Si le DSN cible https://logs.malio-dev.fr (cert auto-signé), baker la CA dans l'image
(deploy/docker/Dockerfile.prod, stage production) — ca-certificates est déjà installé :
COPY deploy/docker/malio-dev-root-ca.crt /usr/local/share/ca-certificates/malio-dev-root-ca.crt
RUN update-ca-certificates
(Le .crt public est récupérable depuis le repo Lesstime : infra/prod/malio-dev-root-ca.crt.)
Vérification :
curl --cacert deploy/docker/malio-dev-root-ca.crt https://logs.malio-dev.fr/api/<id>/store/
Recommandation : préférer l'endpoint HTTP via le tailnet (déjà chiffré par WireGuard) → on évite complètement la CA et cette modif Dockerfile.
4.5 Créer le projet GlitchTip sirh-api
Dans l'UI GlitchTip (org malio) : New Project → plateforme php-symfony → nom sirh-api.
Récupérer le DSN dans Settings → Client Keys (DSN). Adapter le host du DSN à l'IP/nom tailnet
si nécessaire.
Le MCP GlitchTip est en lecture seule (pas de
create_project) → création manuelle UI.
4.6 Injecter le DSN sur le serveur
Ajouter à l'env_file du docker-compose serveur (PAS dans l'image), puis redéployer :
SENTRY_DSN=http://<clé>@100.x.y.z:<port>/<id-sirh-api>
docker compose up -d # recharge l'env_file
docker compose exec php php bin/console cache:clear --env=prod
5. Documentation (règles SIRH)
doc/error-tracking.md(nouveau) : pattern back, activation, runbook Tailscale, CA, lien vers ce spec.CLAUDE.md: nouvelle section « Error tracking (GlitchTip) » résumant le pattern + le fait que c'est prod-only / inerte sans DSN / transport Tailscale.- In-app documentation (
frontend/data/documentation-content.ts) : non concernée — infra invisible pour les utilisateurs RH (employé/chef de site/admin), aucun changement fonctionnel UI.
6. Vérification
| Niveau | Test | Attendu |
|---|---|---|
| Dev (sans DSN) | make test, boot dev |
aucune régression, SDK absent en dev |
| Prod config | build image + APP_ENV=prod cache:clear (DSN bidon) |
bundle chargé, pas d'erreur de conf |
| Inerte | prod sans SENTRY_DSN |
aucun envoi, no-op |
| End-to-end | une fois Tailscale + projet OK : déclencher une erreur ERROR+ | Issue visible dans GlitchTip sirh-api |
7. Hors périmètre (explicite)
- Frontend (SDK Nuxt, source maps, build-args CI) — ajout futur via proxy nginx
/ingest. - APM / tracing / performance (DuckDB-like) — non.
- Exposition publique de GlitchTip — non (tout passe par Tailscale).