Files
SIRH/docs/superpowers/specs/2026-06-28-glitchtip-backend-error-tracking-design.md
T
matthieu 42b02a8148
Auto Tag Develop / tag (push) Successful in 9s
feat : error tracking backend vers GlitchTip (via Tailscale) (#37)
## 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>
2026-06-28 11:46:35 +00:00

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_DSN est vide/absent, le SDK ne fait rien (no-op).
  • Runtime DSN : le DSN est lu à l'exécution depuis l'env_file du 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/cron exclus 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 require ajoute 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éutilise config/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-routes au tailscale 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).