From 7ed8f4d529289df51db1f98a83516c59b2b537fb Mon Sep 17 00:00:00 2001 From: lifegpc Date: Fri, 3 Jan 2025 03:10:23 +0000 Subject: [PATCH] feat: Add logging stack configuration and related localization --- lib/api/client.dart | 27 +++++ lib/api/client.g.dart | 212 +++++++++++++++++++++++++++++++++ lib/api/config.dart | 7 ++ lib/api/config.g.dart | 9 +- lib/api/log.dart | 57 +++++++++ lib/api/log.g.dart | 47 ++++++++ lib/l10n/app_en.arb | 3 +- lib/l10n/app_zh.arb | 3 +- lib/pages/settings/server.dart | 12 ++ 9 files changed, 373 insertions(+), 4 deletions(-) create mode 100644 lib/api/log.dart create mode 100644 lib/api/log.g.dart diff --git a/lib/api/client.dart b/lib/api/client.dart index e4ad511..7c4f2f3 100644 --- a/lib/api/client.dart +++ b/lib/api/client.dart @@ -11,6 +11,7 @@ import 'config.dart'; import 'eh.dart'; import 'file.dart'; import 'gallery.dart'; +import 'log.dart'; import 'status.dart'; import 'tags.dart'; import 'task.dart'; @@ -379,6 +380,32 @@ abstract class _EHApi { @GET('/task/import_cfg') Future> getDefaultImportConfig( {@CancelRequest() CancelToken? cancel}); + + @GET('/log') + Future> queryLog( + {@Query("page") int? page, + @Query("limit") int? limit, + @Query("offset") int? offset, + @Query("type") String? type, + @Query("min_level") int? minLevel, + @Query("allowed_level") String? allowedLevel, + @CancelRequest() CancelToken? cancel}); + @DELETE('/log') + @MultiPart() + Future> clearLog( + {@Part(name: "type") String? type, + @Part(name: "min_level") int? minLevel, + @Part(name: "max_level") int? maxLevel, + @Part(name: "deleted_level") String? deletedLevel, + @Part(name: "end_time") String? endTime, + @CancelRequest() CancelToken? cancel}); + @GET('/log/{id}') + Future> getLog(@Path("id") int id, + {@CancelRequest() CancelToken? cancel}); + @DELETE('/log/{id}') + @MultiPart() + Future> deleteLog(@Path("id") int id, + {@CancelRequest() CancelToken? cancel}); } class EHApi extends __EHApi { diff --git a/lib/api/client.g.dart b/lib/api/client.g.dart index 8b46e90..12ea8a4 100644 --- a/lib/api/client.g.dart +++ b/lib/api/client.g.dart @@ -2283,6 +2283,218 @@ class __EHApi implements _EHApi { return _value; } + @override + Future> queryLog({ + int? page, + int? limit, + int? offset, + String? type, + int? minLevel, + String? allowedLevel, + CancelToken? cancel, + }) async { + final _extra = {}; + final queryParameters = { + r'page': page, + r'limit': limit, + r'offset': offset, + r'type': type, + r'min_level': minLevel, + r'allowed_level': allowedLevel, + }; + queryParameters.removeWhere((k, v) => v == null); + final _headers = {}; + const Map? _data = null; + final _options = _setStreamType>(Options( + method: 'GET', + headers: _headers, + extra: _extra, + ) + .compose( + _dio.options, + '/log', + queryParameters: queryParameters, + data: _data, + cancelToken: cancel, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + ))); + final _result = await _dio.fetch>(_options); + late ApiResult _value; + try { + _value = ApiResult.fromJson( + _result.data!, + (json) => LogEntries.fromJson(json as Map), + ); + } on Object catch (e, s) { + errorLogger?.logError(e, s, _options); + rethrow; + } + return _value; + } + + @override + Future> clearLog({ + String? type, + int? minLevel, + int? maxLevel, + String? deletedLevel, + String? endTime, + CancelToken? cancel, + }) async { + final _extra = {}; + final queryParameters = {}; + queryParameters.removeWhere((k, v) => v == null); + final _headers = {}; + final _data = FormData(); + if (type != null) { + _data.fields.add(MapEntry( + 'type', + type, + )); + } + if (minLevel != null) { + _data.fields.add(MapEntry( + 'min_level', + minLevel.toString(), + )); + } + if (maxLevel != null) { + _data.fields.add(MapEntry( + 'max_level', + maxLevel.toString(), + )); + } + if (deletedLevel != null) { + _data.fields.add(MapEntry( + 'deleted_level', + deletedLevel, + )); + } + if (endTime != null) { + _data.fields.add(MapEntry( + 'end_time', + endTime, + )); + } + final _options = _setStreamType>(Options( + method: 'DELETE', + headers: _headers, + extra: _extra, + contentType: 'multipart/form-data', + ) + .compose( + _dio.options, + '/log', + queryParameters: queryParameters, + data: _data, + cancelToken: cancel, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + ))); + final _result = await _dio.fetch>(_options); + late ApiResult _value; + try { + _value = ApiResult.fromJson( + _result.data!, + (json) => json as bool, + ); + } on Object catch (e, s) { + errorLogger?.logError(e, s, _options); + rethrow; + } + return _value; + } + + @override + Future> getLog( + int id, { + CancelToken? cancel, + }) async { + final _extra = {}; + final queryParameters = {}; + queryParameters.removeWhere((k, v) => v == null); + final _headers = {}; + const Map? _data = null; + final _options = _setStreamType>(Options( + method: 'GET', + headers: _headers, + extra: _extra, + ) + .compose( + _dio.options, + '/log/${id}', + queryParameters: queryParameters, + data: _data, + cancelToken: cancel, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + ))); + final _result = await _dio.fetch>(_options); + late ApiResult _value; + try { + _value = ApiResult.fromJson( + _result.data!, + (json) => LogEntry.fromJson(json as Map), + ); + } on Object catch (e, s) { + errorLogger?.logError(e, s, _options); + rethrow; + } + return _value; + } + + @override + Future> deleteLog( + int id, { + CancelToken? cancel, + }) async { + final _extra = {}; + final queryParameters = {}; + queryParameters.removeWhere((k, v) => v == null); + final _headers = {}; + const Map? _data = null; + final _options = _setStreamType>(Options( + method: 'DELETE', + headers: _headers, + extra: _extra, + contentType: 'multipart/form-data', + ) + .compose( + _dio.options, + '/log/${id}', + queryParameters: queryParameters, + data: _data, + cancelToken: cancel, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + ))); + final _result = await _dio.fetch>(_options); + late ApiResult _value; + try { + _value = ApiResult.fromJson( + _result.data!, + (json) => json as bool, + ); + } on Object catch (e, s) { + errorLogger?.logError(e, s, _options); + rethrow; + } + return _value; + } + RequestOptions _setStreamType(RequestOptions requestOptions) { if (T != dynamic && !(requestOptions.responseType == ResponseType.bytes || diff --git a/lib/api/config.dart b/lib/api/config.dart index 43b6b14..0b1c476 100644 --- a/lib/api/config.dart +++ b/lib/api/config.dart @@ -83,6 +83,7 @@ class Config { required this.maxImportImgCount, required this.enableServerTiming, required this.thumbnailFormat, + required this.loggingStack, }); bool cookies; @JsonKey(name: 'db_path') @@ -151,6 +152,8 @@ class Config { bool enableServerTiming; @JsonKey(name: 'thumbnail_format') ThumbnailFormat thumbnailFormat; + @JsonKey(name: 'logging_stack') + bool loggingStack; factory Config.fromJson(Map json) => _$ConfigFromJson(json); Map toJson() => _$ConfigToJson(this); } @@ -206,6 +209,8 @@ class ConfigOptional { this.importMethod, this.maxImportImgCount, this.enableServerTiming, + this.thumbnailFormat, + this.loggingStack, }); String? cookies; @JsonKey(name: 'db_path') @@ -274,6 +279,8 @@ class ConfigOptional { bool? enableServerTiming; @JsonKey(name: 'thumbnail_format') ThumbnailFormat? thumbnailFormat; + @JsonKey(name: 'logging_stack') + bool? loggingStack; factory ConfigOptional.fromJson(Map json) => _$ConfigOptionalFromJson(json); Map toJson() => _$ConfigOptionalToJson(this); diff --git a/lib/api/config.g.dart b/lib/api/config.g.dart index 3212408..1a980a6 100644 --- a/lib/api/config.g.dart +++ b/lib/api/config.g.dart @@ -51,6 +51,7 @@ Config _$ConfigFromJson(Map json) => Config( enableServerTiming: json['enable_server_timing'] as bool, thumbnailFormat: $enumDecode(_$ThumbnailFormatEnumMap, json['thumbnail_format']), + loggingStack: json['logging_stack'] as bool, ); Map _$ConfigToJson(Config instance) => { @@ -91,6 +92,7 @@ Map _$ConfigToJson(Config instance) => { 'max_import_img_count': instance.maxImportImgCount, 'enable_server_timing': instance.enableServerTiming, 'thumbnail_format': _$ThumbnailFormatEnumMap[instance.thumbnailFormat]!, + 'logging_stack': instance.loggingStack, }; const _$ThumbnailMethodEnumMap = { @@ -165,8 +167,10 @@ ConfigOptional _$ConfigOptionalFromJson(Map json) => $enumDecodeNullable(_$ImportMethodEnumMap, json['import_method']), maxImportImgCount: (json['max_import_img_count'] as num?)?.toInt(), enableServerTiming: json['enable_server_timing'] as bool?, - )..thumbnailFormat = - $enumDecodeNullable(_$ThumbnailFormatEnumMap, json['thumbnail_format']); + thumbnailFormat: $enumDecodeNullable( + _$ThumbnailFormatEnumMap, json['thumbnail_format']), + loggingStack: json['logging_stack'] as bool?, + ); Map _$ConfigOptionalToJson(ConfigOptional instance) => { @@ -207,4 +211,5 @@ Map _$ConfigOptionalToJson(ConfigOptional instance) => 'max_import_img_count': instance.maxImportImgCount, 'enable_server_timing': instance.enableServerTiming, 'thumbnail_format': _$ThumbnailFormatEnumMap[instance.thumbnailFormat], + 'logging_stack': instance.loggingStack, }; diff --git a/lib/api/log.dart b/lib/api/log.dart new file mode 100644 index 0000000..ca0d571 --- /dev/null +++ b/lib/api/log.dart @@ -0,0 +1,57 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'log.g.dart'; + +enum LogLevel { + @JsonValue(1) + trace, + @JsonValue(2) + debug, + @JsonValue(3) + log, + @JsonValue(4) + info, + @JsonValue(5) + warn, + @JsonValue(6) + error; +} + +@JsonSerializable() +class LogEntry { + LogEntry({ + required this.id, + required this.time, + required this.message, + required this.level, + required this.type, + this.stack, + }); + int id; + @JsonKey(fromJson: _fromJson, toJson: _toJson) + DateTime time; + String message; + LogLevel level; + String type; + String? stack; + + static DateTime _fromJson(String d) => DateTime.parse(d); + static String _toJson(DateTime d) => d.toIso8601String(); + factory LogEntry.fromJson(Map json) => + _$LogEntryFromJson(json); + Map toJson() => _$LogEntryToJson(this); +} + +@JsonSerializable() +class LogEntries { + LogEntries({ + required this.datas, + this.count, + }); + List datas; + int? count; + + factory LogEntries.fromJson(Map json) => + _$LogEntriesFromJson(json); + Map toJson() => _$LogEntriesToJson(this); +} diff --git a/lib/api/log.g.dart b/lib/api/log.g.dart new file mode 100644 index 0000000..778c43b --- /dev/null +++ b/lib/api/log.g.dart @@ -0,0 +1,47 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'log.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +LogEntry _$LogEntryFromJson(Map json) => LogEntry( + id: (json['id'] as num).toInt(), + time: LogEntry._fromJson(json['time'] as String), + message: json['message'] as String, + level: $enumDecode(_$LogLevelEnumMap, json['level']), + type: json['type'] as String, + stack: json['stack'] as String?, + ); + +Map _$LogEntryToJson(LogEntry instance) => { + 'id': instance.id, + 'time': LogEntry._toJson(instance.time), + 'message': instance.message, + 'level': _$LogLevelEnumMap[instance.level]!, + 'type': instance.type, + 'stack': instance.stack, + }; + +const _$LogLevelEnumMap = { + LogLevel.trace: 1, + LogLevel.debug: 2, + LogLevel.log: 3, + LogLevel.info: 4, + LogLevel.warn: 5, + LogLevel.error: 6, +}; + +LogEntries _$LogEntriesFromJson(Map json) => LogEntries( + datas: (json['datas'] as List) + .map((e) => LogEntry.fromJson(e as Map)) + .toList(), + count: (json['count'] as num?)?.toInt(), + ); + +Map _$LogEntriesToJson(LogEntries instance) => + { + 'datas': instance.datas, + 'count': instance.count, + }; diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index ffe6f48..ea3e0ce 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -358,5 +358,6 @@ "allUser": "All users", "deleteShareConfirm": "Do you want to delete shared token?", "failedDeleteShare": "Failed to delete shared token: ", - "queryLog": "Query log" + "queryLog": "Query log", + "loggingStack": "Enable logging stack for all log levels" } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index acd9d98..4245cf2 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -358,5 +358,6 @@ "allUser": "所有用户", "deleteShareConfirm": "是否删除分享令牌?", "failedDeleteShare": "删除分享令牌失败:", - "queryLog": "查询日志" + "queryLog": "查询日志", + "loggingStack": "为所有日志级别启用堆栈记录" } diff --git a/lib/pages/settings/server.dart b/lib/pages/settings/server.dart index 1238920..db01cbf 100644 --- a/lib/pages/settings/server.dart +++ b/lib/pages/settings/server.dart @@ -329,6 +329,18 @@ class _ServerSettingsPage extends State }, label: Text(i18n.enableServerTiming), )), + _buildWithVecticalPadding(LabeledCheckbox( + value: _now.loggingStack ?? _config!.loggingStack, + onChanged: (b) { + if (b != null) { + setState(() { + _now.loggingStack = b; + _changed = true; + }); + } + }, + label: Text(i18n.loggingStack), + )), ])); }