xmi-logger 0.0.6__py3-none-any.whl → 0.0.8__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
  # 如果配置失败,使用基本配置
@@ -315,7 +344,7 @@ class XmiLogger:
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,6 +417,7 @@ class XmiLogger:
383
417
  message: 日志消息
384
418
  tag: 标签名称
385
419
  """
420
+ self._update_stats(level)
386
421
  log_method = getattr(self.logger, level.lower(), self.logger.info)
387
422
  tagged_message = self._msg('LOG_TAGGED', tag=tag, message=message)
388
423
  log_method(tagged_message)
@@ -396,6 +431,7 @@ class XmiLogger:
396
431
  message: 日志消息
397
432
  category: 分类名称
398
433
  """
434
+ self._update_stats(level, category=category)
399
435
  log_method = getattr(self.logger, level.lower(), self.logger.info)
400
436
  categorized_message = self._msg('LOG_CATEGORY', category=category, message=message)
401
437
  log_method(categorized_message)
@@ -452,6 +488,9 @@ class XmiLogger:
452
488
  # 如果格式化失败,使用最基本的错误记录
453
489
  self.logger.opt(exception=True).error(f"未处理的异常: {exc_type.__name__}")
454
490
 
491
+ if self._prev_excepthook is None:
492
+ self._prev_excepthook = sys.excepthook
493
+ self._exception_hook = exception_handler
455
494
  sys.excepthook = exception_handler
456
495
 
457
496
  def _get_level_log_path(self, level_name):
@@ -468,117 +507,136 @@ class XmiLogger:
468
507
  log_file = f"{log_level}.log"
469
508
  return os.path.join(self.log_dir, log_file)
470
509
 
471
- def _start_remote_worker(self):
472
- """启动异步远程日志工作器"""
473
- async def remote_worker():
474
- while True:
510
+ def _start_remote_sender(self) -> None:
511
+ if self._remote_thread and self._remote_thread.is_alive():
512
+ return
513
+
514
+ def remote_thread_target():
515
+ loop = asyncio.new_event_loop()
516
+ asyncio.set_event_loop(loop)
517
+ queue = asyncio.Queue()
518
+
519
+ async def remote_worker():
520
+ connector = aiohttp.TCPConnector(limit=10, limit_per_host=5)
521
+ timeout = aiohttp.ClientTimeout(total=5)
522
+ session = aiohttp.ClientSession(connector=connector, timeout=timeout)
475
523
  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
- """异步发送日志到远程服务器,优化性能"""
524
+ while True:
525
+ payload = await queue.get()
526
+ if payload is None:
527
+ queue.task_done()
528
+ break
529
+ try:
530
+ await self._post_remote_payload(session, payload)
531
+ finally:
532
+ queue.task_done()
533
+ finally:
534
+ try:
535
+ await session.close()
536
+ except Exception:
537
+ pass
538
+ loop.stop()
539
+
540
+ self._remote_loop = loop
541
+ self._remote_queue = queue
542
+ self._remote_ready.set()
543
+ loop.create_task(remote_worker())
544
+ loop.run_forever()
545
+ try:
546
+ loop.close()
547
+ except Exception:
548
+ pass
549
+
550
+ self._remote_ready.clear()
551
+ self._remote_thread = threading.Thread(target=remote_thread_target, daemon=True)
552
+ self._remote_thread.start()
553
+ self._remote_ready.wait(timeout=2)
554
+
555
+ async def _post_remote_payload(self, session: aiohttp.ClientSession, payload: Dict[str, Any]) -> None:
556
+ for attempt in range(3):
557
+ try:
558
+ async with session.post(
559
+ self.remote_log_url,
560
+ json=payload,
561
+ headers={"Content-Type": "application/json"},
562
+ ) as response:
563
+ response.raise_for_status()
564
+ return
565
+ except Exception as e:
566
+ if attempt == 2:
567
+ self.logger.warning(self._msg('FAILED_REMOTE', error=f"最终尝试失败: {e}"))
568
+ return
569
+ await asyncio.sleep(1 * (attempt + 1))
570
+
571
+ def _build_remote_payload(self, message: Any) -> Dict[str, Any]:
487
572
  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
- # 优化文件路径处理
573
+ try:
574
+ time_str = log_entry["time"].strftime("%Y-%m-%d %H:%M:%S")
575
+ except Exception:
576
+ time_str = str(log_entry.get("time"))
577
+
495
578
  file_path = ""
