AppLogger service hozzáadása

This commit is contained in:
torok.istvan 2026-06-23 15:55:33 +02:00
parent aa78c7bb6f
commit ccf62aafce
2 changed files with 241 additions and 0 deletions

View File

@ -0,0 +1,239 @@
// Fejlesztési log service fájlba írás terepi teszteléshez.
// Az app külső tárhelyére ír, onnan könnyen elérhető.
//
// Használat:
// AppLogger.i('startRecording', 'Track indítva: $name');
// AppLogger.w('_onPosition', 'Ugrás kiszűrve: ${dist}m');
// AppLogger.e('_onPosition', 'GPS hiba', error: e);
//
// Log fájl helye: /sdcard/Android/data/hu.app_dev.terepi_seged/files/logs/
// Elérhető: Android Studio Device Explorer, adb pull, vagy fájlkezelő app
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';
enum _Level { info, warning, error }
class AppLogger extends GetxService {
static AppLogger get to => Get.find();
// Konfiguráció
/// Csak fejlesztési módban logol (release build-ben kikapcsol)
static const bool _enableInRelease = false;
/// Max log fájl méret MB-ban felette rotál
static const int _maxSizeMb = 5;
/// Max megőrzött log fájlok száma
static const int _maxFiles = 5;
// Belső állapot
File? _logFile;
IOSink? _sink;
String? _logDir;
static final _timeFmt = DateFormat('HH:mm:ss.SSS');
static final _dateFmt = DateFormat('yyyy-MM-dd');
static final _fileFmt = DateFormat('yyyy-MM-dd_HH-mm-ss');
final isEnabled = false.obs;
final currentLogPath = ''.obs;
// Lifecycle
@override
Future<void> onReady() async {
super.onReady();
if (!kDebugMode && !_enableInRelease) return;
try {
await _initLogFile();
isEnabled.value = true;
i('AppLogger', 'Log indítva — ${currentLogPath.value}');
} catch (e) {
debugPrint('AppLogger init hiba: $e');
}
}
@override
Future<void> onClose() async {
await _sink?.flush();
await _sink?.close();
super.onClose();
}
// Publikus API
/// Info szintű log
static void i(String tag, String message) =>
_write(_Level.info, tag, message);
/// Figyelmeztetés
static void w(String tag, String message, {Object? error}) =>
_write(_Level.warning, tag, message, error: error);
/// Hiba
static void e(String tag, String message,
{Object? error, StackTrace? stack}) =>
_write(_Level.error, tag, message, error: error, stack: stack);
/// Szeparátor jól látható elválasztó a logban
static void separator(String label) {
_write(_Level.info, '─────', '─── $label ───────────────────────');
}
// Log fájl megosztása
Future<void> shareLogs() async {
await _sink?.flush();
final dir = _logDir;
if (dir == null) return;
final files = Directory(dir)
.listSync()
.whereType<File>()
.where((f) => f.path.endsWith('.log'))
.toList()
..sort((a, b) => b.path.compareTo(a.path));
if (files.isEmpty) {
Get.snackbar('Log', 'Nincs log fájl',
snackPosition: SnackPosition.BOTTOM);
return;
}
// Legutóbbi fájl megosztása
await SharePlus.instance.share(ShareParams(
files: [XFile(files.first.path, mimeType: 'text/plain')],
subject: 'Terepi Segéd log — ${_dateFmt.format(DateTime.now())}',
));
}
/// Összes log fájl törlése
Future<void> clearLogs() async {
await _sink?.flush();
await _sink?.close();
_sink = null;
final dir = _logDir;
if (dir == null) return;
for (final f in Directory(dir).listSync().whereType<File>()) {
await f.delete().catchError((_) {});
}
await _initLogFile();
i('AppLogger', 'Logok törölve, új fájl nyitva');
}
// Belső
static void _write(
_Level level,
String tag,
String message, {
Object? error,
StackTrace? stack,
}) {
if (!kDebugMode && !_enableInRelease) return;
// Konzolon is megjelenik
final prefix = switch (level) {
_Level.info => '📋',
_Level.warning => '⚠️',
_Level.error => '🔴',
};
debugPrint('$prefix [$tag] $message'
'${error != null ? ' | $error' : ''}');
// Fájlba írás
try {
AppLogger.to._writeLine(level, tag, message, error: error, stack: stack);
} catch (_) {}
}
void _writeLine(
_Level level,
String tag,
String message, {
Object? error,
StackTrace? stack,
}) {
final sink = _sink;
if (sink == null) return;
final time = _timeFmt.format(DateTime.now());
final lvl = switch (level) {
_Level.info => 'I',
_Level.warning => 'W',
_Level.error => 'E',
};
sink.writeln('$time $lvl [$tag] $message');
if (error != null) sink.writeln('$error');
if (stack != null) {
// Csak az első 5 sor a stack trace-ből
final lines = stack.toString().split('\n').take(5);
for (final l in lines) sink.writeln(' $l');
}
// Méret ellenőrzés ha szükséges, rotál
_checkRotation();
}
Future<void> _initLogFile() async {
final ext = await getExternalStorageDirectory();
final dir = Directory(p.join(ext!.path, 'logs'));
await dir.create(recursive: true);
_logDir = dir.path;
final name = 'terepi_${_fileFmt.format(DateTime.now())}.log';
_logFile = File(p.join(dir.path, name));
_sink = _logFile!.openWrite(mode: FileMode.append);
currentLogPath.value = _logFile!.path;
// Fejléc
_sink!.writeln('' * 60);
_sink!.writeln('Terepi Segéd — Log');
_sink!.writeln('Dátum: ${DateTime.now().toIso8601String()}');
_sink!.writeln('' * 60);
// Régi fájlok törlése
await _pruneOldFiles(dir);
}
void _checkRotation() {
final file = _logFile;
if (file == null) return;
if (!file.existsSync()) return;
final sizeMb = file.lengthSync() / (1024 * 1024);
if (sizeMb > _maxSizeMb) {
_sink?.flush();
_sink?.close();
_initLogFile(); // új fájl
}
}
Future<void> _pruneOldFiles(Directory dir) async {
final files = dir
.listSync()
.whereType<File>()
.where((f) => f.path.endsWith('.log'))
.toList()
..sort((a, b) => a.path.compareTo(b.path));
while (files.length > _maxFiles) {
await files.removeAt(0).delete();
}
}
}

View File

@ -45,6 +45,8 @@ dependencies:
firebase_auth: ^5.5.0 firebase_auth: ^5.5.0
cloud_firestore: ^5.6.4 cloud_firestore: ^5.6.4
firebase_storage: ^12.4.3 firebase_storage: ^12.4.3
firebase_crashlytics: ^5.2.4
firebase_analytics: ^12.4.3
google_sign_in: ^6.2.2 google_sign_in: ^6.2.2
flutter_secure_storage: ^9.2.4 flutter_secure_storage: ^9.2.4
googleapis: ^13.2.0 googleapis: ^13.2.0