tamar-file-hub-client 0.1.4__py3-none-any.whl → 0.1.5__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.
- file_hub_client/rpc/gen/file_service_pb2.py +30 -30
- file_hub_client/rpc/interceptors.py +578 -580
- file_hub_client/rpc/protos/file_service.proto +2 -1
- file_hub_client/schemas/file.py +171 -170
- file_hub_client/services/file/async_blob_service.py +1 -0
- file_hub_client/services/file/sync_blob_service.py +1 -0
- file_hub_client/utils/logging.py +335 -318
- {tamar_file_hub_client-0.1.4.dist-info → tamar_file_hub_client-0.1.5.dist-info}/METADATA +17 -2
- {tamar_file_hub_client-0.1.4.dist-info → tamar_file_hub_client-0.1.5.dist-info}/RECORD +11 -11
- {tamar_file_hub_client-0.1.4.dist-info → tamar_file_hub_client-0.1.5.dist-info}/WHEEL +0 -0
- {tamar_file_hub_client-0.1.4.dist-info → tamar_file_hub_client-0.1.5.dist-info}/top_level.txt +0 -0
file_hub_client/utils/logging.py
CHANGED
@@ -1,319 +1,336 @@
|
|
1
|
-
"""
|
2
|
-
日志配置和工具
|
3
|
-
"""
|
4
|
-
import logging
|
5
|
-
import sys
|
6
|
-
import time
|
7
|
-
import json
|
8
|
-
import traceback
|
9
|
-
from typing import Optional, Any, Dict
|
10
|
-
from functools import wraps
|
11
|
-
from contextlib import contextmanager
|
12
|
-
from datetime import datetime
|
13
|
-
|
14
|
-
# 创建SDK专用的日志记录器 - 使用独立的命名空间避免冲突
|
15
|
-
SDK_LOGGER_NAME = "file_hub_client.grpc"
|
16
|
-
logger = logging.getLogger(SDK_LOGGER_NAME)
|
17
|
-
|
18
|
-
|
19
|
-
class GrpcJSONFormatter(logging.Formatter):
|
20
|
-
"""gRPC请求的JSON格式化器"""
|
21
|
-
|
22
|
-
def format(self, record):
|
23
|
-
log_type = getattr(record, "log_type", "info")
|
24
|
-
log_data = {
|
25
|
-
"timestamp": datetime.fromtimestamp(record.created).isoformat(),
|
26
|
-
"level": record.levelname,
|
27
|
-
"type": log_type,
|
28
|
-
"uri": getattr(record, "uri", None),
|
29
|
-
"request_id": getattr(record, "request_id", None),
|
30
|
-
"data": getattr(record, "data", None),
|
31
|
-
"message": record.getMessage(),
|
32
|
-
"duration": getattr(record, "duration", None),
|
33
|
-
"logger": record.name, # 添加logger名称以区分SDK日志
|
34
|
-
}
|
35
|
-
|
36
|
-
# 增加 trace 支持
|
37
|
-
if hasattr(record, "trace"):
|
38
|
-
log_data["trace"] = getattr(record, "trace")
|
39
|
-
|
40
|
-
# 添加异常信息(如果有的话)
|
41
|
-
if hasattr(record, "exc_info") and record.exc_info:
|
42
|
-
log_data["exception"] = {
|
43
|
-
"type": record.exc_info[0].__name__ if record.exc_info[0] else None,
|
44
|
-
"message": str(record.exc_info[1]) if record.exc_info[1] else None,
|
45
|
-
"traceback": traceback.format_exception(*record.exc_info)
|
46
|
-
}
|
47
|
-
|
48
|
-
# 过滤掉None值
|
49
|
-
log_data = {k: v for k, v in log_data.items() if v is not None}
|
50
|
-
|
51
|
-
return json.dumps(log_data, ensure_ascii=False)
|
52
|
-
|
53
|
-
|
54
|
-
def get_default_formatter() -> logging.Formatter:
|
55
|
-
"""获取默认的JSON格式化器"""
|
56
|
-
return GrpcJSONFormatter()
|
57
|
-
|
58
|
-
|
59
|
-
def setup_logging(
|
60
|
-
level: str = "INFO",
|
61
|
-
format_string: Optional[str] = None,
|
62
|
-
enable_grpc_logging: bool = True,
|
63
|
-
log_request_payload: bool = False,
|
64
|
-
log_response_payload: bool = False,
|
65
|
-
handler: Optional[logging.Handler] = None,
|
66
|
-
use_json_format: bool = True
|
67
|
-
):
|
68
|
-
"""
|
69
|
-
设置SDK日志记录配置
|
70
|
-
|
71
|
-
Args:
|
72
|
-
level: 日志级别 (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
73
|
-
format_string: 自定义日志格式(当use_json_format=False时使用)
|
74
|
-
enable_grpc_logging: 是否启用gRPC请求日志
|
75
|
-
log_request_payload: 是否记录请求载荷
|
76
|
-
log_response_payload: 是否记录响应载荷
|
77
|
-
handler: 自定义日志处理器
|
78
|
-
use_json_format: 是否使用JSON格式(默认True)
|
79
|
-
"""
|
80
|
-
# 设置日志级别
|
81
|
-
log_level = getattr(logging, level.upper(), logging.INFO)
|
82
|
-
logger.setLevel(log_level)
|
83
|
-
|
84
|
-
# 清除现有的处理器(只清除SDK的logger)
|
85
|
-
logger.handlers.clear()
|
86
|
-
|
87
|
-
# 创建处理器
|
88
|
-
if handler is None:
|
89
|
-
handler = logging.StreamHandler(sys.stdout)
|
90
|
-
|
91
|
-
# 设置日志格式
|
92
|
-
if use_json_format:
|
93
|
-
formatter = get_default_formatter()
|
94
|
-
else:
|
95
|
-
if format_string is None:
|
96
|
-
format_string = "[%(asctime)s] %(levelname)s [%(name)s.%(funcName)s:%(lineno)d] %(message)s"
|
97
|
-
formatter = logging.Formatter(format_string, datefmt="%Y-%m-%d %H:%M:%S")
|
98
|
-
|
99
|
-
handler.setFormatter(formatter)
|
100
|
-
|
101
|
-
# 添加处理器
|
102
|
-
logger.addHandler(handler)
|
103
|
-
|
104
|
-
# 设置gRPC日志配置
|
105
|
-
logger.grpc_logging_enabled = enable_grpc_logging
|
106
|
-
logger.log_request_payload = log_request_payload
|
107
|
-
logger.log_response_payload = log_response_payload
|
108
|
-
|
109
|
-
# 防止日志传播到根日志记录器 - 保持SDK日志独立
|
110
|
-
logger.propagate = False
|
111
|
-
|
112
|
-
# 对整个 file_hub_client 包设置隔离,确保所有子模块的日志都不会传播
|
113
|
-
parent_logger = logging.getLogger('file_hub_client')
|
114
|
-
parent_logger.propagate = False
|
115
|
-
|
116
|
-
# 初始化日志(使用JSON格式)
|
117
|
-
if enable_grpc_logging:
|
118
|
-
log_record = logging.LogRecord(
|
119
|
-
name=logger.name,
|
120
|
-
level=logging.INFO,
|
121
|
-
pathname="",
|
122
|
-
lineno=0,
|
123
|
-
msg="📡 文件中心客户端 gRPC 日志已初始化",
|
124
|
-
args=(),
|
125
|
-
exc_info=None
|
126
|
-
)
|
127
|
-
log_record.log_type = "info"
|
128
|
-
log_record.data = {
|
129
|
-
"level": level,
|
130
|
-
"grpc_logging": enable_grpc_logging,
|
131
|
-
"json_format": use_json_format
|
132
|
-
}
|
133
|
-
logger.handle(log_record)
|
134
|
-
|
135
|
-
|
136
|
-
def get_logger() -> logging.Logger:
|
137
|
-
"""获取SDK日志记录器"""
|
138
|
-
return logger
|
139
|
-
|
140
|
-
|
141
|
-
class GrpcRequestLogger:
|
142
|
-
"""gRPC请求日志记录器"""
|
143
|
-
|
144
|
-
def __init__(self, logger: logging.Logger):
|
145
|
-
self.logger = logger
|
146
|
-
self.enable_grpc_logging = getattr(logger, 'grpc_logging_enabled', True)
|
147
|
-
self.log_request_payload = getattr(logger, 'log_request_payload', False)
|
148
|
-
self.log_response_payload = getattr(logger, 'log_response_payload', False)
|
149
|
-
|
150
|
-
def log_request_start(self, method_name: str, request_id: str, metadata: Dict[str, Any],
|
151
|
-
request_payload: Any = None):
|
152
|
-
"""记录请求开始"""
|
153
|
-
if not self.enable_grpc_logging:
|
154
|
-
return
|
155
|
-
|
156
|
-
# 提取关键元数据
|
157
|
-
user_info = {}
|
158
|
-
if metadata:
|
159
|
-
metadata_dict = dict(metadata) if isinstance(metadata, list) else metadata
|
160
|
-
user_info = {
|
161
|
-
'org_id': metadata_dict.get('x-org-id'),
|
162
|
-
'user_id': metadata_dict.get('x-user-id'),
|
163
|
-
'client_ip': metadata_dict.get('x-client-ip'),
|
164
|
-
'
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
log_record.
|
182
|
-
log_record.
|
183
|
-
log_record.
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
log_record.
|
215
|
-
log_record.
|
216
|
-
log_record.
|
217
|
-
log_record.
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
log_record.
|
233
|
-
log_record.
|
234
|
-
log_record.
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
def
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
1
|
+
"""
|
2
|
+
日志配置和工具
|
3
|
+
"""
|
4
|
+
import logging
|
5
|
+
import sys
|
6
|
+
import time
|
7
|
+
import json
|
8
|
+
import traceback
|
9
|
+
from typing import Optional, Any, Dict
|
10
|
+
from functools import wraps
|
11
|
+
from contextlib import contextmanager
|
12
|
+
from datetime import datetime
|
13
|
+
|
14
|
+
# 创建SDK专用的日志记录器 - 使用独立的命名空间避免冲突
|
15
|
+
SDK_LOGGER_NAME = "file_hub_client.grpc"
|
16
|
+
logger = logging.getLogger(SDK_LOGGER_NAME)
|
17
|
+
|
18
|
+
|
19
|
+
class GrpcJSONFormatter(logging.Formatter):
|
20
|
+
"""gRPC请求的JSON格式化器"""
|
21
|
+
|
22
|
+
def format(self, record):
|
23
|
+
log_type = getattr(record, "log_type", "info")
|
24
|
+
log_data = {
|
25
|
+
"timestamp": datetime.fromtimestamp(record.created).isoformat(),
|
26
|
+
"level": record.levelname,
|
27
|
+
"type": log_type,
|
28
|
+
"uri": getattr(record, "uri", None),
|
29
|
+
"request_id": getattr(record, "request_id", None),
|
30
|
+
"data": getattr(record, "data", None),
|
31
|
+
"message": record.getMessage(),
|
32
|
+
"duration": getattr(record, "duration", None),
|
33
|
+
"logger": record.name, # 添加logger名称以区分SDK日志
|
34
|
+
}
|
35
|
+
|
36
|
+
# 增加 trace 支持
|
37
|
+
if hasattr(record, "trace"):
|
38
|
+
log_data["trace"] = getattr(record, "trace")
|
39
|
+
|
40
|
+
# 添加异常信息(如果有的话)
|
41
|
+
if hasattr(record, "exc_info") and record.exc_info:
|
42
|
+
log_data["exception"] = {
|
43
|
+
"type": record.exc_info[0].__name__ if record.exc_info[0] else None,
|
44
|
+
"message": str(record.exc_info[1]) if record.exc_info[1] else None,
|
45
|
+
"traceback": traceback.format_exception(*record.exc_info)
|
46
|
+
}
|
47
|
+
|
48
|
+
# 过滤掉None值
|
49
|
+
log_data = {k: v for k, v in log_data.items() if v is not None}
|
50
|
+
|
51
|
+
return json.dumps(log_data, ensure_ascii=False)
|
52
|
+
|
53
|
+
|
54
|
+
def get_default_formatter() -> logging.Formatter:
|
55
|
+
"""获取默认的JSON格式化器"""
|
56
|
+
return GrpcJSONFormatter()
|
57
|
+
|
58
|
+
|
59
|
+
def setup_logging(
|
60
|
+
level: str = "INFO",
|
61
|
+
format_string: Optional[str] = None,
|
62
|
+
enable_grpc_logging: bool = True,
|
63
|
+
log_request_payload: bool = False,
|
64
|
+
log_response_payload: bool = False,
|
65
|
+
handler: Optional[logging.Handler] = None,
|
66
|
+
use_json_format: bool = True
|
67
|
+
):
|
68
|
+
"""
|
69
|
+
设置SDK日志记录配置
|
70
|
+
|
71
|
+
Args:
|
72
|
+
level: 日志级别 (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
73
|
+
format_string: 自定义日志格式(当use_json_format=False时使用)
|
74
|
+
enable_grpc_logging: 是否启用gRPC请求日志
|
75
|
+
log_request_payload: 是否记录请求载荷
|
76
|
+
log_response_payload: 是否记录响应载荷
|
77
|
+
handler: 自定义日志处理器
|
78
|
+
use_json_format: 是否使用JSON格式(默认True)
|
79
|
+
"""
|
80
|
+
# 设置日志级别
|
81
|
+
log_level = getattr(logging, level.upper(), logging.INFO)
|
82
|
+
logger.setLevel(log_level)
|
83
|
+
|
84
|
+
# 清除现有的处理器(只清除SDK的logger)
|
85
|
+
logger.handlers.clear()
|
86
|
+
|
87
|
+
# 创建处理器
|
88
|
+
if handler is None:
|
89
|
+
handler = logging.StreamHandler(sys.stdout)
|
90
|
+
|
91
|
+
# 设置日志格式
|
92
|
+
if use_json_format:
|
93
|
+
formatter = get_default_formatter()
|
94
|
+
else:
|
95
|
+
if format_string is None:
|
96
|
+
format_string = "[%(asctime)s] %(levelname)s [%(name)s.%(funcName)s:%(lineno)d] %(message)s"
|
97
|
+
formatter = logging.Formatter(format_string, datefmt="%Y-%m-%d %H:%M:%S")
|
98
|
+
|
99
|
+
handler.setFormatter(formatter)
|
100
|
+
|
101
|
+
# 添加处理器
|
102
|
+
logger.addHandler(handler)
|
103
|
+
|
104
|
+
# 设置gRPC日志配置
|
105
|
+
logger.grpc_logging_enabled = enable_grpc_logging
|
106
|
+
logger.log_request_payload = log_request_payload
|
107
|
+
logger.log_response_payload = log_response_payload
|
108
|
+
|
109
|
+
# 防止日志传播到根日志记录器 - 保持SDK日志独立
|
110
|
+
logger.propagate = False
|
111
|
+
|
112
|
+
# 对整个 file_hub_client 包设置隔离,确保所有子模块的日志都不会传播
|
113
|
+
parent_logger = logging.getLogger('file_hub_client')
|
114
|
+
parent_logger.propagate = False
|
115
|
+
|
116
|
+
# 初始化日志(使用JSON格式)
|
117
|
+
if enable_grpc_logging:
|
118
|
+
log_record = logging.LogRecord(
|
119
|
+
name=logger.name,
|
120
|
+
level=logging.INFO,
|
121
|
+
pathname="",
|
122
|
+
lineno=0,
|
123
|
+
msg="📡 文件中心客户端 gRPC 日志已初始化",
|
124
|
+
args=(),
|
125
|
+
exc_info=None
|
126
|
+
)
|
127
|
+
log_record.log_type = "info"
|
128
|
+
log_record.data = {
|
129
|
+
"level": level,
|
130
|
+
"grpc_logging": enable_grpc_logging,
|
131
|
+
"json_format": use_json_format
|
132
|
+
}
|
133
|
+
logger.handle(log_record)
|
134
|
+
|
135
|
+
|
136
|
+
def get_logger() -> logging.Logger:
|
137
|
+
"""获取SDK日志记录器"""
|
138
|
+
return logger
|
139
|
+
|
140
|
+
|
141
|
+
class GrpcRequestLogger:
|
142
|
+
"""gRPC请求日志记录器"""
|
143
|
+
|
144
|
+
def __init__(self, logger: logging.Logger):
|
145
|
+
self.logger = logger
|
146
|
+
self.enable_grpc_logging = getattr(logger, 'grpc_logging_enabled', True)
|
147
|
+
self.log_request_payload = getattr(logger, 'log_request_payload', False)
|
148
|
+
self.log_response_payload = getattr(logger, 'log_response_payload', False)
|
149
|
+
|
150
|
+
def log_request_start(self, method_name: str, request_id: str, metadata: Dict[str, Any],
|
151
|
+
request_payload: Any = None):
|
152
|
+
"""记录请求开始"""
|
153
|
+
if not self.enable_grpc_logging:
|
154
|
+
return
|
155
|
+
|
156
|
+
# 提取关键元数据
|
157
|
+
user_info = {}
|
158
|
+
if metadata:
|
159
|
+
metadata_dict = dict(metadata) if isinstance(metadata, list) else metadata
|
160
|
+
user_info = {
|
161
|
+
'org_id': metadata_dict.get('x-org-id'),
|
162
|
+
'user_id': metadata_dict.get('x-user-id'),
|
163
|
+
'client_ip': metadata_dict.get('x-client-ip'), # SDK客户端服务IP
|
164
|
+
'user_ip': metadata_dict.get('x-user-ip'), # 用户真实IP
|
165
|
+
'client_version': metadata_dict.get('x-client-version')
|
166
|
+
}
|
167
|
+
user_info = {k: v for k, v in user_info.items() if v is not None}
|
168
|
+
|
169
|
+
# 创建日志记录
|
170
|
+
log_record = logging.LogRecord(
|
171
|
+
name=self.logger.name,
|
172
|
+
level=logging.INFO,
|
173
|
+
pathname="",
|
174
|
+
lineno=0,
|
175
|
+
msg=f"📤 gRPC 请求: {method_name}",
|
176
|
+
args=(),
|
177
|
+
exc_info=None
|
178
|
+
)
|
179
|
+
|
180
|
+
# 添加自定义字段
|
181
|
+
log_record.log_type = "request"
|
182
|
+
log_record.uri = method_name
|
183
|
+
log_record.request_id = request_id
|
184
|
+
log_record.data = user_info
|
185
|
+
|
186
|
+
# 记录请求载荷
|
187
|
+
if request_payload is not None:
|
188
|
+
if isinstance(request_payload, dict):
|
189
|
+
# 已经是字典格式,直接合并
|
190
|
+
log_record.data.update(request_payload)
|
191
|
+
else:
|
192
|
+
# 其他格式,添加到payload字段
|
193
|
+
log_record.data["payload"] = request_payload
|
194
|
+
|
195
|
+
self.logger.handle(log_record)
|
196
|
+
|
197
|
+
def log_request_end(self, method_name: str, request_id: str, duration_ms: float,
|
198
|
+
response_payload: Any = None, error: Exception = None, metadata: Dict[str, Any] = None):
|
199
|
+
"""记录请求结束"""
|
200
|
+
if not self.enable_grpc_logging:
|
201
|
+
return
|
202
|
+
|
203
|
+
if error:
|
204
|
+
# 错误日志
|
205
|
+
log_record = logging.LogRecord(
|
206
|
+
name=self.logger.name,
|
207
|
+
level=logging.ERROR,
|
208
|
+
pathname="",
|
209
|
+
lineno=0,
|
210
|
+
msg=f"❌ gRPC 错误: {method_name} - {str(error)}",
|
211
|
+
args=(),
|
212
|
+
exc_info=(type(error), error, error.__traceback__) if error else None
|
213
|
+
)
|
214
|
+
log_record.log_type = "error"
|
215
|
+
log_record.uri = method_name
|
216
|
+
log_record.request_id = request_id
|
217
|
+
log_record.duration = duration_ms
|
218
|
+
log_record.data = {"error": str(error)}
|
219
|
+
|
220
|
+
self.logger.handle(log_record)
|
221
|
+
else:
|
222
|
+
# 响应日志
|
223
|
+
log_record = logging.LogRecord(
|
224
|
+
name=self.logger.name,
|
225
|
+
level=logging.INFO,
|
226
|
+
pathname="",
|
227
|
+
lineno=0,
|
228
|
+
msg=f"✅ gRPC 响应: {method_name}",
|
229
|
+
args=(),
|
230
|
+
exc_info=None
|
231
|
+
)
|
232
|
+
log_record.log_type = "response"
|
233
|
+
log_record.uri = method_name
|
234
|
+
log_record.request_id = request_id
|
235
|
+
log_record.duration = duration_ms
|
236
|
+
|
237
|
+
# 初始化data字段用于存储metadata信息
|
238
|
+
log_record.data = {}
|
239
|
+
|
240
|
+
# 记录metadata信息
|
241
|
+
if metadata:
|
242
|
+
metadata_dict = dict(metadata) if isinstance(metadata, list) else metadata
|
243
|
+
user_info = {
|
244
|
+
'org_id': metadata_dict.get('x-org-id'),
|
245
|
+
'user_id': metadata_dict.get('x-user-id'),
|
246
|
+
'client_ip': metadata_dict.get('x-client-ip'), # SDK客户端服务IP
|
247
|
+
'user_ip': metadata_dict.get('x-user-ip'), # 用户真实IP
|
248
|
+
'client_version': metadata_dict.get('x-client-version')
|
249
|
+
}
|
250
|
+
user_info = {k: v for k, v in user_info.items() if v is not None}
|
251
|
+
log_record.data.update(user_info)
|
252
|
+
|
253
|
+
# 记录响应载荷(如果启用)
|
254
|
+
if self.log_response_payload and response_payload is not None:
|
255
|
+
log_record.data["response_payload"] = self._safe_serialize(response_payload)
|
256
|
+
|
257
|
+
self.logger.handle(log_record)
|
258
|
+
|
259
|
+
def _safe_serialize(self, obj: Any) -> str:
|
260
|
+
"""安全地序列化对象,避免敏感信息泄露"""
|
261
|
+
try:
|
262
|
+
if hasattr(obj, 'SerializeToString'):
|
263
|
+
# protobuf 对象
|
264
|
+
return f"<Proto object: {type(obj).__name__}>"
|
265
|
+
elif hasattr(obj, '__dict__'):
|
266
|
+
# 普通对象
|
267
|
+
return f"<Object: {type(obj).__name__}>"
|
268
|
+
else:
|
269
|
+
# 基本类型
|
270
|
+
return str(obj)[:200] # 限制长度
|
271
|
+
except Exception:
|
272
|
+
return f"<Unserializable: {type(obj).__name__}>"
|
273
|
+
|
274
|
+
|
275
|
+
@contextmanager
|
276
|
+
def grpc_request_context(method_name: str, request_id: str, metadata: Dict[str, Any],
|
277
|
+
request_payload: Any = None):
|
278
|
+
"""gRPC请求上下文管理器"""
|
279
|
+
request_logger = GrpcRequestLogger(get_logger())
|
280
|
+
start_time = time.time()
|
281
|
+
|
282
|
+
try:
|
283
|
+
# 记录请求开始
|
284
|
+
request_logger.log_request_start(method_name, request_id, metadata, request_payload)
|
285
|
+
yield request_logger
|
286
|
+
|
287
|
+
except Exception as e:
|
288
|
+
# 记录请求错误
|
289
|
+
duration_ms = (time.time() - start_time) * 1000
|
290
|
+
request_logger.log_request_end(method_name, request_id, duration_ms, error=e, metadata=metadata)
|
291
|
+
raise
|
292
|
+
|
293
|
+
else:
|
294
|
+
# 记录请求成功结束
|
295
|
+
duration_ms = (time.time() - start_time) * 1000
|
296
|
+
request_logger.log_request_end(method_name, request_id, duration_ms, metadata=metadata)
|
297
|
+
|
298
|
+
|
299
|
+
def log_grpc_call(method_name: str):
|
300
|
+
"""gRPC调用日志装饰器"""
|
301
|
+
def decorator(func):
|
302
|
+
@wraps(func)
|
303
|
+
def sync_wrapper(*args, **kwargs):
|
304
|
+
# 提取request_id和metadata
|
305
|
+
request_id = kwargs.get('request_id', 'unknown')
|
306
|
+
metadata = kwargs.get('metadata', {})
|
307
|
+
|
308
|
+
with grpc_request_context(method_name, request_id, metadata) as request_logger:
|
309
|
+
result = func(*args, **kwargs)
|
310
|
+
request_logger.log_request_end(method_name, request_id, 0, response_payload=result, metadata=metadata)
|
311
|
+
return result
|
312
|
+
|
313
|
+
@wraps(func)
|
314
|
+
async def async_wrapper(*args, **kwargs):
|
315
|
+
# 提取request_id和metadata
|
316
|
+
request_id = kwargs.get('request_id', 'unknown')
|
317
|
+
metadata = kwargs.get('metadata', {})
|
318
|
+
|
319
|
+
with grpc_request_context(method_name, request_id, metadata) as request_logger:
|
320
|
+
result = await func(*args, **kwargs)
|
321
|
+
request_logger.log_request_end(method_name, request_id, 0, response_payload=result, metadata=metadata)
|
322
|
+
return result
|
323
|
+
|
324
|
+
# 根据函数类型返回对应的包装器
|
325
|
+
import asyncio
|
326
|
+
if asyncio.iscoroutinefunction(func):
|
327
|
+
return async_wrapper
|
328
|
+
else:
|
329
|
+
return sync_wrapper
|
330
|
+
|
331
|
+
return decorator
|
332
|
+
|
333
|
+
|
334
|
+
# 默认初始化(可以被用户重新配置)
|
335
|
+
if not logger.handlers:
|
319
336
|
setup_logging()
|