📊 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/mini_player.dart';
|
||||||
import 'player/now_playing_screen.dart';
|
import 'player/now_playing_screen.dart';
|
||||||
import 'playlists/playlists_screen.dart';
|
import 'playlists/playlists_screen.dart';
|
||||||
|
import 'services/logger_service.dart';
|
||||||
import 'services/offline_mode.dart';
|
import 'services/offline_mode.dart';
|
||||||
import 'settings/settings_screen.dart';
|
import 'settings/settings_screen.dart';
|
||||||
import 'shared/theme.dart';
|
import 'shared/theme.dart';
|
||||||
@@ -23,6 +24,7 @@ late final OfflineMode _offlineMode;
|
|||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
await logger.init();
|
||||||
_db = MeloDb();
|
_db = MeloDb();
|
||||||
_library = LibraryService(_db);
|
_library = LibraryService(_db);
|
||||||
_playlists = PlaylistService(_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:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'cache_manager.dart';
|
import 'cache_manager.dart';
|
||||||
|
import 'logger_service.dart';
|
||||||
|
|
||||||
class SubsonicSong {
|
class SubsonicSong {
|
||||||
final String id;
|
final String id;
|
||||||
@@ -139,8 +140,15 @@ class NavidromeService {
|
|||||||
Future<bool> ping() async {
|
Future<bool> ping() async {
|
||||||
try {
|
try {
|
||||||
final r = await http.get(_uri('ping.view')).timeout(const Duration(seconds: 10));
|
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) {
|
} catch (e) {
|
||||||
|
logger.error('Navidrome Ping Fehler', e, StackTrace.current);
|
||||||
debugPrint('Navidrome Ping Fehler: $e');
|
debugPrint('Navidrome Ping Fehler: $e');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user