Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 24 additions & 28 deletions backend/common/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import inspect
import logging
import os
import sys

from sys import stderr, stdout

from loguru import logger

Expand Down Expand Up @@ -41,7 +41,7 @@ def setup_logging():
"""
# Intercept everything at the root logger
logging.root.handlers = [InterceptHandler()]
logging.root.setLevel(settings.LOG_ROOT_LEVEL)
logging.root.setLevel(settings.LOG_STD_LEVEL)

# Remove all log handlers and propagate to root logger
for name in logging.root.manager.loggerDict.keys():
Expand All @@ -54,62 +54,58 @@ def setup_logging():
# Debug log handlers
# logging.debug(f'{logging.getLogger(name)}, {logging.getLogger(name).propagate}')

# Remove every other logger's handlers
# Remove default loguru logger
logger.remove()

# Configure loguru logger before starts logging
# Set the loguru default handlers
logger.configure(
handlers=[
{
'sink': stdout,
'level': settings.LOG_STDOUT_LEVEL,
'filter': lambda record: record['level'].no <= 25,
'sink': sys.stdout,
'level': settings.LOG_STD_LEVEL,
'format': settings.LOG_STD_FORMAT,
},
{
'sink': stderr,
'level': settings.LOG_STDERR_LEVEL,
'filter': lambda record: record['level'].no >= 30,
'format': settings.LOG_STD_FORMAT,
},
}
]
)


def set_customize_logfile():
def set_custom_logfile():
log_path = path_conf.LOG_DIR
if not os.path.exists(log_path):
os.mkdir(log_path)

# log files
log_stdout_file = os.path.join(log_path, settings.LOG_STDOUT_FILENAME)
log_stderr_file = os.path.join(log_path, settings.LOG_STDERR_FILENAME)
log_access_file = os.path.join(log_path, settings.LOG_ACCESS_FILENAME)
log_error_file = os.path.join(log_path, settings.LOG_ERROR_FILENAME)

# loguru logger: https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger.add
# set loguru logger default config
# https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger.add
log_config = {
'rotation': '10 MB',
'retention': '15 days',
'compression': 'tar.gz',
'enqueue': True,
'format': settings.LOG_FILE_FORMAT,
'enqueue': True,
'rotation': '5 MB',
'retention': '7 days',
'compression': 'tar.gz',
}

# stdout file
logger.add(
str(log_stdout_file),
level=settings.LOG_STDOUT_LEVEL,
**log_config,
str(log_access_file),
level=settings.LOG_ACCESS_FILE_LEVEL,
filter=lambda record: record['level'].no <= 25,
backtrace=False,
diagnose=False,
**log_config,
)

# stderr file
logger.add(
str(log_stderr_file),
level=settings.LOG_STDERR_LEVEL,
**log_config,
str(log_error_file),
level=settings.LOG_ERROR_FILE_LEVEL,
filter=lambda record: record['level'].no >= 30,
backtrace=True,
diagnose=True,
**log_config,
)


Expand Down
6 changes: 4 additions & 2 deletions backend/common/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from datetime import datetime
from typing import Annotated

from sqlalchemy.ext.asyncio import AsyncAttrs
from sqlalchemy.orm import DeclarativeBase, Mapped, MappedAsDataclass, declared_attr, mapped_column

from backend.utils.timezone import timezone
Expand Down Expand Up @@ -34,10 +35,11 @@ class DateTimeMixin(MappedAsDataclass):
)


class MappedBase(DeclarativeBase):
class MappedBase(AsyncAttrs, DeclarativeBase):
"""
声明性基类, 原始 DeclarativeBase 类, 作为所有基类或数据模型类的父类而存在
声明式基类, 作为所有基类或数据模型类的父类而存在

