butterbot-python 3.1.0.dev1__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.
Files changed (79) hide show
  1. butterbot/__init__.py +15 -0
  2. butterbot/app/__init__.py +41 -0
  3. butterbot/app/bot_app.py +374 -0
  4. butterbot/app/config.py +156 -0
  5. butterbot/app/source_manager.py +367 -0
  6. butterbot/core/__init__.py +46 -0
  7. butterbot/core/api/__init__.py +9 -0
  8. butterbot/core/api/base_api.py +32 -0
  9. butterbot/core/context/README.md +1 -0
  10. butterbot/core/context/__init__.py +9 -0
  11. butterbot/core/context/api_registry.py +109 -0
  12. butterbot/core/context/app_context.py +51 -0
  13. butterbot/core/context/config_provider.py +14 -0
  14. butterbot/core/data/__init__.py +15 -0
  15. butterbot/core/data/base_data.py +29 -0
  16. butterbot/core/data/base_model.py +197 -0
  17. butterbot/core/event/README.md +1 -0
  18. butterbot/core/event/__init__.py +10 -0
  19. butterbot/core/event/event.py +27 -0
  20. butterbot/core/event/event_bus.py +276 -0
  21. butterbot/core/event/subscriber.py +130 -0
  22. butterbot/core/exceptions.py +90 -0
  23. butterbot/core/filter/__init__.py +11 -0
  24. butterbot/core/filter/base_filter.py +74 -0
  25. butterbot/core/source/README.md +1 -0
  26. butterbot/core/source/__init__.py +9 -0
  27. butterbot/core/source/base_source.py +134 -0
  28. butterbot/core/types/__init__.py +9 -0
  29. butterbot/core/types/base_type.py +74 -0
  30. butterbot/sources/__init__.py +1 -0
  31. butterbot/sources/bilibili/README.md +10 -0
  32. butterbot/sources/bilibili/__init__.py +20 -0
  33. butterbot/sources/bilibili/api/__init__.py +5 -0
  34. butterbot/sources/bilibili/api/bili_api.py +123 -0
  35. butterbot/sources/bilibili/data/__init__.py +52 -0
  36. butterbot/sources/bilibili/data/danmaku_gift_data.py +135 -0
  37. butterbot/sources/bilibili/data/danmaku_guard_data.py +54 -0
  38. butterbot/sources/bilibili/data/danmaku_msg_data.py +71 -0
  39. butterbot/sources/bilibili/data/dto/__init__.py +59 -0
  40. butterbot/sources/bilibili/data/dto/danmaku_gift_dto.py +193 -0
  41. butterbot/sources/bilibili/data/dto/danmaku_guard_buy_dto.py +54 -0
  42. butterbot/sources/bilibili/data/dto/danmaku_msg_dto.py +123 -0
  43. butterbot/sources/bilibili/data/dto/dynamic_dto.py +276 -0
  44. butterbot/sources/bilibili/data/dto/live_room_dto.py +169 -0
  45. butterbot/sources/bilibili/data/dto/video_part_dto.py +18 -0
  46. butterbot/sources/bilibili/data/dynamic_data.py +362 -0
  47. butterbot/sources/bilibili/data/live_room_data.py +162 -0
  48. butterbot/sources/bilibili/data/video_part.py +46 -0
  49. butterbot/sources/bilibili/source/__init__.py +15 -0
  50. butterbot/sources/bilibili/source/base_polling_source.py +130 -0
  51. butterbot/sources/bilibili/source/bili_danmaku_source.py +230 -0
  52. butterbot/sources/bilibili/source/bili_dynamic_source.py +135 -0
  53. butterbot/sources/bilibili/source/bili_live_source.py +137 -0
  54. butterbot/sources/bilibili/types/__init__.py +11 -0
  55. butterbot/sources/bilibili/types/bili_type.py +32 -0
  56. butterbot/sources/napcat/README.md +10 -0
  57. butterbot/sources/napcat/__init__.py +20 -0
  58. butterbot/sources/napcat/api/__init__.py +3 -0
  59. butterbot/sources/napcat/api/napcat_api.py +316 -0
  60. butterbot/sources/napcat/data/__init__.py +179 -0
  61. butterbot/sources/napcat/data/event_data.py +441 -0
  62. butterbot/sources/napcat/data/segment_data.py +432 -0
  63. butterbot/sources/napcat/events.py +91 -0
  64. butterbot/sources/napcat/filters/__init__.py +19 -0
  65. butterbot/sources/napcat/filters/filters.py +202 -0
  66. butterbot/sources/napcat/source/__init__.py +5 -0
  67. butterbot/sources/napcat/source/napcat_source.py +58 -0
  68. butterbot/sources/napcat/types/__init__.py +5 -0
  69. butterbot/sources/napcat/types/napcat_type.py +61 -0
  70. butterbot/utils/README.md +15 -0
  71. butterbot/utils/__init__.py +11 -0
  72. butterbot/utils/data_pair.py +27 -0
  73. butterbot/utils/logging_config.py +521 -0
  74. butterbot/utils/terminal.py +308 -0
  75. butterbot/utils/websocket.py +1270 -0
  76. butterbot_python-3.1.0.dev1.dist-info/METADATA +769 -0
  77. butterbot_python-3.1.0.dev1.dist-info/RECORD +79 -0
  78. butterbot_python-3.1.0.dev1.dist-info/WHEEL +4 -0
  79. butterbot_python-3.1.0.dev1.dist-info/licenses/LICENSE +674 -0
