Добавлено логирование

This commit is contained in:
2025-04-01 20:26:22 +03:00
parent 9e8cba5069
commit b1aefa9f11
23 changed files with 1586 additions and 114 deletions

View File

@@ -18,6 +18,7 @@ import 'package:remever/components/notifiers/app_settings.dart';
import 'package:remever/components/listeners/theme_listener.dart';
import 'package:remever/i18n/strings.g.dart';
import 'package:remever/theme/custom_theme.dart';
import 'package:talker_flutter/talker_flutter.dart';
final Completer<GlobalKey<NavigatorState>> navKeyCompleter =
Completer<GlobalKey<NavigatorState>>();
@@ -142,42 +143,41 @@ class _MyAppState extends State<MyApp>
locale: TranslationProvider.of(context).flutterLocale,
supportedLocales: AppLocaleUtils.supportedLocales,
localizationsDelegates: GlobalMaterialLocalizations.delegates,
routerConfig: globalRouter.config(),
routerConfig: globalRouter.config(
navigatorObservers:
() => <NavigatorObserver>[TalkerRouteObserver(talker)],
),
builder: (BuildContext context, Widget? child) {
return MediaQuery(
data: MediaQuery.of(
context,
).copyWith(textScaler: TextScaler.noScaling),
child: ChangeNotifierProvider<AppSettingsNotifier>(
create: (_) => settingsNotifier,
builder: (BuildContext context, Widget? nchild) {
if (nchild != null) return nchild;
return ChangeNotifierProvider<AppSettingsNotifier>(
create: (_) => settingsNotifier,
builder: (BuildContext context, Widget? nchild) {
if (nchild != null) return nchild;
return Consumer<AppSettingsNotifier>(
// TIP: должно убрать мерцание
key: const Key('consumer AppSettingsNotifier'),
child: child,
builder: (
BuildContext context,
AppSettingsNotifier value,
Widget? nchild,
) {
final Widget result = nchild ?? const SizedBox();
return Consumer<AppSettingsNotifier>(
// TIP: должно убрать мерцание
key: const Key('consumer AppSettingsNotifier'),
child: child,
builder: (
BuildContext context,
AppSettingsNotifier value,
Widget? nchild,
) {
final Widget result = nchild ?? const SizedBox();
if (value.showFps) {
return Material(
child: FPSWidget(
alignment: Alignment.centerLeft,
child: result,
),
);
}
if (value.showFps) {
return Material(
child: FPSWidget(
alignment: Alignment.centerLeft,
child: result,
),
);
}
return result;
},
);
},
),
return result;
},
);
},
);
},
);

View File

@@ -8,6 +8,8 @@ import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import 'package:remever/common/resources.dart';
import 'package:remever/components/extensions/context.dart';
import 'package:remever/router.dart';
import 'package:remever/services/logs/logs_service.dart';
import 'package:talker/talker.dart';
import 'events/events.dart';
///
@@ -17,6 +19,17 @@ AppRouter get globalRouter {
return GetIt.I.get<AppRouter>();
}
///
/// Логирование
///
LogsService get logger {
return GetIt.I.get<LogsService>();
}
Talker get talker {
return GetIt.I.get<Talker>();
}
///
/// Показ тоста
///

View File