`AsyncAttrs <https://docs.sqlalchemy.org/en/20/orm/extensions/asyncio.html#sqlalchemy.ext.asyncio.AsyncAttrs>`__
`DeclarativeBase <https://docs.sqlalchemy.org/en/20/orm/declarative_config.html>`__
`mapped_column() <https://docs.sqlalchemy.org/en/20/orm/mapping_api.html#sqlalchemy.orm.mapped_column>`__
"""
Expand Down
11 changes: 6 additions & 5 deletions backend/core/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def validator_api_url(cls, values):

# MYSQL
DATABASE_ECHO: bool = False
DATABASE_POOL_ECHO: bool = False
DATABASE_SCHEMA: str = 'fsm'
DATABASE_CHARSET: str = 'utf8mb4'

Expand All @@ -66,13 +67,13 @@ def validator_api_url(cls, values):
TOKEN_URL_SWAGGER: str = f'{FASTAPI_API_V1_PATH}/auth/login/swagger'

# Log
LOG_ROOT_LEVEL: str = 'NOTSET'
LOG_STD_LEVEL: str = 'INFO'
LOG_ACCESS_FILE_LEVEL: str = 'INFO'
LOG_ERROR_FILE_LEVEL: str = 'ERROR'
LOG_STD_FORMAT: str = '<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</> | <lvl>{level: <8}</> | <lvl>{message}</>'
LOG_FILE_FORMAT: str = '<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</> | <lvl>{level: <8}</> | <lvl>{message}</>'
LOG_STDOUT_LEVEL: str = 'INFO'
LOG_STDERR_LEVEL: str = 'ERROR'
LOG_STDOUT_FILENAME: str = 'fba_access.log'
LOG_STDERR_FILENAME: str = 'fba_error.log'
LOG_ACCESS_FILENAME: str = 'fba_access.log'
LOG_ERROR_FILENAME: str = 'fba_error.log'

# 中间件
MIDDLEWARE_CORS: bool = True
Expand Down
4 changes: 2 additions & 2 deletions backend/core/registrar.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from backend.app.router import route
from backend.common.exception.exception_handler import register_exception
from backend.common.log import setup_logging, set_customize_logfile
from backend.common.log import setup_logging, set_custom_logfile
from backend.core.path_conf import STATIC_DIR
from backend.database.redis import redis_client
from backend.core.conf import settings
Expand Down Expand Up @@ -82,7 +82,7 @@ def register_logger() -> None:
:return:
"""
setup_logging()
set_customize_logfile()
set_custom_logfile()


def register_static_file(app: FastAPI):
Expand Down
53 changes: 28 additions & 25 deletions backend/database/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,29 @@

from fastapi import Depends
from sqlalchemy import URL
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine, AsyncEngine

from backend.common.log import log
from backend.common.model import MappedBase
from backend.core.conf import settings


def create_engine_and_session(url: str | URL):
def create_async_engine_and_session(url: str | URL) -> tuple[AsyncEngine, async_sessionmaker[AsyncSession]]:
try:
# 数据库引擎
engine = create_async_engine(url, echo=settings.DATABASE_ECHO, future=True, pool_pre_ping=True)
# log.success('数据库连接成功')
engine = create_async_engine(
url,
echo=settings.DATABASE_ECHO,
echo_pool=settings.DATABASE_POOL_ECHO,
future=True,
# 中等并发
pool_size=10, # 低:- 高:+
max_overflow=20, # 低:- 高:+
pool_timeout=30, # 低:+ 高:-
pool_recycle=3600, # 低:+ 高:-
pool_pre_ping=True, # 低:False 高:True
pool_use_lifo=False, # 低:False 高:True
)
except Exception as e:
log.error('❌ 数据库链接失败 {}', e)
sys.exit()
Expand All @@ -27,31 +38,13 @@ def create_engine_and_session(url: str | URL):
return engine, db_session


SQLALCHEMY_DATABASE_URL = (
f'mysql+asyncmy://{settings.DATABASE_USER}:{settings.DATABASE_PASSWORD}@{settings.DATABASE_HOST}:'
f'{settings.DATABASE_PORT}/{settings.DATABASE_SCHEMA}?charset={settings.DATABASE_CHARSET}'
)

async_engine, async_db_session = create_engine_and_session(SQLALCHEMY_DATABASE_URL)


async def get_db() -> AsyncSession:
async def get_db():
"""session 生成器"""
session = async_db_session()
try:
async with async_db_session() as session:
yield session
except Exception as se:
await session.rollback()
raise se
finally:
await session.close()


# Session Annotated
CurrentSession = Annotated[AsyncSession, Depends(get_db)]


async def create_table():
async def create_table() -> None:
"""创建数据库表"""
async with async_engine.begin() as coon:
await coon.run_sync(MappedBase.metadata.create_all)
Expand All @@ -60,3 +53,13 @@ async def create_table():
def uuid4_str() -> str:
"""数据库引擎 UUID 类型兼容性解决方案"""
return str(uuid4())


SQLALCHEMY_DATABASE_URL = (
f'mysql+asyncmy://{settings.DATABASE_USER}:{settings.DATABASE_PASSWORD}@{settings.DATABASE_HOST}:'
f'{settings.DATABASE_PORT}/{settings.DATABASE_SCHEMA}?charset={settings.DATABASE_CHARSET}'
)

async_engine, async_db_session = create_async_engine_and_session(SQLALCHEMY_DATABASE_URL)
# Session Annotated
CurrentSession = Annotated[AsyncSession, Depends(get_db)]
82 changes: 4 additions & 78 deletions pdm.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading