feat:日志修改
This commit is contained in:
1 parent
da3eee48c1
commit
5b9ef49564
5 files changed
+147
-49
No files matched your search
@@ -8,6 +8,7 @@ import 'package:logger/logger.dart';
|
||||
import 'package:pj_supermarket_cloud/common/index.dart';
|
||||
|
||||
final logger = Logger(
|
||||
filter: AppLogFilter(),
|
||||
printer: customPrinter,
|
||||
output: ConsoleOutput(),
|
||||
);
|
||||
@@ -18,38 +19,58 @@ final customPrinter = PrettyPrinter(
|
||||
colors: true,
|
||||
printEmojis: false,
|
||||
printTime: true,
|
||||
noBoxingByDefault: true,
|
||||
);
|
||||
|
||||
class AppLogFilter extends LogFilter {
|
||||
@override
|
||||
bool shouldLog(LogEvent event) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class ConsoleOutput extends LogOutput {
|
||||
String? _filePath;
|
||||
IOSink? _sink;
|
||||
Timer? _timer;
|
||||
Future<void> _queue = Future.value();
|
||||
|
||||
Future<void> _enqueue(Future<void> Function() action) {
|
||||
_queue = _queue.then((_) => action()).catchError((e, stack) {
|
||||
print("写入日志错误error:|$e|stack:|$stack|");
|
||||
});
|
||||
return _queue;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
try {
|
||||
// 关闭旧的sink并创建新的
|
||||
await _sink?.flush();
|
||||
await _sink?.close();
|
||||
_filePath = await AppFileTool.getLoggerBackupPath();
|
||||
if (ObjectUtil.isNotEmpty(_filePath)) {
|
||||
_sink = File(_filePath!).openWrite(
|
||||
mode: FileMode.writeOnlyAppend,
|
||||
encoding: utf8,
|
||||
);
|
||||
}
|
||||
await _enqueue(() async {
|
||||
await _sink?.flush();
|
||||
await _sink?.close();
|
||||
_filePath = await AppFileTool.getLoggerBackupPath();
|
||||
if (ObjectUtil.isNotEmpty(_filePath)) {
|
||||
_sink = File(_filePath!).openWrite(
|
||||
mode: FileMode.writeOnlyAppend,
|
||||
encoding: utf8,
|
||||
);
|
||||
} else {
|
||||
_sink = null;
|
||||
}
|
||||
});
|
||||
_timer = Timer.periodic(const Duration(seconds: 60 * 60 * 2),
|
||||
(Timer timer) async {
|
||||
final path = await AppFileTool.getLoggerBackupPath();
|
||||
if (path == null) return;
|
||||
// 关闭旧的sink并创建新的
|
||||
await _sink?.flush();
|
||||
await _sink?.close();
|
||||
_filePath = path;
|
||||
_sink = File(_filePath!).openWrite(
|
||||
mode: FileMode.writeOnlyAppend,
|
||||
encoding: utf8,
|
||||
);
|
||||
unawaited(_enqueue(() async {
|
||||
await _sink?.flush();
|
||||
await _sink?.close();
|
||||
_filePath = path;
|
||||
_sink = File(_filePath!).openWrite(
|
||||
mode: FileMode.writeOnlyAppend,
|
||||
encoding: utf8,
|
||||
);
|
||||
}));
|
||||
});
|
||||
} catch (e, stack) {
|
||||
print("初始化日志错误error:|$e|stack:|$stack|");
|
||||
@@ -58,9 +79,13 @@ class ConsoleOutput extends LogOutput {
|
||||
|
||||
@override
|
||||
Future<void> destroy() async {
|
||||
await _sink?.flush();
|
||||
await _sink?.close();
|
||||
_timer?.cancel();
|
||||
_timer = null;
|
||||
await _enqueue(() async {
|
||||
await _sink?.flush();
|
||||
await _sink?.close();
|
||||
_sink = null;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -70,12 +95,14 @@ class ConsoleOutput extends LogOutput {
|
||||
for (final v in event.lines) {
|
||||
print(v);
|
||||
}
|
||||
} else {
|
||||
if (_filePath == null) return;
|
||||
if (_sink == null) return;
|
||||
if (Level.debug == event.level) return;
|
||||
_sink?.writeAll(event.lines, '\n');
|
||||
}
|
||||
unawaited(_enqueue(() async {
|
||||
if (_filePath == null || _sink == null) return;
|
||||
if (Level.debug == event.level) return;
|
||||
_sink!.writeAll(event.lines, '\n');
|
||||
_sink!.writeln();
|
||||
await _sink!.flush();
|
||||
}));
|
||||
} catch (e, stack) {
|
||||
print("写入日志错误error:|$e|stack:|$stack|");
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:mqtt_client/mqtt_client.dart';
|
||||
|
||||
@@ -44,6 +45,7 @@ class SyncCheckTool {
|
||||
checkPrint();
|
||||
checkBroadcast();
|
||||
checkDeviceStatus();
|
||||
checkExpiredLog();
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
@@ -156,4 +158,54 @@ class SyncCheckTool {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 检查过期日志(超过3个月的日志文件都要删除)
|
||||
checkExpiredLog() async {
|
||||
const logMsg = "检查过期日志";
|
||||
try {
|
||||
final backupPath = await AppFileTool.getLoggerBackupPath();
|
||||
if (backupPath == null || backupPath.isEmpty) {
|
||||
logger.w("|$_tag|$logMsg,日志备份路径为空");
|
||||
return;
|
||||
}
|
||||
final loggerBackupDir = Directory(backupPath).parent.parent;
|
||||
if (!await loggerBackupDir.exists()) {
|
||||
logger.i("|$_tag|$logMsg,日志目录不存在 path:${loggerBackupDir.path}");
|
||||
return;
|
||||
}
|
||||
final expireBefore = DateTime(
|
||||
DateTime.now().year,
|
||||
DateTime.now().month - 3,
|
||||
DateTime.now().day,
|
||||
);
|
||||
final entities = loggerBackupDir.listSync(recursive: true);
|
||||
int deleteCount = 0;
|
||||
for (final entity in entities) {
|
||||
if (entity is! File) continue;
|
||||
if (!entity.path.endsWith(".log")) continue;
|
||||
final stat = await entity.stat();
|
||||
if (stat.modified.isBefore(expireBefore)) {
|
||||
await entity.delete();
|
||||
deleteCount++;
|
||||
logger.i("|$_tag|$logMsg,删除过期日志 path:${entity.path}");
|
||||
}
|
||||
}
|
||||
final monthDirs = loggerBackupDir
|
||||
.listSync()
|
||||
.whereType<Directory>()
|
||||
.toList()
|
||||
..sort((a, b) => b.path.compareTo(a.path));
|
||||
for (final dir in monthDirs) {
|
||||
if (!await dir.exists()) continue;
|
||||
final isEmpty = dir.listSync().isEmpty;
|
||||
if (isEmpty) {
|
||||
await dir.delete();
|
||||
logger.i("|$_tag|$logMsg,删除空目录 path:${dir.path}");
|
||||
}
|
||||
}
|
||||
logger.i("|$_tag|$logMsg,执行完成,删除日志数量:$deleteCount");
|
||||
} catch (e, stack) {
|
||||
logger.e("|$_tag|$logMsg,执行失败,error:$e,stack:$stack");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,6 +92,7 @@ class TerminalHeader extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = FTheme.of(context);
|
||||
final menuController = Get.find<TerminalMenuControl>();
|
||||
return Container(
|
||||
height: 80.h,
|
||||
padding: EdgeInsets.symmetric(horizontal: 20.w),
|
||||
@@ -108,7 +109,17 @@ class TerminalHeader extends StatelessWidget {
|
||||
child: IntrinsicWidth(
|
||||
child: Row(
|
||||
children: [
|
||||
const TerminalBrand(),
|
||||
Obx(() {
|
||||
final soleMenu = menuController.soleVisibleBusinessMenu;
|
||||
return TerminalBrand(
|
||||
targetMenu: soleMenu,
|
||||
onTap: soleMenu == null
|
||||
? null
|
||||
: () => TerminalNavigationTool.open(
|
||||
_terminalNavItem(soleMenu).route,
|
||||
),
|
||||
);
|
||||
}),
|
||||
SizedBox(width: 20.w),
|
||||
Container(
|
||||
width: 1,
|
||||
@@ -192,12 +203,15 @@ class TerminalHeader extends StatelessWidget {
|
||||
/// 这里使用受约束的 `FittedBox` 保留 React 原型中的斜体、紧字距效果,同时避免
|
||||
/// 大字号设置把品牌挤出 80 高的终端顶栏。
|
||||
class TerminalBrand extends StatelessWidget {
|
||||
const TerminalBrand({super.key});
|
||||
const TerminalBrand({super.key, this.targetMenu, this.onTap});
|
||||
|
||||
final TerminalMenuItemType? targetMenu;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = FTheme.of(context);
|
||||
return SizedBox(
|
||||
final brand = SizedBox(
|
||||
width: 100.w,
|
||||
height: 55.h,
|
||||
child: FittedBox(
|
||||
@@ -214,6 +228,22 @@ class TerminalBrand extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
);
|
||||
if (onTap == null || targetMenu == null) return brand;
|
||||
|
||||
// 侧栏因仅剩一个业务菜单而收起时,品牌作为返回该入口的固定兜底;
|
||||
// 实际跳转仍交给统一导航守卫处理绑定、联网和设置密码校验。
|
||||
return Semantics(
|
||||
button: true,
|
||||
label: '返回${targetMenu!.title}',
|
||||
child: Tooltip(
|
||||
message: '返回${targetMenu!.title}',
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(XfjDesignTokens.controlRadius),
|
||||
child: brand,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -120,11 +120,11 @@ class FaceSettingController extends GetxController {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> resetRecognitionConfiguration() async {
|
||||
Future<void> restoreDefaultRecognitionConfiguration() async {
|
||||
if (isSavingRecognitionConfiguration.value) return;
|
||||
final confirmed = await appMessageDialog(
|
||||
Get.context!,
|
||||
'是否恢复默认人脸识别策略?活体检测将恢复为关闭,相似度阈值恢复为 80%。',
|
||||
'是否还原为默认人脸识别策略?活体检测将关闭,相似度阈值将还原为 80%。',
|
||||
);
|
||||
if (!confirmed) return;
|
||||
isSavingRecognitionConfiguration.value = true;
|
||||
@@ -135,23 +135,19 @@ class FaceSettingController extends GetxController {
|
||||
final active = Get.isRegistered<FaceRecognitionSessionService>() &&
|
||||
Get.find<FaceRecognitionSessionService>().isActive;
|
||||
Toast.success(
|
||||
msg: active ? '已恢复默认配置,将在当前识别结束后生效' : '已恢复默认人脸识别配置',
|
||||
msg: active ? '已还原默认配置,将在当前识别结束后生效' : '已还原默认人脸识别配置',
|
||||
);
|
||||
} on PlatformException catch (e, stack) {
|
||||
logger.e('|$_tag|恢复识别策略失败error:|$e|stack:|$stack|');
|
||||
Toast.error(msg: e.message ?? '恢复默认配置失败');
|
||||
logger.e('|$_tag|还原识别策略失败error:|$e|stack:|$stack|');
|
||||
Toast.error(msg: e.message ?? '还原默认配置失败');
|
||||
} catch (e, stack) {
|
||||
logger.e('|$_tag|恢复识别策略失败error:|$e|stack:|$stack|');
|
||||
Toast.error(msg: '恢复默认配置失败');
|
||||
logger.e('|$_tag|还原识别策略失败error:|$e|stack:|$stack|');
|
||||
Toast.error(msg: '还原默认配置失败');
|
||||
} finally {
|
||||
isSavingRecognitionConfiguration.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
void discardRecognitionConfigurationChanges() {
|
||||
recognitionConfiguration.value = savedRecognitionConfiguration.value;
|
||||
}
|
||||
|
||||
void toggleAdvancedRecognitionConfiguration() {
|
||||
showAdvancedRecognitionConfiguration.toggle();
|
||||
}
|
||||
|
||||
@@ -271,18 +271,11 @@ class _RecognitionConfigurationGroup extends StatelessWidget {
|
||||
_CompactActionBar(
|
||||
children: [
|
||||
SettingActionButton(
|
||||
label: '恢复默认',
|
||||
label: '还原默认',
|
||||
icon: FAssets.icons.rotateCcw,
|
||||
enabled: !saving && !configuration.usesDefaults,
|
||||
onTap: controller.resetRecognitionConfiguration,
|
||||
enabled: !saving && (!configuration.usesDefaults || changed),
|
||||
onTap: controller.restoreDefaultRecognitionConfiguration,
|
||||
),
|
||||
if (changed)
|
||||
SettingActionButton(
|
||||
label: '放弃修改',
|
||||
icon: FAssets.icons.x,
|
||||
enabled: !saving,
|
||||
onTap: controller.discardRecognitionConfigurationChanges,
|
||||
),
|
||||
SettingActionButton(
|
||||
label: saving ? '保存中...' : '保存配置',
|
||||
icon: FAssets.icons.save,
|
||||
|
||||
Reference in new issue
Block a user