@@ -1,8 +1,9 @@
// Package imports:
import 'package:curl_logger_dio_interceptor/curl_logger_dio_interceptor.dart';
import 'package:dio/dio.dart';
import 'package:dio_smart_retry/dio_smart_retry.dart';
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
import 'package:remever/common/functions.dart';
import 'package:talker_dio_logger/talker_dio_logger_interceptor.dart';
import 'package:talker_dio_logger/talker_dio_logger_settings.dart';
// Project imports:
import '../../components/env.dart';
@@ -62,15 +63,11 @@ Dio get apiClient {
..add(_auth)
..add(_error)
..add(
PrettyDioLogger(
request: true,
requestBody: true,
requestHeader: true,
responseBody: true,
error: true,
TalkerDioLogger(
talker: talker,
settings: const TalkerDioLoggerSettings(printRequestHeaders: true),
),
)
..add(CurlLoggerDioInterceptor())
..add(
RetryInterceptor(
dio: client,

View File

@@ -1,5 +1,6 @@
// Package imports:
import 'package:drift/drift.dart';
import 'package:remever/common/functions.dart';
import 'package:remever/database/database.dart';
import 'package:remever/database/tables.dart';
import 'package:remever/models/collection_dto.dart';
@@ -16,37 +17,55 @@ class CollectionsDao extends DatabaseAccessor<AppDatabase>
/// Получение коллекций из базы данных
Stream<List<Collection>> getCollections() {
return db.managers.collections.watch();
try {
return db.managers.collections.watch();
} catch (e, st) {
logger.logError('Ошибка в методе getCollections', e, st);
throw ('EXEPTION');
}
}
/// Создание коллекции
Future<void> createCollection(CollectionDto dto) async {
await db.managers.collections.create(
(o) => o(
title: dto.title,
desc: dto.desc,
isPublic: Value<bool>(dto.isPublic),
image: Value<String?>(dto.avatar),
),
);
try {
await db.managers.collections.create(
(o) => o(
title: dto.title,
desc: dto.desc,
isPublic: Value<bool>(dto.isPublic),
image: Value<String?>(dto.avatar),
),
);
} catch (e, st) {
logger.logError('Ошибка в методе createCollection', e, st);
}
}
/// Обновление коллекции
Future<void> updateCollection(CollectionDto dto, String id) async {
await db.managers.collections
.filter((f) => f.id(id))
.update(
(o) => o(
title: Value<String>(dto.title),
desc: Value<String>(dto.desc),
isPublic: Value<bool>(dto.isPublic),
image: Value<String?>(dto.avatar),
),
);
try {
await db.managers.collections
.filter((f) => f.id(id))
.update(
(o) => o(
title: Value<String>(dto.title),
desc: Value<String>(dto.desc),
isPublic: Value<bool>(dto.isPublic),
image: Value<String?>(dto.avatar),
),
);
} catch (e, st) {
logger.logError('Ошибка в методе updateCollection', e, st);
}
}
/// Удаление коллекции
Future<void> deleteCollection(String id) async {
await db.managers.collections.filter((f) => f.id(id)).delete();
try {
await db.managers.collections.filter((f) => f.id(id)).delete();
} catch (e, st) {
logger.logError('Ошибка в методе deleteCollection', e, st);
}
}
}

View File

@@ -6,7 +6,7 @@
/// Locales: 2
/// Strings: 20 (10 per locale)
///
/// Built on 2025-03-25 at 18:21 UTC
/// Built on 2025-04-01 at 17:23 UTC
// coverage:ignore-file
// ignore_for_file: type=lint, unused_import

View File

@@ -20,6 +20,7 @@ import 'services/collection/collections_service.dart' as _i1001;
import 'services/core/enc_keys_service.dart' as _i439;
import 'services/core/lang_service.dart' as _i68;
import 'services/core/theme_service.dart' as _i84;
import 'services/logs/logs_service.dart' as _i393;
import 'services/warmup_service.dart' as _i564;
extension GetItInjectableX on _i174.GetIt {
@@ -33,6 +34,7 @@ extension GetItInjectableX on _i174.GetIt {
gh.factory<_i439.EncKeysService>(() => _i439.EncKeysService());
gh.factory<_i84.ThemeService>(() => _i84.ThemeService());
gh.singleton<_i565.AppDatabase>(() => _i565.AppDatabase());
gh.singleton<_i393.LogsService>(() => _i393.LogsService());
gh.singleton<_i764.CollectionsInterface>(() => _i1001.CollectionsService());
gh.singleton<_i580.AuthInterface>(() => _i975.AuthService());
await gh.singletonAsync<_i564.WarmupService>(() {

View File

@@ -2,27 +2,36 @@ import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:hive_ce_flutter/hive_flutter.dart';
import 'package:remever/app.dart';
import 'package:remever/common/functions.dart';
import 'package:remever/env.dart';
import 'package:remever/i18n/strings.g.dart';
import 'package:remever/inject.dart';
import 'package:remever/router.dart';
import 'package:remever/services/logs/custom_history.dart';
import 'package:remever/services/warmup_service.dart';
import 'package:talker/talker.dart';
import 'package:talker_bloc_logger/talker_bloc_logger_observer.dart';
import 'package:talker_bloc_logger/talker_bloc_logger_settings.dart';
void _onError(Object error, StackTrace trace) {
debugPrint('error ${error.toString()}');
logger.logError('main _onError', error, trace);
}
void _flutterError(FlutterErrorDetails details) {
debugPrint('error ${details.context}');
logger.logError('main _flutterError', details.exception, details.stack);
}
bool _platformDispatcher(Object exception, StackTrace stackTrace) {
if ('$exception'.toLowerCase().contains('dio')) return true;
debugPrint('error ${exception.toString()}');
logger.logError('main _platformDispatcher', exception, stackTrace);
return true;
}
@@ -43,10 +52,26 @@ void main() {
FlutterError.onError = _flutterError;
PlatformDispatcher.instance.onError = _platformDispatcher;
final Talker talker = Talker(
history: CustomHistory(
TalkerSettings(),
history: await logger.getHistory(),
),
);
getIt.registerSingleton<AppRouter>(AppRouter());
getIt.registerSingleton<Talker>(talker);
await getIt<WarmupService>().firstStart();
Bloc.observer = TalkerBlocObserver(
talker: talker,
settings: const TalkerBlocLoggerSettings(
printCreations: true,
printClosings: true,
),
);
runApp(TranslationProvider(child: const MyApp()));
}, _onError);
}

View File

@@ -0,0 +1,15 @@
// Package imports:
import 'package:talker/talker.dart';
class BuildLog extends TalkerLog {
BuildLog(String super.message);
@override
String get title => 'Build';
@override
String? get key => 'build_log_key';
@override
AnsiPen get pen => AnsiPen()..xterm(121);
}

View File

@@ -4,9 +4,12 @@ import 'package:drift_db_viewer/drift_db_viewer.dart';
import 'package:flutter/material.dart';
import 'package:get_it_mixin/get_it_mixin.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:remever/common/functions.dart';
import 'package:remever/common/widgets/typography.dart';
import 'package:remever/common/widgets/wspace.dart';
import 'package:remever/database/database.dart';
import 'package:remever/widgets/primary_button.dart';
import 'package:talker_flutter/talker_flutter.dart';
import '../../components/notifiers/app_settings.dart';
import '../../components/env.dart';
@@ -64,6 +67,17 @@ class _SandboxScreenState extends State<SandboxScreen> {
},
child: const Text('Open Db Viewer'),
),
PrimaryButton(
child: const Text('Логи'),
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<dynamic>(
builder:
(BuildContext context) => TalkerScreen(talker: talker),
),
);
},
),
],
),
);

