Flutter-App Seite: - LoggerService: Centralized Logging (DEBUG/INFO/WARNING/ERROR) - Automatic Upload zu logs.baka-net.de/api/logs - Fehler sofort senden - Andere Logs collected (batch von 20) - Device-Info: Model + OS-Version tracken - Integration in NavidromeService: Login-Fehler loggen + URL + Status - Global logger Instance für überall nutzbar Auto-Features: ✅ Timestamp + Device-Info + Error Stack-Traces ✅ Auto-Upload nach 20 Logs oder sofort bei Fehler ✅ JSON-Format für Server-Verarbeitung Nächste Schritte: - Log-Server deployen (FastAPI, SQLite, HTML-Viewer) - Caddy-Route für logs.baka-net.de - Basic Auth für Log-Seite Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XJzQjUtnvYUtHdnTs3iCru
138 lines
3.6 KiB
Dart
138 lines
3.6 KiB
Dart
import 'package:flutter/foundation.dart';
|
||
import 'package:http/http.dart' as http;
|
||
import 'package:device_info_plus/device_info_plus.dart';
|
||
import 'dart:convert';
|
||
import 'dart:io';
|
||
|
||
class LogEntry {
|
||
final String level; // DEBUG, INFO, WARNING, ERROR
|
||
final String message;
|
||
final String? stackTrace;
|
||
final DateTime timestamp;
|
||
final String? userAgent;
|
||
final String? deviceModel;
|
||
|
||
LogEntry({
|
||
required this.level,
|
||
required this.message,
|
||
this.stackTrace,
|
||
DateTime? timestamp,
|
||
this.userAgent,
|
||
this.deviceModel,
|
||
}) : timestamp = timestamp ?? DateTime.now();
|
||
|
||
Map<String, dynamic> toJson() => {
|
||
'level': level,
|
||
'message': message,
|
||
'stackTrace': stackTrace,
|
||
'timestamp': timestamp.toIso8601String(),
|
||
'userAgent': userAgent,
|
||
'deviceModel': deviceModel,
|
||
};
|
||
}
|
||
|
||
/// Centralized Logging Service mit Auto-Upload zu Server
|
||
class LoggerService extends ChangeNotifier {
|
||
static const String _logServerUrl = 'http://logs.baka-net.de/api/logs';
|
||
static const int _batchSize = 20;
|
||
|
||
final List<LogEntry> _logs = [];
|
||
late final String _deviceModel;
|
||
bool _initialized = false;
|
||
|
||
Future<void> init() async {
|
||
if (_initialized) return;
|
||
try {
|
||
final deviceInfo = DeviceInfoPlugin();
|
||
if (Platform.isAndroid) {
|
||
final info = await deviceInfo.androidInfo;
|
||
_deviceModel = '${info.manufacturer} ${info.model}';
|
||
} else {
|
||
_deviceModel = 'Unknown Device';
|
||
}
|
||
} catch (e) {
|
||
_deviceModel = 'Unknown Device';
|
||
}
|
||
_initialized = true;
|
||
debugPrint('✅ LoggerService initialized for $_deviceModel');
|
||
}
|
||
|
||
/// Logge einen Fehler (wird sofort zum Server geschickt)
|
||
void error(String message, [Object? error, StackTrace? stackTrace]) {
|
||
final entry = LogEntry(
|
||
level: 'ERROR',
|
||
message: message,
|
||
stackTrace: '$error\n$stackTrace',
|
||
deviceModel: _deviceModel,
|
||
);
|
||
_logs.add(entry);
|
||
debugPrint('❌ ERROR: $message');
|
||
_uploadIfError(entry);
|
||
}
|
||
|
||
/// Logge eine Warnung
|
||
void warning(String message) {
|
||
final entry = LogEntry(
|
||
level: 'WARNING',
|
||
message: message,
|
||
deviceModel: _deviceModel,
|
||
);
|
||
_logs.add(entry);
|
||
debugPrint('⚠️ WARNING: $message');
|
||
_checkAndFlush();
|
||
}
|
||
|
||
/// Logge Info-Event
|
||
void info(String message) {
|
||
final entry = LogEntry(
|
||
level: 'INFO',
|
||
message: message,
|
||
deviceModel: _deviceModel,
|
||
);
|
||
_logs.add(entry);
|
||
debugPrint('ℹ️ INFO: $message');
|
||
_checkAndFlush();
|
||
}
|
||
|
||
void _checkAndFlush() {
|
||
if (_logs.length >= _batchSize) {
|
||
_uploadLogs();
|
||
}
|
||
}
|
||
|
||
Future<void> _uploadIfError(LogEntry entry) async {
|
||
debugPrint('📤 Uploading ERROR immediately: ${entry.message}');
|
||
await _uploadLogs();
|
||
}
|
||
|
||
/// Hochladen der Logs zum Server
|
||
Future<void> _uploadLogs() async {
|
||
if (_logs.isEmpty) return;
|
||
try {
|
||
final logsToUpload = List<LogEntry>.from(_logs);
|
||
await http.post(
|
||
Uri.parse(_logServerUrl),
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: jsonEncode({
|
||
'logs': logsToUpload.map((l) => l.toJson()).toList(),
|
||
'timestamp': DateTime.now().toIso8601String(),
|
||
}),
|
||
).timeout(const Duration(seconds: 10));
|
||
_logs.clear();
|
||
debugPrint('✅ Logs uploaded successfully (${logsToUpload.length} entries)');
|
||
} catch (e) {
|
||
debugPrint('⚠️ Failed to upload logs: $e');
|
||
}
|
||
}
|
||
|
||
/// Manuelles Flushen (z.B. vor App-Exit)
|
||
Future<void> flush() async {
|
||
await _uploadLogs();
|
||
}
|
||
|
||
int get logCount => _logs.length;
|
||
}
|
||
|
||
/// Global Logger Instance
|
||
final logger = LoggerService();
|