tamar-file-hub-client 0.1.4__py3-none-any.whl → 0.1.6__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.
@@ -1,581 +1,579 @@
1
- """
2
- gRPC拦截器,用于自动记录请求和响应日志
3
- """
4
- import time
5
- import asyncio
6
- from typing import Any, Callable, Optional, Dict, List, Tuple, Union
7
- import grpc
8
- from grpc import aio
9
- import base64
10
- import re
11
-
12
- from ..utils.logging import get_logger, GrpcRequestLogger
13
-
14
-
15
- def _extract_method_name(method: str) -> str:
16
- """从gRPC方法路径中提取方法名"""
17
- # 方法格式: /package.service/MethodName
18
- parts = method.split('/')
19
- if len(parts) >= 2:
20
- return parts[-1]
21
- return method
22
-
23
- def _extract_request_id(metadata: List[Tuple[str, str]]) -> str:
24
- """从元数据中提取请求ID"""
25
- if not metadata:
26
- return "unknown"
27
-
28
- try:
29
- metadata_dict = dict(metadata)
30
- return metadata_dict.get('x-request-id', 'unknown')
31
- except Exception:
32
- # 如果元数据解析失败,返回unknown
33
- return "unknown"
34
-
35
- def _metadata_to_dict(metadata: List[Tuple[str, str]]) -> Dict[str, str]:
36
- """将元数据转换为字典"""
37
- try:
38
- return dict(metadata) if metadata else {}
39
- except Exception:
40
- # 如果元数据解析失败,返回空字典
41
- return {}
42
-
43
-
44
- def _is_base64_string(value: str, min_length: int = 100) -> bool:
45
- """检查字符串是否可能是base64编码的内容"""
46
- if not isinstance(value, str) or len(value) < min_length:
47
- return False
48
-
49
- # 基本的base64格式检查
50
- base64_pattern = re.compile(r'^[A-Za-z0-9+/]*={0,2}$')
51
- if not base64_pattern.match(value):
52
- return False
53
-
54
- # 尝试解码以验证
55
- try:
56
- decoded = base64.b64decode(value, validate=True)
57
- # 如果能成功解码且长度超过阈值,认为是base64
58
- return len(decoded) > 50
59
- except:
60
- return False
61
-
62
-
63
- def _truncate_long_string(value: str, max_length: int = 200, placeholder: str = "...") -> str:
64
- """截断长字符串,保留开头和结尾部分"""
65
- if len(value) <= max_length:
66
- return value
67
-
68
- # 计算保留的开头和结尾长度
69
- keep_length = (max_length - len(placeholder)) // 2
70
- return f"{value[:keep_length]}{placeholder}{value[-keep_length:]}"
71
-
72
-
73
- def _sanitize_request_data(data: Any, max_string_length: int = 200, max_binary_preview: int = 50) -> Any:
74
- """
75
- 清理请求数据,截断长字符串和二进制内容
76
-
77
- Args:
78
- data: 要清理的数据
79
- max_string_length: 字符串的最大长度
80
- max_binary_preview: 二进制内容预览的最大长度
81
-
82
- Returns:
83
- 清理后的数据
84
- """
85
- if isinstance(data, dict):
86
- # 递归处理字典
87
- result = {}
88
- for key, value in data.items():
89
- # 检查是否是需要特殊处理的字段
90
- if key.lower() in ['operations'] and isinstance(value, list):
91
- # 对于 operations 字段,特殊处理以显示操作类型和数量
92
- if len(value) > 5:
93
- ops_summary = []
94
- # 统计操作类型
95
- op_types = {}
96
- for op in value:
97
- if isinstance(op, dict):
98
- for op_type in ['edit', 'create', 'update', 'delete', 'clear']:
99
- if op_type in op:
100
- op_types[op_type] = op_types.get(op_type, 0) + 1
101
-
102
- # 显示前3个操作
103
- for i in range(min(3, len(value))):
104
- ops_summary.append(_sanitize_request_data(value[i], max_string_length, max_binary_preview))
105
-
106
- # 添加统计信息
107
- ops_summary.append(f"... 总计 {len(value)} 个操作: {', '.join(f'{k}={v}' for k, v in op_types.items())}")
108
- result[key] = ops_summary
109
- else:
110
- result[key] = _sanitize_request_data(value, max_string_length, max_binary_preview)
111
- elif key.lower() in ['content', 'data', 'file', 'file_content', 'binary', 'blob', 'bytes', 'image', 'attachment']:
112
- if isinstance(value, (bytes, bytearray)):
113
- # 二进制内容,显示长度和预览
114
- preview = base64.b64encode(value[:max_binary_preview]).decode('utf-8')
115
- result[key] = f"<binary {len(value)} bytes, preview: {preview}...>"
116
- elif isinstance(value, str):
117
- # 检查是否是base64字符串
118
- if _is_base64_string(value):
119
- result[key] = f"<base64 string, length: {len(value)}, preview: {value[:max_binary_preview]}...>"
120
- elif len(value) > max_string_length:
121
- result[key] = _truncate_long_string(value, max_string_length)
122
- else:
123
- result[key] = value
124
- else:
125
- result[key] = _sanitize_request_data(value, max_string_length, max_binary_preview)
126
- else:
127
- result[key] = _sanitize_request_data(value, max_string_length, max_binary_preview)
128
- return result
129
- elif isinstance(data, list):
130
- # 递归处理列表,限制列表长度以避免日志过长
131
- max_list_items = 10 # 最多显示10个元素
132
- if len(data) > max_list_items:
133
- # 显示前5个和后5个元素
134
- preview_items = (
135
- [_sanitize_request_data(item, max_string_length, max_binary_preview) for item in data[:5]] +
136
- [f"... {len(data) - max_list_items} more items ..."] +
137
- [_sanitize_request_data(item, max_string_length, max_binary_preview) for item in data[-5:]]
138
- )
139
- return preview_items
140
- else:
141
- return [_sanitize_request_data(item, max_string_length, max_binary_preview) for item in data]
142
- elif isinstance(data, tuple):
143
- # 递归处理元组
144
- return tuple(_sanitize_request_data(item, max_string_length, max_binary_preview) for item in data)
145
- elif isinstance(data, (bytes, bytearray)):
146
- # 二进制内容
147
- preview = base64.b64encode(data[:max_binary_preview]).decode('utf-8')
148
- return f"<binary {len(data)} bytes, preview: {preview}...>"
149
- elif isinstance(data, str):
150
- # 字符串内容
151
- if _is_base64_string(data):
152
- return f"<base64 string, length: {len(data)}, preview: {data[:max_binary_preview]}...>"
153
- elif len(data) > max_string_length:
154
- return _truncate_long_string(data, max_string_length)
155
- else:
156
- return data
157
- else:
158
- # 其他类型直接返回
159
- return data
160
-
161
-
162
- class LoggingInterceptor:
163
- """gRPC日志拦截器基类"""
164
-
165
- def __init__(self):
166
- self.logger = get_logger()
167
- self.request_logger = GrpcRequestLogger(self.logger)
168
-
169
-
170
- class AsyncUnaryUnaryLoggingInterceptor(LoggingInterceptor, aio.UnaryUnaryClientInterceptor):
171
- """异步一元-一元gRPC日志拦截器"""
172
-
173
- async def intercept_unary_unary(
174
- self,
175
- continuation: Callable,
176
- client_call_details: grpc.aio.ClientCallDetails,
177
- request: Any
178
- ) -> Any:
179
- """拦截一元-一元调用"""
180
- # 安全提取方法名
181
- try:
182
- method = client_call_details.method
183
- if isinstance(method, bytes):
184
- method = method.decode('utf-8', errors='ignore')
185
- method_name = method.split('/')[-1] if '/' in method else method
186
- except:
187
- method_name = "unknown"
188
-
189
- # 安全提取request_id
190
- request_id = "unknown"
191
- try:
192
- if hasattr(client_call_details, 'metadata') and client_call_details.metadata:
193
- for key, value in client_call_details.metadata:
194
- if key == 'x-request-id':
195
- request_id = value
196
- break
197
- except:
198
- pass
199
-
200
- start_time = time.time()
201
-
202
- # 记录请求开始
203
- if self.logger.handlers:
204
- try:
205
- # 安全地提取请求数据
206
- request_data = None
207
- try:
208
- if hasattr(request, '__class__'):
209
- # protobuf消息转换为字典
210
- from google.protobuf.json_format import MessageToDict
211
- request_data = MessageToDict(request, preserving_proto_field_name=True)
212
- # 清理请求数据,截断长内容
213
- request_data = _sanitize_request_data(request_data)
214
- except:
215
- # 如果转换失败,尝试其他方法
216
- try:
217
- request_data = str(request)
218
- if len(request_data) > 200:
219
- request_data = _truncate_long_string(request_data)
220
- except:
221
- request_data = "<无法序列化>"
222
-
223
- self.request_logger.log_request_start(
224
- method_name, request_id, {}, request_data
225
- )
226
- except:
227
- pass
228
-
229
- try:
230
- # 执行实际的gRPC调用
231
- response = await continuation(client_call_details, request)
232
-
233
- # 记录成功
234
- duration_ms = (time.time() - start_time) * 1000
235
- if self.logger.handlers:
236
- try:
237
- self.request_logger.log_request_end(
238
- method_name, request_id, duration_ms, None
239
- )
240
- except:
241
- pass
242
-
243
- return response
244
-
245
- except Exception as e:
246
- # 记录错误
247
- duration_ms = (time.time() - start_time) * 1000
248
- if self.logger.handlers:
249
- try:
250
- self.request_logger.log_request_end(
251
- method_name, request_id, duration_ms, error=e
252
- )
253
- except:
254
- pass
255
- raise
256
-
257
-
258
- class AsyncUnaryStreamLoggingInterceptor(LoggingInterceptor, aio.UnaryStreamClientInterceptor):
259
- """异步一元-流gRPC日志拦截器"""
260
-
261
- async def intercept_unary_stream(
262
- self,
263
- continuation: Callable,
264
- client_call_details: grpc.aio.ClientCallDetails,
265
- request: Any
266
- ) -> Any:
267
- """拦截一元-流调用"""
268
- # 安全提取方法名
269
- try:
270
- method = client_call_details.method
271
- if isinstance(method, bytes):
272
- method = method.decode('utf-8', errors='ignore')
273
- method_name = method.split('/')[-1] if '/' in method else method
274
- except:
275
- method_name = "unknown"
276
-
277
- # 安全提取request_id
278
- request_id = "unknown"
279
- try:
280
- if hasattr(client_call_details, 'metadata') and client_call_details.metadata:
281
- for key, value in client_call_details.metadata:
282
- if key == 'x-request-id':
283
- request_id = value
284
- break
285
- except:
286
- pass
287
-
288
- start_time = time.time()
289
-
290
- # 记录请求开始
291
- if self.logger.handlers:
292
- try:
293
- # 安全地提取请求数据
294
- request_data = None
295
- try:
296
- if hasattr(request, '__class__'):
297
- from google.protobuf.json_format import MessageToDict
298
- request_data = MessageToDict(request, preserving_proto_field_name=True)
299
- # 清理请求数据,截断长内容
300
- request_data = _sanitize_request_data(request_data)
301
- except:
302
- try:
303
- request_data = str(request)
304
- if len(request_data) > 200:
305
- request_data = _truncate_long_string(request_data)
306
- except:
307
- request_data = "<无法序列化>"
308
-
309
- self.request_logger.log_request_start(
310
- method_name, request_id, {}, request_data
311
- )
312
- except:
313
- pass
314
-
315
- try:
316
- # 执行实际的gRPC调用
317
- response_stream = await continuation(client_call_details, request)
318
-
319
- # 记录流开始
320
- duration_ms = (time.time() - start_time) * 1000
321
- if self.logger.handlers:
322
- try:
323
- self.request_logger.log_request_end(
324
- method_name, request_id, duration_ms, "<Stream started>"
325
- )
326
- except:
327
- pass
328
-
329
- return response_stream
330
-
331
- except Exception as e:
332
- # 记录请求错误
333
- duration_ms = (time.time() - start_time) * 1000
334
- if self.logger.handlers:
335
- try:
336
- self.request_logger.log_request_end(
337
- method_name, request_id, duration_ms, error=e
338
- )
339
- except:
340
- pass
341
- raise
342
-
343
-
344
- class AsyncStreamUnaryLoggingInterceptor(LoggingInterceptor, aio.StreamUnaryClientInterceptor):
345
- """异步流-一元gRPC日志拦截器"""
346
-
347
- async def intercept_stream_unary(
348
- self,
349
- continuation: Callable,
350
- client_call_details: grpc.aio.ClientCallDetails,
351
- request_iterator: Any
352
- ) -> Any:
353
- """拦截流-一元调用"""
354
- # 安全提取方法名
355
- try:
356
- method = client_call_details.method
357
- if isinstance(method, bytes):
358
- method = method.decode('utf-8', errors='ignore')
359
- method_name = method.split('/')[-1] if '/' in method else method
360
- except:
361
- method_name = "unknown"
362
-
363
- # 安全提取request_id
364
- request_id = "unknown"
365
- try:
366
- if hasattr(client_call_details, 'metadata') and client_call_details.metadata:
367
- for key, value in client_call_details.metadata:
368
- if key == 'x-request-id':
369
- request_id = value
370
- break
371
- except:
372
- pass
373
-
374
- start_time = time.time()
375
-
376
- # 记录请求开始
377
- if self.logger.handlers:
378
- try:
379
- self.request_logger.log_request_start(
380
- method_name, request_id, {}, "<Stream request>"
381
- )
382
- except:
383
- pass
384
-
385
- try:
386
- # 执行实际的gRPC调用
387
- response = await continuation(client_call_details, request_iterator)
388
-
389
- # 记录请求成功结束
390
- duration_ms = (time.time() - start_time) * 1000
391
- if self.logger.handlers:
392
- try:
393
- self.request_logger.log_request_end(
394
- method_name, request_id, duration_ms, None
395
- )
396
- except:
397
- pass
398
-
399
- return response
400
-
401
- except Exception as e:
402
- # 记录请求错误
403
- duration_ms = (time.time() - start_time) * 1000
404
- if self.logger.handlers:
405
- try:
406
- self.request_logger.log_request_end(
407
- method_name, request_id, duration_ms, error=e
408
- )
409
- except:
410
- pass
411
- raise
412
-
413
-
414
- class AsyncStreamStreamLoggingInterceptor(LoggingInterceptor, aio.StreamStreamClientInterceptor):
415
- """异步流-流gRPC日志拦截器"""
416
-
417
- async def intercept_stream_stream(
418
- self,
419
- continuation: Callable,
420
- client_call_details: grpc.aio.ClientCallDetails,
421
- request_iterator: Any
422
- ) -> Any:
423
- """拦截流-流调用"""
424
- # 安全提取方法名
425
- try:
426
- method = client_call_details.method
427
- if isinstance(method, bytes):
428
- method = method.decode('utf-8', errors='ignore')
429
- method_name = method.split('/')[-1] if '/' in method else method
430
- except:
431
- method_name = "unknown"
432
-
433
- # 安全提取request_id
434
- request_id = "unknown"
435
- try:
436
- if hasattr(client_call_details, 'metadata') and client_call_details.metadata:
437
- for key, value in client_call_details.metadata:
438
- if key == 'x-request-id':
439
- request_id = value
440
- break
441
- except:
442
- pass
443
-
444
- start_time = time.time()
445
-
446
- # 记录请求开始
447
- if self.logger.handlers:
448
- try:
449
- self.request_logger.log_request_start(
450
- method_name, request_id, {}, "<Stream request>"
451
- )
452
- except:
453
- pass
454
-
455
- try:
456
- # 执行实际的gRPC调用
457
- response_stream = await continuation(client_call_details, request_iterator)
458
-
459
- # 记录流开始
460
- duration_ms = (time.time() - start_time) * 1000
461
- if self.logger.handlers:
462
- try:
463
- self.request_logger.log_request_end(
464
- method_name, request_id, duration_ms, "<Stream started>"
465
- )
466
- except:
467
- pass
468
-
469
- return response_stream
470
-
471
- except Exception as e:
472
- # 记录请求错误
473
- duration_ms = (time.time() - start_time) * 1000
474
- if self.logger.handlers:
475
- try:
476
- self.request_logger.log_request_end(
477
- method_name, request_id, duration_ms, error=e
478
- )
479
- except:
480
- pass
481
- raise
482
-
483
-
484
- class SyncUnaryUnaryLoggingInterceptor(grpc.UnaryUnaryClientInterceptor):
485
- """同步一元-一元gRPC日志拦截器"""
486
-
487
- def intercept_unary_unary(self, continuation, client_call_details, request):
488
- logger = get_logger()
489
- request_logger = GrpcRequestLogger(logger)
490
-
491
- # 安全提取方法名
492
- try:
493
- method = client_call_details.method
494
- if isinstance(method, bytes):
495
- method = method.decode('utf-8', errors='ignore')
496
- method_name = method.split('/')[-1] if '/' in method else method
497
- except:
498
- method_name = "unknown"
499
-
500
- # 安全提取request_id
501
- request_id = "unknown"
502
- try:
503
- if hasattr(client_call_details, 'metadata') and client_call_details.metadata:
504
- for key, value in client_call_details.metadata:
505
- if key == 'x-request-id':
506
- request_id = value
507
- break
508
- except:
509
- pass
510
-
511
- start_time = time.time()
512
-
513
- # 记录请求开始
514
- if logger.handlers:
515
- try:
516
- # 安全地提取请求数据
517
- request_data = None
518
- try:
519
- if hasattr(request, '__class__'):
520
- from google.protobuf.json_format import MessageToDict
521
- request_data = MessageToDict(request, preserving_proto_field_name=True)
522
- # 清理请求数据,截断长内容
523
- request_data = _sanitize_request_data(request_data)
524
- except:
525
- try:
526
- request_data = str(request)
527
- if len(request_data) > 200:
528
- request_data = _truncate_long_string(request_data)
529
- except:
530
- request_data = "<无法序列化>"
531
-
532
- request_logger.log_request_start(
533
- method_name, request_id, {}, request_data
534
- )
535
- except:
536
- pass
537
-
538
- try:
539
- # 执行实际的gRPC调用
540
- response = continuation(client_call_details, request)
541
-
542
- # 记录请求成功结束
543
- duration_ms = (time.time() - start_time) * 1000
544
- if logger.handlers:
545
- try:
546
- request_logger.log_request_end(
547
- method_name, request_id, duration_ms, None
548
- )
549
- except:
550
- pass
551
-
552
- return response
553
-
554
- except Exception as e:
555
- # 记录请求错误
556
- duration_ms = (time.time() - start_time) * 1000
557
- if logger.handlers:
558
- try:
559
- request_logger.log_request_end(
560
- method_name, request_id, duration_ms, error=e
561
- )
562
- except:
563
- pass
564
- raise
565
-
566
-
567
- def create_async_interceptors() -> List[aio.ClientInterceptor]:
568
- """创建异步gRPC拦截器列表"""
569
- return [
570
- AsyncUnaryUnaryLoggingInterceptor(),
571
- AsyncUnaryStreamLoggingInterceptor(),
572
- AsyncStreamUnaryLoggingInterceptor(),
573
- AsyncStreamStreamLoggingInterceptor(),
574
- ]
575
-
576
-
577
- def create_sync_interceptors() -> List:
578
- """创建同步gRPC拦截器列表"""
579
- return [
580
- SyncUnaryUnaryLoggingInterceptor(),
1
+ """
2
+ gRPC拦截器,用于自动记录请求和响应日志
3
+ """
4
+ import time
5
+ import asyncio
6
+ from typing import Any, Callable, Optional, Dict, List, Tuple, Union
7
+ import grpc
8
+ from grpc import aio
9
+ import base64
10
+ import re
11
+
12
+ from ..utils.logging import get_logger, GrpcRequestLogger
13
+
14
+
15
+ def _extract_method_name(method: str) -> str:
16
+ """从gRPC方法路径中提取方法名"""
17
+ # 方法格式: /package.service/MethodName
18
+ parts = method.split('/')
19
+ if len(parts) >= 2:
20
+ return parts[-1]
21
+ return method
22
+
23
+ def _extract_request_id(metadata: List[Tuple[str, str]]) -> str:
24
+ """从元数据中提取请求ID"""
25
+ if not metadata:
26
+ return "unknown"
27
+
28
+ try:
29
+ metadata_dict = dict(metadata)
30
+ return metadata_dict.get('x-request-id', 'unknown')
31
+ except Exception:
32
+ # 如果元数据解析失败,返回unknown
33
+ return "unknown"
34
+
35
+ def _metadata_to_dict(metadata: List[Tuple[str, str]]) -> Dict[str, str]:
36
+ """将元数据转换为字典"""
37
+ try:
38
+ return dict(metadata) if metadata else {}
39
+ except Exception:
40
+ # 如果元数据解析失败,返回空字典
41
+ return {}
42
+
43
+
44
+ def _is_base64_string(value: str, min_length: int = 100) -> bool:
45
+ """检查字符串是否可能是base64编码的内容"""
46
+ if not isinstance(value, str) or len(value) < min_length:
47
+ return False
48
+
49
+ # 基本的base64格式检查
50
+ base64_pattern = re.compile(r'^[A-Za-z0-9+/]*={0,2}$')
51
+ if not base64_pattern.match(value):
52
+ return False
53
+
54
+ # 尝试解码以验证
55
+ try:
56
+ decoded = base64.b64decode(value, validate=True)
57
+ # 如果能成功解码且长度超过阈值,认为是base64
58
+ return len(decoded) > 50
59
+ except:
60
+ return False
61
+
62
+
63
+ def _truncate_long_string(value: str, max_length: int = 200, placeholder: str = "...") -> str:
64
+ """截断长字符串,保留开头和结尾部分"""
65
+ if len(value) <= max_length:
66
+ return value
67
+
68
+ # 计算保留的开头和结尾长度
69
+ keep_length = (max_length - len(placeholder)) // 2
70
+ return f"{value[:keep_length]}{placeholder}{value[-keep_length:]}"
71
+
72
+
73
+ def _sanitize_request_data(data: Any, max_string_length: int = 200, max_binary_preview: int = 50) -> Any:
74
+ """
75
+ 清理请求数据,截断长字符串和二进制内容
76
+
77
+ Args:
78
+ data: 要清理的数据
79
+ max_string_length: 字符串的最大长度
80
+ max_binary_preview: 二进制内容预览的最大长度
81
+
82
+ Returns:
83
+ 清理后的数据
84
+ """
85
+ if isinstance(data, dict):
86
+ # 递归处理字典
87
+ result = {}
88
+ for key, value in data.items():
89
+ # 检查是否是需要特殊处理的字段
90
+ if key.lower() in ['operations'] and isinstance(value, list):
91
+ # 对于 operations 字段,特殊处理以显示操作类型和数量
92
+ if len(value) > 5:
93
+ ops_summary = []
94
+ # 统计操作类型
95
+ op_types = {}
96
+ for op in value:
97
+ if isinstance(op, dict):
98
+ for op_type in ['edit', 'create', 'update', 'delete', 'clear']:
99
+ if op_type in op:
100
+ op_types[op_type] = op_types.get(op_type, 0) + 1
101
+
102
+ # 显示前3个操作
103
+ for i in range(min(3, len(value))):
104
+ ops_summary.append(_sanitize_request_data(value[i], max_string_length, max_binary_preview))
105
+
106
+ # 添加统计信息
107
+ ops_summary.append(f"... 总计 {len(value)} 个操作: {', '.join(f'{k}={v}' for k, v in op_types.items())}")
108
+ result[key] = ops_summary
109
+ else:
110
+ result[key] = _sanitize_request_data(value, max_string_length, max_binary_preview)
111
+ elif key.lower() in ['content', 'data', 'file', 'file_content', 'binary', 'blob', 'bytes', 'image', 'attachment']:
112
+ if isinstance(value, (bytes, bytearray)):
113
+ # 二进制内容,显示长度和预览
114
+ preview = base64.b64encode(value[:max_binary_preview]).decode('utf-8')
115
+ result[key] = f"<binary {len(value)} bytes, preview: {preview}...>"
116
+ elif isinstance(value, str):
117
+ # 检查是否是base64字符串
118
+ if _is_base64_string(value):
119
+ result[key] = f"<base64 string, length: {len(value)}, preview: {value[:max_binary_preview]}...>"
120
+ elif len(value) > max_string_length:
121
+ result[key] = _truncate_long_string(value, max_string_length)
122
+ else:
123
+ result[key] = value
124
+ else:
125
+ result[key] = _sanitize_request_data(value, max_string_length, max_binary_preview)
126
+ else:
127
+ result[key] = _sanitize_request_data(value, max_string_length, max_binary_preview)
128
+ return result
129
+ elif isinstance(data, list):
130
+ # 递归处理列表,限制列表长度以避免日志过长
131
+ max_list_items = 10 # 最多显示10个元素
132
+ if len(data) > max_list_items:
133
+ # 显示前5个和后5个元素
134
+ preview_items = (
135
+ [_sanitize_request_data(item, max_string_length, max_binary_preview) for item in data[:5]] +
136
+ [f"... {len(data) - max_list_items} more items ..."] +
137
+ [_sanitize_request_data(item, max_string_length, max_binary_preview) for item in data[-5:]]
138
+ )
139
+ return preview_items
140
+ else:
141
+ return [_sanitize_request_data(item, max_string_length, max_binary_preview) for item in data]
142
+ elif isinstance(data, tuple):
143
+ # 递归处理元组
144
+ return tuple(_sanitize_request_data(item, max_string_length, max_binary_preview) for item in data)
145
+ elif isinstance(data, (bytes, bytearray)):
146
+ # 二进制内容
147
+ preview = base64.b64encode(data[:max_binary_preview]).decode('utf-8')
148
+ return f"<binary {len(data)} bytes, preview: {preview}...>"
149
+ elif isinstance(data, str):
150
+ # 字符串内容
151
+ if _is_base64_string(data):
152
+ return f"<base64 string, length: {len(data)}, preview: {data[:max_binary_preview]}...>"
153
+ elif len(data) > max_string_length:
154
+ return _truncate_long_string(data, max_string_length)
155
+ else:
156
+ return data
157
+ else:
158
+ # 其他类型直接返回
159
+ return data
160
+
161
+
162
+ class LoggingInterceptor:
163
+ """gRPC日志拦截器基类"""
164
+
165
+ def __init__(self):
166
+ self.logger = get_logger()
167
+ self.request_logger = GrpcRequestLogger(self.logger)
168
+
169
+
170
+ class AsyncUnaryUnaryLoggingInterceptor(LoggingInterceptor, aio.UnaryUnaryClientInterceptor):
171
+ """异步一元-一元gRPC日志拦截器"""
172
+
173
+ async def intercept_unary_unary(
174
+ self,
175
+ continuation: Callable,
176
+ client_call_details: grpc.aio.ClientCallDetails,
177
+ request: Any
178
+ ) -> Any:
179
+ """拦截一元-一元调用"""
180
+ # 安全提取方法名
181
+ try:
182
+ method = client_call_details.method
183
+ if isinstance(method, bytes):
184
+ method = method.decode('utf-8', errors='ignore')
185
+ method_name = method.split('/')[-1] if '/' in method else method
186
+ except:
187
+ method_name = "unknown"
188
+
189
+ # 安全提取request_id和metadata
190
+ request_id = "unknown"
191
+ metadata_dict = {}
192
+ try:
193
+ if hasattr(client_call_details, 'metadata') and client_call_details.metadata:
194
+ metadata_dict = _metadata_to_dict(client_call_details.metadata)
195
+ request_id = metadata_dict.get('x-request-id', 'unknown')
196
+ except:
197
+ pass
198
+
199
+ start_time = time.time()
200
+
201
+ # 记录请求开始
202
+ if self.logger.handlers:
203
+ try:
204
+ # 安全地提取请求数据
205
+ request_data = None
206
+ try:
207
+ if hasattr(request, '__class__'):
208
+ # 将protobuf消息转换为字典
209
+ from google.protobuf.json_format import MessageToDict
210
+ request_data = MessageToDict(request, preserving_proto_field_name=True)
211
+ # 清理请求数据,截断长内容
212
+ request_data = _sanitize_request_data(request_data)
213
+ except:
214
+ # 如果转换失败,尝试其他方法
215
+ try:
216
+ request_data = str(request)
217
+ if len(request_data) > 200:
218
+ request_data = _truncate_long_string(request_data)
219
+ except:
220
+ request_data = "<无法序列化>"
221
+
222
+ self.request_logger.log_request_start(
223
+ method_name, request_id, metadata_dict, request_data
224
+ )
225
+ except:
226
+ pass
227
+
228
+ try:
229
+ # 执行实际的gRPC调用
230
+ response = await continuation(client_call_details, request)
231
+
232
+ # 记录成功
233
+ duration_ms = (time.time() - start_time) * 1000
234
+ if self.logger.handlers:
235
+ try:
236
+ self.request_logger.log_request_end(
237
+ method_name, request_id, duration_ms, None, metadata=metadata_dict
238
+ )
239
+ except:
240
+ pass
241
+
242
+ return response
243
+
244
+ except Exception as e:
245
+ # 记录错误
246
+ duration_ms = (time.time() - start_time) * 1000
247
+ if self.logger.handlers:
248
+ try:
249
+ self.request_logger.log_request_end(
250
+ method_name, request_id, duration_ms, error=e, metadata=metadata_dict
251
+ )
252
+ except:
253
+ pass
254
+ raise
255
+
256
+
257
+ class AsyncUnaryStreamLoggingInterceptor(LoggingInterceptor, aio.UnaryStreamClientInterceptor):
258
+ """异步一元-流gRPC日志拦截器"""
259
+
260
+ async def intercept_unary_stream(
261
+ self,
262
+ continuation: Callable,
263
+ client_call_details: grpc.aio.ClientCallDetails,
264
+ request: Any
265
+ ) -> Any:
266
+ """拦截一元-流调用"""
267
+ # 安全提取方法名
268
+ try:
269
+ method = client_call_details.method
270
+ if isinstance(method, bytes):
271
+ method = method.decode('utf-8', errors='ignore')
272
+ method_name = method.split('/')[-1] if '/' in method else method
273
+ except:
274
+ method_name = "unknown"
275
+
276
+ # 安全提取request_id
277
+ request_id = "unknown"
278
+ try:
279
+ if hasattr(client_call_details, 'metadata') and client_call_details.metadata:
280
+ for key, value in client_call_details.metadata:
281
+ if key == 'x-request-id':
282
+ request_id = value
283
+ break
284
+ except:
285
+ pass
286
+
287
+ start_time = time.time()
288
+
289
+ # 记录请求开始
290
+ if self.logger.handlers:
291
+ try:
292
+ # 安全地提取请求数据
293
+ request_data = None
294
+ try:
295
+ if hasattr(request, '__class__'):
296
+ from google.protobuf.json_format import MessageToDict
297
+ request_data = MessageToDict(request, preserving_proto_field_name=True)
298
+ # 清理请求数据,截断长内容
299
+ request_data = _sanitize_request_data(request_data)
300
+ except:
301
+ try:
302
+ request_data = str(request)
303
+ if len(request_data) > 200:
304
+ request_data = _truncate_long_string(request_data)
305
+ except:
306
+ request_data = "<无法序列化>"
307
+
308
+ self.request_logger.log_request_start(
309
+ method_name, request_id, {}, request_data
310
+ )
311
+ except:
312
+ pass
313
+
314
+ try:
315
+ # 执行实际的gRPC调用
316
+ response_stream = await continuation(client_call_details, request)
317
+
318
+ # 记录流开始
319
+ duration_ms = (time.time() - start_time) * 1000
320
+ if self.logger.handlers:
321
+ try:
322
+ self.request_logger.log_request_end(
323
+ method_name, request_id, duration_ms, "<Stream started>"
324
+ )
325
+ except:
326
+ pass
327
+
328
+ return response_stream
329
+
330
+ except Exception as e:
331
+ # 记录请求错误
332
+ duration_ms = (time.time() - start_time) * 1000
333
+ if self.logger.handlers:
334
+ try:
335
+ self.request_logger.log_request_end(
336
+ method_name, request_id, duration_ms, error=e
337
+ )
338
+ except:
339
+ pass
340
+ raise
341
+
342
+
343
+ class AsyncStreamUnaryLoggingInterceptor(LoggingInterceptor, aio.StreamUnaryClientInterceptor):
344
+ """异步流-一元gRPC日志拦截器"""
345
+
346
+ async def intercept_stream_unary(
347
+ self,
348
+ continuation: Callable,
349
+ client_call_details: grpc.aio.ClientCallDetails,
350
+ request_iterator: Any
351
+ ) -> Any:
352
+ """拦截流-一元调用"""
353
+ # 安全提取方法名
354
+ try:
355
+ method = client_call_details.method
356
+ if isinstance(method, bytes):
357
+ method = method.decode('utf-8', errors='ignore')
358
+ method_name = method.split('/')[-1] if '/' in method else method
359
+ except:
360
+ method_name = "unknown"
361
+
362
+ # 安全提取request_id
363
+ request_id = "unknown"
364
+ try:
365
+ if hasattr(client_call_details, 'metadata') and client_call_details.metadata:
366
+ for key, value in client_call_details.metadata:
367
+ if key == 'x-request-id':
368
+ request_id = value
369
+ break
370
+ except:
371
+ pass
372
+
373
+ start_time = time.time()
374
+
375
+ # 记录请求开始
376
+ if self.logger.handlers:
377
+ try:
378
+ self.request_logger.log_request_start(
379
+ method_name, request_id, {}, "<Stream request>"
380
+ )
381
+ except:
382
+ pass
383
+
384
+ try:
385
+ # 执行实际的gRPC调用
386
+ response = await continuation(client_call_details, request_iterator)
387
+
388
+ # 记录请求成功结束
389
+ duration_ms = (time.time() - start_time) * 1000
390
+ if self.logger.handlers:
391
+ try:
392
+ self.request_logger.log_request_end(
393
+ method_name, request_id, duration_ms, None
394
+ )
395
+ except:
396
+ pass
397
+
398
+ return response
399
+
400
+ except Exception as e:
401
+ # 记录请求错误
402
+ duration_ms = (time.time() - start_time) * 1000
403
+ if self.logger.handlers:
404
+ try:
405
+ self.request_logger.log_request_end(
406
+ method_name, request_id, duration_ms, error=e
407
+ )
408
+ except:
409
+ pass
410
+ raise
411
+
412
+
413
+ class AsyncStreamStreamLoggingInterceptor(LoggingInterceptor, aio.StreamStreamClientInterceptor):
414
+ """异步流-流gRPC日志拦截器"""
415
+
416
+ async def intercept_stream_stream(
417
+ self,
418
+ continuation: Callable,
419
+ client_call_details: grpc.aio.ClientCallDetails,
420
+ request_iterator: Any
421
+ ) -> Any:
422
+ """拦截流-流调用"""
423
+ # 安全提取方法名
424
+ try:
425
+ method = client_call_details.method
426
+ if isinstance(method, bytes):
427
+ method = method.decode('utf-8', errors='ignore')
428
+ method_name = method.split('/')[-1] if '/' in method else method
429
+ except:
430
+ method_name = "unknown"
431
+
432
+ # 安全提取request_id
433
+ request_id = "unknown"
434
+ try:
435
+ if hasattr(client_call_details, 'metadata') and client_call_details.metadata:
436
+ for key, value in client_call_details.metadata:
437
+ if key == 'x-request-id':
438
+ request_id = value
439
+ break
440
+ except:
441
+ pass
442
+
443
+ start_time = time.time()
444
+
445
+ # 记录请求开始
446
+ if self.logger.handlers:
447
+ try:
448
+ self.request_logger.log_request_start(
449
+ method_name, request_id, {}, "<Stream request>"
450
+ )
451
+ except:
452
+ pass
453
+
454
+ try:
455
+ # 执行实际的gRPC调用
456
+ response_stream = await continuation(client_call_details, request_iterator)
457
+
458
+ # 记录流开始
459
+ duration_ms = (time.time() - start_time) * 1000
460
+ if self.logger.handlers:
461
+ try:
462
+ self.request_logger.log_request_end(
463
+ method_name, request_id, duration_ms, "<Stream started>"
464
+ )
465
+ except:
466
+ pass
467
+
468
+ return response_stream
469
+
470
+ except Exception as e:
471
+ # 记录请求错误
472
+ duration_ms = (time.time() - start_time) * 1000
473
+ if self.logger.handlers:
474
+ try:
475
+ self.request_logger.log_request_end(
476
+ method_name, request_id, duration_ms, error=e
477
+ )
478
+ except:
479
+ pass
480
+ raise
481
+
482
+
483
+ class SyncUnaryUnaryLoggingInterceptor(grpc.UnaryUnaryClientInterceptor):
484
+ """同步一元-一元gRPC日志拦截器"""
485
+
486
+ def intercept_unary_unary(self, continuation, client_call_details, request):
487
+ logger = get_logger()
488
+ request_logger = GrpcRequestLogger(logger)
489
+
490
+ # 安全提取方法名
491
+ try:
492
+ method = client_call_details.method
493
+ if isinstance(method, bytes):
494
+ method = method.decode('utf-8', errors='ignore')
495
+ method_name = method.split('/')[-1] if '/' in method else method
496
+ except:
497
+ method_name = "unknown"
498
+
499
+ # 安全提取request_id和metadata
500
+ request_id = "unknown"
501
+ metadata_dict = {}
502
+ try:
503
+ if hasattr(client_call_details, 'metadata') and client_call_details.metadata:
504
+ metadata_dict = _metadata_to_dict(client_call_details.metadata)
505
+ request_id = metadata_dict.get('x-request-id', 'unknown')
506
+ except:
507
+ pass
508
+
509
+ start_time = time.time()
510
+
511
+ # 记录请求开始
512
+ if logger.handlers:
513
+ try:
514
+ # 安全地提取请求数据
515
+ request_data = None
516
+ try:
517
+ if hasattr(request, '__class__'):
518
+ from google.protobuf.json_format import MessageToDict
519
+ request_data = MessageToDict(request, preserving_proto_field_name=True)
520
+ # 清理请求数据,截断长内容
521
+ request_data = _sanitize_request_data(request_data)
522
+ except:
523
+ try:
524
+ request_data = str(request)
525
+ if len(request_data) > 200:
526
+ request_data = _truncate_long_string(request_data)
527
+ except:
528
+ request_data = "<无法序列化>"
529
+
530
+ request_logger.log_request_start(
531
+ method_name, request_id, metadata_dict, request_data
532
+ )
533
+ except:
534
+ pass
535
+
536
+ try:
537
+ # 执行实际的gRPC调用
538
+ response = continuation(client_call_details, request)
539
+
540
+ # 记录请求成功结束
541
+ duration_ms = (time.time() - start_time) * 1000
542
+ if logger.handlers:
543
+ try:
544
+ request_logger.log_request_end(
545
+ method_name, request_id, duration_ms, None, metadata=metadata_dict
546
+ )
547
+ except:
548
+ pass
549
+
550
+ return response
551
+
552
+ except Exception as e:
553
+ # 记录请求错误
554
+ duration_ms = (time.time() - start_time) * 1000
555
+ if logger.handlers:
556
+ try:
557
+ request_logger.log_request_end(
558
+ method_name, request_id, duration_ms, error=e, metadata=metadata_dict
559
+ )
560
+ except:
561
+ pass
562
+ raise
563
+
564
+
565
+ def create_async_interceptors() -> List[aio.ClientInterceptor]:
566
+ """创建异步gRPC拦截器列表"""
567
+ return [
568
+ AsyncUnaryUnaryLoggingInterceptor(),
569
+ AsyncUnaryStreamLoggingInterceptor(),
570
+ AsyncStreamUnaryLoggingInterceptor(),
571
+ AsyncStreamStreamLoggingInterceptor(),
572
+ ]
573
+
574
+
575
+ def create_sync_interceptors() -> List:
576
+ """创建同步gRPC拦截器列表"""
577
+ return [
578
+ SyncUnaryUnaryLoggingInterceptor(),
581
579
  ]