This repository has been archived on 2026-08-19. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
melo-app/lib/services/melo_logger.dart
T
Hermes (Server) 9a880b0f21 Security: Cloud-Auth auf Bearer-JWT umgestellt (IDOR-Luecke geschlossen)
- cloud_service: echter Login gegen baka-auth, Token statt X-API-Key/X-User
- app_config: hartcodierten API-Key-Default entfernt
- download_service + melo_logger: Bearer-Token statt X-API-Key
- navidrome: Passwort in flutter_secure_storage (Keychain/Keystore)
- song: token-haltige stream_url wird nicht mehr in SQLite persistiert
- cloud_screen: Pfad-Traversal beim Download-Dateinamen gefixt (p.basename)
- home_screen: Login-Dialog mit Passwort-Feld, Auto-Sync nutzt restoreLogin
2026-07-31 14:31:12 +02:00

131 lines
3.6 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import '../config/app_config.dart';
/// Umfassendes Log-System für die Melo App.
/// Zeichnet ALLES auf: Aktionen, Fehler, Netzwerk, Performance.
class MeloLogger {
static final MeloLogger _instanz = MeloLogger._();
factory MeloLogger() => _instanz;
MeloLogger._();
/// Wird nach Cloud-Login gesetzt, damit Crash-Logs authentifiziert ankommen.
static String? cloudToken;
bool _initialisiert = false;
String _sessionId = '';
String _version = '2.7';
final List<Map<String, dynamic>> _eintraege = [];
int _logId = 0;
Timer? _flushTimer;
String get _serverUrl => '${AppConfig.logUrl}/api/log';
void init(String version) {
if (_initialisiert) return;
_initialisiert = true;
_version = version;
_sessionId = DateTime.now().millisecondsSinceEpoch.toString();
_flushTimer = Timer.periodic(const Duration(seconds: 30), (_) => _senden());
_addEintrag('app', 'start', {'version': version, 'session': _sessionId});
FlutterError.onError = (details) {
FlutterError.presentError(details);
_addEintrag('crash', 'flutter_error', {
'fehler': details.exceptionAsString(),
'stack': details.stack.toString(),
});
};
PlatformDispatcher.instance.onError = (error, stack) {
_addEintrag('crash', 'dart_error', {
'fehler': error.toString(),
'stack': stack.toString(),
});
return true;
};
debugPrint('📋 MeloLogger ready (Session: $_sessionId)');
}
void _addEintrag(String kategorie, String aktion, [Map<String, dynamic>? details]) {
_eintraege.add({
'id': _logId++,
'zeit': DateTime.now().toIso8601String(),
'kategorie': kategorie,
'aktion': aktion,
'details': details ?? {},
});
}
void aktion(String name, [Map<String, dynamic>? details]) {
_addEintrag('aktion', name, details);
}
void netzwerk(String methode, String url, {int? statusCode, String? fehler, int? dauerMs}) {
_addEintrag('netzwerk', '$methode $url', {
if (statusCode != null) 'status': statusCode,
if (fehler != null) 'fehler': fehler,
if (dauerMs != null) 'dauer_ms': dauerMs,
});
}
void fehler(String kontext, Object? error, [StackTrace? stack]) {
_addEintrag('fehler', kontext, {
'error': error.toString(),
'stack': stack?.toString() ?? '',
});
_senden();
}
void performance(String name, int dauerMs) {
_addEintrag('performance', name, {'dauer_ms': dauerMs});
}
void zustand(String name, Map<String, dynamic> daten) {
_addEintrag('zustand', name, daten);
}
void manuell(String nachricht) {
_addEintrag('user', nachricht);
_senden();
}
Future<void> _senden() async {
if (_eintraege.isEmpty) return;
if (!AppConfig.sendeDiagnosedaten) return;
final batch = _eintraege.toList();
_eintraege.clear();
try {
await http.post(
Uri.parse(_serverUrl),
headers: {
'Content-Type': 'application/json',
if (cloudToken != null) 'Authorization': 'Bearer $cloudToken',
},
body: jsonEncode({
'typ': 'log_batch',
'session': _sessionId,
'version': _version,
'device': '${Platform.operatingSystem} ${Platform.operatingSystemVersion}',
'eintraege': batch,
}),
).timeout(const Duration(seconds: 5));
} catch (_) {
_eintraege.insertAll(0, batch);
}
}
void dispose() {
_flushTimer?.cancel();
_senden();
}
}