- 新增图像生成接口,支持试用、积分和自定义API Key模式 - 实现生成图片结果异步上传至MinIO存储,带重试机制 - 优化积分预扣除和异常退还逻辑,保障用户积分准确 - 添加获取生成历史记录接口,支持时间范围和分页 - 提供本地字典配置接口,支持模型、比例、提示模板和尺寸 - 实现图片批量上传接口,支持S3兼容对象存储 feat(admin): 增加管理员角色管理与权限分配接口 - 实现角色列表查询、角色创建、更新及删除功能 - 增加权限列表查询接口 - 实现用户角色分配接口,便于统一管理用户权限 - 增加系统字典增删查改接口,支持分类过滤和排序 - 权限控制全面覆盖管理接口,保证安全访问 feat(auth): 完善用户登录注册及权限相关接口与页面 - 实现手机号验证码发送及校验功能,保障注册安全 - 支持手机号注册、登录及退出接口,集成日志记录 - 增加修改密码功能,验证原密码后更新 - 提供动态导航菜单接口,基于权限展示不同菜单 - 实现管理界面路由及日志、角色、字典管理页面访问权限控制 - 添加系统日志查询接口,支持关键词和等级筛选 feat(app): 初始化Flask应用并配置蓝图与数据库 - 创建应用程序工厂,加载配置,初始化数据库和Redis客户端 - 注册认证、API及管理员蓝图,整合路由 - 根路由渲染主页模板 - 应用上下文中自动创建数据库表,保证运行环境准备完毕 feat(database): 提供数据库创建与迁移支持脚本 - 新增数据库创建脚本,支持自动检测是否已存在 - 添加数据库表初始化脚本,支持创建和删除所有表 - 实现RBAC权限初始化,包含基础权限和角色创建 - 新增字段手动修复脚本,添加用户API Key和积分字段 - 强制迁移脚本支持清理连接和修复表结构,初始化默认数据及角色分配 feat(config): 新增系统配置参数 - 配置数据库、Redis、Session和MinIO相关参数 - 添加AI接口地址及试用Key配置 - 集成阿里云短信服务配置及开发模式相关参数 feat(extensions): 初始化数据库、Redis和MinIO客户端 - 创建全局SQLAlchemy数据库实例和Redis客户端 - 配置基于boto3的MinIO兼容S3客户端 chore(logs): 添加示例系统日志文件 - 记录用户请求、验证码发送成功与失败的日志信息
289 lines
8.4 KiB
Python
289 lines
8.4 KiB
Python
# log.py
|
|
# Copyright (C) 2006-2025 the SQLAlchemy authors and contributors
|
|
# <see AUTHORS file>
|
|
# Includes alterations by Vinay Sajip vinay_sajip@yahoo.co.uk
|
|
#
|
|
# This module is part of SQLAlchemy and is released under
|
|
# the MIT License: https://www.opensource.org/licenses/mit-license.php
|
|
|
|
"""Logging control and utilities.
|
|
|
|
Control of logging for SA can be performed from the regular python logging
|
|
module. The regular dotted module namespace is used, starting at
|
|
'sqlalchemy'. For class-level logging, the class name is appended.
|
|
|
|
The "echo" keyword parameter, available on SQLA :class:`_engine.Engine`
|
|
and :class:`_pool.Pool` objects, corresponds to a logger specific to that
|
|
instance only.
|
|
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import sys
|
|
from typing import Any
|
|
from typing import Optional
|
|
from typing import overload
|
|
from typing import Set
|
|
from typing import Type
|
|
from typing import TypeVar
|
|
from typing import Union
|
|
|
|
from .util import py311
|
|
from .util import py38
|
|
from .util.typing import Literal
|
|
|
|
|
|
if py38:
|
|
STACKLEVEL = True
|
|
# needed as of py3.11.0b1
|
|
# #8019
|
|
STACKLEVEL_OFFSET = 2 if py311 else 1
|
|
else:
|
|
STACKLEVEL = False
|
|
STACKLEVEL_OFFSET = 0
|
|
|
|
_IT = TypeVar("_IT", bound="Identified")
|
|
|
|
_EchoFlagType = Union[None, bool, Literal["debug"]]
|
|
|
|
# set initial level to WARN. This so that
|
|
# log statements don't occur in the absence of explicit
|
|
# logging being enabled for 'sqlalchemy'.
|
|
rootlogger = logging.getLogger("sqlalchemy")
|
|
if rootlogger.level == logging.NOTSET:
|
|
rootlogger.setLevel(logging.WARN)
|
|
|
|
|
|
def _add_default_handler(logger: logging.Logger) -> None:
|
|
handler = logging.StreamHandler(sys.stdout)
|
|
handler.setFormatter(
|
|
logging.Formatter("%(asctime)s %(levelname)s %(name)s %(message)s")
|
|
)
|
|
logger.addHandler(handler)
|
|
|
|
|
|
_logged_classes: Set[Type[Identified]] = set()
|
|
|
|
|
|
def _qual_logger_name_for_cls(cls: Type[Identified]) -> str:
|
|
return (
|
|
getattr(cls, "_sqla_logger_namespace", None)
|
|
or cls.__module__ + "." + cls.__name__
|
|
)
|
|
|
|
|
|
def class_logger(cls: Type[_IT]) -> Type[_IT]:
|
|
logger = logging.getLogger(_qual_logger_name_for_cls(cls))
|
|
cls._should_log_debug = lambda self: logger.isEnabledFor( # type: ignore[method-assign] # noqa: E501
|
|
logging.DEBUG
|
|
)
|
|
cls._should_log_info = lambda self: logger.isEnabledFor( # type: ignore[method-assign] # noqa: E501
|
|
logging.INFO
|
|
)
|
|
cls.logger = logger
|
|
_logged_classes.add(cls)
|
|
return cls
|
|
|
|
|
|
_IdentifiedLoggerType = Union[logging.Logger, "InstanceLogger"]
|
|
|
|
|
|
class Identified:
|
|
__slots__ = ()
|
|
|
|
logging_name: Optional[str] = None
|
|
|
|
logger: _IdentifiedLoggerType
|
|
|
|
_echo: _EchoFlagType
|
|
|
|
def _should_log_debug(self) -> bool:
|
|
return self.logger.isEnabledFor(logging.DEBUG)
|
|
|
|
def _should_log_info(self) -> bool:
|
|
return self.logger.isEnabledFor(logging.INFO)
|
|
|
|
|
|
class InstanceLogger:
|
|
"""A logger adapter (wrapper) for :class:`.Identified` subclasses.
|
|
|
|
This allows multiple instances (e.g. Engine or Pool instances)
|
|
to share a logger, but have its verbosity controlled on a
|
|
per-instance basis.
|
|
|
|
The basic functionality is to return a logging level
|
|
which is based on an instance's echo setting.
|
|
|
|
Default implementation is:
|
|
|
|
'debug' -> logging.DEBUG
|
|
True -> logging.INFO
|
|
False -> Effective level of underlying logger (
|
|
logging.WARNING by default)
|
|
None -> same as False
|
|
"""
|
|
|
|
# Map echo settings to logger levels
|
|
_echo_map = {
|
|
None: logging.NOTSET,
|
|
False: logging.NOTSET,
|
|
True: logging.INFO,
|
|
"debug": logging.DEBUG,
|
|
}
|
|
|
|
_echo: _EchoFlagType
|
|
|
|
__slots__ = ("echo", "logger")
|
|
|
|
def __init__(self, echo: _EchoFlagType, name: str):
|
|
self.echo = echo
|
|
self.logger = logging.getLogger(name)
|
|
|
|
# if echo flag is enabled and no handlers,
|
|
# add a handler to the list
|
|
if self._echo_map[echo] <= logging.INFO and not self.logger.handlers:
|
|
_add_default_handler(self.logger)
|
|
|
|
#
|
|
# Boilerplate convenience methods
|
|
#
|
|
def debug(self, msg: str, *args: Any, **kwargs: Any) -> None:
|
|
"""Delegate a debug call to the underlying logger."""
|
|
|
|
self.log(logging.DEBUG, msg, *args, **kwargs)
|
|
|
|
def info(self, msg: str, *args: Any, **kwargs: Any) -> None:
|
|
"""Delegate an info call to the underlying logger."""
|
|
|
|
self.log(logging.INFO, msg, *args, **kwargs)
|
|
|
|
def warning(self, msg: str, *args: Any, **kwargs: Any) -> None:
|
|
"""Delegate a warning call to the underlying logger."""
|
|
|
|
self.log(logging.WARNING, msg, *args, **kwargs)
|
|
|
|
warn = warning
|
|
|
|
def error(self, msg: str, *args: Any, **kwargs: Any) -> None:
|
|
"""
|
|
Delegate an error call to the underlying logger.
|
|
"""
|
|
self.log(logging.ERROR, msg, *args, **kwargs)
|
|
|
|
def exception(self, msg: str, *args: Any, **kwargs: Any) -> None:
|
|
"""Delegate an exception call to the underlying logger."""
|
|
|
|
kwargs["exc_info"] = 1
|
|
self.log(logging.ERROR, msg, *args, **kwargs)
|
|
|
|
def critical(self, msg: str, *args: Any, **kwargs: Any) -> None:
|
|
"""Delegate a critical call to the underlying logger."""
|
|
|
|
self.log(logging.CRITICAL, msg, *args, **kwargs)
|
|
|
|
def log(self, level: int, msg: str, *args: Any, **kwargs: Any) -> None:
|
|
"""Delegate a log call to the underlying logger.
|
|
|
|
The level here is determined by the echo
|
|
flag as well as that of the underlying logger, and
|
|
logger._log() is called directly.
|
|
|
|
"""
|
|
|
|
# inline the logic from isEnabledFor(),
|
|
# getEffectiveLevel(), to avoid overhead.
|
|
|
|
if self.logger.manager.disable >= level:
|
|
return
|
|
|
|
selected_level = self._echo_map[self.echo]
|
|
if selected_level == logging.NOTSET:
|
|
selected_level = self.logger.getEffectiveLevel()
|
|
|
|
if level >= selected_level:
|
|
if STACKLEVEL:
|
|
kwargs["stacklevel"] = (
|
|
kwargs.get("stacklevel", 1) + STACKLEVEL_OFFSET
|
|
)
|
|
|
|
self.logger._log(level, msg, args, **kwargs)
|
|
|
|
def isEnabledFor(self, level: int) -> bool:
|
|
"""Is this logger enabled for level 'level'?"""
|
|
|
|
if self.logger.manager.disable >= level:
|
|
return False
|
|
return level >= self.getEffectiveLevel()
|
|
|
|
def getEffectiveLevel(self) -> int:
|
|
"""What's the effective level for this logger?"""
|
|
|
|
level = self._echo_map[self.echo]
|
|
if level == logging.NOTSET:
|
|
level = self.logger.getEffectiveLevel()
|
|
return level
|
|
|
|
|
|
def instance_logger(
|
|
instance: Identified, echoflag: _EchoFlagType = None
|
|
) -> None:
|
|
"""create a logger for an instance that implements :class:`.Identified`."""
|
|
|
|
if instance.logging_name:
|
|
name = "%s.%s" % (
|
|
_qual_logger_name_for_cls(instance.__class__),
|
|
instance.logging_name,
|
|
)
|
|
else:
|
|
name = _qual_logger_name_for_cls(instance.__class__)
|
|
|
|
instance._echo = echoflag # type: ignore
|
|
|
|
logger: Union[logging.Logger, InstanceLogger]
|
|
|
|
if echoflag in (False, None):
|
|
# if no echo setting or False, return a Logger directly,
|
|
# avoiding overhead of filtering
|
|
logger = logging.getLogger(name)
|
|
else:
|
|
# if a specified echo flag, return an EchoLogger,
|
|
# which checks the flag, overrides normal log
|
|
# levels by calling logger._log()
|
|
logger = InstanceLogger(echoflag, name)
|
|
|
|
instance.logger = logger # type: ignore
|
|
|
|
|
|
class echo_property:
|
|
__doc__ = """\
|
|
When ``True``, enable log output for this element.
|
|
|
|
This has the effect of setting the Python logging level for the namespace
|
|
of this element's class and object reference. A value of boolean ``True``
|
|
indicates that the loglevel ``logging.INFO`` will be set for the logger,
|
|
whereas the string value ``debug`` will set the loglevel to
|
|
``logging.DEBUG``.
|
|
"""
|
|
|
|
@overload
|
|
def __get__(
|
|
self, instance: Literal[None], owner: Type[Identified]
|
|
) -> echo_property: ...
|
|
|
|
@overload
|
|
def __get__(
|
|
self, instance: Identified, owner: Type[Identified]
|
|
) -> _EchoFlagType: ...
|
|
|
|
def __get__(
|
|
self, instance: Optional[Identified], owner: Type[Identified]
|
|
) -> Union[echo_property, _EchoFlagType]:
|
|
if instance is None:
|
|
return self
|
|
else:
|
|
return instance._echo
|
|
|
|
def __set__(self, instance: Identified, value: _EchoFlagType) -> None:
|
|
instance_logger(instance, echoflag=value)
|