mirror of
https://github.com/Genshin-bots/gsuid_core.git
synced 2025-06-03 22:19:47 +08:00
✨ 网页控制台新增历史日志
模块, 可以按照日志等级等条件过滤或查询
This commit is contained in:
parent
3f6b926c33
commit
90ba2fd54c
@ -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'
|
||||
]
|
||||
|
@ -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)
|
||||
|
402
gsuid_core/webconsole/create_history_log.py
Normal file
402
gsuid_core/webconsole/create_history_log.py
Normal file
@ -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": {
|
||||
"*": "<span class='label label-default'>其他</span>",
|
||||
"ERROR": "<span class='label label-danger'>错误</span>", # noqa: E501
|
||||
"WARNING": "<span class='label label-label label-warning'>警告</span>", # noqa: E501
|
||||
"SUCCESS": "<span class='label label-success'>成功</span>", # noqa: E501
|
||||
"DEBUG": "<span class=\"label\" style=\"background-color: rgb(58, 118, 251); color: white;\">调试</span>", # noqa: E501
|
||||
"INFO": "<span class=\"label\" style=\"background-color: rgb(140, 140, 140); color: rgb(255, 255, 255);\">正常</span>", # noqa: E501
|
||||
"TRACE": "<span class=\"label\" style=\"background-color: rgb(235, 62, 247); color: white;\">追溯</span>", # 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
|
@ -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(
|
||||
|
Loading…
x
Reference in New Issue
Block a user