View File

@@ -40,7 +40,9 @@ final class AuthService implements AuthInterface {
if (response['success'] == false) return null;
return response['result']['authUid'];
} catch (e) {
} catch (e, st) {
logger.logError('Ошибка в методе login', e, st);
return null;
}
}
@@ -66,10 +68,8 @@ final class AuthService implements AuthInterface {
}
return success;
} catch (e) {
if (e is DioException) {
showErrorToast(e.response?.data['message']);
}
} catch (e, st) {
logger.logError('Ошибка в методе send code', e, st);
return false;
}

View File

@@ -0,0 +1,87 @@
// Dart imports:
import 'dart:convert';
import 'dart:io';
// Flutter imports:
import 'package:flutter/widgets.dart';
// Package imports:
import 'package:path_provider/path_provider.dart';
import 'package:talker_flutter/talker_flutter.dart';
class CustomHistory implements TalkerHistory {
CustomHistory(this.settings, {List<TalkerData>? history}) {
if (history != null) {
_history.addAll(history);
}
}
final TalkerSettings settings;
final List<TalkerData> _history = <TalkerData>[];
@override
List<TalkerData> get history => _history;
@override
void clean() async {
if (settings.useHistory) {
_history.clear();
final Directory docDir = await getApplicationDocumentsDirectory();
final String path = '${docDir.path}/history.log';
final File logFile = File(path);
if (logFile.existsSync()) {
logFile.deleteSync();
}
}
}
@override
void write(TalkerData data) {
if (settings.useHistory && settings.enabled) {
if (settings.maxHistoryItems <= _history.length) {
_history.removeAt(0);
}
_history.add(data);
_writeToFile(data);
}
}
void _writeToFile(TalkerData data) async {
try {
final Directory docDir = await getApplicationDocumentsDirectory();
final String path = '${docDir.path}/history.log';
final File logFile = File(path);
if (!logFile.existsSync()) {
logFile.createSync();
}
final Map<String, dynamic> jData = <String, dynamic>{
'error': data.error,
'exception': data.exception,
'logLevel': data.logLevel,
'key': data.key,
'message': data.message,
'stackTrace': data.stackTrace,
'title': data.title,
'time': data.time.toString(),
};
logFile.writeAsStringSync(
'${jsonEncode(jData)}\n',
mode: FileMode.writeOnlyAppend,
);
} catch (e) {
debugPrint('ALARMA ERROR in _writeToFile customHistory $e');
}
}
}

