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/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, Union, List, Tuple
17
+ from typing import Optional, Dict, Any, List, Tuple
18
18
 
19
- from functools import wraps, lru_cache
19
+ from functools import wraps
20
20
  from time import perf_counter
21
21
  from contextvars import ContextVar
22
- from concurrent.futures import ThreadPoolExecutor, as_completed
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._async_queue = asyncio.Queue() if remote_log_url else None
142
- self._remote_task = None
143
- if self._async_queue:
144
- self._start_remote_worker()
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._executor = ThreadPoolExecutor(max_workers=max_workers)
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] = [str(item) for item in v]
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.logger.remove()
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.setup_exception_handler()
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>:<cyan>{function}</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.logger.remove()
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
- log_method = getattr(self.logger, level.lower(), self.logger.info)
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
- log_method = getattr(self.logger, level.lower(), self.logger.info)
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 _start_remote_worker(self):
472
- """启动异步远程日志工作器"""
473
- async def remote_worker():
474
- while True:
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
- message = await self._async_queue.get()
477
- await self._send_to_remote_async(message)
478
- self._async_queue.task_done()
479
- except Exception as e:
480
- self.logger.error(f"远程日志工作器错误: {e}")
481
- await asyncio.sleep(0.1)
482
-
483
- self._remote_task = asyncio.create_task(remote_worker())
484
-
485
- async def _send_to_remote_async(self, message: Any) -> None:
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
- time_str = log_entry["time"].strftime("%Y-%m-%d %H:%M:%S")
491
- level_name = log_entry["level"].name
492
- request_id = log_entry["extra"].get("request_id", "no-request-id")
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
- if log_entry["file"]:
581
+ file_obj = log_entry.get("file")
582
+ if file_obj:
497
583
  try:
498
- file_path = os.path.basename(log_entry["file"].path)
499
- except (AttributeError, OSError):
500
- file_path = str(log_entry["file"])
501
-
502
- payload = {
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": level_name,
505
- "message": log_entry["message"],
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["line"],
508
- "function": log_entry["function"],
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._async_queue:
536
- asyncio.create_task(self._async_queue.put(message))
537
- else:
538
- self._executor.submit(self._send_to_remote, message)
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 _send_to_remote(self, message) -> None:
541
- """Send log message to remote server with retry logic.
542
-
543
- Args:
544
- message: Log message to send
545
-
546
- Returns:
547
- None
548
- """
549
- """
550
- 线程池中实际执行的远程日志发送逻辑。
551
- """
552
- log_entry = message.record
553
- payload = {
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 # seconds
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: # Last attempt
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
- return getattr(self.logger, level)
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
- total_time = (datetime.now() - self._stats_start_time).total_seconds()
819
- if total_time > 0:
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
- stack = inspect.stack()
921
- if len(stack) > 1:
922
- # 获取调用者的信息
923
- caller = stack[1]
924
- filename = caller.filename
925
- lineno = caller.lineno
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
- # 清理frame引用
943
- del frame
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
- log_method = getattr(self.logger, level.lower(), self.logger.info)
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
- log_method = getattr(self.logger, level.lower(), self.logger.info)
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
- log_method = getattr(self.logger, level.lower(), self.logger.info)
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
- log_method = getattr(self.logger, level.lower(), self.logger.info)
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
- log_method = getattr(self.logger, level.lower(), self.logger.info)
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
-