496
- if log_entry["file"]:
579
+ file_obj = log_entry.get("file")
580
+ if file_obj:
497
581
  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 = {
582
+ file_path = os.path.basename(file_obj.path)
583
+ except Exception:
584
+ file_path = str(file_obj)
585
+
586
+ return {
503
587
  "time": time_str,
504
- "level": level_name,
505
- "message": log_entry["message"],
588
+ "level": getattr(log_entry.get("level"), "name", str(log_entry.get("level"))),
589
+ "message": log_entry.get("message", ""),
506
590
  "file": file_path,
507
- "line": log_entry["line"],
508
- "function": log_entry["function"],
509
- "request_id": request_id
591
+ "line": log_entry.get("line"),
592
+ "function": log_entry.get("function"),
593
+ "request_id": log_entry.get("extra", {}).get("request_id", "no-request-id"),
510
594
  }
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
-
595
+
533
596
  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)
597
+ payload = self._build_remote_payload(message)
598
+ if self._remote_loop and self._remote_queue and self._remote_ready.is_set():
599
+ try:
600
+ self._remote_loop.call_soon_threadsafe(self._remote_queue.put_nowait, payload)
601
+ return
602
+ except Exception:
603
+ pass
604
+ if self._executor is None:
605
+ self._executor = ThreadPoolExecutor(max_workers=self._max_workers)
606
+ self._executor.submit(self._send_payload_sync, payload)
539
607
 
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"}
608
+ def _stop_remote_sender(self) -> None:
609
+ loop = self._remote_loop
610
+ queue = self._remote_queue
611
+ thread = self._remote_thread
612
+ if loop and queue and thread and thread.is_alive():
613
+ try:
614
+ loop.call_soon_threadsafe(queue.put_nowait, None)
615
+ except Exception:
616
+ pass
617
+ thread.join(timeout=2)
618
+ self._remote_loop = None
619
+ self._remote_queue = None
620
+ self._remote_thread = None
621
+ self._remote_ready.clear()
563
622
 
623
+ def _send_payload_sync(self, payload: Dict[str, Any]) -> None:
624
+ headers = {"Content-Type": "application/json"}
564
625
  max_retries = 3
565
- retry_delay = 1 # seconds
566
-
626
+ retry_delay = 1
567
627
  for attempt in range(max_retries):
568
628
  try:
569
629
  response = requests.post(
570
630
  self.remote_log_url,
571
631
  headers=headers,
572
632
  json=payload,
573
- timeout=5
633
+ timeout=5,
574
634
  )
575
635
  response.raise_for_status()
576
636
  return
577
637
  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
- )
638
+ if attempt == max_retries - 1:
639
+ self.logger.warning(self._msg('FAILED_REMOTE', error=f"Final attempt failed: {e}"))
582
640
  else:
583
641
  time.sleep(retry_delay * (attempt + 1))
584
642
 
@@ -608,6 +666,34 @@ class XmiLogger:
608
666
  """
609
667
  return getattr(self.logger, level)
610
668
 
669
+ def log(self, level: str, message: str, *args, **kwargs):
670
+ self._update_stats(level)
671
+ return self.logger.log(level, message, *args, **kwargs)
672
+
673
+ def debug(self, message: str, *args, **kwargs):
674
+ self._update_stats("DEBUG")
675
+ return self.logger.debug(message, *args, **kwargs)
676
+
677
+ def info(self, message: str, *args, **kwargs):
678
+ self._update_stats("INFO")
679
+ return self.logger.info(message, *args, **kwargs)
680
+
681
+ def warning(self, message: str, *args, **kwargs):
682
+ self._update_stats("WARNING")
683
+ return self.logger.warning(message, *args, **kwargs)
684
+
685
+ def error(self, message: str, *args, **kwargs):
686
+ self._update_stats("ERROR")
687
+ return self.logger.error(message, *args, **kwargs)
688
+
689
+ def critical(self, message: str, *args, **kwargs):
690
+ self._update_stats("CRITICAL")
691
+ return self.logger.critical(message, *args, **kwargs)
692
+
693
+ def exception(self, message: str, *args, **kwargs):
694
+ self._update_stats("ERROR")
695
+ return self.logger.exception(message, *args, **kwargs)
696
+
611
697
  def log_decorator(self, msg: Optional[str] = None, level: str = "ERROR", trace: bool = True):
612
698
  """
613
699
  增强版日志装饰器,支持自定义日志级别和跟踪配置
@@ -815,9 +901,8 @@ class XmiLogger:
815
901
  self._stats['last_error_time'] = datetime.now()
816
902
 
817
903
  # 计算错误率
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
904
+ if self._stats['total'] > 0:
905
+ self._stats['error_rate'] = self._stats['error'] / self._stats['total']
821
906
 
822
907
  def get_stats(self) -> Dict[str, Any]:
823
908
  """获取详细的日志统计信息,优化性能"""
@@ -903,47 +988,30 @@ class XmiLogger:
903
988
 
