127 lines
4.4 KiB
Markdown
127 lines
4.4 KiB
Markdown
|
|
# Logging — Dateieingang Service
|
||
|
|
|
||
|
|
## Stack
|
||
|
|
|
||
|
|
### Entwicklung (Dev Services)
|
||
|
|
|
||
|
|
Quarkus startet den gesamten Observability-Stack automatisch über Dev Services.
|
||
|
|
|
||
|
|
**Maven-Profil** (`pom.xml`):
|
||
|
|
```xml
|
||
|
|
<profile>
|
||
|
|
<id>grafana</id>
|
||
|
|
<dependencies>
|
||
|
|
<dependency>
|
||
|
|
<groupId>io.quarkus</groupId>
|
||
|
|
<artifactId>quarkus-observability-devservices-lgtm</artifactId>
|
||
|
|
<scope>provided</scope>
|
||
|
|
</dependency>
|
||
|
|
</dependencies>
|
||
|
|
</profile>
|
||
|
|
```
|
||
|
|
|
||
|
|
Mit `./mvnw quarkus:dev -Pgrafana` startet automatisch:
|
||
|
|
- **Loki** — Log-Aggregation
|
||
|
|
- **Grafana** — Visualisierung / Dashboards (http://localhost:3000, admin/admin)
|
||
|
|
- **Tempo** — Distributed Tracing (optional nutzbar)
|
||
|
|
- **Mimir** — Metriken (optional nutzbar)
|
||
|
|
|
||
|
|
**Laufzeit-Dependency** (immer aktiv):
|
||
|
|
```xml
|
||
|
|
<dependency>
|
||
|
|
<groupId>io.quarkus</groupId>
|
||
|
|
<artifactId>quarkus-opentelemetry</artifactId>
|
||
|
|
</dependency>
|
||
|
|
```
|
||
|
|
|
||
|
|
Quarkus schickt Logs via **OTLP** direkt an den LGTM-Stack — kein Promtail, kein Log-File-Scraping.
|
||
|
|
|
||
|
|
### Produktion
|
||
|
|
|
||
|
|
Einen OTLP-fähigen Collector vor dem produktiven Loki/Grafana-Stack betreiben:
|
||
|
|
- **Grafana Alloy** (Nachfolger von Promtail, OTLP-nativ)
|
||
|
|
- **OpenTelemetry Collector**
|
||
|
|
|
||
|
|
Quarkus-Konfiguration bleibt identisch — nur der OTLP-Endpoint-URL ändert sich.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Log-Hierarchie (MDC-Felder)
|
||
|
|
|
||
|
|
Ein ZIP-Verarbeitungslauf erzeugt Logs auf zwei Ebenen:
|
||
|
|
|
||
|
|
```
|
||
|
|
runId → ein ZIP-Verarbeitungslauf (gesetzt am Anfang jeder ZIP)
|
||
|
|
step → aktueller Pipeline-Step (gesetzt zu Beginn jedes Steps)
|
||
|
|
```
|
||
|
|
|
||
|
|
`zipFilename` wird zusätzlich als menschenlesbarer Kontext geloggt, ist aber kein MDC-Feld
|
||
|
|
(kein Filter-Kriterium, da runId eindeutiger ist).
|
||
|
|
|
||
|
|
### MDC-Felder im Detail
|
||
|
|
|
||
|
|
| Feld | Typ | Beispielwert | Gesetzt in | Gelöscht in |
|
||
|
|
|---|---|---|---|---|
|
||
|
|
| `runId` | UUID | `a1b2c3d4-...` | Beginn ZIP-Verarbeitung | `finally` nach ZIP |
|
||
|
|
| `step` | String | `oci-upload` | Beginn jedes Pipeline-Steps | Ende jedes Steps |
|
||
|
|
|
||
|
|
`MDC.clear()` wird **immer im `finally`-Block** nach jeder ZIP aufgerufen —
|
||
|
|
kein Kontext sickert in die nächste ZIP.
|
||
|
|
|
||
|
|
### Filtermöglichkeiten in Grafana (LogQL)
|
||
|
|
|
||
|
|
```logql
|
||
|
|
# Alle Logs eines Verarbeitungslaufs
|
||
|
|
{service="dateieingang"} | json | runId="a1b2c3d4"
|
||
|
|
|
||
|
|
# Alle Logs eines Pipeline-Steps über alle Läufe
|
||
|
|
{service="dateieingang"} | json | step="oci-upload"
|
||
|
|
|
||
|
|
# Nur Fehler
|
||
|
|
{service="dateieingang"} | json | level="ERROR"
|
||
|
|
|
||
|
|
# Fehler in einem bestimmten Lauf
|
||
|
|
{service="dateieingang"} | json | runId="a1b2c3d4" | level="ERROR"
|
||
|
|
```
|
||
|
|
|
||
|
|
**Loki-Labels** (wenige, statische Werte — keine hohe Kardinalität):
|
||
|
|
- `service` = `dateieingang`
|
||
|
|
- `level` = `INFO` / `WARN` / `ERROR`
|
||
|
|
- `environment` = `dev` / `prod`
|
||
|
|
|
||
|
|
Alle anderen Felder (`runId`, `step`) werden per `| json` im Query-Body gefiltert, nicht als Label.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Log-Events pro Step
|
||
|
|
|
||
|
|
| Step | Level | Event / Inhalt |
|
||
|
|
|---|---|---|
|
||
|
|
| `api-trigger` | INFO | "Processing triggered by APEX, starting async pipeline" |
|
||
|
|
| `api-trigger` | WARN | "Pipeline already running, rejecting trigger (409)" |
|
||
|
|
| `sftp-list` | INFO | "`N` new ZIP files found on SFTP" |
|
||
|
|
| `sftp-list` | INFO | "No new ZIP files found on SFTP" |
|
||
|
|
| `sftp-download` | INFO | "ZIP `<filename>` downloaded (`X` bytes)" |
|
||
|
|
| `zip-extract` | INFO | "ZIP `<filename>` extracted: `N` files, `M` directories" |
|
||
|
|
| `zip-extract` | ERROR | "ZIP `<filename>` is corrupt or unreadable" |
|
||
|
|
| `oci-upload` | INFO | "Uploaded `N` files + marker to OCI in `X`ms" |
|
||
|
|
| `oci-upload` | WARN | "OCI upload attempt `N`/3 failed (503), retrying..." |
|
||
|
|
| `oci-upload` | ERROR | "OCI upload permanently failed (403) — check credentials" |
|
||
|
|
| `sftp-rename` | INFO | "SFTP rename: `<file>` → `<file>.processed`" |
|
||
|
|
| `sftp-rename` | INFO | "SFTP rename: `<file>` → `<file>.error`" |
|
||
|
|
| `ords-notify` | INFO | "ORDS endpoint called, HTTP 200" |
|
||
|
|
| `ords-notify` | WARN | "ORDS call failed (attempt `N`/3), retrying..." |
|
||
|
|
| `ords-notify` | WARN | "ORDS notification failed — marker is present, APEX will pick up next run" |
|
||
|
|
| `cleanup` | DEBUG | "Local files deleted: `<path>`" |
|
||
|
|
| Alle | ERROR | Fehler mit vollem MDC-Kontext, Exception-Message (nie Passwörter/Keys loggen) |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Fehlerverhalten (Logging-Perspektive)
|
||
|
|
|
||
|
|
- Fehler werden **immer vor** dem Rename zu `.error` geloggt — damit der Log-Eintrag
|
||
|
|
auch dann vorhanden ist, wenn das Rename selbst fehlschlägt
|
||
|
|
- Der `runId`-Kontext bleibt für den gesamten ZIP-Verarbeitungslauf erhalten —
|
||
|
|
alle Logs einer ZIP sind in Grafana über `runId` filterbar
|
||
|
|
- Retry-Verhalten: siehe `Architecture.md` — Abschnitt Fehlerbehandlung
|