mlog-util 0.1.2__tar.gz → 0.1.3__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.
Potentially problematic release.
This version of mlog-util might be problematic. Click here for more details.
- {mlog_util-0.1.2 → mlog_util-0.1.3}/PKG-INFO +2 -1
- mlog_util-0.1.3/mlog_util/__init__.py +3 -0
- mlog_util-0.1.3/mlog_util/handlers.py +324 -0
- mlog_util-0.1.3/mlog_util/log_manager.py +116 -0
- {mlog_util-0.1.2 → mlog_util-0.1.3}/mlog_util.egg-info/PKG-INFO +2 -1
- {mlog_util-0.1.2 → mlog_util-0.1.3}/mlog_util.egg-info/SOURCES.txt +2 -0
- mlog_util-0.1.3/mlog_util.egg-info/requires.txt +2 -0
- {mlog_util-0.1.2 → mlog_util-0.1.3}/setup.py +2 -2
- mlog_util-0.1.2/mlog_util/__init__.py +0 -38
- mlog_util-0.1.2/mlog_util.egg-info/requires.txt +0 -1
- {mlog_util-0.1.2 → mlog_util-0.1.3}/mlog_util.egg-info/dependency_links.txt +0 -0
- {mlog_util-0.1.2 → mlog_util-0.1.3}/mlog_util.egg-info/top_level.txt +0 -0
- {mlog_util-0.1.2 → mlog_util-0.1.3}/setup.cfg +0 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mlog_util
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: 自用日志库
|
|
5
5
|
Classifier: Programming Language :: Python :: 3
|
|
6
6
|
Classifier: License :: OSI Approved :: MIT License
|
|
7
7
|
Classifier: Operating System :: OS Independent
|
|
8
8
|
Requires-Python: >=3.6
|
|
9
9
|
Requires-Dist: rich
|
|
10
|
+
Requires-Dist: portalocker
|
|
10
11
|
Dynamic: classifier
|
|
11
12
|
Dynamic: requires-dist
|
|
12
13
|
Dynamic: requires-python
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import os
|
|
3
|
+
import getpass
|
|
4
|
+
import glob
|
|
5
|
+
import errno
|
|
6
|
+
import logging
|
|
7
|
+
import portalocker
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# 常量:锁文件最大存活时间(秒)
|
|
12
|
+
LOCK_TIMEOUT = 120 # 2 minutes
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MultiProcessSafeRotatingHandlerBase(logging.Handler, ABC):
|
|
16
|
+
"""
|
|
17
|
+
日志轮转 Handler 基类
|
|
18
|
+
"""
|
|
19
|
+
def __init__(self, filename, backupCount=3):
|
|
20
|
+
super().__init__()
|
|
21
|
+
self.filename = filename
|
|
22
|
+
self.backupCount = backupCount
|
|
23
|
+
self.lockfile = filename + ".lock"
|
|
24
|
+
|
|
25
|
+
# 本进程临时文件
|
|
26
|
+
pid = os.getpid()
|
|
27
|
+
user = getpass.getuser()
|
|
28
|
+
self.tmp_file = f"{filename}.tmp.{user}.{pid}"
|
|
29
|
+
|
|
30
|
+
self.stream = None
|
|
31
|
+
|
|
32
|
+
def emit(self, record):
|
|
33
|
+
try:
|
|
34
|
+
# ✅ 检查锁文件
|
|
35
|
+
if os.path.exists(self.lockfile):
|
|
36
|
+
if self._is_lock_expired():
|
|
37
|
+
# 清理过期锁
|
|
38
|
+
try:
|
|
39
|
+
os.remove(self.lockfile)
|
|
40
|
+
print(f"[MPLog] Removed stale lock: {self.lockfile}")
|
|
41
|
+
except Exception as e:
|
|
42
|
+
print(f"[MPLog] Failed to remove stale lock {self.lockfile}: {e}")
|
|
43
|
+
self._write_to_tmp(record)
|
|
44
|
+
return
|
|
45
|
+
else:
|
|
46
|
+
# 有效锁,写临时文件
|
|
47
|
+
self._write_to_tmp(record)
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
# ✅ 尝试写主文件
|
|
51
|
+
try:
|
|
52
|
+
self._open_log()
|
|
53
|
+
msg = self.format(record) + '\n'
|
|
54
|
+
self.stream.write(msg)
|
|
55
|
+
self.stream.flush()
|
|
56
|
+
except Exception:
|
|
57
|
+
self._write_to_tmp(record)
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
# ✅ 子类决定是否需要轮转
|
|
61
|
+
if self._should_rollover(record):
|
|
62
|
+
self.doRollover()
|
|
63
|
+
|
|
64
|
+
except Exception:
|
|
65
|
+
self.handleError(record)
|
|
66
|
+
|
|
67
|
+
def _write_to_tmp(self, record):
|
|
68
|
+
"""写入本进程临时文件"""
|
|
69
|
+
msg = self.format(record) + '\n'
|
|
70
|
+
try:
|
|
71
|
+
with open(self.tmp_file, 'a', encoding='utf-8') as f:
|
|
72
|
+
f.write(msg)
|
|
73
|
+
except Exception as e:
|
|
74
|
+
print(f"Failed to write tmp: {e}")
|
|
75
|
+
|
|
76
|
+
@abstractmethod
|
|
77
|
+
def _should_rollover(self, record) -> bool:
|
|
78
|
+
"""子类实现:判断是否需要轮转"""
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
@abstractmethod
|
|
82
|
+
def _do_rollover_impl(self):
|
|
83
|
+
"""子类实现:具体的轮转归档逻辑"""
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
def _open_log(self):
|
|
87
|
+
"""打开主日志文件"""
|
|
88
|
+
if self.stream is None:
|
|
89
|
+
try:
|
|
90
|
+
self.stream = open(self.filename, 'a', encoding='utf-8')
|
|
91
|
+
except Exception as e:
|
|
92
|
+
print(f"Failed to open {self.filename}: {e}")
|
|
93
|
+
raise
|
|
94
|
+
|
|
95
|
+
def _get_file_size(self, filepath):
|
|
96
|
+
"""安全获取文件大小"""
|
|
97
|
+
try:
|
|
98
|
+
return os.path.getsize(filepath)
|
|
99
|
+
except (OSError, IOError) as e:
|
|
100
|
+
if e.errno == errno.ENOENT:
|
|
101
|
+
return 0
|
|
102
|
+
raise
|
|
103
|
+
|
|
104
|
+
def _is_lock_expired(self):
|
|
105
|
+
"""检查锁文件是否存在且是否超时"""
|
|
106
|
+
try:
|
|
107
|
+
st = os.stat(self.lockfile)
|
|
108
|
+
return time.time() - st.st_mtime > LOCK_TIMEOUT
|
|
109
|
+
except (OSError, IOError) as e:
|
|
110
|
+
if e.errno == errno.ENOENT:
|
|
111
|
+
return False
|
|
112
|
+
return False
|
|
113
|
+
|
|
114
|
+
def doRollover(self):
|
|
115
|
+
"""执行轮转(跨平台安全)"""
|
|
116
|
+
# ✅ 使用 portalocker 获取独占锁(非阻塞)
|
|
117
|
+
try:
|
|
118
|
+
lock_fd = os.open(self.lockfile, os.O_CREAT | os.O_WRONLY | os.O_TRUNC)
|
|
119
|
+
except Exception as e:
|
|
120
|
+
return # 无法创建锁文件
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
# 尝试立即获得独占锁(非阻塞)
|
|
124
|
+
portalocker.lock(lock_fd, portalocker.LOCK_EX | portalocker.LOCK_NB)
|
|
125
|
+
# 加锁成功 → 写入锁信息(可选)
|
|
126
|
+
os.write(lock_fd, f"{os.getpid()}\n{time.time()}".encode())
|
|
127
|
+
os.close(lock_fd)
|
|
128
|
+
lock_fd = None # 已关闭
|
|
129
|
+
except portalocker.LockException:
|
|
130
|
+
# 无法获得锁 → 其他进程正在轮转
|
|
131
|
+
if lock_fd is not None:
|
|
132
|
+
os.close(lock_fd)
|
|
133
|
+
return
|
|
134
|
+
except Exception as e:
|
|
135
|
+
# 其他异常
|
|
136
|
+
if lock_fd is not None:
|
|
137
|
+
os.close(lock_fd)
|
|
138
|
+
return
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
# ✅ 关闭主文件流
|
|
142
|
+
if self.stream:
|
|
143
|
+
self.stream.close()
|
|
144
|
+
self.stream = None
|
|
145
|
+
|
|
146
|
+
# ✅ 执行子类的具体轮转逻辑
|
|
147
|
+
self._do_rollover_impl()
|
|
148
|
+
|
|
149
|
+
# ✅ 合并所有临时文件
|
|
150
|
+
self._merge_temp_files()
|
|
151
|
+
|
|
152
|
+
finally:
|
|
153
|
+
# ✅ 删除锁文件(释放锁)
|
|
154
|
+
try:
|
|
155
|
+
if os.path.exists(self.lockfile):
|
|
156
|
+
os.remove(self.lockfile)
|
|
157
|
+
except Exception:
|
|
158
|
+
pass
|
|
159
|
+
|
|
160
|
+
def _merge_temp_files(self):
|
|
161
|
+
"""合并所有临时文件到主日志"""
|
|
162
|
+
tmp_pattern = f"{self.filename}.tmp.*"
|
|
163
|
+
for tmp_path in glob.glob(tmp_pattern):
|
|
164
|
+
try:
|
|
165
|
+
with open(tmp_path, 'r', encoding='utf-8') as f:
|
|
166
|
+
data = f.read()
|
|
167
|
+
if data.strip():
|
|
168
|
+
with open(self.filename, 'a', encoding='utf-8') as logf:
|
|
169
|
+
logf.write(data)
|
|
170
|
+
os.remove(tmp_path)
|
|
171
|
+
except Exception as e:
|
|
172
|
+
print(f"Merge failed {tmp_path}: {e}")
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# ========================================
|
|
176
|
+
# 子类:按文件大小轮转
|
|
177
|
+
# ========================================
|
|
178
|
+
def parse_bytes_size(size_str: str) -> int:
|
|
179
|
+
"""
|
|
180
|
+
将表示大小的字符串(如 '1 M', '5K', '2 G')解析为字节数。
|
|
181
|
+
支持单位:K (KB), M (MB), G (GB)
|
|
182
|
+
不区分大小写,空格可选。
|
|
183
|
+
"""
|
|
184
|
+
size_str = size_str.strip().upper()
|
|
185
|
+
|
|
186
|
+
# 定义单位到字节数的映射(以 1024 为基数)
|
|
187
|
+
units = {
|
|
188
|
+
'K': 1024,
|
|
189
|
+
'M': 1024 * 1024,
|
|
190
|
+
'G': 1024 * 1024 * 1024,
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
# 默认单位是字节(无单位时)
|
|
194
|
+
unit = 'B'
|
|
195
|
+
num_part = size_str
|
|
196
|
+
|
|
197
|
+
# 从后往前找单位
|
|
198
|
+
for u in units:
|
|
199
|
+
if size_str.endswith(u):
|
|
200
|
+
unit = u
|
|
201
|
+
num_part = size_str[:-len(u)].strip()
|
|
202
|
+
break
|
|
203
|
+
|
|
204
|
+
# 解析数值(支持整数和小数)
|
|
205
|
+
try:
|
|
206
|
+
value = float(num_part)
|
|
207
|
+
except ValueError:
|
|
208
|
+
raise ValueError(f"无法解析大小字符串: {size_str}")
|
|
209
|
+
|
|
210
|
+
# 计算总字节数
|
|
211
|
+
if unit == 'B':
|
|
212
|
+
return int(value) # 假设单位是字节
|
|
213
|
+
else:
|
|
214
|
+
return int(value * units[unit])
|
|
215
|
+
|
|
216
|
+
class MultiProcessSafeSizeRotatingHandler(MultiProcessSafeRotatingHandlerBase):
|
|
217
|
+
"""
|
|
218
|
+
使用案例
|
|
219
|
+
>>> handler = MultiProcessSafeSizeRotatingHandler(filename="a1.log", maxBytes="1 M")
|
|
220
|
+
>>> get_logger(custom_handler=handler)
|
|
221
|
+
"""
|
|
222
|
+
def __init__(self, filename, maxBytes=5 * 1024 * 1024, backupCount=3):
|
|
223
|
+
super().__init__(filename, backupCount)
|
|
224
|
+
if isinstance(maxBytes, str):
|
|
225
|
+
maxBytes = parse_bytes_size(maxBytes)
|
|
226
|
+
|
|
227
|
+
if maxBytes <= 0:
|
|
228
|
+
raise ValueError("maxBytes must be positive")
|
|
229
|
+
|
|
230
|
+
self.maxBytes = maxBytes
|
|
231
|
+
|
|
232
|
+
def _should_rollover(self, record) -> bool:
|
|
233
|
+
return self._get_file_size(self.filename) >= self.maxBytes
|
|
234
|
+
|
|
235
|
+
def _do_rollover_impl(self):
|
|
236
|
+
# 轮转备份文件
|
|
237
|
+
for i in range(self.backupCount - 1, 0, -1):
|
|
238
|
+
sfn = f"{self.filename}.{i}"
|
|
239
|
+
dfn = f"{self.filename}.{i+1}"
|
|
240
|
+
if os.path.exists(sfn):
|
|
241
|
+
if os.path.exists(dfn):
|
|
242
|
+
os.remove(dfn)
|
|
243
|
+
os.rename(sfn, dfn)
|
|
244
|
+
if os.path.exists(self.filename):
|
|
245
|
+
dfn = f"{self.filename}.1"
|
|
246
|
+
if os.path.exists(dfn):
|
|
247
|
+
os.remove(dfn)
|
|
248
|
+
os.rename(self.filename, dfn)
|
|
249
|
+
|
|
250
|
+
# 重新创建空的日志文件
|
|
251
|
+
try:
|
|
252
|
+
with open(self.filename, 'w', encoding='utf-8') as f:
|
|
253
|
+
pass
|
|
254
|
+
except Exception as e:
|
|
255
|
+
print(f"Failed to recreate log file {self.filename}: {e}")
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
# ========================================
|
|
259
|
+
# 子类:按时间轮转
|
|
260
|
+
# ========================================
|
|
261
|
+
class MultiProcessSafeTimeRotatingHandler(MultiProcessSafeRotatingHandlerBase):
|
|
262
|
+
"""
|
|
263
|
+
使用案例
|
|
264
|
+
>>> handler = MultiProcessSafeTimeRotatingHandler(filename="a2.log", when='H')
|
|
265
|
+
>>> get_logger(custom_handler=handler)
|
|
266
|
+
"""
|
|
267
|
+
def __init__(self, filename, when='D', interval= 1, backupCount=7):
|
|
268
|
+
super().__init__(filename, backupCount)
|
|
269
|
+
self.when = when.upper()
|
|
270
|
+
self.interval = max(1, int(interval)) # 至少为 1
|
|
271
|
+
self.last_rollover = int(time.time())
|
|
272
|
+
|
|
273
|
+
# 支持的单位映射
|
|
274
|
+
self.when_to_seconds = {
|
|
275
|
+
'S': 10, # 最少 10s
|
|
276
|
+
'M': 60, # 分钟
|
|
277
|
+
'H': 3600, # 小时
|
|
278
|
+
'D': 86400, # 天
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
def _should_rollover(self, record) -> bool:
|
|
282
|
+
now = int(time.time())
|
|
283
|
+
|
|
284
|
+
if self.when not in self.when_to_seconds:
|
|
285
|
+
return False
|
|
286
|
+
|
|
287
|
+
seconds_per_unit = self.when_to_seconds[self.when]
|
|
288
|
+
total_interval_seconds = seconds_per_unit * self.interval
|
|
289
|
+
|
|
290
|
+
# 计算当前属于第几个周期(从 0 开始)
|
|
291
|
+
current_cycle = now // total_interval_seconds
|
|
292
|
+
last_cycle = self.last_rollover // total_interval_seconds
|
|
293
|
+
|
|
294
|
+
return current_cycle > last_cycle
|
|
295
|
+
|
|
296
|
+
def _do_rollover_impl(self):
|
|
297
|
+
# 按日期重命名,如 log.txt -> log.txt.2025-09-16
|
|
298
|
+
date_str = time.strftime(self._get_rollover_format())
|
|
299
|
+
dfn = f"{self.filename}.{date_str}"
|
|
300
|
+
if os.path.exists(self.filename):
|
|
301
|
+
os.rename(self.filename, dfn)
|
|
302
|
+
with open(self.filename, 'w'): pass
|
|
303
|
+
self.last_rollover = int(time.time())
|
|
304
|
+
|
|
305
|
+
def _get_rollover_format(self):
|
|
306
|
+
"""
|
|
307
|
+
根据 when 和 interval 返回时间格式字符串
|
|
308
|
+
"""
|
|
309
|
+
if self.when == 'S':
|
|
310
|
+
return "%Y-%m-%d-%H:%M:%S" # 精确到分钟
|
|
311
|
+
if self.when == 'M':
|
|
312
|
+
return "%Y-%m-%d-%H:%M" # 精确到分钟
|
|
313
|
+
elif self.when == 'H':
|
|
314
|
+
if self.interval >= 24:
|
|
315
|
+
return "%Y-%m-%d" # 每N小时但N>=24 → 按天
|
|
316
|
+
else:
|
|
317
|
+
return "%Y-%m-%d-%H" # 按小时
|
|
318
|
+
elif self.when == 'D':
|
|
319
|
+
if self.interval == 1:
|
|
320
|
+
return "%Y-%m-%d" # 每天
|
|
321
|
+
else:
|
|
322
|
+
return "%Y-%m-%d" # 每N天,仍用日期表示(如 2025-09-16)
|
|
323
|
+
else:
|
|
324
|
+
return "%Y-%m-%d" # 默认按天
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import threading
|
|
3
|
+
from rich.logging import RichHandler
|
|
4
|
+
from typing import Type, List, Optional
|
|
5
|
+
|
|
6
|
+
# from .handlers import MultiProcessSafeSizeRotatingHandler, MultiProcessSafeTimeRotatingHandler
|
|
7
|
+
|
|
8
|
+
class LogManager:
|
|
9
|
+
_logger_cache = {}
|
|
10
|
+
_lock = threading.Lock() # 多线程安全
|
|
11
|
+
|
|
12
|
+
@classmethod
|
|
13
|
+
def get_logger(
|
|
14
|
+
cls,
|
|
15
|
+
name: str,
|
|
16
|
+
logger_cls: Type[logging.Logger] = logging.Logger,
|
|
17
|
+
log_file: str | None = None,
|
|
18
|
+
add_console: bool = True,
|
|
19
|
+
level: int = logging.INFO,
|
|
20
|
+
custom_handlers: list[logging.Handler] | None = None,
|
|
21
|
+
) -> logging.Logger:
|
|
22
|
+
"""
|
|
23
|
+
获取或创建 logger。
|
|
24
|
+
|
|
25
|
+
:param name: logger 名称
|
|
26
|
+
:param logger_cls: logger 类
|
|
27
|
+
:param log_file: 日志文件路径
|
|
28
|
+
:param add_console: 是否添加控制台 RichHandler
|
|
29
|
+
:param level: 日志级别
|
|
30
|
+
:param custom_handlers: 自定义 Handler 列表
|
|
31
|
+
"""
|
|
32
|
+
cache_key = (name, logger_cls)
|
|
33
|
+
with cls._lock:
|
|
34
|
+
if cache_key not in cls._logger_cache:
|
|
35
|
+
# 创建 logger
|
|
36
|
+
if logger_cls == logging.Logger:
|
|
37
|
+
logger = logging.getLogger(name)
|
|
38
|
+
else:
|
|
39
|
+
logger = logger_cls(name)
|
|
40
|
+
|
|
41
|
+
logger.setLevel(level)
|
|
42
|
+
logger.propagate = False # 不向 root logger 冒泡
|
|
43
|
+
|
|
44
|
+
# 添加控制台 handler
|
|
45
|
+
if add_console:
|
|
46
|
+
if not any(isinstance(h, RichHandler) for h in logger.handlers):
|
|
47
|
+
console_handler = RichHandler(rich_tracebacks=True)
|
|
48
|
+
console_formatter = logging.Formatter(
|
|
49
|
+
"%(message)s [%(name)s - %(asctime)s]",
|
|
50
|
+
datefmt="%Y-%m-%d %H:%M:%S"
|
|
51
|
+
)
|
|
52
|
+
console_handler.setFormatter(console_formatter)
|
|
53
|
+
logger.addHandler(console_handler)
|
|
54
|
+
|
|
55
|
+
# 添加文件 handler
|
|
56
|
+
if log_file:
|
|
57
|
+
if not any(isinstance(h, logging.FileHandler) for h in logger.handlers):
|
|
58
|
+
# handler = MultiProcessSafeSizeRotatingHandler(log_file, maxBytes=10*200, backupCount=3)
|
|
59
|
+
# file_handler = logging.FileHandler(log_file, encoding="utf-8")
|
|
60
|
+
|
|
61
|
+
handler = logging.FileHandler(log_file, encoding="utf-8")
|
|
62
|
+
file_formatter = logging.Formatter(
|
|
63
|
+
"%(asctime)s | %(name)s | %(levelname)s | %(message)s",
|
|
64
|
+
datefmt="%Y-%m-%d %H:%M:%S"
|
|
65
|
+
)
|
|
66
|
+
handler.setFormatter(file_formatter)
|
|
67
|
+
logger.addHandler(handler)
|
|
68
|
+
|
|
69
|
+
# 添加自定义 handler
|
|
70
|
+
if custom_handlers:
|
|
71
|
+
h = custom_handlers
|
|
72
|
+
if h not in logger.handlers:
|
|
73
|
+
file_formatter = logging.Formatter(
|
|
74
|
+
"%(asctime)s | %(name)s | %(levelname)s | %(message)s",
|
|
75
|
+
datefmt="%Y-%m-%d %H:%M:%S"
|
|
76
|
+
)
|
|
77
|
+
h.setFormatter(file_formatter)
|
|
78
|
+
logger.addHandler(h)
|
|
79
|
+
logger.addHandler(h)
|
|
80
|
+
|
|
81
|
+
cls._logger_cache[cache_key] = logger
|
|
82
|
+
|
|
83
|
+
return cls._logger_cache[cache_key]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# 全局可用的 get_logger 函数(无需引用 LogManager)
|
|
87
|
+
def get_logger(
|
|
88
|
+
name: str=None,
|
|
89
|
+
logger_cls: Type[logging.Logger] = logging.Logger,
|
|
90
|
+
log_file: Optional[str] = None,
|
|
91
|
+
add_console: bool = True,
|
|
92
|
+
level: int = logging.INFO,
|
|
93
|
+
custom_handlers: Optional[List[logging.Handler]] = None,
|
|
94
|
+
):
|
|
95
|
+
"""
|
|
96
|
+
便捷函数:获取日志记录器,无需关心 LogManager 实例化。
|
|
97
|
+
|
|
98
|
+
使用示例:
|
|
99
|
+
from log_manager import get_logger
|
|
100
|
+
logger = get_logger("my_module", log_file="app.log")
|
|
101
|
+
logger.info("Hello world")
|
|
102
|
+
"""
|
|
103
|
+
if name is None:
|
|
104
|
+
name = "tmp_log"
|
|
105
|
+
|
|
106
|
+
return LogManager.get_logger(
|
|
107
|
+
name=name,
|
|
108
|
+
logger_cls=logger_cls,
|
|
109
|
+
log_file=log_file,
|
|
110
|
+
add_console=add_console,
|
|
111
|
+
level=level,
|
|
112
|
+
custom_handlers=custom_handlers
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# logger = LogManager().get_logger("tmp_log")
|
|
116
|
+
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mlog_util
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: 自用日志库
|
|
5
5
|
Classifier: Programming Language :: Python :: 3
|
|
6
6
|
Classifier: License :: OSI Approved :: MIT License
|
|
7
7
|
Classifier: Operating System :: OS Independent
|
|
8
8
|
Requires-Python: >=3.6
|
|
9
9
|
Requires-Dist: rich
|
|
10
|
+
Requires-Dist: portalocker
|
|
10
11
|
Dynamic: classifier
|
|
11
12
|
Dynamic: requires-dist
|
|
12
13
|
Dynamic: requires-python
|
|
@@ -2,9 +2,9 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name="mlog_util",
|
|
5
|
-
version="0.1.
|
|
5
|
+
version="0.1.3",
|
|
6
6
|
packages=find_packages(),
|
|
7
|
-
install_requires=["rich"], # 依赖库
|
|
7
|
+
install_requires=["rich", "portalocker"], # 依赖库
|
|
8
8
|
# author="may",
|
|
9
9
|
# author_email="no",
|
|
10
10
|
description="自用日志库",
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from rich.logging import RichHandler
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
def get_logger(name=None, log_file=None, add_console=True, level=logging.INFO):
|
|
6
|
-
logger = logging.getLogger(name=name)
|
|
7
|
-
logger.setLevel(level) # 日志级别
|
|
8
|
-
|
|
9
|
-
logger.propagate = False # 不向 root logger 冒泡
|
|
10
|
-
|
|
11
|
-
has_console = any(isinstance(h, RichHandler) for h in logger.handlers)
|
|
12
|
-
has_file = any(isinstance(h, logging.FileHandler) for h in logger.handlers)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if add_console and not has_console:
|
|
16
|
-
# 控制台 Rich 日志
|
|
17
|
-
console_handler = RichHandler(rich_tracebacks=True)
|
|
18
|
-
console_formatter = logging.Formatter(
|
|
19
|
-
"%(message)s [%(name)s - %(asctime)s]",
|
|
20
|
-
datefmt="%Y-%m-%d %H:%M:%S"
|
|
21
|
-
)
|
|
22
|
-
console_handler.setFormatter(console_formatter)
|
|
23
|
-
logger.addHandler(console_handler)
|
|
24
|
-
|
|
25
|
-
# 文件日志
|
|
26
|
-
if log_file and not has_file:
|
|
27
|
-
file_handler = logging.FileHandler(log_file, encoding="utf-8")
|
|
28
|
-
file_formatter = logging.Formatter(
|
|
29
|
-
"%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
30
|
-
datefmt="%Y-%m-%d %H:%M:%S"
|
|
31
|
-
)
|
|
32
|
-
file_handler.setFormatter(file_formatter)
|
|
33
|
-
logger.addHandler(file_handler)
|
|
34
|
-
|
|
35
|
-
return logger
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
logger = get_logger()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
rich
|
|
File without changes
|
|
File without changes
|
|
File without changes
|