904
989
  def get_current_location(self) -> str:
905
990
  """获取当前调用位置信息,优化性能"""
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
991
  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:
992
+ frame = inspect.currentframe()
993
+ if not frame:
994
+ return "未知位置"
995
+ caller = frame.f_back
996
+ if caller and caller.f_code.co_name == "log_with_location":
997
+ caller = caller.f_back
998
+ if not caller:
940
999
  return "未知位置"
1000
+ filename = caller.f_code.co_filename
1001
+ lineno = caller.f_lineno
1002
+ function = caller.f_code.co_name
1003
+ return f"{filename}:{lineno}:{function}"
1004
+ except Exception:
1005
+ return "未知位置"
941
1006
  finally:
942
- # 清理frame引用
943
- del frame
1007
+ try:
1008
+ del frame
1009
+ except Exception:
1010
+ pass
944
1011
 
945
1012
  def log_with_location(self, level: str, message: str, include_location: bool = True):
946
1013
  """带位置信息的日志记录"""
1014
+ self._update_stats(level)
947
1015
  if include_location:
948
1016
  location = self.get_current_location()
949
1017
  full_message = f"[{location}] {message}"
@@ -1002,8 +1070,7 @@ class XmiLogger:
1002
1070
  elif category:
1003
1071
  self.log_with_category(level, message, category)
1004
1072
  else:
1005
- log_method = getattr(self.logger, level.lower(), self.logger.info)
1006
- log_method(message)
1073
+ self.log(level, message)
1007
1074
 
1008
1075
  async def async_batch_log(self, logs: List[Dict[str, Any]]) -> None:
1009
1076
  """异步批量记录日志"""
@@ -1018,14 +1085,14 @@ class XmiLogger:
1018
1085
  elif category:
1019
1086
  self.log_with_category(level, message, category)
1020
1087
  else:
1021
- log_method = getattr(self.logger, level.lower(), self.logger.info)
1022
- log_method(message)
1088
+ self.log(level, message)
1023
1089
 
1024
1090
  # 小延迟避免阻塞
1025
1091
  await asyncio.sleep(0.001)
1026
1092
 
1027
1093
  def log_with_context(self, level: str, message: str, context: Dict[str, Any] = None):
1028
1094
  """带上下文的日志记录"""
1095
+ self._update_stats(level)
1029
1096
  if context:
1030
1097
  context_str = " | ".join([f"{k}={v}" for k, v in context.items()])
1031
1098
  full_message = f"{message} | {context_str}"
@@ -1037,6 +1104,7 @@ class XmiLogger:
1037
1104
 
1038
1105
  def log_with_timing(self, level: str, message: str, timing_data: Dict[str, float]):
1039
1106
  """带计时信息的日志记录"""
1107
+ self._update_stats(level)
1040
1108
  timing_str = " | ".join([f"{k}={v:.3f}s" for k, v in timing_data.items()])
1041
1109
  full_message = f"{message} | {timing_str}"
1042
1110
 
@@ -1075,8 +1143,7 @@ class XmiLogger:
1075
1143
  def _update_logger_level(self) -> None:
1076
1144
  """更新日志记录器级别"""
1077
1145
  try:
1078
- # 移除现有处理器
1079
- self.logger.remove()
1146
+ self._remove_handlers()
1080
1147
  # 重新配置日志记录器
1081
1148
  self.configure_logger()
1082
1149
  except Exception as e:
@@ -1300,13 +1367,28 @@ class XmiLogger:
1300
1367
 
1301
1368
  def cleanup(self) -> None:
1302
1369
  """清理资源"""
1303
- # 如果有聚合器,停止
1304
1370
  if hasattr(self, 'aggregator'):
1305
1371
  self.aggregator.stop()
1306
- # 清理缓存
1372
+ try:
1373
+ self._stop_remote_sender()
1374
+ except Exception:
1375
+ pass
1376
+ if self._exception_hook and self._prev_excepthook and sys.excepthook is self._exception_hook:
1377
+ try:
1378
+ sys.excepthook = self._prev_excepthook
1379
+ except Exception:
1380
+ pass
1381
+ if self._executor is not None:
1382
+ try:
1383
+ self._executor.shutdown(wait=False, cancel_futures=True)
1384
+ except Exception:
1385
+ try:
1386
+ self._executor.shutdown(wait=False)
1387
+ except Exception:
1388
+ pass
1389
+ self._executor = None
1390
+ self._remove_handlers()
1307
1391
  self.clear_caches()
1308
- # 强制垃圾回收
1309
1392
  import gc
1310
1393
  gc.collect()
1311
1394
  print("XmiLogger 资源清理完成")
1312
-