Files
umbrix/lib/features/log/data/log_repository.dart
2026-01-15 12:28:40 +03:00

130 lines
4.6 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:io';
import 'package:fpdart/fpdart.dart';
import 'package:hiddify/core/utils/exception_handler.dart';
import 'package:hiddify/features/log/data/log_parser.dart';
import 'package:hiddify/features/log/data/log_path_resolver.dart';
import 'package:hiddify/features/log/model/log_entity.dart';
import 'package:hiddify/features/log/model/log_failure.dart';
import 'package:hiddify/singbox/service/singbox_service.dart';
import 'package:hiddify/utils/custom_loggers.dart';
abstract interface class LogRepository {
TaskEither<LogFailure, Unit> init();
Stream<Either<LogFailure, List<LogEntity>>> watchLogs();
TaskEither<LogFailure, Unit> clearLogs();
}
class LogRepositoryImpl with ExceptionHandler, InfraLogger implements LogRepository {
LogRepositoryImpl({
required this.singbox,
required this.logPathResolver,
});
final SingboxService singbox;
final LogPathResolver logPathResolver;
// Ограничения на размер файлов логов
static const int _maxLogFileSize = 5 * 1024 * 1024; // 5 МБ
static const int _maxBackupFiles = 2; // Храним только 2 backup файла
@override
TaskEither<LogFailure, Unit> init() {
return exceptionHandler(
() async {
if (!await logPathResolver.directory.exists()) {
await logPathResolver.directory.create(recursive: true);
}
// Инициализация core логов с ротацией
await _initLogFileWithRotation(logPathResolver.coreFile());
// Инициализация app логов с ротацией
await _initLogFileWithRotation(logPathResolver.appFile());
return right(unit);
},
LogUnexpectedFailure.new,
);
}
/// Инициализация файла логов с автоматической ротацией
Future<void> _initLogFileWithRotation(File logFile) async {
try {
if (await logFile.exists()) {
final fileSize = await logFile.length();
// Если файл превышает лимит - делаем ротацию
if (fileSize > _maxLogFileSize) {
loggy.info('Log file too large: ${fileSize / 1024 / 1024}MB, rotating...');
await _rotateLogFile(logFile);
} else {
// Просто очищаем если размер нормальный
await logFile.writeAsString("");
}
} else {
await logFile.create(recursive: true);
}
} catch (e, st) {
loggy.warning('Error initializing log file: $e', e, st);
// В случае ошибки просто создаём новый файл
await logFile.create(recursive: true);
}
}
/// Ротация файла логов (создаём backup и очищаем текущий)
Future<void> _rotateLogFile(File logFile) async {
try {
final basePath = logFile.path;
// Удаляем самый старый backup если есть
for (int i = _maxBackupFiles; i > 0; i--) {
final oldBackup = File('$basePath.$i');
if (i == _maxBackupFiles && await oldBackup.exists()) {
await oldBackup.delete();
loggy.debug('Deleted old backup: $basePath.$i');
}
// Переименовываем backups (example.log.1 → example.log.2)
if (i > 1) {
final prevBackup = File('$basePath.${i - 1}');
if (await prevBackup.exists()) {
await prevBackup.rename('$basePath.$i');
}
}
}
// Текущий файл → backup.1
if (await logFile.exists()) {
await logFile.rename('$basePath.1');
loggy.debug('Rotated log file to: $basePath.1');
}
// Создаём новый пустой файл
await logFile.create();
loggy.info('Log file rotation completed successfully');
} catch (e, st) {
loggy.warning('Error rotating log file: $e', e, st);
// В случае ошибки просто перезаписываем файл
await logFile.writeAsString("");
}
}
@override
Stream<Either<LogFailure, List<LogEntity>>> watchLogs() {
return singbox.watchLogs(logPathResolver.coreFile().path).map((event) => event.map(LogParser.parseSingbox).toList()).handleExceptions(
(error, stackTrace) {
loggy.warning("error watching logs", error, stackTrace);
return LogFailure.unexpected(error, stackTrace);
},
);
}
@override
TaskEither<LogFailure, Unit> clearLogs() {
return exceptionHandler(
() => singbox.clearLogs().mapLeft(LogFailure.unexpected).run(),
LogFailure.unexpected,
);
}
}