xmi-logger 0.0.7__py3-none-any.whl → 0.0.9__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.
- xmi_logger/__version__.py +2 -2
- xmi_logger/advanced_features.py +743 -579
- xmi_logger/xmi_logger.py +266 -178
- {xmi_logger-0.0.7.dist-info → xmi_logger-0.0.9.dist-info}/METADATA +151 -69
- xmi_logger-0.0.9.dist-info/RECORD +8 -0
- {xmi_logger-0.0.7.dist-info → xmi_logger-0.0.9.dist-info}/WHEEL +1 -1
- xmi_logger-0.0.7.dist-info/RECORD +0 -8
- {xmi_logger-0.0.7.dist-info → xmi_logger-0.0.9.dist-info}/top_level.txt +0 -0
xmi_logger/xmi_logger.py
CHANGED
|
@@ -14,12 +14,12 @@ import requests
|
|
|
14
14
|
import asyncio
|
|
15
15
|
import aiohttp
|
|
16
16
|
|
|
17
|
-
from typing import Optional, Dict, Any,
|
|
17
|
+
from typing import Optional, Dict, Any, List, Tuple
|
|
18
18
|
|
|
19
|
-
from functools import wraps
|
|
19
|
+
from functools import wraps
|
|
20
20
|
from time import perf_counter
|
|
21
21
|
from contextvars import ContextVar
|
|
22
|
-
from concurrent.futures import ThreadPoolExecutor
|
|
22
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
23
23
|
from collections import defaultdict
|
|
24
24
|
from datetime import datetime, timedelta
|
|
25
25
|
import threading
|
|
@@ -81,14 +81,6 @@ class XmiLogger:
|
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
# 添加类级别的缓存,使用 LRU 缓存提高性能
|
|
85
|
-
_format_cache: Dict[str, str] = {}
|
|
86
|
-
_message_cache: Dict[str, str] = {}
|
|
87
|
-
_location_cache: Dict[str, str] = {}
|
|
88
|
-
_stats_cache: Dict[str, Any] = {}
|
|
89
|
-
_stats_cache_time = 0
|
|
90
|
-
_stats_cache_ttl = 5 # 统计缓存TTL(秒)
|
|
91
|
-
|
|
92
84
|
def __init__(
|
|
93
85
|
self,
|
|
94
86
|
file_name: str,
|
|
@@ -108,6 +100,7 @@ class XmiLogger:
|
|
|
108
100
|
cache_size: int = 128, # 新增:缓存大小配置
|
|
109
101
|
adaptive_level: bool = False, # 新增:自适应日志级别
|
|
110
102
|
performance_mode: bool = False, # 新增:性能模式
|
|
103
|
+
enable_exception_hook: bool = False,
|
|
111
104
|
) -> None:
|
|
112
105
|
"""
|
|
113
106
|
初始化日志记录器。
|
|
@@ -136,12 +129,23 @@ class XmiLogger:
|
|
|
136
129
|
self.enable_stats = enable_stats
|
|
137
130
|
self.categories = categories or []
|
|
138
131
|
self._cache_size = cache_size
|
|
132
|
+
self._format_cache: Dict[Any, str] = {}
|
|
133
|
+
self._message_cache: Dict[Any, str] = {}
|
|
134
|
+
self._location_cache: Dict[Any, str] = {}
|
|
135
|
+
self._stats_cache: Dict[str, Any] = {}
|
|
136
|
+
self._stats_cache_time = 0.0
|
|
137
|
+
self._stats_cache_ttl = 5
|
|
139
138
|
self.adaptive_level = adaptive_level
|
|
140
139
|
self.performance_mode = performance_mode
|
|
141
|
-
self.
|
|
142
|
-
self.
|
|
143
|
-
|
|
144
|
-
|
|
140
|
+
self._handler_ids: List[int] = []
|
|
141
|
+
self._removed_default_handler = False
|
|
142
|
+
self._exception_hook_enabled = bool(enable_exception_hook)
|
|
143
|
+
self._exception_hook = None
|
|
144
|
+
self._prev_excepthook = None
|
|
145
|
+
self._remote_loop = None
|
|
146
|
+
self._remote_thread = None
|
|
147
|
+
self._remote_queue = None
|
|
148
|
+
self._remote_ready = threading.Event()
|
|
145
149
|
|
|
146
150
|
# 语言选项
|
|
147
151
|
self.language = language if language in ('zh', 'en') else 'zh'
|
|
@@ -165,7 +169,11 @@ class XmiLogger:
|
|
|
165
169
|
self.backtrace = True
|
|
166
170
|
|
|
167
171
|
# 用于远程日志发送的线程池
|
|
168
|
-
self.
|
|
172
|
+
self._max_workers = max_workers
|
|
173
|
+
self._executor = None
|
|
174
|
+
if self.remote_log_url:
|
|
175
|
+
self._executor = ThreadPoolExecutor(max_workers=max_workers)
|
|
176
|
+
self._start_remote_sender()
|
|
169
177
|
|
|
170
178
|
# 初始化 Logger 配置
|
|
171
179
|
self.configure_logger()
|
|
@@ -188,18 +196,14 @@ class XmiLogger:
|
|
|
188
196
|
def _msg(self, key: str, **kwargs) -> str:
|
|
189
197
|
"""消息格式化处理,优化性能"""
|
|
190
198
|
try:
|
|
191
|
-
# 使用缓存键
|
|
192
|
-
cache_key = f"{self.language}:{key}:{hash(frozenset(kwargs.items()))}"
|
|
193
|
-
|
|
194
|
-
# 检查缓存
|
|
195
|
-
if cache_key in self._message_cache:
|
|
196
|
-
return self._message_cache[cache_key]
|
|
197
|
-
|
|
198
199
|
# 获取消息模板
|
|
199
200
|
text = self._LANG_MAP.get(self.language, {}).get(key, key)
|
|
200
201
|
|
|
201
202
|
# 如果没有参数,直接返回模板
|
|
202
203
|
if not kwargs:
|
|
204
|
+
cache_key = (self.language, key, None)
|
|
205
|
+
if cache_key in self._message_cache:
|
|
206
|
+
return self._message_cache[cache_key]
|
|
203
207
|
self._message_cache[cache_key] = text
|
|
204
208
|
return text
|
|
205
209
|
|
|
@@ -208,13 +212,23 @@ class XmiLogger:
|
|
|
208
212
|
for k, v in kwargs.items():
|
|
209
213
|
try:
|
|
210
214
|
if isinstance(v, (list, tuple)):
|
|
211
|
-
str_kwargs[k] =
|
|
215
|
+
str_kwargs[k] = tuple(str(item) for item in v)
|
|
212
216
|
elif isinstance(v, dict):
|
|
213
217
|
str_kwargs[k] = {str(kk): str(vv) for kk, vv in v.items()}
|
|
214
218
|
else:
|
|
215
219
|
str_kwargs[k] = str(v)
|
|
216
220
|
except Exception:
|
|
217
221
|
str_kwargs[k] = f"<{type(v).__name__}>"
|
|
222
|
+
|
|
223
|
+
frozen_kwargs = tuple(
|
|
224
|
+
sorted(
|
|
225
|
+
(k, tuple(sorted(v.items())) if isinstance(v, dict) else v)
|
|
226
|
+
for k, v in str_kwargs.items()
|
|
227
|
+
)
|
|
228
|
+
)
|
|
229
|
+
cache_key = (self.language, key, frozen_kwargs)
|
|
230
|
+
if cache_key in self._message_cache:
|
|
231
|
+
return self._message_cache[cache_key]
|
|
218
232
|
|
|
219
233
|
# 格式化消息
|
|
220
234
|
result = text.format(**str_kwargs)
|
|
@@ -237,11 +251,25 @@ class XmiLogger:
|
|
|
237
251
|
text = self._LANG_MAP.get(self.language, {}).get(key, key)
|
|
238
252
|
return f"{text} (格式化错误: {str(e)})"
|
|
239
253
|
|
|
254
|
+
def _remove_handlers(self) -> None:
|
|
255
|
+
handler_ids = list(self._handler_ids)
|
|
256
|
+
self._handler_ids.clear()
|
|
257
|
+
for handler_id in handler_ids:
|
|
258
|
+
try:
|
|
259
|
+
self.logger.remove(handler_id)
|
|
260
|
+
except Exception:
|
|
261
|
+
continue
|
|
262
|
+
|
|
240
263
|
def configure_logger(self) -> None:
|
|
241
264
|
"""配置日志记录器,添加错误处理和安全性检查"""
|
|
242
265
|
try:
|
|
243
|
-
|
|
244
|
-
self.
|
|
266
|
+
self._remove_handlers()
|
|
267
|
+
if not self._removed_default_handler:
|
|
268
|
+
try:
|
|
269
|
+
self.logger.remove(0)
|
|
270
|
+
except Exception:
|
|
271
|
+
pass
|
|
272
|
+
self._removed_default_handler = True
|
|
245
273
|
|
|
246
274
|
# 验证配置参数
|
|
247
275
|
self._validate_config()
|
|
@@ -263,7 +291,8 @@ class XmiLogger:
|
|
|
263
291
|
self._configure_remote_logging()
|
|
264
292
|
|
|
265
293
|
# 设置异常处理器
|
|
266
|
-
self.
|
|
294
|
+
if self._exception_hook_enabled:
|
|
295
|
+
self.setup_exception_handler()
|
|
267
296
|
|
|
268
297
|
except Exception as e:
|
|
269
298
|
# 如果配置失败,使用基本配置
|
|
@@ -308,14 +337,14 @@ class XmiLogger:
|
|
|
308
337
|
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
|
|
309
338
|
"<level>{level: <8}</level> | "
|
|
310
339
|
"ReqID:{extra[request_id]} | "
|
|
311
|
-
"<cyan>{file}</cyan>:<cyan>{line}</cyan
|
|
340
|
+
"<cyan>{file}</cyan>:<cyan>{line}</cyan> | "
|
|
312
341
|
"<magenta>{process}</magenta> | "
|
|
313
342
|
"<level>{message}</level>"
|
|
314
343
|
)
|
|
315
344
|
|
|
316
345
|
def _add_console_handler(self, log_format: str) -> None:
|
|
317
346
|
"""添加控制台处理器"""
|
|
318
|
-
self.logger.add(
|
|
347
|
+
handler_id = self.logger.add(
|
|
319
348
|
sys.stdout,
|
|
320
349
|
format=log_format,
|
|
321
350
|
level=self.filter_level,
|
|
@@ -323,11 +352,12 @@ class XmiLogger:
|
|
|
323
352
|
diagnose=self.diagnose,
|
|
324
353
|
backtrace=self.backtrace,
|
|
325
354
|
)
|
|
355
|
+
self._handler_ids.append(handler_id)
|
|
326
356
|
|
|
327
357
|
def _add_file_handlers(self, log_format: str) -> None:
|
|
328
358
|
"""添加文件处理器"""
|
|
329
359
|
# 主日志文件
|
|
330
|
-
self.logger.add(
|
|
360
|
+
handler_id = self.logger.add(
|
|
331
361
|
os.path.join(self.log_dir, f"{self.file_name}.log"),
|
|
332
362
|
format=log_format,
|
|
333
363
|
level=self.filter_level,
|
|
@@ -339,9 +369,10 @@ class XmiLogger:
|
|
|
339
369
|
diagnose=self.diagnose,
|
|
340
370
|
backtrace=self.backtrace,
|
|
341
371
|
)
|
|
372
|
+
self._handler_ids.append(handler_id)
|
|
342
373
|
|
|
343
374
|
# 错误日志文件
|
|
344
|
-
self.logger.add(
|
|
375
|
+
handler_id = self.logger.add(
|
|
345
376
|
self._get_level_log_path("error"),
|
|
346
377
|
format=log_format,
|
|
347
378
|
level="ERROR",
|
|
@@ -353,26 +384,29 @@ class XmiLogger:
|
|
|
353
384
|
diagnose=self.diagnose,
|
|
354
385
|
backtrace=self.backtrace,
|
|
355
386
|
)
|
|
387
|
+
self._handler_ids.append(handler_id)
|
|
356
388
|
|
|
357
389
|
def _fallback_configuration(self) -> None:
|
|
358
390
|
"""配置失败时的后备方案"""
|
|
359
|
-
self.
|
|
360
|
-
self.logger.add(
|
|
391
|
+
self._remove_handlers()
|
|
392
|
+
handler_id = self.logger.add(
|
|
361
393
|
sys.stderr,
|
|
362
394
|
format="<red>{time:YYYY-MM-DD HH:mm:ss}</red> | <level>{level: <8}</level> | <level>{message}</level>",
|
|
363
395
|
level="ERROR"
|
|
364
396
|
)
|
|
397
|
+
self._handler_ids.append(handler_id)
|
|
365
398
|
|
|
366
399
|
def _configure_remote_logging(self):
|
|
367
400
|
"""
|
|
368
401
|
配置远程日志收集。
|
|
369
402
|
"""
|
|
370
403
|
# 当远程日志收集启用时,只发送 ERROR 及以上级别的日志
|
|
371
|
-
self.logger.add(
|
|
404
|
+
handler_id = self.logger.add(
|
|
372
405
|
self.remote_sink,
|
|
373
406
|
level="ERROR",
|
|
374
407
|
enqueue=self.enqueue,
|
|
375
408
|
)
|
|
409
|
+
self._handler_ids.append(handler_id)
|
|
376
410
|
|
|
377
411
|
def log_with_tag(self, level: str, message: str, tag: str):
|
|
378
412
|
"""
|
|
@@ -383,7 +417,9 @@ class XmiLogger:
|
|
|
383
417
|
message: 日志消息
|
|
384
418
|
tag: 标签名称
|
|
385
419
|
"""
|
|
386
|
-
|
|
420
|
+
self._update_stats(level)
|
|
421
|
+
logger_opt = self.logger.opt(depth=1)
|
|
422
|
+
log_method = getattr(logger_opt, level.lower(), logger_opt.info)
|
|
387
423
|
tagged_message = self._msg('LOG_TAGGED', tag=tag, message=message)
|
|
388
424
|
log_method(tagged_message)
|
|
389
425
|
|
|
@@ -396,7 +432,9 @@ class XmiLogger:
|
|
|
396
432
|
message: 日志消息
|
|
397
433
|
category: 分类名称
|
|
398
434
|
"""
|
|
399
|
-
|
|
435
|
+
self._update_stats(level, category=category)
|
|
436
|
+
logger_opt = self.logger.opt(depth=1)
|
|
437
|
+
log_method = getattr(logger_opt, level.lower(), logger_opt.info)
|
|
400
438
|
categorized_message = self._msg('LOG_CATEGORY', category=category, message=message)
|
|
401
439
|
log_method(categorized_message)
|
|
402
440
|
|
|
@@ -452,6 +490,9 @@ class XmiLogger:
|
|
|
452
490
|
# 如果格式化失败,使用最基本的错误记录
|
|
453
491
|
self.logger.opt(exception=True).error(f"未处理的异常: {exc_type.__name__}")
|
|
454
492
|
|
|
493
|
+
if self._prev_excepthook is None:
|
|
494
|
+
self._prev_excepthook = sys.excepthook
|
|
495
|
+
self._exception_hook = exception_handler
|
|
455
496
|
sys.excepthook = exception_handler
|
|
456
497
|
|
|
457
498
|
def _get_level_log_path(self, level_name):
|
|
@@ -468,117 +509,136 @@ class XmiLogger:
|
|
|
468
509
|
log_file = f"{log_level}.log"
|
|
469
510
|
return os.path.join(self.log_dir, log_file)
|
|
470
511
|
|
|
471
|
-
def
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
512
|
+
def _start_remote_sender(self) -> None:
|
|
513
|
+
if self._remote_thread and self._remote_thread.is_alive():
|
|
514
|
+
return
|
|
515
|
+
|
|
516
|
+
def remote_thread_target():
|
|
517
|
+
loop = asyncio.new_event_loop()
|
|
518
|
+
asyncio.set_event_loop(loop)
|
|
519
|
+
queue = asyncio.Queue()
|
|
520
|
+
|
|
521
|
+
async def remote_worker():
|
|
522
|
+
connector = aiohttp.TCPConnector(limit=10, limit_per_host=5)
|
|
523
|
+
timeout = aiohttp.ClientTimeout(total=5)
|
|
524
|
+
session = aiohttp.ClientSession(connector=connector, timeout=timeout)
|
|
475
525
|
try:
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
526
|
+
while True:
|
|
527
|
+
payload = await queue.get()
|
|
528
|
+
if payload is None:
|
|
529
|
+
queue.task_done()
|
|
530
|
+
break
|
|
531
|
+
try:
|
|
532
|
+
await self._post_remote_payload(session, payload)
|
|
533
|
+
finally:
|
|
534
|
+
queue.task_done()
|
|
535
|
+
finally:
|
|
536
|
+
try:
|
|
537
|
+
await session.close()
|
|
538
|
+
except Exception:
|
|
539
|
+
pass
|
|
540
|
+
loop.stop()
|
|
541
|
+
|
|
542
|
+
self._remote_loop = loop
|
|
543
|
+
self._remote_queue = queue
|
|
544
|
+
self._remote_ready.set()
|
|
545
|
+
loop.create_task(remote_worker())
|
|
546
|
+
loop.run_forever()
|
|
547
|
+
try:
|
|
548
|
+
loop.close()
|
|
549
|
+
except Exception:
|
|
550
|
+
pass
|
|
551
|
+
|
|
552
|
+
self._remote_ready.clear()
|
|
553
|
+
self._remote_thread = threading.Thread(target=remote_thread_target, daemon=True)
|
|
554
|
+
self._remote_thread.start()
|
|
555
|
+
self._remote_ready.wait(timeout=2)
|
|
556
|
+
|
|
557
|
+
async def _post_remote_payload(self, session: aiohttp.ClientSession, payload: Dict[str, Any]) -> None:
|
|
558
|
+
for attempt in range(3):
|
|
559
|
+
try:
|
|
560
|
+
async with session.post(
|
|
561
|
+
self.remote_log_url,
|
|
562
|
+
json=payload,
|
|
563
|
+
headers={"Content-Type": "application/json"},
|
|
564
|
+
) as response:
|
|
565
|
+
response.raise_for_status()
|
|
566
|
+
return
|
|
567
|
+
except Exception as e:
|
|
568
|
+
if attempt == 2:
|
|
569
|
+
self.logger.warning(self._msg('FAILED_REMOTE', error=f"最终尝试失败: {e}"))
|
|
570
|
+
return
|
|
571
|
+
await asyncio.sleep(1 * (attempt + 1))
|
|
572
|
+
|
|
573
|
+
def _build_remote_payload(self, message: Any) -> Dict[str, Any]:
|
|
487
574
|
log_entry = message.record
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
# 优化文件路径处理
|
|
575
|
+
try:
|
|
576
|
+
time_str = log_entry["time"].strftime("%Y-%m-%d %H:%M:%S")
|
|
577
|
+
except Exception:
|
|
578
|
+
time_str = str(log_entry.get("time"))
|
|
579
|
+
|
|
495
580
|
file_path = ""
|
|
496
|
-
|
|
581
|
+
file_obj = log_entry.get("file")
|
|
582
|
+
if file_obj:
|
|
497
583
|
try:
|
|
498
|
-
file_path = os.path.basename(
|
|
499
|
-
except
|
|
500
|
-
file_path = str(
|
|
501
|
-
|
|
502
|
-
|
|
584
|
+
file_path = os.path.basename(file_obj.path)
|
|
585
|
+
except Exception:
|
|
586
|
+
file_path = str(file_obj)
|
|
587
|
+
|
|
588
|
+
return {
|
|
503
589
|
"time": time_str,
|
|
504
|
-
"level":
|
|
505
|
-
"message": log_entry
|
|
590
|
+
"level": getattr(log_entry.get("level"), "name", str(log_entry.get("level"))),
|
|
591
|
+
"message": log_entry.get("message", ""),
|
|
506
592
|
"file": file_path,
|
|
507
|
-
"line": log_entry
|
|
508
|
-
"function": log_entry
|
|
509
|
-
"request_id": request_id
|
|
593
|
+
"line": log_entry.get("line"),
|
|
594
|
+
"function": log_entry.get("function"),
|
|
595
|
+
"request_id": log_entry.get("extra", {}).get("request_id", "no-request-id"),
|
|
510
596
|
}
|
|
511
|
-
|
|
512
|
-
# 使用连接池优化网络请求
|
|
513
|
-
connector = aiohttp.TCPConnector(limit=10, limit_per_host=5)
|
|
514
|
-
timeout = aiohttp.ClientTimeout(total=5)
|
|
515
|
-
|
|
516
|
-
async with aiohttp.ClientSession(connector=connector, timeout=timeout) as session:
|
|
517
|
-
for attempt in range(3):
|
|
518
|
-
try:
|
|
519
|
-
async with session.post(
|
|
520
|
-
self.remote_log_url,
|
|
521
|
-
json=payload,
|
|
522
|
-
headers={"Content-Type": "application/json"}
|
|
523
|
-
) as response:
|
|
524
|
-
response.raise_for_status()
|
|
525
|
-
return
|
|
526
|
-
except Exception as e:
|
|
527
|
-
if attempt == 2:
|
|
528
|
-
self.logger.warning(
|
|
529
|
-
self._msg('FAILED_REMOTE', error=f"最终尝试失败: {e}")
|
|
530
|
-
)
|
|
531
|
-
await asyncio.sleep(1 * (attempt + 1))
|
|
532
|
-
|
|
597
|
+
|
|
533
598
|
def remote_sink(self, message):
|
|
534
|
-
|
|
535
|
-
if self.
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
599
|
+
payload = self._build_remote_payload(message)
|
|
600
|
+
if self._remote_loop and self._remote_queue and self._remote_ready.is_set():
|
|
601
|
+
try:
|
|
602
|
+
self._remote_loop.call_soon_threadsafe(self._remote_queue.put_nowait, payload)
|
|
603
|
+
return
|
|
604
|
+
except Exception:
|
|
605
|
+
pass
|
|
606
|
+
if self._executor is None:
|
|
607
|
+
self._executor = ThreadPoolExecutor(max_workers=self._max_workers)
|
|
608
|
+
self._executor.submit(self._send_payload_sync, payload)
|
|
539
609
|
|
|
540
|
-
def
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
"time": log_entry["time"].strftime("%Y-%m-%d %H:%M:%S"),
|
|
555
|
-
"level": log_entry["level"].name,
|
|
556
|
-
"message": log_entry["message"],
|
|
557
|
-
"file": os.path.basename(log_entry["file"].path) if log_entry["file"] else "",
|
|
558
|
-
"line": log_entry["line"],
|
|
559
|
-
"function": log_entry["function"],
|
|
560
|
-
"request_id": log_entry["extra"].get("request_id", "no-request-id")
|
|
561
|
-
}
|
|
562
|
-
headers = {"Content-Type": "application/json"}
|
|
610
|
+
def _stop_remote_sender(self) -> None:
|
|
611
|
+
loop = self._remote_loop
|
|
612
|
+
queue = self._remote_queue
|
|
613
|
+
thread = self._remote_thread
|
|
614
|
+
if loop and queue and thread and thread.is_alive():
|
|
615
|
+
try:
|
|
616
|
+
loop.call_soon_threadsafe(queue.put_nowait, None)
|
|
617
|
+
except Exception:
|
|
618
|
+
pass
|
|
619
|
+
thread.join(timeout=2)
|
|
620
|
+
self._remote_loop = None
|
|
621
|
+
self._remote_queue = None
|
|
622
|
+
self._remote_thread = None
|
|
623
|
+
self._remote_ready.clear()
|
|
563
624
|
|
|
625
|
+
def _send_payload_sync(self, payload: Dict[str, Any]) -> None:
|
|
626
|
+
headers = {"Content-Type": "application/json"}
|
|
564
627
|
max_retries = 3
|
|
565
|
-
retry_delay = 1
|
|
566
|
-
|
|
628
|
+
retry_delay = 1
|
|
567
629
|
for attempt in range(max_retries):
|
|
568
630
|
try:
|
|
569
631
|
response = requests.post(
|
|
570
632
|
self.remote_log_url,
|
|
571
633
|
headers=headers,
|
|
572
634
|
json=payload,
|
|
573
|
-
timeout=5
|
|
635
|
+
timeout=5,
|
|
574
636
|
)
|
|
575
637
|
response.raise_for_status()
|
|
576
638
|
return
|
|
577
639
|
except requests.RequestException as e:
|
|
578
|
-
if attempt == max_retries - 1:
|
|
579
|
-
self.logger.warning(
|
|
580
|
-
self._msg('FAILED_REMOTE', error=f"Final attempt failed: {e}")
|
|
581
|
-
)
|
|
640
|
+
if attempt == max_retries - 1:
|
|
641
|
+
self.logger.warning(self._msg('FAILED_REMOTE', error=f"Final attempt failed: {e}"))
|
|
582
642
|
else:
|
|
583
643
|
time.sleep(retry_delay * (attempt + 1))
|
|
584
644
|
|
|
@@ -606,7 +666,36 @@ class XmiLogger:
|
|
|
606
666
|
Args:
|
|
607
667
|
level (str): 日志级别方法名称。
|
|
608
668
|
"""
|
|
609
|
-
|
|
669
|
+
logger_method = getattr(self.logger.opt(depth=1), level)
|
|
670
|
+
return logger_method
|
|
671
|
+
|
|
672
|
+
def log(self, level: str, message: str, *args, **kwargs):
|
|
673
|
+
self._update_stats(level)
|
|
674
|
+
return self.logger.opt(depth=1).log(level, message, *args, **kwargs)
|
|
675
|
+
|
|
676
|
+
def debug(self, message: str, *args, **kwargs):
|
|
677
|
+
self._update_stats("DEBUG")
|
|
678
|
+
return self.logger.opt(depth=1).debug(message, *args, **kwargs)
|
|
679
|
+
|
|
680
|
+
def info(self, message: str, *args, **kwargs):
|
|
681
|
+
self._update_stats("INFO")
|
|
682
|
+
return self.logger.opt(depth=1).info(message, *args, **kwargs)
|
|
683
|
+
|
|
684
|
+
def warning(self, message: str, *args, **kwargs):
|
|
685
|
+
self._update_stats("WARNING")
|
|
686
|
+
return self.logger.opt(depth=1).warning(message, *args, **kwargs)
|
|
687
|
+
|
|
688
|
+
def error(self, message: str, *args, **kwargs):
|
|
689
|
+
self._update_stats("ERROR")
|
|
690
|
+
return self.logger.opt(depth=1).error(message, *args, **kwargs)
|
|
691
|
+
|
|
692
|
+
def critical(self, message: str, *args, **kwargs):
|
|
693
|
+
self._update_stats("CRITICAL")
|
|
694
|
+
return self.logger.opt(depth=1).critical(message, *args, **kwargs)
|
|
695
|
+
|
|
696
|
+
def exception(self, message: str, *args, **kwargs):
|
|
697
|
+
self._update_stats("ERROR")
|
|
698
|
+
return self.logger.opt(depth=1).exception(message, *args, **kwargs)
|
|
610
699
|
|
|
611
700
|
def log_decorator(self, msg: Optional[str] = None, level: str = "ERROR", trace: bool = True):
|
|
612
701
|
"""
|
|
@@ -815,9 +904,8 @@ class XmiLogger:
|
|
|
815
904
|
self._stats['last_error_time'] = datetime.now()
|
|
816
905
|
|
|
817
906
|
# 计算错误率
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
self._stats['error_rate'] = self._stats['error'] / total_time
|
|
907
|
+
if self._stats['total'] > 0:
|
|
908
|
+
self._stats['error_rate'] = self._stats['error'] / self._stats['total']
|
|
821
909
|
|
|
822
910
|
def get_stats(self) -> Dict[str, Any]:
|
|
823
911
|
"""获取详细的日志统计信息,优化性能"""
|
|
@@ -903,54 +991,38 @@ class XmiLogger:
|
|
|
903
991
|
|
|
904
992
|
def get_current_location(self) -> str:
|
|
905
993
|
"""获取当前调用位置信息,优化性能"""
|
|
906
|
-
import inspect
|
|
907
|
-
import threading
|
|
908
|
-
|
|
909
|
-
# 使用线程本地缓存
|
|
910
|
-
thread_id = threading.get_ident()
|
|
911
|
-
cache_key = f"location_{thread_id}"
|
|
912
|
-
|
|
913
|
-
# 检查缓存
|
|
914
|
-
if cache_key in self._location_cache:
|
|
915
|
-
return self._location_cache[cache_key]
|
|
916
|
-
|
|
917
|
-
frame = inspect.currentframe()
|
|
918
994
|
try:
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
function = caller.function
|
|
927
|
-
location = f"{filename}:{lineno}:{function}"
|
|
928
|
-
|
|
929
|
-
# 缓存结果
|
|
930
|
-
self._location_cache[cache_key] = location
|
|
931
|
-
|
|
932
|
-
# 限制缓存大小
|
|
933
|
-
if len(self._location_cache) > self._cache_size:
|
|
934
|
-
# 清除最旧的缓存项
|
|
935
|
-
oldest_key = next(iter(self._location_cache))
|
|
936
|
-
del self._location_cache[oldest_key]
|
|
937
|
-
|
|
938
|
-
return location
|
|
939
|
-
else:
|
|
995
|
+
frame = inspect.currentframe()
|
|
996
|
+
if not frame:
|
|
997
|
+
return "未知位置"
|
|
998
|
+
caller = frame.f_back
|
|
999
|
+
if caller and caller.f_code.co_name == "log_with_location":
|
|
1000
|
+
caller = caller.f_back
|
|
1001
|
+
if not caller:
|
|
940
1002
|
return "未知位置"
|
|
1003
|
+
filename = caller.f_code.co_filename
|
|
1004
|
+
lineno = caller.f_lineno
|
|
1005
|
+
function = caller.f_code.co_name
|
|
1006
|
+
return f"{filename}:{lineno}:{function}"
|
|
1007
|
+
except Exception:
|
|
1008
|
+
return "未知位置"
|
|
941
1009
|
finally:
|
|
942
|
-
|
|
943
|
-
|
|
1010
|
+
try:
|
|
1011
|
+
del frame
|
|
1012
|
+
except Exception:
|
|
1013
|
+
pass
|
|
944
1014
|
|
|
945
1015
|
def log_with_location(self, level: str, message: str, include_location: bool = True):
|
|
946
1016
|
"""带位置信息的日志记录"""
|
|
1017
|
+
self._update_stats(level)
|
|
947
1018
|
if include_location:
|
|
948
1019
|
location = self.get_current_location()
|
|
949
1020
|
full_message = f"[{location}] {message}"
|
|
950
1021
|
else:
|
|
951
1022
|
full_message = message
|
|
952
1023
|
|
|
953
|
-
|
|
1024
|
+
logger_opt = self.logger.opt(depth=1)
|
|
1025
|
+
log_method = getattr(logger_opt, level.lower(), logger_opt.info)
|
|
954
1026
|
log_method(full_message)
|
|
955
1027
|
|
|
956
1028
|
def get_performance_stats(self) -> Dict[str, Any]:
|
|
@@ -1002,8 +1074,7 @@ class XmiLogger:
|
|
|
1002
1074
|
elif category:
|
|
1003
1075
|
self.log_with_category(level, message, category)
|
|
1004
1076
|
else:
|
|
1005
|
-
|
|
1006
|
-
log_method(message)
|
|
1077
|
+
self.log(level, message)
|
|
1007
1078
|
|
|
1008
1079
|
async def async_batch_log(self, logs: List[Dict[str, Any]]) -> None:
|
|
1009
1080
|
"""异步批量记录日志"""
|
|
@@ -1018,29 +1089,32 @@ class XmiLogger:
|
|
|
1018
1089
|
elif category:
|
|
1019
1090
|
self.log_with_category(level, message, category)
|
|
1020
1091
|
else:
|
|
1021
|
-
|
|
1022
|
-
log_method(message)
|
|
1092
|
+
self.log(level, message)
|
|
1023
1093
|
|
|
1024
1094
|
# 小延迟避免阻塞
|
|
1025
1095
|
await asyncio.sleep(0.001)
|
|
1026
1096
|
|
|
1027
1097
|
def log_with_context(self, level: str, message: str, context: Dict[str, Any] = None):
|
|
1028
1098
|
"""带上下文的日志记录"""
|
|
1099
|
+
self._update_stats(level)
|
|
1029
1100
|
if context:
|
|
1030
1101
|
context_str = " | ".join([f"{k}={v}" for k, v in context.items()])
|
|
1031
1102
|
full_message = f"{message} | {context_str}"
|
|
1032
1103
|
else:
|
|
1033
1104
|
full_message = message
|
|
1034
1105
|
|
|
1035
|
-
|
|
1106
|
+
logger_opt = self.logger.opt(depth=1)
|
|
1107
|
+
log_method = getattr(logger_opt, level.lower(), logger_opt.info)
|
|
1036
1108
|
log_method(full_message)
|
|
1037
1109
|
|
|
1038
1110
|
def log_with_timing(self, level: str, message: str, timing_data: Dict[str, float]):
|
|
1039
1111
|
"""带计时信息的日志记录"""
|
|
1112
|
+
self._update_stats(level)
|
|
1040
1113
|
timing_str = " | ".join([f"{k}={v:.3f}s" for k, v in timing_data.items()])
|
|
1041
1114
|
full_message = f"{message} | {timing_str}"
|
|
1042
1115
|
|
|
1043
|
-
|
|
1116
|
+
logger_opt = self.logger.opt(depth=1)
|
|
1117
|
+
log_method = getattr(logger_opt, level.lower(), logger_opt.info)
|
|
1044
1118
|
log_method(full_message)
|
|
1045
1119
|
|
|
1046
1120
|
def set_adaptive_level(self, error_rate_threshold: float = 0.1,
|
|
@@ -1075,8 +1149,7 @@ class XmiLogger:
|
|
|
1075
1149
|
def _update_logger_level(self) -> None:
|
|
1076
1150
|
"""更新日志记录器级别"""
|
|
1077
1151
|
try:
|
|
1078
|
-
|
|
1079
|
-
self.logger.remove()
|
|
1152
|
+
self._remove_handlers()
|
|
1080
1153
|
# 重新配置日志记录器
|
|
1081
1154
|
self.configure_logger()
|
|
1082
1155
|
except Exception as e:
|
|
@@ -1300,13 +1373,28 @@ class XmiLogger:
|
|
|
1300
1373
|
|
|
1301
1374
|
def cleanup(self) -> None:
|
|
1302
1375
|
"""清理资源"""
|
|
1303
|
-
# 如果有聚合器,停止
|
|
1304
1376
|
if hasattr(self, 'aggregator'):
|
|
1305
1377
|
self.aggregator.stop()
|
|
1306
|
-
|
|
1378
|
+
try:
|
|
1379
|
+
self._stop_remote_sender()
|
|
1380
|
+
except Exception:
|
|
1381
|
+
pass
|
|
1382
|
+
if self._exception_hook and self._prev_excepthook and sys.excepthook is self._exception_hook:
|
|
1383
|
+
try:
|
|
1384
|
+
sys.excepthook = self._prev_excepthook
|
|
1385
|
+
except Exception:
|
|
1386
|
+
pass
|
|
1387
|
+
if self._executor is not None:
|
|
1388
|
+
try:
|
|
1389
|
+
self._executor.shutdown(wait=False, cancel_futures=True)
|
|
1390
|
+
except Exception:
|
|
1391
|
+
try:
|
|
1392
|
+
self._executor.shutdown(wait=False)
|
|
1393
|
+
except Exception:
|
|
1394
|
+
pass
|
|
1395
|
+
self._executor = None
|
|
1396
|
+
self._remove_handlers()
|
|
1307
1397
|
self.clear_caches()
|
|
1308
|
-
# 强制垃圾回收
|
|
1309
1398
|
import gc
|
|
1310
1399
|
gc.collect()
|
|
1311
1400
|
print("XmiLogger 资源清理完成")
|
|
1312
|
-
|