@@ -0,0 +1,521 @@
1
+ # -------------------------
2
+ # @Author : Fish-LP fish.zh@outlook.com
3
+ # @Date : 2025-02-12 13:41:02
4
+ # @LastEditors : Fish-LP fish.zh@outlook.com
5
+ # @LastEditTime : 2025-06-22 21:51:42
6
+ # @Description : 日志格式化
7
+ # @Copyright (c) 2025 by Fish-LP, MIT 使用许可协议
8
+ # -------------------------
9
+ import json
10
+ import logging
11
+ import os
12
+ import warnings
13
+ from logging.handlers import TimedRotatingFileHandler
14
+ from pathlib import Path
15
+
16
+ from .terminal import Color, set_console_mode
17
+
18
+ # 尝试从 tqdm 导入进度条类,不强制依赖
19
+ try:
20
+ from tqdm import tqdm as tqdm_original # type: ignore[import-untyped]
21
+ except ImportError:
22
+ tqdm_original = None
23
+
24
+ __author__ = "Fish-LP <Fish.zh@outlook.com>"
25
+ __status__ = "dev"
26
+ __version__ = "2.1.1-dev"
27
+
28
+ # NOTE: 这里保存的是针对不同目标(console/file)和不同日志级别的消息格式模板
29
+ LOG_MESSAGE_FORMATS = {
30
+ "console": {
31
+ "DEBUG": (
32
+ Color.CYAN
33
+ + "[%(asctime)s.%(msecs)s]"
34
+ + Color.RESET
35
+ + " "
36
+ + Color.BLUE
37
+ + "%(colored_levelname)-8s"
38
+ + Color.RESET
39
+ + " "
40
+ + Color.GRAY
41
+ + "[%(threadName)s|%(processName)s]"
42
+ + Color.RESET
43
+ + " "
44
+ + Color.MAGENTA
45
+ + "%(name)s"
46
+ + Color.RESET
47
+ + " "
48
+ + Color.YELLOW
49
+ + "%(filename)s:%(lineno)d %(funcName)s"
50
+ + Color.RESET
51
+ + " "
52
+ + Color.RESET
53
+ + "| %(message)s"
54
+ + Color.RESET
55
+ ),
56
+ "INFO": (
57
+ Color.CYAN
58
+ + "[%(asctime)s]"
59
+ + Color.RESET
60
+ + " "
61
+ + Color.GREEN
62
+ + "%(colored_levelname)-8s"
63
+ + Color.RESET
64
+ + " "
65
+ + Color.MAGENTA
66
+ + "%(name)s"
67
+ + Color.RESET
68
+ + " ➜ "
69
+ + Color.RESET
70
+ + "%(message)s"
71
+ + Color.RESET
72
+ ),
73
+ "WARNING": (
74
+ Color.CYAN
75
+ + "[%(asctime)s]"
76
+ + Color.RESET
77
+ + " "
78
+ + Color.YELLOW
79
+ + "%(colored_levelname)-8s"
80
+ + Color.RESET
81
+ + " "
82
+ + Color.MAGENTA
83
+ + "%(name)s"
84
+ + Color.RESET
85
+ + " "
86
+ + Color.YELLOW
87
+ + "➜"
88
+ + Color.RESET
89
+ + " "
90
+ + Color.RESET
91
+ + "%(message)s"
92
+ + Color.RESET
93
+ ),
94
+ "ERROR": (
95
+ Color.CYAN
96
+ + "[%(asctime)s]"
97
+ + Color.RESET
98
+ + " "
99
+ + Color.RED
100
+ + "%(colored_levelname)-8s"
101
+ + Color.RESET
102
+ + " "
103
+ + Color.GRAY
104
+ + "[%(filename)s]"
105
+ + Color.RESET
106
+ + Color.MAGENTA
107
+ + "%(name)s:%(lineno)d"
108
+ + Color.RESET
109
+ + " "
110
+ + Color.RED
111
+ + "➜"
112
+ + Color.RESET
113
+ + " "
114
+ + Color.RESET
115
+ + "%(message)s"
116
+ + Color.RESET
117
+ ),
118
+ "CRITICAL": (
119
+ Color.CYAN
120
+ + "[%(asctime)s]"
121
+ + Color.RESET
122
+ + " "
123
+ + Color.RED
124
+ + Color.BOLD
125
+ + "%(colored_levelname)-8s"
126
+ + Color.RESET
127
+ + " "
128
+ + Color.GRAY
129
+ + "{%(module)s}"
130
+ + Color.RESET
131
+ + Color.MAGENTA
132
+ + "[%(filename)s]"
133
+ + Color.RESET
134
+ + Color.MAGENTA
135
+ + "%(name)s:%(lineno)d"
136
+ + Color.RESET
137
+ + " "
138
+ + Color.RED
139
+ + "➜"
140
+ + Color.RESET
141
+ + " "
142
+ + Color.RESET
143
+ + "%(message)s"
144
+ + Color.RESET
145
+ ),
146
+ },
147
+ "file": {
148
+ "DEBUG": "[%(asctime)s] %(levelname)-8s [%(threadName)s|%(processName)s] %(name)s (%(filename)s:%(funcName)s:%(lineno)d) | %(message)s",
149
+ "INFO": "[%(asctime)s] %(levelname)-8s %(name)s ➜ %(message)s",
150
+ "WARNING": "[%(asctime)s] %(levelname)-8s %(name)s ➜ %(message)s",
151
+ "ERROR": "[%(asctime)s] %(levelname)-8s [%(filename)s]%(name)s:%(lineno)d ➜ %(message)s",
152
+ "CRITICAL": "[%(asctime)s] %(levelname)-8s {%(module)s}[%(filename)s]%(name)s:%(lineno)d ➜ %(message)s",
153
+ },
154
+ }
155
+
156
+ if tqdm_original is not None:
157
+
158
+ class tqdm(tqdm_original):
159
+ """
160
+ 自定义 tqdm 类的初始化方法
161
+ 通过设置默认参数,确保每次创建 tqdm 进度条时都能应用统一的风格
162
+
163
+ 参数说明:
164
+ :param args: 原生 tqdm 支持的非关键字参数(如可迭代对象等)
165
+ :param kwargs: 原生 tqdm 支持的关键字参数,用于自定义进度条的行为和外观
166
+ - bar_format (str): 进度条的格式化字符串
167
+ - ncols (int): 进度条的宽度(以字符为单位)
168
+ - colour (str): 进度条的颜色
169
+ - desc (str): 进度条的描述信息
170
+ - unit (str): 进度条的单位
171
+ - leave (bool): 进度条完成后是否保留显示
172
+ """
173
+
174
+ _STYLE_MAP = {
175
+ "BLACK": Color.BLACK,
176
+ "RED": Color.RED,
177
+ "GREEN": Color.GREEN,
178
+ "YELLOW": Color.YELLOW,
179
+ "BLUE": Color.BLUE,
180
+ "MAGENTA": Color.MAGENTA,
181
+ "CYAN": Color.CYAN,
182
+ "WHITE": Color.WHITE,
183
+ }
184
+
185
+ def __init__(self, *args, **kwargs):
186
+ # 保存颜色参数以便后续处理
187
+ self._custom_colour = kwargs.get("colour", "GREEN")
188
+
189
+ # 设置默认进度条格式
190
+ kwargs.setdefault(
191
+ "bar_format",
192
+ (
193
+ Color.CYAN
194
+ + "{desc}"
195
+ + Color.RESET
196
+ + " "
197
+ + Color.WHITE
198
+ + "{percentage:3.0f}%"
199
+ + Color.RESET
200
+ + " "
201
+ + Color.GRAY
202
+ + "[{n_fmt}]"
203
+ + Color.RESET
204
+ + Color.WHITE
205
+ + "|{bar:20}|"
206
+ + Color.RESET
207
+ + Color.BLUE
208
+ + "[{elapsed}]"
209
+ + Color.RESET
210
+ ),
211
+ )
212
+ kwargs.setdefault("ncols", 80)
213
+ kwargs.setdefault("colour", None) # 避免基类处理颜色
214
+
215
+ super().__init__(*args, **kwargs)
216
+
217
+ # 在初始化完成后应用颜色
218
+ self.colour = self._custom_colour
219
+
220
+ @property
221
+ def colour(self):
222
+ return self._colour
223
+
224
+ @colour.setter
225
+ def colour(self, color):
226
+ # 确保颜色值有效
227
+ if not color:
228
+ color = "GREEN"
229
+
230
+ color_upper = color.upper()
231
+ valid_color = self._STYLE_MAP.get(color_upper, "GREEN")
232
+
233
+ # 保存颜色值
234
+ self._colour = color_upper
235
+
236
+ # 更新描述信息颜色
237
+ if hasattr(self, "GREEN") and self.desc:
238
+ self.desc = "%s%s%s" % (
239
+ getattr(Color, valid_color),
240
+ self.desc,
241
+ Color.RESET,
242
+ )
243
+
244
+
245
+ # 日志级别颜色映射
246
+ LOG_LEVEL_TO_COLOR = {
247
+ "DEBUG": Color.CYAN,
248
+ "INFO": Color.GREEN,
249
+ "WARNING": Color.YELLOW,
250
+ "ERROR": Color.RED,
251
+ "CRITICAL": Color.MAGENTA,
252
+ }
253
+
254
+ _managed_handlers: list[tuple[logging.Logger, logging.Handler]] = []
255
+ _redirect_logger_state: dict[logging.Logger, tuple[int, bool]] = {}
256
+
257
+
258
+ # 定义动态格式化器,根据日志级别选择不同的格式
259
+ class DynamicFormatter(logging.Formatter):
260
+ """根据日志记录级别动态选择格式的格式化器"""
261
+
262
+ def __init__(
263
+ self, fmt_dict: dict, datefmt: str | None = None, use_color: bool = True
264
+ ):
265
+ """
266
+ 初始化动态格式化器
267
+
268
+ Args:
269
+ fmt_dict: 包含不同日志级别格式字符串的字典,键为级别名称(如"DEBUG")
270
+ datefmt: 日期时间格式字符串
271
+ use_color: 是否使用颜色
272
+ """
273
+ super().__init__(datefmt=datefmt)
274
+ self.fmt_dict = fmt_dict
275
+ self.use_color = use_color
276
+
277
+ # 为每个级别预创建Formatter实例,提高性能
278
+ self._formatters = {}
279
+ for level_name, fmt in fmt_dict.items():
280
+ self._formatters[level_name] = logging.Formatter(fmt, datefmt=datefmt)
281
+
282
+ # 默认格式(使用第一个可用的格式)
283
+ self._default_formatter = list(self._formatters.values())[0]
284
+
285
+ def format(self, record: logging.LogRecord) -> str:
286
+ """格式化日志记录,根据记录级别选择对应的格式"""
287
+ # 动态颜色处理
288
+ if self.use_color:
289
+ record.colored_levelname = "%s%8s%s" % (
290
+ LOG_LEVEL_TO_COLOR.get(record.levelname, Color.RESET),
291
+ record.levelname,
292
+ Color.RESET,
293
+ )
294
+ # 添加统一颜色字段
295
+ record.colored_name = "%s%s%s" % (Color.MAGENTA, record.name, Color.RESET)
296
+ # 添加毫秒信息用于ERROR级别格式
297
+ else:
298
+ record.colored_levelname = record.levelname
299
+ record.colored_name = record.name
300
+
301
+ # 根据记录级别选择格式
302
+ level_name = record.levelname
303
+ formatter = self._formatters.get(level_name, self._default_formatter)
304
+
305
+ try:
306
+ return formatter.format(record)
307
+ except Exception as e:
308
+ warnings.warn("日志格式化错误: %s" % e)
309
+ # 使用默认格式作为备选
310
+ return self._default_formatter.format(record)
311
+
312
+
313
+ def _get_valid_log_level(level_name: str, default: str) -> int:
314
+ """验证并获取有效的日志级别"""
315
+ level = getattr(logging, level_name.upper(), None)
316
+ if not isinstance(level, int):
317
+ warnings.warn(
318
+ "Invalid log level: %s, using %s instead." % (level_name, default)
319
+ )
320
+ return getattr(logging, default)
321
+ return level
322
+
323
+
324
+ def _resolve_log_file(log_dir: Path, file_name: str) -> Path:
325
+ """解析日志文件路径,并阻止文件逃逸出配置目录."""
326
+ if not file_name or Path(file_name).is_absolute():
327
+ raise ValueError("日志文件名必须是 LOG_FILE_PATH 下的非空相对路径")
328
+
329
+ resolved_dir = log_dir.resolve()
330
+ resolved_file = (resolved_dir / file_name).resolve()
331
+ try:
332
+ resolved_file.relative_to(resolved_dir)
333
+ except ValueError as exc:
334
+ raise ValueError("日志文件必须位于 LOG_FILE_PATH 配置目录内") from exc
335
+ if resolved_file == resolved_dir:
336
+ raise ValueError("日志文件名不能指向 LOG_FILE_PATH 目录本身")
337
+ return resolved_file
338
+
339
+
340
+ def _load_redirect_rules() -> dict[str, str]:
341
+ """读取并校验日志重定向规则."""
342
+ redirect_rules_json = os.getenv("LOG_REDIRECT_RULES", "{}")
343
+ try:
344
+ raw_rules = json.loads(redirect_rules_json)
345
+ except json.JSONDecodeError:
346
+ warnings.warn("LOG_REDIRECT_RULES 不是有效的 JSON,将忽略重定向规则")
347
+ return {}
348
+
349
+ if not isinstance(raw_rules, dict):
350
+ warnings.warn("LOG_REDIRECT_RULES 必须是记录器名称到文件名的对象")
351
+ return {}
352
+
353
+ redirect_rules: dict[str, str] = {}
354
+ for logger_name, file_name in raw_rules.items():
355
+ if not isinstance(logger_name, str) or not logger_name:
356
+ raise ValueError("日志重定向规则中的记录器名称必须是非空字符串")
357
+ if not isinstance(file_name, str):
358
+ raise ValueError("日志重定向规则中的日志文件名必须是字符串")
359
+ redirect_rules[logger_name] = file_name
360
+ return redirect_rules
361
+
362
+
363
+ def _clear_managed_handlers() -> None:
364
+ """移除并关闭上一次初始化创建的 handler."""
365
+ for logger, handler in _managed_handlers:
366
+ logger.removeHandler(handler)
367
+ handler.close()
368
+ _managed_handlers.clear()
369
+
370
+ for logger, (level, propagate) in _redirect_logger_state.items():
371
+ logger.setLevel(level)
372
+ logger.propagate = propagate
373
+ _redirect_logger_state.clear()
374
+
375
+
376
+ def setup_logging(console_level: str | None = None) -> None:
377
+ """显式设置日志系统,支持根据记录器名称重定向到不同文件."""
378
+ # 环境变量读取
379
+ console_level = console_level or os.getenv("LOG_LEVEL", "INFO").upper()
380
+ file_level = os.getenv("FILE_LOG_LEVEL", "DEBUG").upper()
381
+
382
+ # 验证并转换日志级别
383
+ console_log_level = _get_valid_log_level(console_level, "INFO")
384
+ file_log_level = _get_valid_log_level(file_level, "DEBUG")
385
+
386
+ # 文件路径配置 - 使用固定名称,不使用日期
387
+ log_dir = os.getenv("LOG_FILE_PATH", "./logs")
388
+ file_name = os.getenv("LOG_FILE_NAME", "bot.log") # 改为固定名称
389
+
390
+ # 备份数量验证
391
+ try:
392
+ backup_count = int(os.getenv("BACKUP_COUNT", "7"))
393
+ if backup_count < 0:
394
+ raise ValueError
395
+ except ValueError:
396
+ backup_count = 7
397
+ warnings.warn("BACKUP_COUNT 必须是非负整数,将使用默认值 7")
398
+
399
+ # 在修改现有日志配置前完成全部路径校验。
400
+ log_dir_path = Path(log_dir)
401
+ root_file_path = _resolve_log_file(log_dir_path, file_name)
402
+ redirect_rules = _load_redirect_rules()
403
+ redirect_paths = {
404
+ logger_name: _resolve_log_file(log_dir_path, redirect_file_name)
405
+ for logger_name, redirect_file_name in redirect_rules.items()
406
+ }
407
+
408
+ # ===== 1. 配置根记录器 =====
409
+ root_logger = logging.getLogger()
410
+
411
+ # 先创建完整的新 handler 集合;失败时保留当前日志配置。
412
+ file_formatter = DynamicFormatter(
413
+ fmt_dict=LOG_MESSAGE_FORMATS["file"],
414
+ datefmt="%Y-%m-%d %H:%M:%S",
415
+ use_color=False,
416
+ )
417
+ console_handler: logging.StreamHandler | None = None
418
+ root_file_handler: TimedRotatingFileHandler | None = None
419
+ redirect_handlers: dict[str, TimedRotatingFileHandler] = {}
420
+ try:
421
+ for path in {root_file_path, *redirect_paths.values()}:
422
+ path.parent.mkdir(parents=True, exist_ok=True)
423
+
424
+ set_console_mode()
425
+ console_handler = logging.StreamHandler()
426
+ console_handler.setLevel(console_log_level)
427
+ console_handler.setFormatter(
428
+ DynamicFormatter(
429
+ fmt_dict=LOG_MESSAGE_FORMATS["console"],
430
+ datefmt="%H:%M:%S",
431
+ use_color=True,
432
+ )
433
+ )
434
+
435
+ root_file_handler = TimedRotatingFileHandler(
436
+ filename=root_file_path,
437
+ when="midnight",
438
+ interval=1,
439
+ backupCount=backup_count,
440
+ encoding="utf-8",
441
+ utc=True,
442
+ )
443
+ root_file_handler.setLevel(file_log_level)
444
+ root_file_handler.setFormatter(file_formatter)
445
+
446
+ for logger_name, redirect_file_path in redirect_paths.items():
447
+ file_handler = TimedRotatingFileHandler(
448
+ filename=redirect_file_path,
449
+ when="midnight",
450
+ interval=1,
451
+ backupCount=backup_count,
452
+ encoding="utf-8",
453
+ utc=True,
454
+ )
455
+ file_handler.setLevel(file_log_level)
456
+ file_handler.setFormatter(file_formatter)
457
+ redirect_handlers[logger_name] = file_handler
458
+ except Exception:
459
+ if console_handler is not None:
460
+ console_handler.close()
461
+ if root_file_handler is not None:
462
+ root_file_handler.close()
463
+ for file_handler in redirect_handlers.values():
464
+ file_handler.close()
465
+ raise
466
+
467
+ assert console_handler is not None
468
+ assert root_file_handler is not None
469
+
470
+ # 所有新 handler 创建成功后再替换上一次由本模块管理的配置。
471
+ _clear_managed_handlers()
472
+ root_logger.setLevel(logging.DEBUG)
473
+ root_logger.handlers = [console_handler, root_file_handler]
474
+ _managed_handlers.extend(
475
+ [(root_logger, console_handler), (root_logger, root_file_handler)]
476
+ )
477
+
478
+ for logger_name, file_handler in redirect_handlers.items():
479
+ logger = logging.getLogger(logger_name)
480
+ _redirect_logger_state[logger] = (logger.level, logger.propagate)
481
+ logger.setLevel(file_log_level)
482
+ logger.addHandler(file_handler)
483
+ logger.propagate = False
484
+ _managed_handlers.append((logger, file_handler))
485
+
486
+
487
+ def get_log(name="Logger"):
488
+ """
489
+ 获取日志记录器
490
+ """
491
+ warnings.warn(
492
+ "The 'get_log' method is deprecated, use 'logging.getLogger' instead",
493
+ DeprecationWarning,
494
+ 2,
495
+ )
496
+ return logging.getLogger(name)
497
+
498
+
499
+ # 示例用法
500
+ if __name__ == "__main__":
501
+ # 获取不同记录器的日志
502
+ root_logger = logging.getLogger()
503
+ db_logger = logging.getLogger("database")
504
+ net_logger = logging.getLogger("network")
505
+ sec_logger = logging.getLogger("security")
506
+ setup_logging("DEBUG")
507
+
508
+ print("测试不同级别的日志输出(使用动态格式):")
509
+ root_logger.debug("根记录器调试信息")
510
+ root_logger.info("根记录器普通信息")
511
+ root_logger.warning("根记录器警告信息")
512
+ net_logger.error("网络错误: 连接超时")
513
+ sec_logger.critical("安全警报: 检测到异常登录尝试")
514
+
515
+ # 测试不同格式的差异
516
+ print("\n测试不同日志级别的格式差异:")
517
+ root_logger.debug("调试信息 - 包含文件名、行号和函数名")
518
+ root_logger.info("普通信息 - 简洁格式")
519
+ root_logger.warning("警告信息 - 带警告符号")
520
+ root_logger.error("错误信息 - 包含文件名和行号")
521
+ root_logger.critical("严重错误 - 包含模块名和文件名")