rbm_tools 0.0.0__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.
Potentially problematic release.
This version of rbm_tools might be problematic. Click here for more details.
- rbm_tools/__init__.py +5 -0
- rbm_tools/client/rbm_tools_cli.py +51 -0
- rbm_tools/client/sort_class_methods.py +78 -0
- rbm_tools/env.py +11 -0
- rbm_tools/logger.py +278 -0
- rbm_tools/middleware/llm.py +520 -0
- rbm_tools/middleware/redis_pool.py +121 -0
- rbm_tools/middleware/sqlalchemy_engine_provider.py +98 -0
- rbm_tools/model/api/common.py +62 -0
- rbm_tools/model/dao/common.py +57 -0
- rbm_tools/model/dao/redis.py +15 -0
- rbm_tools/model/exception/error_code_exception.py +47 -0
- rbm_tools/model/exception/http_exception.py +34 -0
- rbm_tools/model/exception/internal_exception.py +26 -0
- rbm_tools/util/common_util.py +299 -0
- rbm_tools/util/context_util.py +336 -0
- rbm_tools/util/fastapi/endpoint/ops.py +17 -0
- rbm_tools/util/fastapi/endpoint/prometheus.py +9 -0
- rbm_tools/util/fastapi/fastapi_util.py +291 -0
- rbm_tools/util/fastapi/middleware/header.py +30 -0
- rbm_tools/util/fastapi/middleware/log.py +52 -0
- rbm_tools/util/fastapi/middleware/prometheus.py +36 -0
- rbm_tools/util/httpx_util.py +208 -0
- rbm_tools/util/prompt_util.py +37 -0
- rbm_tools/util/time_util.py +230 -0
- rbm_tools-0.0.0.dist-info/METADATA +127 -0
- rbm_tools-0.0.0.dist-info/RECORD +30 -0
- rbm_tools-0.0.0.dist-info/WHEEL +4 -0
- rbm_tools-0.0.0.dist-info/entry_points.txt +3 -0
- rbm_tools-0.0.0.dist-info/licenses/LICENSE +21 -0
rbm_tools/__init__.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
命令行工具类
|
|
3
|
+
"""
|
|
4
|
+
import sys
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
from jsonargparse import auto_cli
|
|
8
|
+
from rbm_tools.client.sort_class_methods import reorder_class_methods
|
|
9
|
+
|
|
10
|
+
class RBMToolsCLI:
|
|
11
|
+
"""
|
|
12
|
+
命令行工具类
|
|
13
|
+
"""
|
|
14
|
+
def __init__(self):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
def check_code_style(self):
|
|
18
|
+
"""
|
|
19
|
+
检查代码风格
|
|
20
|
+
"""
|
|
21
|
+
success_flag = True
|
|
22
|
+
directory = os.getcwd()
|
|
23
|
+
entries = os.listdir(directory)
|
|
24
|
+
# 只看 py 文件
|
|
25
|
+
filename_list = [
|
|
26
|
+
os.path.join(directory, i) for i in entries
|
|
27
|
+
if os.path.isfile(os.path.join(directory, i)) and i.endswith(".py")
|
|
28
|
+
]
|
|
29
|
+
for filename in filename_list:
|
|
30
|
+
# 检查对象的成员方法排序
|
|
31
|
+
old_code = ""
|
|
32
|
+
with open(filename, "r", encoding="utf-8") as f:
|
|
33
|
+
old_code = f.read()
|
|
34
|
+
new_code = reorder_class_methods(old_code)
|
|
35
|
+
with open(filename, "w", encoding="utf-8") as f:
|
|
36
|
+
f.write(new_code)
|
|
37
|
+
if old_code != new_code:
|
|
38
|
+
success_flag = False
|
|
39
|
+
if success_flag:
|
|
40
|
+
sys.exit(0)
|
|
41
|
+
else:
|
|
42
|
+
sys.exit(1)
|
|
43
|
+
|
|
44
|
+
def main():
|
|
45
|
+
"""
|
|
46
|
+
入口函数
|
|
47
|
+
"""
|
|
48
|
+
auto_cli(RBMToolsCLI)
|
|
49
|
+
|
|
50
|
+
if __name__ == "__main__":
|
|
51
|
+
main()
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""
|
|
2
|
+
自动整理 Python 类中的方法顺序
|
|
3
|
+
|
|
4
|
+
依赖:
|
|
5
|
+
需要安装 libcst 库(pip install libcst)
|
|
6
|
+
|
|
7
|
+
适用场景:
|
|
8
|
+
适用于需要规范化类方法顺序、提升代码可读性和维护性的场景
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import libcst as cst
|
|
12
|
+
import libcst.matchers as m
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ClassMethodSorter(cst.CSTTransformer):
|
|
16
|
+
"""
|
|
17
|
+
一个 CSTTransformer,用于对每个类中的方法按如下规则排序:
|
|
18
|
+
1. 魔法方法(即名称前后都有双下划线,如 __init__、__str__ 等),按名称排序,排在最前面;
|
|
19
|
+
2. 公共方法(不以下划线开头),按名称排序,排在中间;
|
|
20
|
+
3. 私有方法(以下划线开头,但不是魔法方法),按名称排序,排在最后;
|
|
21
|
+
其他类成员(如属性、docstring、嵌套类等)保持原有顺序,并始终排在方法之前。
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def leave_ClassDef(self, original_node, updated_node):
|
|
25
|
+
"""
|
|
26
|
+
在离开类定义节点时,对类体中的方法按指定规则排序。
|
|
27
|
+
非方法成员保持原有顺序,并排在方法之前。
|
|
28
|
+
|
|
29
|
+
:param original_node: 原始的 ClassDef 节点
|
|
30
|
+
:param updated_node: 可能已被修改的 ClassDef 节点
|
|
31
|
+
:return: 排序后的新的 ClassDef 节点
|
|
32
|
+
"""
|
|
33
|
+
# 分离方法和其他成员
|
|
34
|
+
magic_methods = []
|
|
35
|
+
public_methods = []
|
|
36
|
+
private_methods = []
|
|
37
|
+
others = []
|
|
38
|
+
for stmt in updated_node.body.body:
|
|
39
|
+
if m.matches(stmt, m.FunctionDef()):
|
|
40
|
+
name = stmt.name.value # type: ignore
|
|
41
|
+
# 魔法方法:前后都有双下划线
|
|
42
|
+
if name.startswith("__") and name.endswith("__"):
|
|
43
|
+
magic_methods.append(stmt)
|
|
44
|
+
# 私有方法:以单下划线或双下划线开头,但不是魔法方法
|
|
45
|
+
elif name.startswith("_"):
|
|
46
|
+
private_methods.append(stmt)
|
|
47
|
+
# 公共方法:不以下划线开头
|
|
48
|
+
else:
|
|
49
|
+
public_methods.append(stmt)
|
|
50
|
+
else:
|
|
51
|
+
others.append(stmt)
|
|
52
|
+
# 分别排序
|
|
53
|
+
magic_methods_sorted = sorted(magic_methods, key=lambda f: f.name.value)
|
|
54
|
+
public_methods_sorted = sorted(public_methods, key=lambda f: f.name.value)
|
|
55
|
+
private_methods_sorted = sorted(private_methods, key=lambda f: f.name.value)
|
|
56
|
+
# 合并:其他成员 + 魔法方法 + 公共方法 + 私有方法
|
|
57
|
+
new_body = (
|
|
58
|
+
list(others)
|
|
59
|
+
+ magic_methods_sorted
|
|
60
|
+
+ public_methods_sorted
|
|
61
|
+
+ private_methods_sorted
|
|
62
|
+
)
|
|
63
|
+
# 返回新的类定义节点
|
|
64
|
+
return updated_node.with_changes(
|
|
65
|
+
body=updated_node.body.with_changes(body=new_body)
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def reorder_class_methods(source_code):
|
|
70
|
+
"""
|
|
71
|
+
对给定 Python 源代码中的每个类的方法和成员按照 ClassMethodSorter 定义的规则排序
|
|
72
|
+
|
|
73
|
+
:param source_code: 需要处理的 Python 源代码字符串
|
|
74
|
+
:return: 方法已按类型排序后的新源代码字符串
|
|
75
|
+
"""
|
|
76
|
+
tree = cst.parse_module(source_code)
|
|
77
|
+
new_tree = tree.visit(ClassMethodSorter())
|
|
78
|
+
return new_tree.code
|
rbm_tools/env.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""
|
|
2
|
+
环境变量注册
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
CONF_ENV = os.getenv("CONF_ENV", "test")
|
|
8
|
+
HOSTNAME = os.getenv("HOSTNAME", "localhost")
|
|
9
|
+
MODULE = os.getenv("MODULE", "defaultmodule")
|
|
10
|
+
POD_IP = os.getenv("POD_IP", "127.0.0.1")
|
|
11
|
+
POD_NAME = os.getenv("POD_NAME", "defaultpodname")
|
rbm_tools/logger.py
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"""
|
|
2
|
+
日志类
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
from copy import copy
|
|
9
|
+
from logging.handlers import QueueHandler, QueueListener, RotatingFileHandler
|
|
10
|
+
from multiprocessing import Queue
|
|
11
|
+
|
|
12
|
+
import click
|
|
13
|
+
from opentelemetry import trace
|
|
14
|
+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
|
15
|
+
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
|
|
16
|
+
from opentelemetry.instrumentation.logging import LoggingInstrumentor
|
|
17
|
+
from opentelemetry.instrumentation.openai import OpenAIInstrumentor
|
|
18
|
+
from opentelemetry.instrumentation.openai.shared import chat_wrappers
|
|
19
|
+
from opentelemetry.sdk.resources import Resource
|
|
20
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
21
|
+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
22
|
+
from pydantic import BaseModel, Field
|
|
23
|
+
|
|
24
|
+
from rbm_tools.env import CONF_ENV, HOSTNAME, MODULE, POD_IP
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ColourizedFormatter(logging.Formatter):
|
|
28
|
+
"""
|
|
29
|
+
日志内容上颜色模块
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
level_name_colors = {
|
|
33
|
+
logging.DEBUG: lambda field: click.style(str(field), fg="bright_cyan"),
|
|
34
|
+
logging.WARNING: lambda field: click.style(str(field), fg="bright_yellow"),
|
|
35
|
+
logging.ERROR: lambda field: click.style(str(field), fg="bright_red"),
|
|
36
|
+
logging.CRITICAL: lambda field: click.style(str(field), fg="bright_red"),
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
def formatMessage(self, record: logging.LogRecord) -> str:
|
|
40
|
+
recordcopy = copy(record)
|
|
41
|
+
# message
|
|
42
|
+
if "color_message" in recordcopy.__dict__:
|
|
43
|
+
recordcopy.msg = recordcopy.__dict__["color_message"]
|
|
44
|
+
recordcopy.__dict__["message"] = recordcopy.getMessage()
|
|
45
|
+
recordcopy.__dict__["message"] = self.__color_field_by_level_no(
|
|
46
|
+
recordcopy.message, recordcopy.levelno
|
|
47
|
+
)
|
|
48
|
+
# asctime
|
|
49
|
+
recordcopy.__dict__["asctime"] = self.__color_field_by_fg(
|
|
50
|
+
recordcopy.asctime, "bright_green"
|
|
51
|
+
)
|
|
52
|
+
# levelname
|
|
53
|
+
seperator = " " * (8 - len(recordcopy.levelname))
|
|
54
|
+
levelname = self.__color_field_by_level_no(
|
|
55
|
+
recordcopy.levelname, recordcopy.levelno
|
|
56
|
+
)
|
|
57
|
+
recordcopy.__dict__["levelname"] = levelname + seperator
|
|
58
|
+
# otelTraceID
|
|
59
|
+
otel_trace_id = recordcopy.otelTraceID if hasattr(recordcopy, "otelTraceID") else "null" # type: ignore # pylint: disable=C0301
|
|
60
|
+
seperator = " " * (32 - len(otel_trace_id))
|
|
61
|
+
recordcopy.__dict__["otelTraceID"] = otel_trace_id + seperator
|
|
62
|
+
# otelSpanID
|
|
63
|
+
otel_span_id = recordcopy.otelSpanID if hasattr(recordcopy, "otelSpanID") else "null" # type: ignore # pylint: disable=C0301
|
|
64
|
+
seperator = " " * (16 - len(otel_span_id))
|
|
65
|
+
recordcopy.__dict__["otelSpanID"] = otel_span_id + seperator
|
|
66
|
+
# otelTraceSampled
|
|
67
|
+
otel_trace_sampled = recordcopy.otelTraceSampled if hasattr(recordcopy, "otelTraceSampled") else "null" # type: ignore # pylint: disable=C0301
|
|
68
|
+
seperator = " " * (5 - len(str(otel_trace_sampled)))
|
|
69
|
+
recordcopy.__dict__["otelTraceSampled"] = str(otel_trace_sampled) + seperator
|
|
70
|
+
# name
|
|
71
|
+
recordcopy.__dict__["name"] = self.__color_field_by_fg(
|
|
72
|
+
recordcopy.name, "bright_magenta"
|
|
73
|
+
)
|
|
74
|
+
# module
|
|
75
|
+
recordcopy.__dict__["module"] = self.__color_field_by_fg(
|
|
76
|
+
recordcopy.module, "bright_blue"
|
|
77
|
+
)
|
|
78
|
+
# funcName
|
|
79
|
+
recordcopy.__dict__["funcName"] = self.__color_field_by_fg(
|
|
80
|
+
recordcopy.funcName, "bright_blue"
|
|
81
|
+
)
|
|
82
|
+
# lineno
|
|
83
|
+
recordcopy.__dict__["lineno"] = self.__color_field_by_fg(
|
|
84
|
+
str(recordcopy.lineno), "bright_blue"
|
|
85
|
+
)
|
|
86
|
+
# customized_data_info
|
|
87
|
+
customized_data_info = "{}"
|
|
88
|
+
if hasattr(recordcopy, "customized_data_info"):
|
|
89
|
+
try:
|
|
90
|
+
customized_data_info = json.dumps(
|
|
91
|
+
recordcopy.customized_data_info, cls=CustomJSONEncoder, ensure_ascii=False # type: ignore # pylint: disable=C0301
|
|
92
|
+
)
|
|
93
|
+
except Exception: # pylint: disable=W0718
|
|
94
|
+
customized_data_info = recordcopy.customized_data_info # type: ignore
|
|
95
|
+
recordcopy.__dict__["customized_data_info"] = self.__color_field_by_level_no(
|
|
96
|
+
field=customized_data_info,
|
|
97
|
+
level_no=recordcopy.levelno,
|
|
98
|
+
default_fg="bright_green",
|
|
99
|
+
)
|
|
100
|
+
return super().formatMessage(recordcopy)
|
|
101
|
+
|
|
102
|
+
def __color_field_by_fg(self, field: str, fg: str) -> str:
|
|
103
|
+
return click.style(str(field), fg=fg)
|
|
104
|
+
|
|
105
|
+
def __color_field_by_level_no(
|
|
106
|
+
self, field: str, level_no: int, default_fg: str = "reset"
|
|
107
|
+
) -> str:
|
|
108
|
+
def default(field: str) -> str:
|
|
109
|
+
return click.style(str(field), fg=default_fg)
|
|
110
|
+
|
|
111
|
+
func = self.level_name_colors.get(level_no, default)
|
|
112
|
+
return func(field)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class CustomJSONEncoder(json.JSONEncoder):
|
|
116
|
+
"""
|
|
117
|
+
自定义的 JSONEncoder
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
def default(self, o):
|
|
121
|
+
if isinstance(o, BaseModel):
|
|
122
|
+
return o.model_dump()
|
|
123
|
+
return super().default(o)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class NoneRootWarningFilter(logging.Filter):
|
|
127
|
+
"""
|
|
128
|
+
非 root 的 logger 只允许 WARNING 级别的日志(除 uvicorn 相关的日志外)
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
def filter(self, record):
|
|
132
|
+
return (
|
|
133
|
+
"uvicorn" in record.name
|
|
134
|
+
or record.name == "root"
|
|
135
|
+
or record.levelno >= logging.WARNING
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def process_image_item(item: dict) -> dict:
|
|
140
|
+
"""
|
|
141
|
+
自定义大模型调用中图片 base64 字符串的处理
|
|
142
|
+
"""
|
|
143
|
+
customized_url_for_trace = item.get("image_url", {}).get(
|
|
144
|
+
"customized_url_for_trace", ""
|
|
145
|
+
)
|
|
146
|
+
if customized_url_for_trace:
|
|
147
|
+
return {"type": "image_url", "image_url": {"url": customized_url_for_trace}}
|
|
148
|
+
else:
|
|
149
|
+
return item
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
async def _process_image_item(
|
|
153
|
+
item, trace_id, span_id, message_index, content_index # pylint: disable=W0613
|
|
154
|
+
) -> dict:
|
|
155
|
+
return process_image_item(item)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# 不优雅的解决方案,如果要优雅的话需要开源社区支持
|
|
159
|
+
chat_wrappers._process_image_item = _process_image_item # pylint: disable=W0212
|
|
160
|
+
|
|
161
|
+
# 获取环境变量
|
|
162
|
+
app_name = MODULE
|
|
163
|
+
app_env = CONF_ENV
|
|
164
|
+
hostname = HOSTNAME
|
|
165
|
+
ip = POD_IP
|
|
166
|
+
# 日志配置
|
|
167
|
+
format_str = "%(asctime)s | %(levelname)-8s | trace_id=%(otelTraceID)-32s span_id=%(otelSpanID)-16s sampled=%(otelTraceSampled)-5s | %(name)s | %(module)s:%(funcName)s:%(lineno)s - %(message)s | %(customized_data_info)s" # pylint: disable=C0103,C0301
|
|
168
|
+
log_config = {
|
|
169
|
+
"version": 1,
|
|
170
|
+
"loggers": {
|
|
171
|
+
"uvicorn": {"level": "INFO", "propagate": True},
|
|
172
|
+
"uvicorn.error": {"level": "INFO", "propagate": True},
|
|
173
|
+
"uvicorn.access": {"level": "INFO", "propagate": True},
|
|
174
|
+
},
|
|
175
|
+
}
|
|
176
|
+
resource = Resource.create(
|
|
177
|
+
attributes={
|
|
178
|
+
"service_name": app_name,
|
|
179
|
+
"service_env": app_env,
|
|
180
|
+
"service.name": app_name,
|
|
181
|
+
"service.env": app_env,
|
|
182
|
+
}
|
|
183
|
+
)
|
|
184
|
+
# trace Tempo 配置
|
|
185
|
+
# os.environ["OTEL_EXPORTER_OTLP_TRACES_PROTOCOL"] = "http/protobuf"
|
|
186
|
+
# os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"] = tempo_url
|
|
187
|
+
# os.environ["OTEL_EXPORTER_OTLP_HEADERS"] = f"Authorization=Basic%20{tempo_credentials}"
|
|
188
|
+
# os.environ["OTEL_PYTHON_LOG_CORRELATION"] = "true"
|
|
189
|
+
# os.environ["OTEL_TRACES_SAMPLER"] = "traceidratio"
|
|
190
|
+
# os.environ["OTEL_TRACES_SAMPLER_ARG"] = "1.00"
|
|
191
|
+
tracer_provider = TracerProvider(resource=resource)
|
|
192
|
+
tracer_provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
|
|
193
|
+
# 设置 trace
|
|
194
|
+
trace.set_tracer_provider(tracer_provider)
|
|
195
|
+
# 初始化 root logger
|
|
196
|
+
logger = logging.getLogger()
|
|
197
|
+
logger.setLevel(logging.DEBUG)
|
|
198
|
+
logger.addFilter(NoneRootWarningFilter())
|
|
199
|
+
logger.handlers.clear()
|
|
200
|
+
formatter = ColourizedFormatter(fmt=format_str)
|
|
201
|
+
# 设置 logger 的控制台输出
|
|
202
|
+
console_handler = logging.StreamHandler()
|
|
203
|
+
console_handler.setLevel(logging.INFO)
|
|
204
|
+
console_handler.addFilter(NoneRootWarningFilter())
|
|
205
|
+
console_handler.setFormatter(formatter)
|
|
206
|
+
logger.addHandler(console_handler)
|
|
207
|
+
# 设置 logger 的文件输出(最多保留 3 + 1 个文件)
|
|
208
|
+
file_handler = RotatingFileHandler(
|
|
209
|
+
filename=os.getcwd() + "/logger.log",
|
|
210
|
+
maxBytes=500 * 1024 * 1024, # 每个文件最大 500 MB
|
|
211
|
+
backupCount=3,
|
|
212
|
+
encoding="utf-8"
|
|
213
|
+
)
|
|
214
|
+
file_handler.setLevel(logging.DEBUG)
|
|
215
|
+
file_handler.addFilter(NoneRootWarningFilter())
|
|
216
|
+
file_handler.setFormatter(formatter)
|
|
217
|
+
# 使用 Python 官方推荐的异步日志方案,注意程序结束前需要调用 queue_listener.stop()
|
|
218
|
+
report_queue = Queue(-1)
|
|
219
|
+
queue_handler = QueueHandler(report_queue)
|
|
220
|
+
queue_handler.setLevel(logging.DEBUG)
|
|
221
|
+
queue_handler.addFilter(NoneRootWarningFilter())
|
|
222
|
+
queue_listener = QueueListener(report_queue, file_handler, respect_handler_level=True)
|
|
223
|
+
queue_listener.start()
|
|
224
|
+
logger.addHandler(queue_handler)
|
|
225
|
+
# 设置 Instrumentor
|
|
226
|
+
FastAPIInstrumentor().instrument(
|
|
227
|
+
tracer_provider=tracer_provider, excluded_urls="metrics,health"
|
|
228
|
+
)
|
|
229
|
+
LoggingInstrumentor().instrument(set_logging_format=True, log_format=format_str)
|
|
230
|
+
OpenAIInstrumentor().instrument(tracer_provider=tracer_provider)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def close_logger():
|
|
234
|
+
"""
|
|
235
|
+
确保日志和 trace 都写入完成
|
|
236
|
+
"""
|
|
237
|
+
logger.info("close_logger start")
|
|
238
|
+
queue_listener.stop()
|
|
239
|
+
tracer_provider.shutdown()
|
|
240
|
+
logger.info("close_logger success") # 这条日志永远写不了
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
if __name__ == "__main__":
|
|
244
|
+
from typing import Generic, TypeVar
|
|
245
|
+
|
|
246
|
+
T = TypeVar("T")
|
|
247
|
+
|
|
248
|
+
class TestClass(BaseModel, Generic[T]):
|
|
249
|
+
"""
|
|
250
|
+
用来测试日志输出的类
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
code: int = Field(..., title="业务相关的 code")
|
|
254
|
+
message: str = Field(..., title="业务相关的信息")
|
|
255
|
+
data: T = Field(..., title="数据")
|
|
256
|
+
|
|
257
|
+
a = TestClass[str](code=0, message="testa", data="testa")
|
|
258
|
+
b = TestClass[str](code=0, message="testb", data="testb")
|
|
259
|
+
logger.info(
|
|
260
|
+
"logger init success",
|
|
261
|
+
extra={
|
|
262
|
+
"customized_data_info": {
|
|
263
|
+
"test_list": [1, 2, 3],
|
|
264
|
+
# "test_object_list": [a, b], # bad
|
|
265
|
+
"test_object_str_list": [a.model_dump_json(), b.model_dump_json()],
|
|
266
|
+
"test_dict": {"a": 1, "b": 2, "c": 3},
|
|
267
|
+
# "test_object_dict": {"a": a, "b": b}, # bad
|
|
268
|
+
"test_object_str_dict": {
|
|
269
|
+
"a": a.model_dump_json(),
|
|
270
|
+
"b": b.model_dump_json(),
|
|
271
|
+
},
|
|
272
|
+
"test_none": None,
|
|
273
|
+
# "test_bytes": "test_bytes".encode(), # bad
|
|
274
|
+
"test_bytes_str": str("test_bytes".encode()),
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
)
|
|
278
|
+
close_logger()
|