import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:flutter/foundation.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> _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? details]) { _eintraege.add({ 'id': _logId++, 'zeit': DateTime.now().toIso8601String(), 'kategorie': kategorie, 'aktion': aktion, 'details': details ?? {}, }); } void aktion(String name, [Map? 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 daten) { _addEintrag('zustand', name, daten); } void manuell(String nachricht) { _addEintrag('user', nachricht); _senden(); } Future _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(); } }