From 90ba2fd54cc9a6a742871c23a77d83aabef46b91 Mon Sep 17 00:00:00 2001 From: KimigaiiWuyi <444835641@qq.com> Date: Fri, 22 Nov 2024 05:36:09 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=E7=BD=91=E9=A1=B5=E6=8E=A7?= =?UTF-8?q?=E5=88=B6=E5=8F=B0=E6=96=B0=E5=A2=9E`=E5=8E=86=E5=8F=B2?= =?UTF-8?q?=E6=97=A5=E5=BF=97`=E6=A8=A1=E5=9D=97,=20=E5=8F=AF=E4=BB=A5?= =?UTF-8?q?=E6=8C=89=E7=85=A7=E6=97=A5=E5=BF=97=E7=AD=89=E7=BA=A7=E7=AD=89?= =?UTF-8?q?=E6=9D=A1=E4=BB=B6=E8=BF=87=E6=BB=A4=E6=88=96=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gsuid_core/logger.py | 66 +++- gsuid_core/web_app.py | 45 ++- gsuid_core/webconsole/create_history_log.py | 402 ++++++++++++++++++++ gsuid_core/webconsole/mount_app.py | 16 + 4 files changed, 522 insertions(+), 7 deletions(-) create mode 100644 gsuid_core/webconsole/create_history_log.py diff --git a/gsuid_core/logger.py b/gsuid_core/logger.py index 4f5bb58..8b74f9b 100644 --- a/gsuid_core/logger.py +++ b/gsuid_core/logger.py @@ -1,11 +1,14 @@ +import re import sys import asyncio import logging import datetime +from pathlib import Path from functools import wraps -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING, Dict, List import loguru +import aiofiles from uvicorn.config import LOGGING_CONFIG from gsuid_core.config import core_config @@ -23,7 +26,7 @@ if TYPE_CHECKING: logger: 'Logger' = loguru.logger logging.getLogger().handlers = [] -LOGGING_CONFIG["disable_existing_loggers"] = False +LOGGING_CONFIG['disable_existing_loggers'] = False # https://loguru.readthedocs.io/en/stable/overview.html#entirely-compatible-with-standard-logging @@ -143,12 +146,12 @@ if 'stdout' in logger_list: level=LEVEL, diagnose=True, backtrace=True, - filter=lambda record: record["level"].no < 40, + filter=lambda record: record['level'].no < 40, format=std_format_event, ) if 'stderr' in logger_list: - logger.add(sys.stderr, level="ERROR") + logger.add(sys.stderr, level='ERROR') if 'file' in logger_list: logger.add( @@ -186,7 +189,60 @@ def handle_exceptions(async_function): try: return await async_function(*args, **kwargs) except Exception as e: - logger.exception("[错误发生] %s: %s", async_function.__name__, e) + logger.exception('[错误发生] %s: %s', async_function.__name__, e) return None return wrapper + + +class HistoryLogData: + def __init__(self): + self.log_list: Dict[str, List[Dict]] = {} + + async def get_parse_logs(self, log_file_path: Path): + if log_file_path.name in self.log_list: + return self.log_list[log_file_path.name] + + log_entries: List[Dict] = [] + + log_entry_pattern = re.compile( + r'^(\d{2}-\d{2} \d{2}:\d{2}:\d{2}) \[(\w+)] ([^\|]+) \| (.*)' + ) + + async with aiofiles.open(log_file_path, 'r', encoding='utf-8') as file: + lines = await file.readlines() + + current_entry = None + + _id = 1 + for line in lines: + line = line.strip() + match = log_entry_pattern.match(line) + + if match: + if current_entry: + log_entries.append(current_entry) + current_entry = { + 'id': _id, + '时间': match.group(1), + '日志等级': match.group(2), + '模块': match.group(3).strip(), + '内容': match.group(4).strip(), + } + _id += 1 + elif current_entry: + current_entry['内容'] += '\n' + line + + if current_entry: + log_entries.append(current_entry) + + self.log_list[log_file_path.name] = log_entries + return log_entries + + +def get_all_log_path(): + return [ + file + for file in LOG_PATH.iterdir() + if file.is_file() and file.suffix == '.log' + ] diff --git a/gsuid_core/web_app.py b/gsuid_core/web_app.py index e69b316..1f9237f 100644 --- a/gsuid_core/web_app.py +++ b/gsuid_core/web_app.py @@ -1,7 +1,8 @@ import asyncio from io import BytesIO from pathlib import Path -from typing import Dict, List +from datetime import datetime +from typing import Dict, List, Optional from contextlib import asynccontextmanager from PIL import Image @@ -18,7 +19,6 @@ from gsuid_core.data_store import image_res from gsuid_core.webconsole.mount_app import site from gsuid_core.segment import Message, MessageSegment from gsuid_core.config import CONFIG_DEFAULT, core_config -from gsuid_core.logger import logger, read_log, clean_log from gsuid_core.aps import start_scheduler, shutdown_scheduler from gsuid_core.server import core_start_def, core_shutdown_def from gsuid_core.utils.database.models import CoreUser, CoreGroup @@ -27,6 +27,13 @@ from gsuid_core.utils.plugins_config.gs_config import ( all_config_list, core_plugins_config, ) +from gsuid_core.logger import ( + LOG_PATH, + HistoryLogData, + logger, + read_log, + clean_log, +) from gsuid_core.utils.plugins_update._plugins import ( check_status, check_plugins, @@ -425,4 +432,38 @@ async def core_log(): return StreamingResponse(read_log(), media_type='text/plain') +@app.get('/genshinuid/api/historyLogs') +@site.auth.requires('root') +async def get_history_logs( + request: Request, + date: Optional[str] = None, + page: int = 0, + perPage: int = 0, +): + if date is None: + date = datetime.now().strftime('%Y-%m-%d') + + if date.endswith('.log'): + date = date.removesuffix('.log') + + history_log_data = HistoryLogData() + log_files = await history_log_data.get_parse_logs(LOG_PATH / f'{date}.log') + total = len(log_files) + if page != 0 and perPage != 0: + start = (page - 1) * perPage + end = start + perPage + log_file = log_files[start:end] + else: + log_file = log_files + return { + 'status': 0, + 'msg': 'ok', + 'data': { + 'count': total, + 'rows': log_file, + }, + } + + +site.mount_app(app) site.mount_app(app) diff --git a/gsuid_core/webconsole/create_history_log.py b/gsuid_core/webconsole/create_history_log.py new file mode 100644 index 0000000..9e4ea3b --- /dev/null +++ b/gsuid_core/webconsole/create_history_log.py @@ -0,0 +1,402 @@ +from gsuid_core.logger import get_all_log_path + + +def get_history_logs_page(): + all_log_path = get_all_log_path() + ACTION = { + "actionType": "dialog", + "dialog": { + "body": { + "id": "u:448f2ad2a090", + "type": "form", + "title": "查看数据", + "mode": "flex", + "labelAlign": "top", + "dsType": "api", + "feat": "View", + "body": [ + { + "name": "id", + "label": "id", + "row": 0, + "type": "input-number", + }, + { + "name": "时间", + "label": "时间", + "row": 1, + "type": "input-text", + }, + { + "name": "日志等级", + "label": "日志等级", + "row": 2, + "type": "select", + }, + { + "name": "模块", + "label": "模块", + "row": 3, + "type": "input-text", + }, + { + "name": "内容", + "label": "内容", + "row": 4, + "type": "textarea", + }, + ], + "static": True, + "actions": [ + { + "type": "button", + "actionType": "cancel", + "label": "关闭", + } + ], + "onEvent": { + "submitSucc": { + "actions": [ + { + "actionType": "search", + "groupType": "component", + "componentId": "u:b1f51d7a324a", + } + ] + } + }, + }, + "title": "查看数据", + "size": "md", + "actions": [ + { + "type": "button", + "actionType": "cancel", + "label": "关闭", + } + ], + }, + } + data = { + "type": "page", + "title": "历史日志过滤查询", + "id": "u:908f0ee5fa73", + "asideResizor": False, + "pullRefresh": {"disabled": True}, + "body": [ + { + "type": "select", + "label": "选择日期", + "name": "select", + "options": [ + {"label": i.name, "value": i.name} for i in all_log_path + ], + "id": "u:30962bfb9f83", + "multiple": False, + "onEvent": { + "change": { + "weight": 0, + "actions": [ + { + "componentId": "u:b1f51d7a324a", + "ignoreError": False, + "actionType": "reload", + "data": {"date": "${event.data.value}"}, + "dataMergeMode": "merge", + } + ], + } + }, + }, + { + "type": "divider", + "id": "u:d1c9f48ea328", + "lineStyle": "solid", + "direction": "horizontal", + "rotate": 0, + }, + { + "id": "u:b1f51d7a324a", + "type": "crud2", + "mode": "table2", + "dsType": "api", + "syncLocation": True, + "primaryField": "id", + "loadType": "pagination", + "api": { + "url": "/genshinuid/api/historyLogs", + "method": "get", + "requestAdaptor": "", + "adaptor": "", + "messages": {}, + "dataType": "json", + }, + "filter": { + "type": "form", + "title": "条件查询", + "mode": "inline", + "columnCount": 3, + "clearValueOnHidden": True, + "behavior": ["SimpleQuery"], + "body": [ + { + "name": "id", + "label": "id", + "type": "input-number", + "size": "full", + "required": False, + "behavior": "SimpleQuery", + "id": "u:7d5dfd6fb4a5", + "keyboard": True, + "step": 1, + }, + { + "name": "时间", + "label": "时间", + "type": "input-text", + "size": "full", + "required": False, + "behavior": "SimpleQuery", + "id": "u:946cd5942b15", + }, + { + "name": "日志等级", + "label": "日志等级", + "type": "select", + "size": "full", + "required": False, + "behavior": "SimpleQuery", + "id": "u:d0bf26304939", + "multiple": False, + "options": [ + {"label": "INFO", "value": "INFO"}, + {"label": "SUCCESS", "value": "SUCCESS"}, + {"label": "WARNING", "value": "WARNING"}, + {"label": "ERROR", "value": "ERROR"}, + {"label": "DEBUG", "value": "DEBUG"}, + {"label": "TRACE", "value": "TRACE"}, + ], + "selectFirst": False, + "removable": False, + "clearable": True, + "checkAll": False, + "joinValues": True, + }, + { + "name": "模块", + "label": "模块", + "type": "input-text", + "size": "full", + "required": False, + "behavior": "SimpleQuery", + "id": "u:294ae6f80ff2", + }, + { + "name": "内容", + "label": "内容", + "type": "textarea", + "size": "full", + "required": False, + "behavior": "SimpleQuery", + "id": "u:ee5fc2b996d7", + }, + ], + "actions": [ + { + "type": "reset", + "label": "重置", + "id": "u:22b8ade74682", + }, + { + "type": "submit", + "label": "查询", + "level": "primary", + "id": "u:e853a21b2077", + }, + ], + "id": "u:2a882fb870a0", + "feat": "Insert", + }, + "headerToolbar": [ + { + "type": "flex", + "direction": "row", + "justify": "flex-start", + "alignItems": "stretch", + "style": {"position": "static"}, + "items": [ + { + "type": "container", + "align": "left", + "behavior": [ + "Insert", + "BulkEdit", + "BulkDelete", + ], + "body": [], + "wrapperBody": False, + "style": { + "flexGrow": 1, + "flex": "1 1 auto", + "position": "static", + "display": "flex", + "flexBasis": "auto", + "flexDirection": "row", + "flexWrap": "nowrap", + "alignItems": "stretch", + "justifyContent": "flex-start", + }, + "id": "u:f7dee2ce6250", + }, + { + "type": "container", + "align": "right", + "behavior": ["FuzzyQuery"], + "body": [], + "wrapperBody": False, + "style": { + "flexGrow": 1, + "flex": "1 1 auto", + "position": "static", + "display": "flex", + "flexBasis": "auto", + "flexDirection": "row", + "flexWrap": "nowrap", + "alignItems": "stretch", + "justifyContent": "flex-end", + }, + "id": "u:8d2dc85fed4b", + }, + ], + "id": "u:14f26e218177", + } + ], + "footerToolbar": [ + { + "type": "flex", + "direction": "row", + "justify": "flex-start", + "alignItems": "stretch", + "style": {"position": "static"}, + "items": [ + { + "type": "container", + "align": "left", + "body": [], + "wrapperBody": False, + "style": { + "flexGrow": 1, + "flex": "1 1 auto", + "position": "static", + "display": "flex", + "flexBasis": "auto", + "flexDirection": "row", + "flexWrap": "nowrap", + "alignItems": "stretch", + "justifyContent": "flex-start", + }, + "id": "u:ffc293c3af21", + }, + { + "type": "container", + "align": "right", + "body": [ + { + "type": "pagination", + "behavior": "Pagination", + "layout": [ + "total", + "perPage", + "pager", + ], + "perPage": 10, + "perPageAvailable": [10, 20, 50, 100], + "align": "right", + "id": "u:515f5d3a63d3", + "size": "", + } + ], + "wrapperBody": False, + "style": { + "flexGrow": 1, + "flex": "1 1 auto", + "position": "static", + "display": "flex", + "flexBasis": "auto", + "flexDirection": "row", + "flexWrap": "nowrap", + "alignItems": "stretch", + "justifyContent": "flex-end", + }, + "id": "u:6b708bcf8cf1", + }, + ], + "id": "u:6cedb9797d50", + } + ], + "columns": [ + { + "type": "tpl", + "title": "id", + "name": "id", + "id": "u:f360a70eb838", + }, + { + "type": "tpl", + "title": "时间", + "name": "时间", + "id": "u:40ba8ecccd71", + }, + { + "type": "mapping", + "title": "日志等级", + "name": "日志等级", + "id": "u:8af643bd0487", + "placeholder": "-", + "map": { + "*": "其他", + "ERROR": "错误", # noqa: E501 + "WARNING": "警告", # noqa: E501 + "SUCCESS": "成功", # noqa: E501 + "DEBUG": "调试", # noqa: E501 + "INFO": "正常", # noqa: E501 + "TRACE": "追溯", # noqa: E501 + }, + }, + { + "type": "tpl", + "title": "模块", + "name": "模块", + "id": "u:561fed6e37bf", + }, + { + "type": "tpl", + "title": "内容", + "name": "内容", + "id": "u:306a49203d90", + }, + { + "type": "operation", + "title": "操作", + "buttons": [ + { + "type": "button", + "label": "查看", + "level": "link", + "behavior": "View", + "onEvent": {"click": {"actions": [ACTION]}}, + "id": "u:2a40b63939db", + } + ], + "id": "u:b1aead938964", + }, + ], + "editorSetting": { + "mock": {"enable": True, "maxDisplayRows": 5} + }, + "loadDataOnce": True, + "showHeader": True, + }, + ], + } + return data diff --git a/gsuid_core/webconsole/mount_app.py b/gsuid_core/webconsole/mount_app.py index 7a58a79..1a4258b 100644 --- a/gsuid_core/webconsole/mount_app.py +++ b/gsuid_core/webconsole/mount_app.py @@ -49,6 +49,7 @@ from gsuid_core.webconsole.create_task_panel import get_tasks_panel from gsuid_core.webconsole.create_config_panel import get_config_page from gsuid_core.utils.plugins_config.gs_config import core_plugins_config from gsuid_core.webconsole.create_analysis_panel import get_analysis_page +from gsuid_core.webconsole.create_history_log import get_history_logs_page from gsuid_core.utils.database.models import GsBind, GsPush, GsUser, GsCache from gsuid_core.webconsole.create_batch_push_panel import get_batch_push_panel from gsuid_core.webconsole.create_core_config_panel import get_core_config_page @@ -508,6 +509,21 @@ class LogsPage(GsAdminPage): return Page.parse_obj(create_log_page()) +@site.register_admin +class HistoryLogsPage(GsAdminPage): + page_schema = PageSchema( + label=('历史日志'), + icon='fa fa-columns', + url='/logs', + isDefaultPage=True, + sort=100, + ) # type: ignore + + @handle_exceptions + async def get_page(self, request: Request) -> Page: + return Page.parse_obj(get_history_logs_page()) + + @site.register_admin class PushPage(GsAdminPage): page_schema = PageSchema(