unitlog 0.0.3__py3-none-any.whl → 0.0.6__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.
- unitlog/__init__.py +16 -1
- unitlog/handlers.py +7 -4
- unitlog/unit.py +175 -11
- {unitlog-0.0.3.dist-info → unitlog-0.0.6.dist-info}/METADATA +10 -2
- unitlog-0.0.6.dist-info/RECORD +9 -0
- {unitlog-0.0.3.dist-info → unitlog-0.0.6.dist-info}/WHEEL +1 -1
- unitlog-0.0.3.dist-info/RECORD +0 -9
- {unitlog-0.0.3.dist-info → unitlog-0.0.6.dist-info/licenses}/LICENSE +0 -0
- {unitlog-0.0.3.dist-info → unitlog-0.0.6.dist-info}/top_level.txt +0 -0
unitlog/__init__.py
CHANGED
|
@@ -1 +1,16 @@
|
|
|
1
|
-
__version__ = "0.0.
|
|
1
|
+
__version__ = "0.0.6"
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
updates = """
|
|
5
|
+
### 版本说明
|
|
6
|
+
|
|
7
|
+
**✨ 核心亮点**
|
|
8
|
+
- **多进程支持**:我们引入了更加稳健的多进程支持,现在可以通过 spawn 方式来启动和管理多个进程,提升了应用的并行处理能力。
|
|
9
|
+
- **日志系统增强**:为了确保日志记录的可靠性,我们更新了日志系统,现在会自动确保日志路径目录的存在,避免了因目录问题导致的日志记录失败。
|
|
10
|
+
|
|
11
|
+
**🚀 体验优化**
|
|
12
|
+
- **系统稳定性提升**:通过对多进程支持的改进和日志系统的优化,我们增强了应用的稳定性和可靠性,为用户提供了更加流畅的体验。
|
|
13
|
+
|
|
14
|
+
**🐛 问题修复**
|
|
15
|
+
- *(本次更新没有涉及到老功能的修复,因此此部分留空)*
|
|
16
|
+
"""
|
unitlog/handlers.py
CHANGED
|
@@ -13,7 +13,10 @@ class LogBox(object):
|
|
|
13
13
|
|
|
14
14
|
class UnitHandler(logging.StreamHandler):
|
|
15
15
|
LOG_TYPE = "console"
|
|
16
|
-
|
|
16
|
+
|
|
17
|
+
def __init__(self, stream=None, bus_queue=None):
|
|
18
|
+
super().__init__(stream)
|
|
19
|
+
self.bus_queue = bus_queue
|
|
17
20
|
|
|
18
21
|
def handle(self, record):
|
|
19
22
|
""" without acquiring lock
|
|
@@ -39,7 +42,7 @@ class UnitHandler(logging.StreamHandler):
|
|
|
39
42
|
msg = self.format(record)
|
|
40
43
|
# issue 35046: merged two stream.writes into one.
|
|
41
44
|
log_msg = msg + self.terminator
|
|
42
|
-
|
|
45
|
+
self.bus_queue.put(self.wrap_msg(log_msg))
|
|
43
46
|
except RecursionError: # See issue 36272
|
|
44
47
|
raise
|
|
45
48
|
except Exception:
|
|
@@ -53,8 +56,8 @@ class UnitConsoleHandler(UnitHandler):
|
|
|
53
56
|
class UnitFileHandler(UnitHandler):
|
|
54
57
|
LOG_TYPE = "file"
|
|
55
58
|
|
|
56
|
-
def __init__(self, log_filepath, mode):
|
|
57
|
-
super().__init__()
|
|
59
|
+
def __init__(self, log_filepath, mode, bus_queue=None):
|
|
60
|
+
super().__init__(bus_queue=bus_queue)
|
|
58
61
|
self.log_filepath = log_filepath
|
|
59
62
|
self.mode = mode
|
|
60
63
|
|
unitlog/unit.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import sys
|
|
3
|
+
import inspect
|
|
3
4
|
import atexit
|
|
4
5
|
import logging
|
|
5
6
|
import traceback
|
|
@@ -7,8 +8,23 @@ from queue import Empty
|
|
|
7
8
|
import multiprocessing as mp
|
|
8
9
|
from multiprocessing.synchronize import Event
|
|
9
10
|
|
|
10
|
-
from unitlog.handlers import
|
|
11
|
-
|
|
11
|
+
from unitlog.handlers import LogBox, UnitFileHandler, UnitConsoleHandler
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def is_under_testing():
|
|
16
|
+
"""
|
|
17
|
+
检查当前是否处于单元测试环境 (unittest 或 pytest)。
|
|
18
|
+
原理:向上遍历栈帧,如果发现调用链中有 unittest 或 pytest 的相关文件,则认为是测试环境。
|
|
19
|
+
"""
|
|
20
|
+
# 简单的白名单检查
|
|
21
|
+
for frame_info in inspect.stack():
|
|
22
|
+
module_name = frame_info.frame.f_globals.get('__name__', '')
|
|
23
|
+
# 如果调用栈里包含 unittest 或 pytest,说明是在测试
|
|
24
|
+
if module_name and ('unittest' in module_name or 'pytest' in module_name):
|
|
25
|
+
return True
|
|
26
|
+
return False
|
|
27
|
+
|
|
12
28
|
|
|
13
29
|
|
|
14
30
|
class PoxyConsoleLogWriter(object):
|
|
@@ -35,7 +51,8 @@ class UnitLog(object):
|
|
|
35
51
|
self.started: Event = mp.Event()
|
|
36
52
|
self.stopped: Event = mp.Event()
|
|
37
53
|
self.log_num = mp.Value('i', 0)
|
|
38
|
-
self.worker =
|
|
54
|
+
self.worker = None
|
|
55
|
+
self.bus_queue = None
|
|
39
56
|
self._proxy_handler_map = {}
|
|
40
57
|
|
|
41
58
|
def _init_proxy_handler(self, log_box: LogBox) -> PoxyConsoleLogWriter:
|
|
@@ -57,11 +74,11 @@ class UnitLog(object):
|
|
|
57
74
|
raise TypeError(f"Unsupported log type: {log_box.log_type}")
|
|
58
75
|
return self._proxy_handler_map[hkey]
|
|
59
76
|
|
|
60
|
-
def listening_log_msg(self):
|
|
77
|
+
def listening_log_msg(self, bus_queue):
|
|
61
78
|
while True:
|
|
62
79
|
self.started.set()
|
|
63
80
|
try:
|
|
64
|
-
log_box: LogBox =
|
|
81
|
+
log_box: LogBox = bus_queue.get(timeout=0.1)
|
|
65
82
|
except Empty:
|
|
66
83
|
if self.stopped.is_set():
|
|
67
84
|
break
|
|
@@ -83,8 +100,49 @@ class UnitLog(object):
|
|
|
83
100
|
def register_logger(self, name, level=logging.INFO,
|
|
84
101
|
console_log=True, file_log=False, file_log_mode="a",
|
|
85
102
|
log_filepath=None,
|
|
86
|
-
parent_logger_name=None
|
|
103
|
+
parent_logger_name=None,
|
|
104
|
+
force_all_console_log_to_file=False) -> logging.Logger:
|
|
105
|
+
|
|
106
|
+
caller_frame = inspect.currentframe().f_back
|
|
107
|
+
caller_module_name = caller_frame.f_globals.get('__name__')
|
|
108
|
+
caller_func_name = caller_frame.f_code.co_name
|
|
109
|
+
|
|
110
|
+
# ---------------------------------------------------------
|
|
111
|
+
# 规则 1: 严禁全局裸奔 (必须包在函数里)
|
|
112
|
+
# ---------------------------------------------------------
|
|
113
|
+
if caller_func_name == '<module>':
|
|
114
|
+
raise RuntimeError(
|
|
115
|
+
"⛔️ [禁止全局调用] 你必须把此函数放在一个 def 函数内部调用,\n"
|
|
116
|
+
"""
|
|
117
|
+
例如
|
|
118
|
+
def main():
|
|
119
|
+
register_logger()
|
|
120
|
+
|
|
121
|
+
if __name__ == '__main__':
|
|
122
|
+
main()
|
|
123
|
+
"""
|
|
124
|
+
"严禁在脚本顶层直接执行,否则会导致多进程 spawn 时的无限递归。"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# ---------------------------------------------------------
|
|
128
|
+
# 规则 2: 必须是 Main 入口,或者是 单元测试
|
|
129
|
+
# ---------------------------------------------------------
|
|
130
|
+
# 如果是直接运行脚本,name 是 __main__ -> 通过
|
|
131
|
+
# 如果是 unittest,name 是 文件名 -> 但 is_under_testing() 为真 -> 通过
|
|
132
|
+
# 如果是 spawn 子进程,name 是 文件名 -> 且不在测试栈中 -> 拦截
|
|
133
|
+
if caller_module_name != '__main__' and not is_under_testing():
|
|
134
|
+
raise RuntimeError(
|
|
135
|
+
f"⛔️ [禁止导入执行] 检测到当前模块名为 '{caller_module_name}'。\n"
|
|
136
|
+
"此函数只能在 'if __name__ == \"__main__\":' 入口下执行,\n"
|
|
137
|
+
"或者是通过单元测试(unittest/pytest)执行。\n"
|
|
138
|
+
"禁止在多进程 spawn 导入阶段或作为普通模块被 import 时执行。"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
print(f"✅ 安全检查通过 (调用者: {caller_module_name}, 环境安全)")
|
|
142
|
+
|
|
87
143
|
if not self.started.is_set():
|
|
144
|
+
self.bus_queue = mp.Queue()
|
|
145
|
+
self.worker = mp.Process(target=self.listening_log_msg, args=(self.bus_queue, ), daemon=True)
|
|
88
146
|
self.worker.start()
|
|
89
147
|
if not self.started.wait(timeout=3):
|
|
90
148
|
raise ValueError("unit log process is not started")
|
|
@@ -108,28 +166,134 @@ class UnitLog(object):
|
|
|
108
166
|
datefmt="%a, %d %b %Y %H:%M:%S"
|
|
109
167
|
)
|
|
110
168
|
if console_log:
|
|
111
|
-
console_handler = UnitConsoleHandler()
|
|
169
|
+
console_handler = UnitConsoleHandler(bus_queue=self.bus_queue)
|
|
112
170
|
console_handler.setFormatter(simple_formatter)
|
|
113
171
|
logger.handlers.append(console_handler)
|
|
114
172
|
if file_log:
|
|
115
173
|
assert log_filepath, "log_filepath must be set"
|
|
116
|
-
|
|
174
|
+
os.makedirs(os.path.dirname(log_filepath), exist_ok=True)
|
|
175
|
+
file_handler = UnitFileHandler(log_filepath, mode=file_log_mode, bus_queue=self.bus_queue)
|
|
117
176
|
file_handler.setFormatter(full_formatter)
|
|
118
177
|
logger.handlers.append(file_handler)
|
|
119
178
|
logger.info("\nLog_filename: {}".format(log_filepath))
|
|
120
179
|
|
|
180
|
+
if force_all_console_log_to_file: # 强制控制所有标准输出到 文件
|
|
181
|
+
self.force_all_console_log_to_file(log_filepath)
|
|
182
|
+
|
|
121
183
|
return logger
|
|
122
184
|
|
|
123
185
|
|
|
186
|
+
@classmethod
|
|
187
|
+
def force_all_console_log_to_file(cls, log_filepath):
|
|
188
|
+
# ==========================================
|
|
189
|
+
# 新增函数:重定向底层 C/C++ 输出
|
|
190
|
+
# ==========================================
|
|
191
|
+
def _redirect_c_libraries_output(log_path):
|
|
192
|
+
"""
|
|
193
|
+
使用 os.dup2 强制将底层 C/C++ 的 stdout/stderr 重定向到日志文件。
|
|
194
|
+
解决 sherpa-onnx, PyQt, OpenCV 等 C 库打印无法被 Python 捕获的问题。
|
|
195
|
+
"""
|
|
196
|
+
# 1. 打开日志文件 (使用 append 模式)
|
|
197
|
+
# 这里的 buffer 设置为 0 (unbuffered) 或者 line buffered,确保 C 代码崩溃前能写入
|
|
198
|
+
try:
|
|
199
|
+
# 打开文件获取文件描述符
|
|
200
|
+
# distinct file object specifically for low-level redirection
|
|
201
|
+
log_file = open(log_path, 'a+')
|
|
202
|
+
log_fd = log_file.fileno()
|
|
203
|
+
|
|
204
|
+
# 2. 刷新 Python 的缓冲区,防止重定向导致之前的日志丢失
|
|
205
|
+
sys.stdout.flush()
|
|
206
|
+
sys.stderr.flush()
|
|
207
|
+
|
|
208
|
+
# 3. 核心:重定向 FD 1 (stdout) 和 FD 2 (stderr)
|
|
209
|
+
# 这一步之后,所有的 C printf/std::cout 都会直接写进文件
|
|
210
|
+
os.dup2(log_fd, 1)
|
|
211
|
+
os.dup2(log_fd, 2)
|
|
212
|
+
|
|
213
|
+
# 保持 log_file 对象引用,防止被垃圾回收导致 FD 关闭
|
|
214
|
+
return log_file
|
|
215
|
+
except Exception as e:
|
|
216
|
+
print(f"Failed to redirect C logs: {e}")
|
|
217
|
+
return None
|
|
218
|
+
|
|
219
|
+
class Logger(object):
|
|
220
|
+
def __init__(self, filename):
|
|
221
|
+
self.terminal = sys.stdout # 记录原来的控制台,防止 IDE 里看不到了
|
|
222
|
+
self.log = open(filename, "a", encoding="utf-8") # 'a' 追加模式
|
|
223
|
+
|
|
224
|
+
def write(self, message):
|
|
225
|
+
# 1. 尝试写回控制台(方便开发调试)
|
|
226
|
+
try:
|
|
227
|
+
if self.terminal:
|
|
228
|
+
self.terminal.write(message)
|
|
229
|
+
except:
|
|
230
|
+
pass # 打包成 no console 后这里可能会报错,直接忽略
|
|
231
|
+
|
|
232
|
+
# 2. 写入文件
|
|
233
|
+
try:
|
|
234
|
+
self.log.write(message)
|
|
235
|
+
# 【关键】立即刷新缓冲区,否则崩溃瞬间可能来不及写入文件
|
|
236
|
+
self.log.flush()
|
|
237
|
+
except:
|
|
238
|
+
pass
|
|
239
|
+
|
|
240
|
+
def flush(self):
|
|
241
|
+
# 兼容性函数,必须保留
|
|
242
|
+
try:
|
|
243
|
+
if self.terminal:
|
|
244
|
+
self.terminal.flush()
|
|
245
|
+
self.log.flush()
|
|
246
|
+
except:
|
|
247
|
+
pass
|
|
248
|
+
|
|
249
|
+
# 3. 【关键改动】区分环境进行重定向
|
|
250
|
+
# 判断是否是打包后的环境
|
|
251
|
+
# 逻辑:如果是 PyInstaller 打包 (frozen) 或者 环境变量 MIT_LOG=1,都视为需要重定向
|
|
252
|
+
FORCE_ALL_CONSOLE_LOG_TO_FILE = os.environ.get("FORCE_ALL_CONSOLE_LOG_TO_FILE") == "1"
|
|
253
|
+
IS_FROZEN = getattr(sys, 'frozen', False) or FORCE_ALL_CONSOLE_LOG_TO_FILE
|
|
254
|
+
print(f"FORCE_ALL_CONSOLE_LOG_TO_FILE: {FORCE_ALL_CONSOLE_LOG_TO_FILE} \t IS_FROZEN: {IS_FROZEN}")
|
|
255
|
+
|
|
256
|
+
if IS_FROZEN:
|
|
257
|
+
# --- 打包环境 (Exe) ---
|
|
258
|
+
# 1. 接管 Python 层面 (你原来的做法)
|
|
259
|
+
sys.stdout = Logger(log_filepath)
|
|
260
|
+
sys.stderr = sys.stdout
|
|
261
|
+
|
|
262
|
+
# 2. 接管 C/C++ 层面 (新增的做法)
|
|
263
|
+
# 这会让 sherpa-onnx 的报错也进文件
|
|
264
|
+
# 注意:在 Linux/Mac 上非常有效,Windows 上通常也有效
|
|
265
|
+
_c_log_ref = _redirect_c_libraries_output(log_filepath)
|
|
266
|
+
|
|
267
|
+
print(f"Native C++ stdout/stderr redirected to {log_filepath}")
|
|
268
|
+
|
|
269
|
+
else:
|
|
270
|
+
# --- 开发环境 (IDE) ---
|
|
271
|
+
# 在开发时,我们通常不希望 C++ 输出消失在控制台
|
|
272
|
+
# 所以这里我们 *只* 使用你原来的 Logger 记录 Python print
|
|
273
|
+
# 这样 IDE 控制台里既能看到 Python print,也能看到 C++ print (IDE 会自己捕获 FD)
|
|
274
|
+
# 同时 Python print 也会被写入文件
|
|
275
|
+
|
|
276
|
+
# 如果你确实希望开发时文件里也有 sherpa-onnx 的日志,
|
|
277
|
+
# 你可以把上面的 redirect_c_libraries_output 打开,
|
|
278
|
+
# 但代价是你的 PyCharm 控制台里那行红色的警告会消失。
|
|
279
|
+
sys.stdout = Logger(log_filepath)
|
|
280
|
+
sys.stderr = sys.stdout
|
|
281
|
+
|
|
124
282
|
DEFAULT_LOG = UnitLog()
|
|
125
283
|
|
|
126
|
-
register_logger = DEFAULT_LOG.register_logger
|
|
284
|
+
register_logger: UnitLog.register_logger = DEFAULT_LOG.register_logger
|
|
127
285
|
atexit.register(lambda: DEFAULT_LOG.stopped.set())
|
|
128
286
|
|
|
129
|
-
|
|
130
|
-
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def main():
|
|
131
290
|
|
|
132
291
|
_logger = logging.getLogger("test")
|
|
133
292
|
register_logger(name="test", level=logging.DEBUG,
|
|
134
293
|
log_filepath="./temp/test.log")
|
|
135
294
|
_logger.info("lllll")
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
if __name__ == "__main__":
|
|
299
|
+
main()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: unitlog
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.6
|
|
4
4
|
Home-page: https://github.com/yujun2647/unitlog
|
|
5
5
|
Download-URL:
|
|
6
6
|
Author: walkerjun
|
|
@@ -9,6 +9,14 @@ License: Apache-2.0
|
|
|
9
9
|
Requires-Python: >=3.6
|
|
10
10
|
Description-Content-Type: text/markdown
|
|
11
11
|
License-File: LICENSE
|
|
12
|
+
Dynamic: author
|
|
13
|
+
Dynamic: author-email
|
|
14
|
+
Dynamic: description
|
|
15
|
+
Dynamic: description-content-type
|
|
16
|
+
Dynamic: home-page
|
|
17
|
+
Dynamic: license
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
Dynamic: requires-python
|
|
12
20
|
|
|
13
21
|
# unitlog
|
|
14
22
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
unitlog/__init__.py,sha256=CJV0OpC1u1NCaYQrNSTPritcGY0rS_t76-sCmgALopo,765
|
|
2
|
+
unitlog/handlers.py,sha256=NFoHPppwoAZt65JLJY5SM9UolJGo7qFA_5SqfS5zMSM,1875
|
|
3
|
+
unitlog/unit.py,sha256=rciZqbaOGOd8_zpGfvxaTbI51np_DsGNpvhsmhjlS-M,12206
|
|
4
|
+
unitlog/util_log.py,sha256=dbdImt7acamMl1u2HaHgj1Lf0-EF9KnqcLOGr9yPDi8,5202
|
|
5
|
+
unitlog-0.0.6.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
6
|
+
unitlog-0.0.6.dist-info/METADATA,sha256=YhIIcyduslPC4IjWP9TkwmpD0PJt5YJndWT4c9fcpzI,750
|
|
7
|
+
unitlog-0.0.6.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
8
|
+
unitlog-0.0.6.dist-info/top_level.txt,sha256=u_R9qvDeq23Wg6BGAP1QB7CuDosByWfI-EfXWa2MO-c,8
|
|
9
|
+
unitlog-0.0.6.dist-info/RECORD,,
|
unitlog-0.0.3.dist-info/RECORD
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
unitlog/__init__.py,sha256=4GZKi13lDTD25YBkGakhZyEQZWTER_OWQMNPoH_UM2c,22
|
|
2
|
-
unitlog/handlers.py,sha256=K0ph6OxZey8AA7ZuxEycqEkEgZnckghsmN0NKxNg_bI,1768
|
|
3
|
-
unitlog/unit.py,sha256=dOgn40pq1H7yUCxLQBM2ehe6nGGBcg-lXWY3Tkl4-rE,4753
|
|
4
|
-
unitlog/util_log.py,sha256=dbdImt7acamMl1u2HaHgj1Lf0-EF9KnqcLOGr9yPDi8,5202
|
|
5
|
-
unitlog-0.0.3.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
6
|
-
unitlog-0.0.3.dist-info/METADATA,sha256=5GXpOu6R4Rw6XEfWIrR_TBhPHtcSd_djjtrHbevyJ9M,574
|
|
7
|
-
unitlog-0.0.3.dist-info/WHEEL,sha256=-oYQCr74JF3a37z2nRlQays_SX2MqOANoqVjBBAP2yE,91
|
|
8
|
-
unitlog-0.0.3.dist-info/top_level.txt,sha256=u_R9qvDeq23Wg6BGAP1QB7CuDosByWfI-EfXWa2MO-c,8
|
|
9
|
-
unitlog-0.0.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|