bella-openapi 1.0.2__py3-none-any.whl → 1.0.2.2__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.
- bella_openapi/__init__.py +26 -22
- bella_openapi/auth_billing.py +91 -91
- bella_openapi/authorize.py +61 -61
- bella_openapi/bella_trace/__init__.py +13 -13
- bella_openapi/bella_trace/_context.py +61 -61
- bella_openapi/bella_trace/fastapi_interceptor.py +28 -28
- bella_openapi/bella_trace/record_log.py +58 -58
- bella_openapi/bella_trace/trace_requests.py +58 -58
- bella_openapi/config.py +15 -15
- bella_openapi/console/__init__.py +2 -2
- bella_openapi/console/models.py +44 -44
- bella_openapi/domtree/__init__.py +3 -0
- bella_openapi/domtree/standard_domtree.py +491 -0
- bella_openapi/domtree/utils.py +12 -0
- bella_openapi/exception.py +7 -7
- bella_openapi/log.py +222 -222
- bella_openapi/middleware/__init__.py +3 -3
- bella_openapi/middleware/context_middleware.py +108 -108
- bella_openapi/openapi_contexvar.py +6 -6
- bella_openapi/schema.py +63 -63
- {bella_openapi-1.0.2.dist-info → bella_openapi-1.0.2.2.dist-info}/METADATA +260 -260
- bella_openapi-1.0.2.2.dist-info/RECORD +25 -0
- {bella_openapi-1.0.2.dist-info → bella_openapi-1.0.2.2.dist-info}/licenses/LICENSE +20 -20
- bella_openapi-1.0.2.dist-info/RECORD +0 -22
- {bella_openapi-1.0.2.dist-info → bella_openapi-1.0.2.2.dist-info}/WHEEL +0 -0
- {bella_openapi-1.0.2.dist-info → bella_openapi-1.0.2.2.dist-info}/top_level.txt +0 -0
bella_openapi/log.py
CHANGED
@@ -1,222 +1,222 @@
|
|
1
|
-
import asyncio
|
2
|
-
import functools
|
3
|
-
import inspect
|
4
|
-
import json
|
5
|
-
import logging
|
6
|
-
import traceback
|
7
|
-
from asyncio import AbstractEventLoop, CancelledError
|
8
|
-
from concurrent.futures import Future
|
9
|
-
from threading import Thread
|
10
|
-
|
11
|
-
import httpx
|
12
|
-
|
13
|
-
from .config import openapi_config
|
14
|
-
from .schema import OperationLog
|
15
|
-
|
16
|
-
|
17
|
-
class operation_log:
|
18
|
-
"""
|
19
|
-
当前操作日志装饰器,同时支持同步和异步协程
|
20
|
-
|
21
|
-
用法:
|
22
|
-
@operation_log(op_type='safety_check', is_cost_log=True, ucid_key="ucid")
|
23
|
-
def safety_check(request, *, validate_output: bool):
|
24
|
-
pass
|
25
|
-
|
26
|
-
@operation_log(op_type='safety_check', is_cost_log=True, ucid_key="ucid")
|
27
|
-
async def safety_check(request, *, validate_output: bool):
|
28
|
-
pass
|
29
|
-
"""
|
30
|
-
|
31
|
-
def __init__(self, *, op_type: str = None, is_cost_log=False, ucid_key="ucid"):
|
32
|
-
"""
|
33
|
-
:param op_type: 表示当前操作动作, 如果不传入则默认使用被装饰的函数名
|
34
|
-
:param is_cost_log: 表示当前日志是否是计费相关日志, 默认为False
|
35
|
-
|
36
|
-
"""
|
37
|
-
self.op_type = op_type
|
38
|
-
self.is_cost_log = is_cost_log
|
39
|
-
self.ucid_key = ucid_key
|
40
|
-
|
41
|
-
def __call__(self, func):
|
42
|
-
if inspect.iscoroutinefunction(func):
|
43
|
-
return operation_log.async_log_decorator(func, op_type=self.op_type, is_cost_log=self.is_cost_log, ucid_key=self.ucid_key)
|
44
|
-
return operation_log.log_decorator(func, op_type=self.op_type, is_cost_log=self.is_cost_log, ucid_key=self.ucid_key)
|
45
|
-
|
46
|
-
@staticmethod
|
47
|
-
def log_decorator(func, *, op_type, is_cost_log, ucid_key):
|
48
|
-
"""
|
49
|
-
日志装饰器,在函数调用前后记录日志
|
50
|
-
:param func:
|
51
|
-
:param op_type:
|
52
|
-
:param is_cost_log:
|
53
|
-
:return:
|
54
|
-
"""
|
55
|
-
|
56
|
-
@functools.wraps(func)
|
57
|
-
def wrapper(*args, **kwargs):
|
58
|
-
nonlocal op_type
|
59
|
-
op_type = op_type if op_type else func.__name__
|
60
|
-
ucid_value = operation_log._get_ucid(ucid_key, func, args, kwargs)
|
61
|
-
opt_in_log = OperationLog(opLogType='in', opType=op_type,
|
62
|
-
operationStatus='success',
|
63
|
-
request=[args, kwargs],
|
64
|
-
response=None,
|
65
|
-
isCostLog=is_cost_log,
|
66
|
-
ucid=ucid_value,
|
67
|
-
)
|
68
|
-
|
69
|
-
submit_log(opt_in_log)
|
70
|
-
|
71
|
-
opt_out_log = None
|
72
|
-
try:
|
73
|
-
resp = func(*args, **kwargs)
|
74
|
-
opt_out_log = OperationLog(opLogType='out', opType=op_type,
|
75
|
-
operationStatus='success',
|
76
|
-
request=[args, kwargs],
|
77
|
-
response=resp,
|
78
|
-
isCostLog=is_cost_log,
|
79
|
-
ucid=ucid_value,
|
80
|
-
)
|
81
|
-
return resp
|
82
|
-
except Exception as e:
|
83
|
-
opt_out_log = OperationLog(opLogType='out', opType=op_type,
|
84
|
-
operationStatus='failed',
|
85
|
-
request=[args, kwargs],
|
86
|
-
response=None,
|
87
|
-
errMsg=traceback.format_exc()[:1024],
|
88
|
-
isCostLog=is_cost_log,
|
89
|
-
ucid=ucid_value,
|
90
|
-
)
|
91
|
-
raise e
|
92
|
-
finally:
|
93
|
-
submit_log(opt_out_log)
|
94
|
-
|
95
|
-
return wrapper
|
96
|
-
|
97
|
-
@staticmethod
|
98
|
-
def async_log_decorator(func, *, op_type, is_cost_log, ucid_key):
|
99
|
-
"""
|
100
|
-
日志装饰器,在函数调用前后记录日志
|
101
|
-
:param func:
|
102
|
-
:param op_type:
|
103
|
-
:param is_cost_log:
|
104
|
-
:return:
|
105
|
-
"""
|
106
|
-
|
107
|
-
@functools.wraps(func)
|
108
|
-
async def wrapper(*args, **kwargs):
|
109
|
-
nonlocal op_type
|
110
|
-
op_type = op_type if op_type else func.__name__
|
111
|
-
ucid_value = operation_log._get_ucid(ucid_key, func, args, kwargs)
|
112
|
-
opt_in_log = OperationLog(opLogType='in', opType=op_type,
|
113
|
-
operationStatus='success',
|
114
|
-
request=[args, kwargs],
|
115
|
-
response=None,
|
116
|
-
isCostLog=is_cost_log,
|
117
|
-
ucid=ucid_value,
|
118
|
-
)
|
119
|
-
|
120
|
-
submit_log(opt_in_log)
|
121
|
-
|
122
|
-
opt_out_log = None
|
123
|
-
try:
|
124
|
-
resp = await func(*args, **kwargs)
|
125
|
-
opt_out_log = OperationLog(opLogType='out', opType=op_type,
|
126
|
-
operationStatus='success',
|
127
|
-
request=[args, kwargs],
|
128
|
-
response=resp,
|
129
|
-
isCostLog=is_cost_log,
|
130
|
-
ucid=ucid_value,
|
131
|
-
)
|
132
|
-
return resp
|
133
|
-
except Exception as e:
|
134
|
-
opt_out_log = OperationLog(opLogType='out', opType=op_type,
|
135
|
-
operationStatus='failed',
|
136
|
-
request=[args, kwargs],
|
137
|
-
response=None,
|
138
|
-
errMsg=traceback.format_exc()[:1024],
|
139
|
-
isCostLog=is_cost_log,
|
140
|
-
ucid=ucid_value,
|
141
|
-
)
|
142
|
-
raise e
|
143
|
-
finally:
|
144
|
-
submit_log(opt_out_log)
|
145
|
-
|
146
|
-
return wrapper
|
147
|
-
@staticmethod
|
148
|
-
def _get_ucid(ucid_key, func, args, kwargs):
|
149
|
-
# 尝试从关键字参数中获取 UCID
|
150
|
-
ucid = kwargs.get(ucid_key)
|
151
|
-
if ucid is None:
|
152
|
-
# 如果 UCID 不在关键字参数中,尝试从位置参数中获取
|
153
|
-
func_params = func.__code__.co_varnames
|
154
|
-
if ucid_key in func_params:
|
155
|
-
ucid_index = func_params.index(ucid_key)
|
156
|
-
if ucid_index < len(args):
|
157
|
-
ucid = args[ucid_index]
|
158
|
-
return ucid
|
159
|
-
|
160
|
-
def submit_log(log: OperationLog):
|
161
|
-
try:
|
162
|
-
task = asyncio.create_task(_async_log(log))
|
163
|
-
task.add_done_callback(log_callback)
|
164
|
-
except RuntimeError:
|
165
|
-
_submit_log_in_thread_event_log(log)
|
166
|
-
|
167
|
-
|
168
|
-
# 监控任务日志执行结果
|
169
|
-
def log_callback(future: Future):
|
170
|
-
try:
|
171
|
-
future.result()
|
172
|
-
except CancelledError:
|
173
|
-
logging.exception(f'openapi log report task cancelled')
|
174
|
-
except Exception:
|
175
|
-
logging.exception(f'openapi log report task failed')
|
176
|
-
|
177
|
-
|
178
|
-
class ThreadedEventLoop(Thread):
|
179
|
-
"""
|
180
|
-
如果当前线程没有event loop, 则使用独立线程中的async_log_event_loop
|
181
|
-
"""
|
182
|
-
|
183
|
-
def __init__(self, loop: AbstractEventLoop):
|
184
|
-
super().__init__()
|
185
|
-
self.loop = loop
|
186
|
-
self.daemon = True
|
187
|
-
|
188
|
-
def run(self) -> None:
|
189
|
-
self.loop.run_forever()
|
190
|
-
|
191
|
-
|
192
|
-
# 启动asyncio event loop, 如果用户当前线程没有event loop,则使用独立线程中的async_log_event_loop
|
193
|
-
async_log_event_loop = asyncio.new_event_loop()
|
194
|
-
asyncio_thread = ThreadedEventLoop(async_log_event_loop)
|
195
|
-
asyncio_thread.start()
|
196
|
-
|
197
|
-
|
198
|
-
def _submit_log_in_thread_event_log(log):
|
199
|
-
future = asyncio.run_coroutine_threadsafe(
|
200
|
-
_async_log(log), async_log_event_loop)
|
201
|
-
future.add_done_callback(log_callback)
|
202
|
-
|
203
|
-
|
204
|
-
# 异步上报日志
|
205
|
-
async_httpx_client = None
|
206
|
-
|
207
|
-
|
208
|
-
async def _async_log(log: OperationLog):
|
209
|
-
global async_httpx_client
|
210
|
-
if async_httpx_client is None:
|
211
|
-
async_httpx_client = await httpx.AsyncClient(limits=httpx.Limits(max_connections=50)).__aenter__()
|
212
|
-
# 异步写入日志
|
213
|
-
url = openapi_config.OPENAPI_HOST + "/v1/openapi/report/log"
|
214
|
-
log_data = json.loads(json.dumps(log.dict(by_alias=True), default=lambda x: None))
|
215
|
-
logging.info(f'openapi链路日志上报:{log_data}')
|
216
|
-
response = await async_httpx_client.post(url, json=log_data)
|
217
|
-
if response.status_code != 200:
|
218
|
-
logging.warning(f'log failed, status_code: {response.status_code}, response: {response.text}')
|
219
|
-
return response.status_code
|
220
|
-
|
221
|
-
|
222
|
-
__all__ = ['operation_log', 'OperationLog', 'submit_log']
|
1
|
+
import asyncio
|
2
|
+
import functools
|
3
|
+
import inspect
|
4
|
+
import json
|
5
|
+
import logging
|
6
|
+
import traceback
|
7
|
+
from asyncio import AbstractEventLoop, CancelledError
|
8
|
+
from concurrent.futures import Future
|
9
|
+
from threading import Thread
|
10
|
+
|
11
|
+
import httpx
|
12
|
+
|
13
|
+
from .config import openapi_config
|
14
|
+
from .schema import OperationLog
|
15
|
+
|
16
|
+
|
17
|
+
class operation_log:
|
18
|
+
"""
|
19
|
+
当前操作日志装饰器,同时支持同步和异步协程
|
20
|
+
|
21
|
+
用法:
|
22
|
+
@operation_log(op_type='safety_check', is_cost_log=True, ucid_key="ucid")
|
23
|
+
def safety_check(request, *, validate_output: bool):
|
24
|
+
pass
|
25
|
+
|
26
|
+
@operation_log(op_type='safety_check', is_cost_log=True, ucid_key="ucid")
|
27
|
+
async def safety_check(request, *, validate_output: bool):
|
28
|
+
pass
|
29
|
+
"""
|
30
|
+
|
31
|
+
def __init__(self, *, op_type: str = None, is_cost_log=False, ucid_key="ucid"):
|
32
|
+
"""
|
33
|
+
:param op_type: 表示当前操作动作, 如果不传入则默认使用被装饰的函数名
|
34
|
+
:param is_cost_log: 表示当前日志是否是计费相关日志, 默认为False
|
35
|
+
|
36
|
+
"""
|
37
|
+
self.op_type = op_type
|
38
|
+
self.is_cost_log = is_cost_log
|
39
|
+
self.ucid_key = ucid_key
|
40
|
+
|
41
|
+
def __call__(self, func):
|
42
|
+
if inspect.iscoroutinefunction(func):
|
43
|
+
return operation_log.async_log_decorator(func, op_type=self.op_type, is_cost_log=self.is_cost_log, ucid_key=self.ucid_key)
|
44
|
+
return operation_log.log_decorator(func, op_type=self.op_type, is_cost_log=self.is_cost_log, ucid_key=self.ucid_key)
|
45
|
+
|
46
|
+
@staticmethod
|
47
|
+
def log_decorator(func, *, op_type, is_cost_log, ucid_key):
|
48
|
+
"""
|
49
|
+
日志装饰器,在函数调用前后记录日志
|
50
|
+
:param func:
|
51
|
+
:param op_type:
|
52
|
+
:param is_cost_log:
|
53
|
+
:return:
|
54
|
+
"""
|
55
|
+
|
56
|
+
@functools.wraps(func)
|
57
|
+
def wrapper(*args, **kwargs):
|
58
|
+
nonlocal op_type
|
59
|
+
op_type = op_type if op_type else func.__name__
|
60
|
+
ucid_value = operation_log._get_ucid(ucid_key, func, args, kwargs)
|
61
|
+
opt_in_log = OperationLog(opLogType='in', opType=op_type,
|
62
|
+
operationStatus='success',
|
63
|
+
request=[args, kwargs],
|
64
|
+
response=None,
|
65
|
+
isCostLog=is_cost_log,
|
66
|
+
ucid=ucid_value,
|
67
|
+
)
|
68
|
+
|
69
|
+
submit_log(opt_in_log)
|
70
|
+
|
71
|
+
opt_out_log = None
|
72
|
+
try:
|
73
|
+
resp = func(*args, **kwargs)
|
74
|
+
opt_out_log = OperationLog(opLogType='out', opType=op_type,
|
75
|
+
operationStatus='success',
|
76
|
+
request=[args, kwargs],
|
77
|
+
response=resp,
|
78
|
+
isCostLog=is_cost_log,
|
79
|
+
ucid=ucid_value,
|
80
|
+
)
|
81
|
+
return resp
|
82
|
+
except Exception as e:
|
83
|
+
opt_out_log = OperationLog(opLogType='out', opType=op_type,
|
84
|
+
operationStatus='failed',
|
85
|
+
request=[args, kwargs],
|
86
|
+
response=None,
|
87
|
+
errMsg=traceback.format_exc()[:1024],
|
88
|
+
isCostLog=is_cost_log,
|
89
|
+
ucid=ucid_value,
|
90
|
+
)
|
91
|
+
raise e
|
92
|
+
finally:
|
93
|
+
submit_log(opt_out_log)
|
94
|
+
|
95
|
+
return wrapper
|
96
|
+
|
97
|
+
@staticmethod
|
98
|
+
def async_log_decorator(func, *, op_type, is_cost_log, ucid_key):
|
99
|
+
"""
|
100
|
+
日志装饰器,在函数调用前后记录日志
|
101
|
+
:param func:
|
102
|
+
:param op_type:
|
103
|
+
:param is_cost_log:
|
104
|
+
:return:
|
105
|
+
"""
|
106
|
+
|
107
|
+
@functools.wraps(func)
|
108
|
+
async def wrapper(*args, **kwargs):
|
109
|
+
nonlocal op_type
|
110
|
+
op_type = op_type if op_type else func.__name__
|
111
|
+
ucid_value = operation_log._get_ucid(ucid_key, func, args, kwargs)
|
112
|
+
opt_in_log = OperationLog(opLogType='in', opType=op_type,
|
113
|
+
operationStatus='success',
|
114
|
+
request=[args, kwargs],
|
115
|
+
response=None,
|
116
|
+
isCostLog=is_cost_log,
|
117
|
+
ucid=ucid_value,
|
118
|
+
)
|
119
|
+
|
120
|
+
submit_log(opt_in_log)
|
121
|
+
|
122
|
+
opt_out_log = None
|
123
|
+
try:
|
124
|
+
resp = await func(*args, **kwargs)
|
125
|
+
opt_out_log = OperationLog(opLogType='out', opType=op_type,
|
126
|
+
operationStatus='success',
|
127
|
+
request=[args, kwargs],
|
128
|
+
response=resp,
|
129
|
+
isCostLog=is_cost_log,
|
130
|
+
ucid=ucid_value,
|
131
|
+
)
|
132
|
+
return resp
|
133
|
+
except Exception as e:
|
134
|
+
opt_out_log = OperationLog(opLogType='out', opType=op_type,
|
135
|
+
operationStatus='failed',
|
136
|
+
request=[args, kwargs],
|
137
|
+
response=None,
|
138
|
+
errMsg=traceback.format_exc()[:1024],
|
139
|
+
isCostLog=is_cost_log,
|
140
|
+
ucid=ucid_value,
|
141
|
+
)
|
142
|
+
raise e
|
143
|
+
finally:
|
144
|
+
submit_log(opt_out_log)
|
145
|
+
|
146
|
+
return wrapper
|
147
|
+
@staticmethod
|
148
|
+
def _get_ucid(ucid_key, func, args, kwargs):
|
149
|
+
# 尝试从关键字参数中获取 UCID
|
150
|
+
ucid = kwargs.get(ucid_key)
|
151
|
+
if ucid is None:
|
152
|
+
# 如果 UCID 不在关键字参数中,尝试从位置参数中获取
|
153
|
+
func_params = func.__code__.co_varnames
|
154
|
+
if ucid_key in func_params:
|
155
|
+
ucid_index = func_params.index(ucid_key)
|
156
|
+
if ucid_index < len(args):
|
157
|
+
ucid = args[ucid_index]
|
158
|
+
return ucid
|
159
|
+
|
160
|
+
def submit_log(log: OperationLog):
|
161
|
+
try:
|
162
|
+
task = asyncio.create_task(_async_log(log))
|
163
|
+
task.add_done_callback(log_callback)
|
164
|
+
except RuntimeError:
|
165
|
+
_submit_log_in_thread_event_log(log)
|
166
|
+
|
167
|
+
|
168
|
+
# 监控任务日志执行结果
|
169
|
+
def log_callback(future: Future):
|
170
|
+
try:
|
171
|
+
future.result()
|
172
|
+
except CancelledError:
|
173
|
+
logging.exception(f'openapi log report task cancelled')
|
174
|
+
except Exception:
|
175
|
+
logging.exception(f'openapi log report task failed')
|
176
|
+
|
177
|
+
|
178
|
+
class ThreadedEventLoop(Thread):
|
179
|
+
"""
|
180
|
+
如果当前线程没有event loop, 则使用独立线程中的async_log_event_loop
|
181
|
+
"""
|
182
|
+
|
183
|
+
def __init__(self, loop: AbstractEventLoop):
|
184
|
+
super().__init__()
|
185
|
+
self.loop = loop
|
186
|
+
self.daemon = True
|
187
|
+
|
188
|
+
def run(self) -> None:
|
189
|
+
self.loop.run_forever()
|
190
|
+
|
191
|
+
|
192
|
+
# 启动asyncio event loop, 如果用户当前线程没有event loop,则使用独立线程中的async_log_event_loop
|
193
|
+
async_log_event_loop = asyncio.new_event_loop()
|
194
|
+
asyncio_thread = ThreadedEventLoop(async_log_event_loop)
|
195
|
+
asyncio_thread.start()
|
196
|
+
|
197
|
+
|
198
|
+
def _submit_log_in_thread_event_log(log):
|
199
|
+
future = asyncio.run_coroutine_threadsafe(
|
200
|
+
_async_log(log), async_log_event_loop)
|
201
|
+
future.add_done_callback(log_callback)
|
202
|
+
|
203
|
+
|
204
|
+
# 异步上报日志
|
205
|
+
async_httpx_client = None
|
206
|
+
|
207
|
+
|
208
|
+
async def _async_log(log: OperationLog):
|
209
|
+
global async_httpx_client
|
210
|
+
if async_httpx_client is None:
|
211
|
+
async_httpx_client = await httpx.AsyncClient(limits=httpx.Limits(max_connections=50)).__aenter__()
|
212
|
+
# 异步写入日志
|
213
|
+
url = openapi_config.OPENAPI_HOST + "/v1/openapi/report/log"
|
214
|
+
log_data = json.loads(json.dumps(log.dict(by_alias=True), default=lambda x: None))
|
215
|
+
logging.info(f'openapi链路日志上报:{log_data}')
|
216
|
+
response = await async_httpx_client.post(url, json=log_data)
|
217
|
+
if response.status_code != 200:
|
218
|
+
logging.warning(f'log failed, status_code: {response.status_code}, response: {response.text}')
|
219
|
+
return response.status_code
|
220
|
+
|
221
|
+
|
222
|
+
__all__ = ['operation_log', 'OperationLog', 'submit_log']
|
@@ -1,3 +1,3 @@
|
|
1
|
-
from .context_middleware import HttpContextMiddleware, WebSocketHttpContextMiddleware
|
2
|
-
|
3
|
-
__all__ = ['HttpContextMiddleware', 'WebSocketHttpContextMiddleware']
|
1
|
+
from .context_middleware import HttpContextMiddleware, WebSocketHttpContextMiddleware
|
2
|
+
|
3
|
+
__all__ = ['HttpContextMiddleware', 'WebSocketHttpContextMiddleware']
|