Premiere commit - Omika
This commit is contained in:
@@ -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.
|
||||
@@ -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é.
|
||||
@@ -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)
|
||||
```
|
||||
@@ -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).
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
Reference in New Issue
Block a user