View File

@@ -0,0 +1,123 @@
// Dart imports:
import 'dart:async';
import 'dart:convert';
import 'dart:io';
// Flutter imports:
import 'package:flutter/cupertino.dart';
// Package imports:
import 'package:fluttertoast/fluttertoast.dart';
import 'package:injectable/injectable.dart';
import 'package:path_provider/path_provider.dart';
import 'package:remever/common/functions.dart';
import 'package:remever/common/typedef.dart';
import 'package:remever/models/logs/build_log.dart';
import 'package:talker_flutter/talker_flutter.dart';
@Singleton()
class LogsService {
void log(String message) {
talker.log(message);
}
void logDebug(String message) {
talker.debug(message);
}
void logInfo(String message) {
talker.info(message);
}
void logError(String message, Object? exception, StackTrace? stackTrace) {
talker.error(message, exception, stackTrace);
showErrorToast('$message ${exception.toString()}');
}
void logCritical(String message, Object? exception, StackTrace? stackTrace) {
talker.critical(message, exception, stackTrace);
}
void logBuild(String message) {
talker.logCustom(BuildLog(message));
}
// Метод для получения истории
Future<List<TalkerData>?> getHistory() async {
try {
final Directory docDir = await getApplicationDocumentsDirectory();
final String path = '${docDir.path}/history.log';
final File logFile = File(path);
if (!logFile.existsSync()) {
return null;
}
final List<String> lines = await logFile.readAsLines();
if (lines.length > 1500) {
lines.removeRange(0, lines.length - 1500);
logFile.deleteSync();
logFile.createSync();
logFile.writeAsStringSync(lines.join('\n'));
}
final List<Json> entities = await _parseLines(lines, logFile);
if (entities.isEmpty) {
return null;
}
final List<TalkerData> talkerData = _convertEntitiesToTalkerData(
entities,
);
return talkerData;
} catch (e) {
debugPrint('ALARM ERROR IN GETHISTORY $e');
return null;
}
}
// Парсинг строк из файла
Future<List<Json>> _parseLines(List<String> lines, File logFile) async {
final List<Json> entities = <Json>[];
for (String line in lines) {
try {
entities.add(jsonDecode(line));
} catch (e) {
debugPrint('Error $e in line $line of ${logFile.path}');
if (e.toString().contains('Unexpected character')) {
unawaited(logFile.delete());
break;
}
}
}
return entities;
}
// Преобразование JSON-сущностей в TalkerData
List<TalkerData> _convertEntitiesToTalkerData(List<Json> entities) {
final List<TalkerData> talkerData = <TalkerData>[];
for (Json json in entities) {
talkerData.add(
TalkerData(
json['message'],
error: json['error'],
exception: json['exception'],
key: json['key'],
logLevel: json['logLevel'],
stackTrace: json['stackTrace'],
time: DateTime.tryParse(json['time']),
title: 'SAVED ${json['title']}',
),
);
}
return talkerData;
}
}