toms-fast 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- toms_fast-0.2.1.dist-info/METADATA +467 -0
- toms_fast-0.2.1.dist-info/RECORD +60 -0
- toms_fast-0.2.1.dist-info/WHEEL +4 -0
- toms_fast-0.2.1.dist-info/entry_points.txt +2 -0
- tomskit/__init__.py +0 -0
- tomskit/celery/README.md +693 -0
- tomskit/celery/__init__.py +4 -0
- tomskit/celery/celery.py +306 -0
- tomskit/celery/config.py +377 -0
- tomskit/cli/__init__.py +207 -0
- tomskit/cli/__main__.py +8 -0
- tomskit/cli/scaffold.py +123 -0
- tomskit/cli/templates/__init__.py +42 -0
- tomskit/cli/templates/base.py +348 -0
- tomskit/cli/templates/celery.py +101 -0
- tomskit/cli/templates/extensions.py +213 -0
- tomskit/cli/templates/fastapi.py +400 -0
- tomskit/cli/templates/migrations.py +281 -0
- tomskit/cli/templates_config.py +122 -0
- tomskit/logger/README.md +466 -0
- tomskit/logger/__init__.py +4 -0
- tomskit/logger/config.py +106 -0
- tomskit/logger/logger.py +290 -0
- tomskit/py.typed +0 -0
- tomskit/redis/README.md +462 -0
- tomskit/redis/__init__.py +6 -0
- tomskit/redis/config.py +85 -0
- tomskit/redis/redis_pool.py +87 -0
- tomskit/redis/redis_sync.py +66 -0
- tomskit/server/__init__.py +47 -0
- tomskit/server/config.py +117 -0
- tomskit/server/exceptions.py +412 -0
- tomskit/server/middleware.py +371 -0
- tomskit/server/parser.py +312 -0
- tomskit/server/resource.py +464 -0
- tomskit/server/server.py +276 -0
- tomskit/server/type.py +263 -0
- tomskit/sqlalchemy/README.md +590 -0
- tomskit/sqlalchemy/__init__.py +20 -0
- tomskit/sqlalchemy/config.py +125 -0
- tomskit/sqlalchemy/database.py +125 -0
- tomskit/sqlalchemy/pagination.py +359 -0
- tomskit/sqlalchemy/property.py +19 -0
- tomskit/sqlalchemy/sqlalchemy.py +131 -0
- tomskit/sqlalchemy/types.py +32 -0
- tomskit/task/README.md +67 -0
- tomskit/task/__init__.py +4 -0
- tomskit/task/task_manager.py +124 -0
- tomskit/tools/README.md +63 -0
- tomskit/tools/__init__.py +18 -0
- tomskit/tools/config.py +70 -0
- tomskit/tools/warnings.py +37 -0
- tomskit/tools/woker.py +81 -0
- tomskit/utils/README.md +666 -0
- tomskit/utils/README_SERIALIZER.md +644 -0
- tomskit/utils/__init__.py +35 -0
- tomskit/utils/fields.py +434 -0
- tomskit/utils/marshal_utils.py +137 -0
- tomskit/utils/response_utils.py +13 -0
- tomskit/utils/serializers.py +447 -0
tomskit/logger/logger.py
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
"""
|
|
2
|
+
日志模块
|
|
3
|
+
|
|
4
|
+
提供结构化的日志配置和追踪 ID 支持,支持应用日志、访问日志和 SQL 日志的分离。
|
|
5
|
+
支持集中管理框架日志(uvicorn、fastapi)和第三方库日志。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import logging
|
|
10
|
+
from contextvars import ContextVar
|
|
11
|
+
from logging.handlers import TimedRotatingFileHandler
|
|
12
|
+
from typing import Optional
|
|
13
|
+
from tomskit.logger.config import LoggerConfig
|
|
14
|
+
|
|
15
|
+
# === Trace ID 上下文变量 ===
|
|
16
|
+
# 使用 ContextVar 实现线程/协程安全的上下文变量,支持异步环境
|
|
17
|
+
_trace_id_context_var: ContextVar[str] = ContextVar("app_trace_id", default="-")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def set_app_trace_id(trace_id: str) -> None:
|
|
21
|
+
"""
|
|
22
|
+
设置当前请求的追踪 ID
|
|
23
|
+
|
|
24
|
+
该 ID 会自动添加到所有日志记录中,用于分布式追踪和问题定位。
|
|
25
|
+
通常在 FastAPI 中间件中调用,为每个请求生成唯一的追踪 ID。
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
trace_id: 追踪 ID 字符串,通常是 UUID
|
|
29
|
+
"""
|
|
30
|
+
_trace_id_context_var.set(trace_id)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_app_trace_id() -> str:
|
|
34
|
+
"""
|
|
35
|
+
获取当前请求的追踪 ID
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
追踪 ID 字符串,如果未设置则返回默认值 "-"
|
|
39
|
+
"""
|
|
40
|
+
return _trace_id_context_var.get()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class TraceIdFormatter(logging.Formatter):
|
|
44
|
+
"""
|
|
45
|
+
追踪 ID 格式化器
|
|
46
|
+
|
|
47
|
+
继承自 logging.Formatter,自动为每条日志记录注入 trace_id 和 prefix。
|
|
48
|
+
确保所有日志都包含追踪信息,便于问题定位和日志关联。
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(self, prefix: str = "", *args, **kwargs):
|
|
52
|
+
"""
|
|
53
|
+
初始化格式化器
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
prefix: 日志前缀,会添加到每条日志记录中
|
|
57
|
+
*args, **kwargs: 传递给父类的其他参数
|
|
58
|
+
"""
|
|
59
|
+
self.prefix = prefix
|
|
60
|
+
super().__init__(*args, **kwargs)
|
|
61
|
+
|
|
62
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
63
|
+
"""
|
|
64
|
+
格式化日志记录
|
|
65
|
+
|
|
66
|
+
自动注入 trace_id 和 prefix 到日志记录中。
|
|
67
|
+
注意:LOG_FORMAT 中需要包含 %(trace_id)s 和 %(prefix)s 才能显示。
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
record: 日志记录对象
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
格式化后的日志字符串
|
|
74
|
+
"""
|
|
75
|
+
record.trace_id = get_app_trace_id()
|
|
76
|
+
record.prefix = self.prefix
|
|
77
|
+
return super().format(record)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def setup_logging(settings: LoggerConfig) -> None:
|
|
81
|
+
"""
|
|
82
|
+
配置日志系统
|
|
83
|
+
|
|
84
|
+
设置应用日志、访问日志、SQL 日志和 Celery 日志的处理器和格式化器。
|
|
85
|
+
集中管理框架日志(uvicorn、fastapi、celery)和第三方库日志。
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
settings: 日志配置对象
|
|
89
|
+
|
|
90
|
+
Raises:
|
|
91
|
+
OSError: 当日志目录创建失败时抛出
|
|
92
|
+
PermissionError: 当没有权限创建日志文件时抛出
|
|
93
|
+
"""
|
|
94
|
+
# === 基础配置 ===
|
|
95
|
+
log_level = settings.LOG_LEVEL.upper()
|
|
96
|
+
application_log_path = os.path.join(settings.LOG_DIR, f"{settings.LOG_NAME}.log")
|
|
97
|
+
access_log_path = os.path.join(settings.LOG_DIR, f"{settings.LOG_ACCESS_NAME}.log")
|
|
98
|
+
sql_log_path = os.path.join(settings.LOG_DIR, f"{settings.LOG_SQL_NAME}.log")
|
|
99
|
+
celery_log_path = os.path.join(settings.LOG_DIR, f"{settings.LOG_CELERY_NAME}.log")
|
|
100
|
+
|
|
101
|
+
# 创建日志目录(如果不存在)
|
|
102
|
+
try:
|
|
103
|
+
os.makedirs(settings.LOG_DIR, exist_ok=True)
|
|
104
|
+
except (OSError, PermissionError) as e:
|
|
105
|
+
raise OSError(f"无法创建日志目录 {settings.LOG_DIR}: {e}")
|
|
106
|
+
|
|
107
|
+
# === 格式化器 ===
|
|
108
|
+
application_log_formatter = TraceIdFormatter(
|
|
109
|
+
prefix=settings.LOG_PREFIX,
|
|
110
|
+
fmt=settings.LOG_FORMAT,
|
|
111
|
+
datefmt=settings.LOG_DATE_FORMAT
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
access_log_formatter = TraceIdFormatter(
|
|
115
|
+
prefix=settings.LOG_PREFIX,
|
|
116
|
+
fmt=settings.LOG_ACCESS_FORMAT,
|
|
117
|
+
datefmt=settings.LOG_DATE_FORMAT
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# === 处理器 ===
|
|
121
|
+
# 控制台处理器:输出到标准输出
|
|
122
|
+
console_handler = logging.StreamHandler()
|
|
123
|
+
console_handler.setFormatter(application_log_formatter)
|
|
124
|
+
|
|
125
|
+
# 应用日志文件处理器:按天轮转
|
|
126
|
+
try:
|
|
127
|
+
application_file_handler = TimedRotatingFileHandler(
|
|
128
|
+
filename=application_log_path,
|
|
129
|
+
when=settings.LOG_ROTATE_WHEN,
|
|
130
|
+
backupCount=settings.LOG_BACKUP_COUNT,
|
|
131
|
+
encoding="utf-8",
|
|
132
|
+
utc=settings.LOG_USE_UTC,
|
|
133
|
+
)
|
|
134
|
+
application_file_handler.suffix = "%Y-%m-%d"
|
|
135
|
+
application_file_handler.setFormatter(application_log_formatter)
|
|
136
|
+
except (OSError, PermissionError) as e:
|
|
137
|
+
raise OSError(f"无法创建应用日志文件 {application_log_path}: {e}")
|
|
138
|
+
|
|
139
|
+
# 访问日志文件处理器:按天轮转
|
|
140
|
+
try:
|
|
141
|
+
access_file_handler = TimedRotatingFileHandler(
|
|
142
|
+
filename=access_log_path,
|
|
143
|
+
when=settings.LOG_ROTATE_WHEN,
|
|
144
|
+
backupCount=settings.LOG_BACKUP_COUNT,
|
|
145
|
+
encoding="utf-8",
|
|
146
|
+
utc=settings.LOG_USE_UTC,
|
|
147
|
+
)
|
|
148
|
+
access_file_handler.suffix = "%Y-%m-%d"
|
|
149
|
+
access_file_handler.setFormatter(access_log_formatter)
|
|
150
|
+
except (OSError, PermissionError) as e:
|
|
151
|
+
raise OSError(f"无法创建访问日志文件 {access_log_path}: {e}")
|
|
152
|
+
|
|
153
|
+
# SQL 日志文件处理器(可选):按天轮转
|
|
154
|
+
sql_file_handler: Optional[TimedRotatingFileHandler] = None
|
|
155
|
+
if settings.LOG_SQL_ENABLED:
|
|
156
|
+
try:
|
|
157
|
+
sql_file_handler = TimedRotatingFileHandler(
|
|
158
|
+
filename=sql_log_path,
|
|
159
|
+
when=settings.LOG_ROTATE_WHEN,
|
|
160
|
+
backupCount=settings.LOG_BACKUP_COUNT,
|
|
161
|
+
encoding="utf-8",
|
|
162
|
+
utc=settings.LOG_USE_UTC,
|
|
163
|
+
)
|
|
164
|
+
sql_file_handler.suffix = "%Y-%m-%d"
|
|
165
|
+
sql_file_handler.setFormatter(application_log_formatter)
|
|
166
|
+
except (OSError, PermissionError) as e:
|
|
167
|
+
raise OSError(f"无法创建 SQL 日志文件 {sql_log_path}: {e}")
|
|
168
|
+
|
|
169
|
+
# Celery 日志文件处理器(可选):按天轮转
|
|
170
|
+
celery_file_handler: Optional[TimedRotatingFileHandler] = None
|
|
171
|
+
if settings.LOG_CELERY_ENABLED:
|
|
172
|
+
try:
|
|
173
|
+
celery_file_handler = TimedRotatingFileHandler(
|
|
174
|
+
filename=celery_log_path,
|
|
175
|
+
when=settings.LOG_ROTATE_WHEN,
|
|
176
|
+
backupCount=settings.LOG_BACKUP_COUNT,
|
|
177
|
+
encoding="utf-8",
|
|
178
|
+
utc=settings.LOG_USE_UTC,
|
|
179
|
+
)
|
|
180
|
+
celery_file_handler.suffix = "%Y-%m-%d"
|
|
181
|
+
celery_file_handler.setFormatter(application_log_formatter)
|
|
182
|
+
except (OSError, PermissionError) as e:
|
|
183
|
+
raise OSError(f"无法创建 Celery 日志文件 {celery_log_path}: {e}")
|
|
184
|
+
|
|
185
|
+
# === Root Logger:所有 getLogger(__name__) 会冒泡到这里 ===
|
|
186
|
+
root_logger = logging.getLogger()
|
|
187
|
+
root_logger.setLevel(log_level)
|
|
188
|
+
root_logger.handlers.clear()
|
|
189
|
+
root_logger.addHandler(console_handler)
|
|
190
|
+
root_logger.addHandler(application_file_handler)
|
|
191
|
+
|
|
192
|
+
# === 访问日志(gunicorn/uvicorn)===
|
|
193
|
+
# 访问日志单独处理,不冒泡到 root,避免重复
|
|
194
|
+
for access_logger_name in ["uvicorn.access", "gunicorn.access"]:
|
|
195
|
+
access_logger = logging.getLogger(access_logger_name)
|
|
196
|
+
access_logger.setLevel("INFO")
|
|
197
|
+
access_logger.handlers.clear()
|
|
198
|
+
access_logger.addHandler(console_handler)
|
|
199
|
+
access_logger.addHandler(access_file_handler)
|
|
200
|
+
access_logger.propagate = False # 不冒泡到 root,避免重复
|
|
201
|
+
|
|
202
|
+
# === 框架日志(uvicorn、fastapi)===
|
|
203
|
+
# uvicorn 错误日志和主日志
|
|
204
|
+
for uvicorn_logger_name in ["uvicorn.error", "uvicorn"]:
|
|
205
|
+
uvicorn_logger = logging.getLogger(uvicorn_logger_name)
|
|
206
|
+
uvicorn_logger.setLevel(log_level)
|
|
207
|
+
uvicorn_logger.handlers.clear()
|
|
208
|
+
uvicorn_logger.addHandler(console_handler)
|
|
209
|
+
uvicorn_logger.addHandler(application_file_handler)
|
|
210
|
+
uvicorn_logger.propagate = False # 不冒泡到 root,避免重复
|
|
211
|
+
|
|
212
|
+
# FastAPI 日志
|
|
213
|
+
fastapi_logger = logging.getLogger("fastapi")
|
|
214
|
+
fastapi_logger.setLevel(log_level)
|
|
215
|
+
fastapi_logger.handlers.clear()
|
|
216
|
+
fastapi_logger.addHandler(console_handler)
|
|
217
|
+
fastapi_logger.addHandler(application_file_handler)
|
|
218
|
+
fastapi_logger.propagate = False # 不冒泡到 root,避免重复
|
|
219
|
+
|
|
220
|
+
# === SQLAlchemy 日志 ===
|
|
221
|
+
if settings.LOG_SQL_ENABLED and sql_file_handler:
|
|
222
|
+
# SQL 日志单独文件处理
|
|
223
|
+
for sql_logger_name in ["sqlalchemy.engine", "sqlalchemy.pool"]:
|
|
224
|
+
sql_logger = logging.getLogger(sql_logger_name)
|
|
225
|
+
sql_logger.setLevel(settings.LOG_SQL_LEVEL.upper())
|
|
226
|
+
sql_logger.handlers.clear()
|
|
227
|
+
sql_logger.addHandler(sql_file_handler) # 只写入 SQL 日志文件
|
|
228
|
+
sql_logger.propagate = False # 不冒泡,避免重复
|
|
229
|
+
else:
|
|
230
|
+
# SQL 日志合并到应用日志
|
|
231
|
+
for sql_logger_name in ["sqlalchemy.engine", "sqlalchemy.pool"]:
|
|
232
|
+
sql_logger = logging.getLogger(sql_logger_name)
|
|
233
|
+
sql_logger.setLevel(settings.LOG_SQL_LEVEL.upper())
|
|
234
|
+
sql_logger.handlers.clear()
|
|
235
|
+
sql_logger.addHandler(console_handler)
|
|
236
|
+
sql_logger.addHandler(application_file_handler)
|
|
237
|
+
sql_logger.propagate = False # 不冒泡,避免重复
|
|
238
|
+
|
|
239
|
+
# === Celery 日志 ===
|
|
240
|
+
celery_logger_names = ["celery", "celery.task", "celery.worker", "celery.beat"]
|
|
241
|
+
if settings.LOG_CELERY_ENABLED and celery_file_handler:
|
|
242
|
+
# Celery 日志单独文件处理
|
|
243
|
+
for celery_logger_name in celery_logger_names:
|
|
244
|
+
celery_logger = logging.getLogger(celery_logger_name)
|
|
245
|
+
celery_logger.setLevel(settings.LOG_CELERY_LEVEL.upper())
|
|
246
|
+
celery_logger.handlers.clear()
|
|
247
|
+
celery_logger.addHandler(celery_file_handler) # 只写入 Celery 日志文件
|
|
248
|
+
celery_logger.propagate = False # 不冒泡,避免重复
|
|
249
|
+
|
|
250
|
+
# Kombu 日志(消息队列)也写入 Celery 日志文件
|
|
251
|
+
kombu_logger = logging.getLogger("kombu")
|
|
252
|
+
kombu_logger.setLevel(settings.LOG_CELERY_LEVEL.upper())
|
|
253
|
+
kombu_logger.handlers.clear()
|
|
254
|
+
kombu_logger.addHandler(celery_file_handler)
|
|
255
|
+
kombu_logger.propagate = False # 不冒泡,避免重复
|
|
256
|
+
else:
|
|
257
|
+
# Celery 日志合并到应用日志
|
|
258
|
+
for celery_logger_name in celery_logger_names:
|
|
259
|
+
celery_logger = logging.getLogger(celery_logger_name)
|
|
260
|
+
celery_logger.setLevel(settings.LOG_CELERY_LEVEL.upper())
|
|
261
|
+
celery_logger.handlers.clear()
|
|
262
|
+
celery_logger.addHandler(console_handler)
|
|
263
|
+
celery_logger.addHandler(application_file_handler)
|
|
264
|
+
celery_logger.propagate = False # 不冒泡,避免重复
|
|
265
|
+
|
|
266
|
+
# Kombu 日志合并到应用日志
|
|
267
|
+
kombu_logger = logging.getLogger("kombu")
|
|
268
|
+
kombu_logger.setLevel(settings.LOG_CELERY_LEVEL.upper())
|
|
269
|
+
kombu_logger.handlers.clear()
|
|
270
|
+
kombu_logger.addHandler(console_handler)
|
|
271
|
+
kombu_logger.addHandler(application_file_handler)
|
|
272
|
+
kombu_logger.propagate = False # 不冒泡,避免重复
|
|
273
|
+
|
|
274
|
+
# === 系统日志降噪 ===
|
|
275
|
+
# asyncio 日志:只显示 WARNING 及以上级别
|
|
276
|
+
logging.getLogger("asyncio").setLevel(logging.WARNING)
|
|
277
|
+
|
|
278
|
+
# === 第三方库日志降噪 ===
|
|
279
|
+
# HTTP 客户端库
|
|
280
|
+
third_party_log_level = getattr(logging, settings.LOG_THIRD_PARTY_LEVEL.upper(), logging.WARNING)
|
|
281
|
+
for http_lib_name in ["httpx", "httpcore", "urllib3"]:
|
|
282
|
+
logging.getLogger(http_lib_name).setLevel(third_party_log_level)
|
|
283
|
+
|
|
284
|
+
# AWS SDK(如果使用)
|
|
285
|
+
for aws_lib_name in ["boto3", "botocore"]:
|
|
286
|
+
logging.getLogger(aws_lib_name).setLevel(third_party_log_level)
|
|
287
|
+
|
|
288
|
+
# 其他可能产生大量日志的库
|
|
289
|
+
logging.getLogger("PIL").setLevel(third_party_log_level) # Pillow
|
|
290
|
+
logging.getLogger("matplotlib").setLevel(third_party_log_level)
|
tomskit/py.typed
ADDED
|
File without changes
|