📊 Feature: Auto-Bugreport System (Logger Service)
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
This commit is contained in:
co-authored by
Claude Haiku 4.5
parent
42be85a552
commit
45141a5f05
@@ -11,6 +11,7 @@ import 'player/audio_handler.dart';
|
||||
import 'player/mini_player.dart';
|
||||
import 'player/now_playing_screen.dart';
|
||||
import 'playlists/playlists_screen.dart';
|
||||
import 'services/logger_service.dart';
|
||||
import 'services/offline_mode.dart';
|
||||
import 'settings/settings_screen.dart';
|
||||
import 'shared/theme.dart';
|
||||
@@ -23,6 +24,7 @@ late final OfflineMode _offlineMode;
|
||||
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await logger.init();
|
||||
_db = MeloDb();
|
||||
_library = LibraryService(_db);
|
||||
_playlists = PlaylistService(_db);
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
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();
|
||||
@@ -6,6 +6,7 @@ import 'package:http/http.dart' as http;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'cache_manager.dart';
|
||||
import 'logger_service.dart';
|
||||
|
||||
class SubsonicSong {
|
||||
final String id;
|
||||
@@ -139,8 +140,15 @@ class NavidromeService {
|
||||
Future<bool> ping() async {
|
||||
try {
|
||||
final r = await http.get(_uri('ping.view')).timeout(const Duration(seconds: 10));
|
||||
return r.statusCode == 200;
|
||||
if (r.statusCode == 200) {
|
||||
logger.info('✅ Navidrome Verbindung erfolgreich: $_serverUrl');
|
||||
return true;
|
||||
} else {
|
||||
logger.warning('Navidrome Ping fehlgeschlagen (HTTP ${r.statusCode}): $_serverUrl');
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error('Navidrome Ping Fehler', e, StackTrace.current);
|
||||
debugPrint('Navidrome Ping Fehler: $e');
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user