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/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']