Premiere commit - Omika

This commit is contained in:
2026-10-23 18:46:50 +02:00
commit 8ea76c1b24
2155 changed files with 984301 additions and 0 deletions
+101
View File
@@ -0,0 +1,101 @@
# Observabilité externe Omika — Phase 5
Centralisation externe **optionnelle** de l'observabilité :
```text
OpenTelemetry (adapter Omika) → OpenTelemetry Collector → SigNoz → ClickHouse
```
> **Règle absolue** : *Les logs servent Omika. Omika ne dépend jamais des logs.*
> OpenTelemetry, Collector, SigNoz et ClickHouse apportent visibilité, recherche,
> diagnostic et corrélation. Ils n'apportent **aucune** dépendance au fonctionnement
> métier. En cas de doute : **GARANTIR OMIKA**.
## Sommaire
| Document | Contenu |
|---|---|
| [configuration.md](configuration.md) | Options `Observability:External`, kill switch, endpoints |
| [deployment.md](deployment.md) | VM, ressources, ports, installation, démarrage, rétention, sauvegarde |
| [support-guide.md](support-guide.md) | Retrouver un problème dans SigNoz depuis un TraceId |
| [unplug.md](unplug.md) | Supprimer entièrement la pile externe sans casser Omika |
| [data-whitelist.md](data-whitelist.md) | Inventaire des données exportées (liste blanche officielle) |
## Architecture finale
```text
┌── LocalFileTelemetryExporter (JSONL local)
│
IOmikaTelemetry ├── DiagnosticOutboxExporter (omika-diagnostics.db)
↓ │
SafeTelemetrySink ──┤
↓ │
PriorityQueue └── OpenTelemetryExporterAdapter ←─ RemoteDiagnosticForwarder
↓ │ ↑
TelemetryWorker OTLP HTTP/protobuf │
↓ (relit le JSONL serveur)
OTel Collector RemoteDiagnosticIngestionStore
↓ ↑
SigNoz POST /api/diagnostics
↓ ↑
ClickHouse Mobile
```
**Points clés :**
1. **OpenTelemetry est UN exporter optionnel**, derrière l'architecture Omika
(`IOmikaTelemetry → SafeTelemetrySink → PriorityTelemetryQueue → TelemetryWorker → exporters`).
Jamais devant, jamais référencé par le métier.
2. **Le mobile ne parle jamais OTLP directement.** Flux mobile :
`Mobile → POST /api/diagnostics → RemoteDiagnosticIngestionStore (JSONL serveur) → ACK`.
Le forwarding vers l'externe (`RemoteDiagnosticForwarder`) est asynchrone et
best-effort, **après** l'ACK.
3. **`POST /api/diagnostics` ne dépend jamais du Collector.** L'ACK signifie
uniquement « le serveur Omika a persisté localement le diagnostic ».
4. **Kill switch** `Observability:External:Enabled=false` (défaut) : désactive
l'adaptateur OTel et le forwarder externe, sans toucher au local.
5. **Fail-open** : arrêter Collector/SigNoz/ClickHouse, bloquer le port OTLP,
éteindre la VM, DNS/TLS invalide → **Omika fonctionne normalement** (seule
conséquence : absence temporaire de centralisation).
## Choix du protocole OTLP (§11)
| Critère | gRPC | HTTP/protobuf |
|---|---|---|
| Dépendance native | `Grpc.Net.Client` (HTTP/2, ALPN) | `HttpClient` pur |
| Simplicité Windows | moyenne (TLS/HTTP2) | **haute** |
| Firewall | un port (4317) | un port (4318) |
| Debug/curl | non | **oui** (POST clair) |
**Choix : OTLP HTTP/protobuf** (port 4318) — un seul protocole, `HttpClient` pur,
simple à déployer entre l'API Windows et le Collector. Le gRPC reste configuré côté
Collector pour interopérabilité mais n'est pas utilisé par Omika.
## Versions (réellement installées, §10)
| Package / service | Version | Notes |
|---|---|---|
| `OpenTelemetry` (SDK .NET) | **1.13.0** | stable, compatible .NET 10 |
| `OpenTelemetry.Exporter.OpenTelemetryProtocol` | **1.13.0** | train 1.13 aligné |
| OpenTelemetry Collector | 0.128.x | distribution contrib |
| SigNoz | 0.72.x (à épingler au stable officiel) | via installateur officiel |
Les packages OpenTelemetry sont localisés **uniquement** dans le projet
`src/Omika.Observability.External`. Aucun package OpenTelemetry dans `Omika.Core`,
ni dans `Omika.App`/`Omika.App.Local`/`Omika.Data` (garanti par un test d'architecture).
## Identifiants — ne pas confondre (§33/§34)
| Identifiant | Format | Rôle | Où |
|---|---|---|---|
| **Omika TraceId** | GUID (`X-Omika-TraceId`) | corrélation applicative **principale** | header HTTP, attribut `omika.trace_id` |
| **OpenTelemetry TraceId** | 128 bits | trace native OTel | dérivé déterministiquement du GUID Omika |
| **OpenTelemetry SpanId** | 64 bits | span OTel | généré par le SDK (logé en `omika.span_id`) |
| **SessionId** | GUID | une exécution d'app | attribut `omika.session_id` |
| **OperationId** | GUID | une opération métier/technique | attribut `omika.operation_id` |
| **EventId** | GUID | un événement unique (dédup/doublons) | attribut + identifiant du log |
| **BatchId** | GUID | un lot de transport mobile | Phase 4 |
Le contrat `X-Omika-TraceId` reste **inchangé** : c'est l'identifiant visible par
Omika/support. L'OTel TraceId est un mapping déterministe (GUID = 16 octets = 128 bits),
jamais un remplacement silencieux du contrat.
+80
View File
@@ -0,0 +1,80 @@
# Configuration de l'observabilité externe
## Section `Observability:External` (§74)
```json
{
"Observability": {
"External": {
"Enabled": false,
"OtlpEndpoint": "",
"ExportTimeoutSeconds": 3,
"MaxQueueSize": 2048,
"MaxExportBatchSize": 512,
"ScheduledDelayMilliseconds": 2000,
"CircuitFailureThreshold": 5,
"CircuitCooldownSeconds": 30,
"IncludeP4": false
}
}
}
```
| Clé | Défaut | Rôle |
|---|---|---|
| `Enabled` | `false` | **Kill switch**. `false` = aucun export, aucun réseau, aucun thread externe. |
| `OtlpEndpoint` | `""` | Endpoint OTLP HTTP/protobuf du Collector (ex. `http://10.0.0.10:4318`). |
| `ExportTimeoutSeconds` | `3` | Timeout réseau court d'un export OTLP (2–5 s max, §13). |
| `MaxQueueSize` | `2048` | File SDK OTel bornée (§14). |
| `MaxExportBatchSize` | `512` | Taille max d'un lot OTel. |
| `ScheduledDelayMilliseconds` | `2000` | Délai de regroupement du batch. |
| `CircuitFailureThreshold` | `5` | Échecs consécutifs avant ouverture du circuit. |
| `CircuitCooldownSeconds` | `30` | Durée d'ouverture du circuit avant sonde. |
| `IncludeP4` | `false` | Exporter les événements P4 (analytics). Non exportés par défaut (§44). |
Autres clés (forwarder) : `ForwarderBatchSize` (100), `ForwarderIntervalSeconds` (15),
`CheckpointFileName` (`forwarder-checkpoint.json`).
## Default safe (§18)
**`Enabled=false` par défaut.** Activation explicite après installation et validation
du Collector (test §88). Ne jamais activer en production sans avoir validé la chaîne.
## Kill switch (§17)
Mettre `Enabled=false` désactive **entièrement** :
- `OpenTelemetryExporterAdapter` (aucun provider, aucun réseau, aucun thread) ;
- `RemoteDiagnosticForwarder` externe (la boucle reste au ralenti).
Sans désactiver (inchangé) :
- `LocalFileTelemetryExporter` (JSONL local) ;
- `DiagnosticOutbox` (`omika-diagnostics.db`) ;
- `POST /api/diagnostics` ;
- `RemoteDiagnosticIngestionStore` (JSONL serveur).
## Configuration invalide (§75)
`Enabled=true` + `OtlpEndpoint=""` (ou URL non http/https, ou bornes invalides) :
- l'adaptateur externe est **désactivé** (NoOp) ;
- un warning `EmergencyLogger` est écrit ;
- **Omika démarre et fonctionne normalement** (jamais de crash au démarrage).
## Environnements (§73)
| Environnement | `Enabled` | Endpoint |
|---|---|---|
| Development | `false` (ou `true` + endpoint local) | `http://localhost:4318` |
| Production | explicite | `http://<ip-vm-observability>:4318` |
Les endpoints ne sont **jamais codés en dur** dans le code : ils viennent de la
configuration (appsettings / variables d'environnement / secrets).
## Secrets (§30)
Aucun secret dans un fichier versionné. Les credentials éventuels (ClickHouse) sont
fournis via `.env` (non versionné) / variables d'environnement. **Ne jamais réutiliser**
un mot de passe utilisateur, un token mobile, une clé licence ou un secret SMTP pour
l'observabilité.
+67
View File
@@ -0,0 +1,67 @@
# Liste blanche des données exportées (§79)
> On n'envoie **jamais** « tout parce que c'est utile ». Seuls les attributs ci-dessous
> sont exportés vers la plateforme externe. Tout le reste (identité métier, corps HTTP,
> contenus, secrets) reste **local**.
## Attributs de logs (exportés)
| Attribut | Raison | Cardinalité | Sensibilité | Rétention |
|---|---|---|---|---|
| `event.code` | pivot de normalisation (§39) | moyenne (catalogue borné) | faible | logs |
| `level` | sévérité | faible (6 valeurs) | faible | logs |
| `event.priority` | priorité P0–P4 | faible (5 valeurs) | faible | logs |
| `component` | émetteur | faible (liste bornée) | faible | logs |
| `omika.trace_id` | corrélation | **haute** (GUID) — OK en log, jamais en métrique | faible (GUID, pas de PII) | logs |
| `omika.session_id` | corrélation | **haute** (GUID) | faible | logs |
| `omika.operation_id` | corrélation | **haute** (GUID) | faible | logs |
| `app.version` | diagnostic par version | faible | faible | logs |
| `platform` | plateforme | faible (Windows/Android/iOS) | faible | logs |
| `os.name` / `os.version` | contexte appareil | faible | faible | logs |
| `device.model` / `device.manufacturer` | contexte appareil | faible | faible | logs |
| `error.code` | code d'erreur terminal | moyenne (codes catalogués) | faible | logs |
| `event.duration_ms` | durée | numérique | faible | logs |
| `event.success` | issue | booléen | faible | logs |
| `event.entity_type` | type d'entité | faible (énum borné) | faible | logs |
| métadonnées **filtrées** | contexte contrôlé | bornée (≤ 16, chaînes ≤ 256) | filtrée | logs |
## Attributs **jamais** exportés (§38/§71)
```text
Authorization, Password, Token, Cookie, ConnectionString,
RequestBody, ResponseBody, EmailBody, SMTP password,
documents, photos, signatures
```
et les identités métier : `DeviceId`, `ClientId`, `UserId`, `CollaborateurId`, `EntityId`
(restent locaux, uniquement utiles côté Omika).
## Labels de métriques (autorisés, low-cardinality, §37)
```text
component, status_class, method, route_template,
event_priority, level, result, reason, signal
```
**Interdit** comme label de métrique (§36) :
```text
TraceId, SpanId, SessionId, OperationId, EventId, BatchId,
UserId, ClientId, CollaborateurId, DeviceId, EntityId,
Email, URL complète, GUID métier
```
## Métriques techniques (premières, §35)
```text
omika.telemetry.accepted omika.telemetry.dropped
omika.telemetry.queue.depth
omika.external.exported omika.external.dropped
omika.external.circuit_state
omika.api.requests omika.api.errors
omika.api.duration
omika.diagnostic.forwarder.forwarded / .failure (côté API)
```
+94
View File
@@ -0,0 +1,94 @@
# Déploiement de la pile d'observabilité
## Hébergement recommandé (§21)
```text
Serveur Windows
│
├── Omika API / Worker / services métier
│
└── Hyper-V
↓
VM Ubuntu Observability
├── OpenTelemetry Collector
├── SigNoz
└── ClickHouse
```
Objectif : isoler **processus / RAM / CPU / disque** de l'observabilité du métier.
Le métier Omika a **toujours priorité** sur les ressources du serveur.
Si Hyper-V n'est pas disponible : héberger la pile sur une **machine Ubuntu distincte**
(ou un hôte Docker distinct) ; le principe d'isolation reste identique.
## Ressources initiales proposées (§23, à ajuster)
| Ressource | VM observabilité | Notes |
|---|---|---|
| vCPU | 2 (plafond 4) | ne pas allouer tous les cœurs du serveur |
| RAM | 8 Go (plafond 12 Go) | ClickHouse + SigNoz + Collector |
| Disque | 100 Go (volume dédié) | ne pas remplir le disque système Windows |
## Ports / firewall (§26, §U)
| Port | Service | Exposition |
|---|---|---|
| 4318 | Collector Omika — OTLP HTTP/protobuf | **réseau privé uniquement** (API/Worker Omika) |
| 4317 | Collector Omika — OTLP gRPC | réseau privé (désactivable) |
| 3301 | SigNoz UI | accès support autorisé uniquement (VPN/reverse-proxy + auth) |
| 8085 | SigNoz query-service | interne à la pile |
| 8123/9000 | ClickHouse | interne à la pile, **jamais** public/mobile |
Règles :
- Le port OTLP n'est **jamais** exposé publiquement.
- SigNoz est réservé à l'équipe dev/support (§27) ; ClickHouse jamais accessible
depuis le mobile/Internet/utilisateurs finaux (§28).
## Installation
1. Créer la VM Ubuntu (ressources ci-dessus).
2. Installer Docker + Docker Compose.
3. Copier `infra/observability/` sur la VM.
4. `cp .env.example .env` puis renseigner les secrets (mot de passe ClickHouse).
5. Épingler les versions d'images SigNoz aux versions stables officielles.
6. `docker compose up -d`.
7. Valider la chaîne (§88) : envoyer un événement test depuis l'API et vérifier
qu'il apparaît dans SigNoz (recherche par `EventCode` puis `TraceId`).
## Démarrage / arrêt
```bash
docker compose up -d # démarrer
docker compose down # arrêter (ne supprime pas les volumes)
```
Arrêter la pile ne doit avoir **aucun** effet sur Omika (test d'acceptation majeur, §59).
## Stockage / disque (§24)
- Volume dédié pour ClickHouse (`clickhouse-data`), **jamais** le disque système Windows.
- Taille maximale raisonnable + alerte de remplissage à configurer (ex. 80 %).
- Le disque d'observabilité ne doit pas pouvoir remplir le disque système d'Omika.
## Rétention (§25, §X)
Proposé pour une première production (à ajuster au volume observé) :
| Signal | Rétention |
|---|---|
| Logs | 7 jours |
| Traces | 7 jours |
| Métriques | 30 jours |
Ne pas retenir indéfiniment. La rétention se configure côté SigNoz/ClickHouse (TTL).
## Sauvegarde
- Sauvegarder les fichiers de configuration (`otel-collector-config.yaml`, `.env`).
- Les données ClickHouse sont de la télémétrie : leur perte n'affecte jamais Omika
(la sécurité durable reste les JSONL locaux Omika + l'outbox mobile).
## Désinstallation complète
Voir [unplug.md](unplug.md).
+36
View File
@@ -0,0 +1,36 @@
# Guide support — retrouver un problème dans SigNoz (§80/§81)
## Identifiants utiles
| Identifiant | Où le trouver | Attribut SigNoz |
|---|---|---|
| **TraceId** (`X-Omika-TraceId`) | header de réponse API, log mobile | `omika.trace_id` |
| **SessionId** | header/log | `omika.session_id` |
| **OperationId** | log de synchronisation | `omika.operation_id` |
| **EventId** | payload diagnostic | identifiant du log / attribut |
## Scénario type (§81)
1. Un utilisateur signale une erreur.
2. On dispose d'un **TraceId** (visible dans la réponse API ou le log mobile).
3. Dans SigNoz → **Logs** → filtre `omika.trace_id = <GUID>`.
4. On retrouve, corrélés par ce TraceId :
- l'événement **mobile** (s'il a été forwardé) ;
- la **requête API** (span HTTP dérivée) ;
- l'**erreur serveur** (EventCode `OMK-API-...`).
Aucune donnée sensible n'est exposée : les attributs exportés sont uniquement ceux de
la [liste blanche](data-whitelist.md).
## Codes d'événements (pivot, §39)
Les `EventCode` restent le pivot de normalisation (`OMK-MOB-...`, `OMK-API-...`,
`OMK-DIAG-...`). Ne jamais remplacer ces codes par du texte libre. Rechercher par
`EventCode` pour agréger, puis affiner par `TraceId`/`SessionId`.
## Doublons (§42)
La chaîne est **at-least-once** : des doublons peuvent apparaître dans SigNoz
(réessai après panne, checkpoint perdu). Utiliser `EventId` pour les identifier.
La déduplication centrale n'est pas critique et peut être ajoutée plus tard si
nécessaire, sans rendre le pipeline critique.
+41
View File
@@ -0,0 +1,41 @@
# Procédure « unplug » — supprimer l'infrastructure externe sans casser Omika (§83)
## Objectif
Supprimer **entièrement** OpenTelemetry Collector / SigNoz / ClickHouse et vérifier
qu'Omika continue de fonctionner normalement. C'est un **test d'acceptation obligatoire**.
## Étapes
1. **Kill switch** : mettre `Observability:External:Enabled=false` dans la
configuration de l'API et du Worker.
2. **Redémarrer** l'API et le Worker (ou recharger la configuration).
3. **Arrêter la pile** sur la VM :
```bash
docker compose down
```
4. **Supprimer les volumes** (télémétrie externe) :
```bash
docker compose down -v
```
5. **Supprimer la VM** observabilité (Hyper-V) si la désinstallation est définitive.
6. **Vérifier** le fonctionnement complet d'Omika (critères §97/H) :
- `LocalFileTelemetryExporter` écrit les JSONL locaux ;
- `DiagnosticOutbox` (`omika-diagnostics.db`) fonctionne ;
- `POST /api/diagnostics` répond (ACK après persistance locale) ;
- `RemoteDiagnosticIngestionStore` écrit le JSONL serveur ;
- API métier, Worker, synchronisation, mobile : **tout fonctionne**.
## Résultat attendu
```text
Omika totalement fonctionnel.
Seule conséquence : absence de centralisation externe des diagnostics.
```
## Re-branchement (§91)
Réinstaller la pile, remettre `Enabled=true` avec un endpoint valide, redémarrer.
Les **nouveaux** événements recommencent à arriver. Aucun replay des événements
perdus hors mécanismes déjà persistés (les JSONL locaux + l'outbox restent la
sécurité durable).