himile-python-log 0.3.0__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: himile_python_log
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: A HIMILE utils package
5
5
  Author-email: qhw <20244111@himile.net>
6
6
  Requires-Python: >=3.10
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "himile_python_log"
7
- version = "0.3.0"
7
+ version = "0.4.0"
8
8
  authors = [{ name = "qhw", email = "20244111@himile.net" }]
9
9
  description = "A HIMILE utils package"
10
10
  readme = "README.md"
@@ -0,0 +1,159 @@
1
+ import sys
2
+ import uuid
3
+ from contextvars import ContextVar
4
+ from pathlib import Path
5
+
6
+ from loguru import logger
7
+ from loguru._logger import Logger as _LoguruLogger
8
+
9
+ _trace_id_var: ContextVar[str] = ContextVar("trace_id", default="")
10
+
11
+
12
+ def set_trace_id(trace_id: str = "") -> str:
13
+ """设置当前上下文的 trace_id。为空时自动生成。"""
14
+ if not trace_id:
15
+ trace_id = str(uuid.uuid4())[:8]
16
+ _trace_id_var.set(trace_id)
17
+ return trace_id
18
+
19
+
20
+ def get_trace_id() -> str:
21
+ """获取当前上下文的 trace_id。"""
22
+ return _trace_id_var.get()
23
+
24
+
25
+ def _patch_record(record):
26
+ """patcher: 自动将上下文中的 trace_id 注入 extra。"""
27
+ record["extra"]["trace_id"] = _trace_id_var.get()
28
+
29
+
30
+ class LogWrapper:
31
+ def __init__(self, logs_dir: str = "logs", log_level: str = "DEBUG", appid: str = ""):
32
+ self.logger = logger
33
+ self.logger.remove()
34
+
35
+ appid_segment = f"{appid} | " if appid else ""
36
+ appid_file_segment = f"{appid} | " if appid else ""
37
+
38
+ Path(logs_dir).mkdir(parents=True, exist_ok=True)
39
+
40
+ # 打包成 exe 后(尤其 --noconsole / windowed 模式)sys.stdout 可能是 None,
41
+ # 直接 add(sys.stdout, ...) 会报错,这里判断存在才添加控制台输出。
42
+ if sys.stdout is not None:
43
+ self.logger.add(
44
+ sys.stdout,
45
+ format=(
46
+ "<green>{time:YYYYMMDD HH:mm:ss.SSS}</green> | "
47
+ + appid_segment +
48
+ "{process.name} | "
49
+ "{thread.name} | "
50
+ "<cyan>{module}</cyan>.<cyan>{function}</cyan>:"
51
+ "<cyan>{line}</cyan> | "
52
+ "<level>{level}</level>: "
53
+ "<level>{message}</level> | "
54
+ "{extra[trace_id]}"
55
+ ),
56
+ level=log_level
57
+ )
58
+
59
+ log_file_path = Path(logs_dir) / "{time:YYYY-MM-DD}.log"
60
+ self.logger.add(
61
+ log_file_path,
62
+ level=log_level,
63
+ format=(
64
+ "{time:YYYYMMDD HH:mm:ss.SSS} | "
65
+ + appid_file_segment +
66
+ "{process.name} | "
67
+ "{thread.name} | "
68
+ "{module}.{function}:{line} | {level} | {message} | "
69
+ "{extra[trace_id]}"
70
+ ),
71
+ rotation="500 MB",
72
+ encoding="utf-8"
73
+ )
74
+
75
+ self.logger.configure(patcher=_patch_record)
76
+
77
+ def get_logger(self):
78
+ return PrintfLoggerProxy(self.logger)
79
+
80
+
81
+ # ---------------------------------------------------------------------------
82
+ # printf-style 兼容层
83
+ #
84
+ # 历史代码大量使用 Python logging 风格:logger.info("%s xxx", arg, ...),
85
+ # 以及 exc_info=True 参数。但 loguru 只认 {} 占位符,%s 会原样打印、
86
+ # 参数被丢弃。这里做一层薄代理,统一把 printf 风格 / exc_info 转换成 loguru
87
+ # 原生调用,避免逐个改造几十处调用点。
88
+ # ---------------------------------------------------------------------------
89
+ _PRINTF_LEVELS = ("trace", "debug", "info", "success", "warning", "error", "critical")
90
+
91
+
92
+ def _copy_printf_kwargs(kwargs):
93
+ """取出 logging 风格参数并拷贝,避免污染调用点传入的原始 kwargs。"""
94
+ out = dict(kwargs)
95
+
96
+ # logging 风格 exc_info:True / Exception / (type, value, tb)。
97
+ # 为 False 或 None 时移除(loguru 会把任何非空值当作 traceback 打印)。
98
+ exc_info = out.pop("exc_info", None)
99
+ if exc_info is False or exc_info is None:
100
+ pass
101
+ elif exc_info is True:
102
+ out["exc_info"] = None # loguru 的 "自动捕获当前异常"
103
+ else:
104
+ out["exc_info"] = exc_info # 直接传异常/traceback 三元组给 loguru
105
+ return out
106
+
107
+
108
+ class PrintfLoggerProxy:
109
+ """兼容 printf 风格(%s/%d/%r + 位置参数)与 exc_info 的 loguru 代理。
110
+
111
+ 优先包装原生 loguru 方法,仅针对默认/带 extra 的日志方法做参数转换,
112
+ 其它 loguru 特性(bind / with / catch / opt / level 等)透传到原生 logger。
113
+ """
114
+
115
+ def __init__(self, loguru_logger: _LoguruLogger):
116
+ object.__setattr__(self, "_loguru", loguru_logger)
117
+ self._set_level_methods()
118
+
119
+ def _set_level_methods(self):
120
+ # opt(depth=1):让 loguru 记录"业务调用点"的 module/function/line,
121
+ # 而不是本代理里 _log 这一帧,否则所有日志都会显示成 himile_log._log。
122
+ opt_logger = self._loguru.opt(depth=1)
123
+ for level in _PRINTF_LEVELS:
124
+ native = getattr(opt_logger, level)
125
+ setattr(self, level, self._wrap_level(native))
126
+
127
+ def _wrap_level(self, native):
128
+ def _log(message, *args, **kwargs):
129
+ # 优先兼容 logging 风格:格式串含 %s/%d 占位符且有位置参数
130
+ if args and isinstance(message, str) and "%" in message:
131
+ message = message % args
132
+ args = ()
133
+ return native(message, *args, **_copy_printf_kwargs(kwargs))
134
+
135
+ return _log
136
+
137
+ def __getattr__(self, name):
138
+ # 未显式包装的属性(bind / with / catch / opt ...)透传到原生 logger
139
+ return getattr(self._loguru, name)
140
+
141
+ def __setattr__(self, name, value):
142
+ try:
143
+ object.__setattr__(self, name, value)
144
+ except AttributeError:
145
+ setattr(self._loguru, name, value)
146
+
147
+
148
+ default_logger = LogWrapper()
149
+ logger = default_logger.get_logger()
150
+
151
+
152
+ def setup_logger(**kwargs) -> LogWrapper:
153
+ """Create a new logger instance with custom configuration."""
154
+ return LogWrapper(**kwargs)
155
+
156
+
157
+ def get_default_logger():
158
+ """Get the default logger instance."""
159
+ return logger
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: himile_python_log
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: A HIMILE utils package
5
5
  Author-email: qhw <20244111@himile.net>
6
6
  Requires-Python: >=3.10