-
Notifications
You must be signed in to change notification settings - Fork 26
Description
Kontext & Motivation
Wir wollen Logs lokal auf dem Gerät sammeln (Datenminimierung) und Nutzer:innen im Dev-Mode-Screen das Aktivieren, Einsehen und manuelle Teilen der Logs ermöglichen. Es werden keine Logs automatisch an Server gesendet. Das erfüllt das Prinzip der Datenminimierung (Art. 5(1)(c) DSGVO: „…angemessen, erheblich und auf das für die Zwecke notwendige Maß beschränkt…“). 
Entscheidung (Vorschlag)
Variante A: OpenTelemetry-Logs (JS) mit eigenem lokalen Exporter, der JSON-Lines (NDJSON) in rotierende Dateien schreibt. Vorteile:
• Standardisierte Log-API/Modell (später erweiterbar auf OTLP/HTTP, falls jemals gewünscht). 
• Einfaches, stream-freundliches Format (JSON-Lines) – robust für Datei-Append, leicht zu parsen/teilen. 
• Keine Netzwerkabhängigkeit, volle Offline-Funktionalität.
Scope
• In-Scope
• OTel-Logger-Pipeline (Provider → Batch-Processor → lokaler Datei-Exporter). 
• Dev-Mode-Screen: Logging aktivieren/deaktivieren, anzeigen, Datei teilen.
• Log-Rotation + Retention (Größe/Zeit).
• Grundlegende PII-Redaktion/Whitelist.
• Out-of-Scope
• Server-Upload, Remote-Collectors, Alerting.
⸻
Architektur & Design
High-Level
App Code ──► LogService (Port)
└── OTel Adapter (LoggerProvider + BatchProcessor)
└── FileLogExporter (JSONL via react-native-fs)
└── /AppData/Logs/*.jsonl (rotierend)
Dev-Mode-UI ─► toggelt LogService, liest Datei(en), Share-Sheet
Design-Prinzipien
• Separation of Concerns: UI kennt nur LogService (Port). OTel + FS sind Adapter.
• Dependency Inversion: App-Code loggt über LogService-Interface; OTel/FS austauschbar.
• Feature Flag / Runtime-Toggle: Logging aus per Default; nur manuell aktivierbar im Dev-Mode-Screen.
• Security & Privacy by Design: Whitelist-Attribute, PII-Redaktion, lokale Speicherung, kurze Retention; keine automatischen Exporte. OWASP-Guidelines gegen „Sensitive Data in Logs“. 
⸻
Technische Details
Libraries
• OpenTelemetry Logs (JS): @opentelemetry/api-logs, @opentelemetry/sdk-logs. 
• Filesystem: react-native-fs (oder gepflegte Forks, falls nötig). 
• Teilen: Core-API Share (Text) und/oder react-native-share (Dateien/URLs). 
Dateipfade & Backup-Exklusion (iOS)
• Logs unter …/Library/Application Support/Logs/ oder …/Documents/Logs/.
• Bei iOS: Ordner mit NSURLIsExcludedFromBackupKey anlegen, um iCloud-Backup auszuschließen. (RNFS unterstützt Flag bei mkdir). 
Datenformat
• JSON-Lines (.jsonl): ein LogRecord pro Zeile, robust für Append/Streaming. 
• Optional spätere OTLP/HTTP JSON-Kompatibilität, falls Export gewünscht. 
⸻
UI/UX (Dev-Mode-Screen)
• Schalter „Lokales Logging aktivieren“ (Default aus).
• Liste mit letzten N Einträgen (Level, Zeit, Kurztext; Tap für Details).
• Buttons: „Teilen“ (öffnet Share-Sheet mit Logdatei), „Löschen“, „Export als ZIP“ (optional).
⸻
Beispiel-Implementierung (Ausschnitt)
1) OTel Setup + Datei-Exporter (TypeScript)
// logging/otel.ts
import * as logsAPI from '@opentelemetry/api-logs';
import { LoggerProvider, BatchLogRecordProcessor, ReadableLogRecord, LogRecordExporter } from '@opentelemetry/sdk-logs';
import { Resource } from '@opentelemetry/resources';
import RNFS from 'react-native-fs';
const LOG_DIR = `${RNFS.LibraryDirectoryPath ?? RNFS.DocumentDirectoryPath}/Logs`;
const LOG_FILE = `${LOG_DIR}/app-logs.jsonl`;
export async function ensureLogDir() {
await RNFS.mkdir(LOG_DIR, { NSURLIsExcludedFromBackupKey: true }); // iOS: exclude from iCloud backup
}
class FileLogExporter implements LogRecordExporter {
constructor(private path: string) {}
export(batch: ReadableLogRecord[], done: (r:{code:number}) => void) {
const lines = batch.map(r => JSON.stringify({
ts: Date.now(),
level: r.severityText ?? 'INFO',
body: typeof r.body === 'string' ? r.body : String(r.body ?? ''),
attrs: r.attributes ?? {},
traceId: r.spanContext?.().traceId,
spanId: r.spanContext?.().spanId,
})).join('\n') + '\n';
RNFS.appendFile(this.path, lines, 'utf8').then(
() => done({ code: 0 }),
() => done({ code: 1 })
);
}
async shutdown() {}
}
let provider: LoggerProvider | null = null;
export async function startLocalLogging() {
await ensureLogDir();
provider = new LoggerProvider({ resource: new Resource({ 'service.name': 'my-rn-app' }) });
provider.addLogRecordProcessor(new BatchLogRecordProcessor(new FileLogExporter(LOG_FILE)));
logsAPI.logs.setGlobalLoggerProvider(provider);
}
export async function stopLocalLogging() {
await provider?.shutdown();
provider = null;
}
export const getLogger = () => logsAPI.logs.getLogger('app');
export const paths = { LOG_DIR, LOG_FILE };OTel-Logs: LoggerProvider/Logger/Emit sind das vorgesehene Modell. 
2) Dev-Mode-Screen (Toggle, View, Share)
// screens/DevLogsScreen.tsx
import React, { useEffect, useState } from 'react';
import { View, Text, Switch, FlatList, Button } from 'react-native';
import RNFS from 'react-native-fs';
import Share from 'react-native-share';
import { startLocalLogging, stopLocalLogging, getLogger, paths } from '../logging/otel';
export default function DevLogsScreen() {
const [enabled, setEnabled] = useState(false);
const [rows, setRows] = useState<any[]>([]);
useEffect(() => {
let id: any;
if (enabled) {
startLocalLogging().then(() => getLogger().emit({ severityText: 'INFO', body: 'Local logging enabled' }));
id = setInterval(async () => {
try {
const txt = await RNFS.readFile(paths.LOG_FILE, 'utf8');
const lines = txt.trim().split('\n').slice(-400).map(l => JSON.parse(l));
setRows(lines.reverse());
} catch {}
}, 1500);
} else {
stopLocalLogging();
setRows([]);
}
return () => { if (id) clearInterval(id); };
}, [enabled]);
const share = async () => {
await Share.open({ url: 'file://' + paths.LOG_FILE, type: 'application/json', failOnCancel: false });
};
return (
<View style={{ flex:1, padding:16, gap:12 }}>
<View style={{ flexDirection:'row', justifyContent:'space-between', alignItems:'center' }}>
<Text>Lokales Logging</Text>
<Switch value={enabled} onValueChange={setEnabled} />
</View>
<Button title="Teilen (JSONL)" onPress={share} />
<FlatList
data={rows}
keyExtractor={(_,i) => String(i)}
renderItem={({item}) => <Text>{item.level} • {item.body}</Text>}
/>
</View>
);
}Teilen via RN-Share/react-native-share. 
3) Rotation & Retention (Skizze)
• Neue Datei pro Tag (app-logs-YYYY-MM-DD.jsonl) oder nach z. B. 2 MB.
• Beim App-Start: alte Dateien > 14 Tage löschen (konfigurierbar).
⸻
Sicherheits- & Datenschutz-Leitplanken
• Default: aus; explizites Opt-In im Dev-Mode.
• Whitelist-Logging: Nur freigegebene Schlüssel/Attribute; keine PII, keine Tokens/Secrets; redigiere Muster (z. B. E-Mails, IBANs) vor dem Schreiben. (OWASP: „avoid logging sensitive data“) 
• Speicherort: App-Sandbox, iOS ohne iCloud-Backup (Backup-Exklusion). 
• Integrität/Schutz: Nur App-Zugriff; optional Verschlüsselung der Logdatei (Follow-Up).
• Löschbarkeit: „Alles löschen“-Button in der UI.
• Transparenz: Kurzer Hinweistext im Dev-Mode-Screen.
⸻
Akzeptanzkriterien (DoD)
1. Logging ist standardmäßig deaktiviert; Status persistiert app-lokal (nur für Dev-Mode).
2. Bei aktiviertem Logging werden Events als JSON-Lines in Datei geschrieben; Datei existiert nur im aktivierten Zustand. 
3. Dev-Mode-Screen zeigt die letzten N Einträge (mind. 400) ohne spürbare UI-Lags.
4. Share-Sheet kann aktuelle Logdatei erfolgreich teilen. 
5. Rotation & Retention: max. Dateigröße/Tag; automatische Bereinigung nach konfigurierbarer Frist.
6. Keine PII/Secrets in Logs (statische Checks + Tests, OWASP-konform). 
7. iOS-Logs sind von iCloud-Backups ausgeschlossen. 
⸻
Testplan
• Unit:
• FileLogExporter schreibt valide JSON-Lines (pro Zeile valides JSON).
• Redaktions-/Whitelist-Funktionen (PII-Muster).
• Integration:
• Toggle → Start/Stop-Pipeline; Append-Pfad; Rotation.
• Share-Flow (Mock/CI mit File-URL).
• E2E (Device/Simulator):
• UI-Responsiveness bei 1k/5k Zeilen.
• iOS: Ordner mit NSURLIsExcludedFromBackupKey angelegt. 
• Static/Policy:
• Linter-Regel verbietet logger-Aufrufe mit „verbotenen Keys“ (PWD, token, …).
• CI-Job scannt Log-Fixtures auf PII-Muster.
⸻
Risiken & Gegenmaßnahmen
• RNFS-Maintenance: Upstream schwankte teils; ggf. gepflegten Fork evaluieren. 
• Performance: Batch-Größe/Flush-Interval feinjustieren; UI zeigt nur letzte N Einträge.
• Fehlkonfiguration (PII): Whitelist-Ansatz + Tests + Code Reviews (OWASP). 
⸻
Alternativen
• SQLite-Storage: bessere Queries, aber höherer Aufwand.
• Ohne OTel: Custom-Logger; schneller, aber weniger standardkonform/korrigierbar.
• Collector on-device: Overkill für unseren Use-Case.
⸻
Aufgaben (Checklist)
• logging/ Modul (Port LogService, Adapter OtelLogService)
• FileLogExporter + Rotation + Retention
• Dev-Mode-Screen (Toggle, List, Share, Clear)
• PII-Whitelist + Redaktions-Helper + Tests (Unit/Integration)
• iOS: NSURLIsExcludedFromBackupKey beim Log-Ordner setzen 
• Android/iOS-Permissions/Manifeste prüfen (keine unnötigen Berechtigungen)
• CI: JSONL-Lint + PII-Pattern-Scan
• Docs: Kurze Developer-Readme „Wie loggen“ + Troubleshooting
⸻
Referenzen (Auswahl)
• OpenTelemetry Logs API/Spezifikationen. 
• OTLP Spezifikation & HTTP/JSON-Konfiguration. 
• JSON-Lines (NDJSON) Format. 
• React Native Share / react-native-share. 
• react-native-fs & iOS Backup-Exklusion. 
• DSGVO Datenminimierung. 
• OWASP (keine sensitiven Daten in Logs, Mobile-Leitlinien).