tretool 0.2.1__py3-none-any.whl → 1.0.0__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.
- tretool/__init__.py +39 -12
- tretool/config.py +406 -170
- tretool/decoratorlib.py +423 -0
- tretool/encoding.py +404 -75
- tretool/httplib.py +730 -0
- tretool/jsonlib.py +619 -151
- tretool/logger.py +712 -0
- tretool/mathlib.py +0 -33
- tretool/path.py +19 -0
- tretool/platformlib.py +469 -314
- tretool/plugin.py +437 -237
- tretool/smartCache.py +569 -0
- tretool/tasklib.py +730 -0
- tretool/transform/docx.py +544 -0
- tretool/transform/pdf.py +273 -95
- tretool/ziplib.py +664 -0
- {tretool-0.2.1.dist-info → tretool-1.0.0.dist-info}/METADATA +11 -5
- tretool-1.0.0.dist-info/RECORD +24 -0
- tretool/markfunc.py +0 -152
- tretool/memorizeTools.py +0 -24
- tretool/writeLog.py +0 -69
- tretool-0.2.1.dist-info/RECORD +0 -20
- {tretool-0.2.1.dist-info → tretool-1.0.0.dist-info}/WHEEL +0 -0
- {tretool-0.2.1.dist-info → tretool-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {tretool-0.2.1.dist-info → tretool-1.0.0.dist-info}/top_level.txt +0 -0
tretool/logger.py
ADDED
@@ -0,0 +1,712 @@
|
|
1
|
+
import sys
|
2
|
+
import os
|
3
|
+
import datetime
|
4
|
+
import traceback
|
5
|
+
import threading
|
6
|
+
import gzip
|
7
|
+
import json
|
8
|
+
from typing import (
|
9
|
+
Any, Tuple, List, Optional, Union, TextIO, Callable, Dict,
|
10
|
+
TypeVar, Generic, Type
|
11
|
+
)
|
12
|
+
from enum import Enum, auto
|
13
|
+
from pathlib import Path
|
14
|
+
from dataclasses import dataclass, asdict
|
15
|
+
from abc import ABC, abstractmethod
|
16
|
+
from queue import Queue, Empty, Full
|
17
|
+
from collections import deque
|
18
|
+
import inspect
|
19
|
+
import socket
|
20
|
+
import uuid
|
21
|
+
|
22
|
+
# 类型变量
|
23
|
+
T = TypeVar('T')
|
24
|
+
|
25
|
+
class LogLevel(Enum):
|
26
|
+
"""日志级别枚举"""
|
27
|
+
DEBUG = auto()
|
28
|
+
INFO = auto()
|
29
|
+
WARNING = auto()
|
30
|
+
ERROR = auto()
|
31
|
+
CRITICAL = auto()
|
32
|
+
AUDIT = auto() # 审计日志
|
33
|
+
|
34
|
+
@classmethod
|
35
|
+
def from_string(cls, level_str: str) -> 'LogLevel':
|
36
|
+
"""从字符串转换日志级别"""
|
37
|
+
try:
|
38
|
+
return cls[level_str.upper()]
|
39
|
+
except KeyError:
|
40
|
+
raise ValueError(f"无效的日志级别: {level_str}") from None
|
41
|
+
|
42
|
+
@dataclass
|
43
|
+
class LogRecord:
|
44
|
+
"""日志记录数据结构"""
|
45
|
+
level: LogLevel
|
46
|
+
message: str
|
47
|
+
timestamp: str
|
48
|
+
logger_name: str
|
49
|
+
user: Optional[str] = None
|
50
|
+
module: Optional[str] = None
|
51
|
+
func_name: Optional[str] = None
|
52
|
+
lineno: Optional[int] = None
|
53
|
+
process_id: Optional[int] = None
|
54
|
+
thread_id: Optional[int] = None
|
55
|
+
hostname: Optional[str] = None
|
56
|
+
request_id: Optional[str] = None
|
57
|
+
extra: Optional[Dict[str, Any]] = None
|
58
|
+
exc_info: Optional[Tuple[Type[BaseException], BaseException, Any]] = None
|
59
|
+
|
60
|
+
class LogHandler(ABC):
|
61
|
+
"""日志处理器抽象基类"""
|
62
|
+
|
63
|
+
def __init__(self, level: LogLevel = LogLevel.INFO):
|
64
|
+
self.level = level
|
65
|
+
self._lock = threading.Lock()
|
66
|
+
self._formatter = self.default_formatter
|
67
|
+
|
68
|
+
@abstractmethod
|
69
|
+
def emit(self, record: LogRecord) -> None:
|
70
|
+
"""处理日志记录"""
|
71
|
+
pass
|
72
|
+
|
73
|
+
def set_level(self, level: LogLevel) -> None:
|
74
|
+
"""设置处理器级别"""
|
75
|
+
with self._lock:
|
76
|
+
self.level = level
|
77
|
+
|
78
|
+
def filter(self, record: LogRecord) -> bool:
|
79
|
+
"""过滤日志记录"""
|
80
|
+
return record.level.value >= self.level.value
|
81
|
+
|
82
|
+
@staticmethod
|
83
|
+
def default_formatter(record: LogRecord) -> str:
|
84
|
+
"""默认日志格式化方法"""
|
85
|
+
parts = [
|
86
|
+
f"[{record.timestamp}]",
|
87
|
+
f"({record.level.name})",
|
88
|
+
f"{record.logger_name}:",
|
89
|
+
]
|
90
|
+
|
91
|
+
if record.user:
|
92
|
+
parts.append(f"user={record.user}")
|
93
|
+
|
94
|
+
if record.module:
|
95
|
+
parts.append(f"module={record.module}")
|
96
|
+
|
97
|
+
if record.func_name:
|
98
|
+
parts.append(f"func={record.func_name}")
|
99
|
+
|
100
|
+
if record.lineno:
|
101
|
+
parts.append(f"line={record.lineno}")
|
102
|
+
|
103
|
+
parts.append(record.message)
|
104
|
+
|
105
|
+
if record.exc_info:
|
106
|
+
exc_type, exc_value, exc_tb = record.exc_info
|
107
|
+
tb_lines = traceback.format_exception(exc_type, exc_value, exc_tb)
|
108
|
+
parts.append('\n' + ''.join(tb_lines).strip())
|
109
|
+
|
110
|
+
return ' '.join(parts)
|
111
|
+
|
112
|
+
def format(self, record: LogRecord) -> str:
|
113
|
+
"""格式化日志记录"""
|
114
|
+
return self.default_formatter(record)
|
115
|
+
|
116
|
+
def close(self) -> None:
|
117
|
+
"""关闭处理器"""
|
118
|
+
pass
|
119
|
+
|
120
|
+
class StreamHandler(LogHandler):
|
121
|
+
"""流处理器(控制台输出)"""
|
122
|
+
|
123
|
+
def __init__(
|
124
|
+
self,
|
125
|
+
stream: TextIO = sys.stdout,
|
126
|
+
level: LogLevel = LogLevel.INFO,
|
127
|
+
formatter: Optional[Callable[[LogRecord], str]] = None
|
128
|
+
):
|
129
|
+
super().__init__(level)
|
130
|
+
self.stream = stream
|
131
|
+
if formatter is not None:
|
132
|
+
self._formatter = formatter
|
133
|
+
|
134
|
+
def emit(self, record: LogRecord) -> None:
|
135
|
+
"""输出日志到流"""
|
136
|
+
if not self.filter(record):
|
137
|
+
return
|
138
|
+
|
139
|
+
formatted = self.format(record)
|
140
|
+
|
141
|
+
with self._lock:
|
142
|
+
try:
|
143
|
+
self.stream.write(formatted + '\n')
|
144
|
+
self.stream.flush()
|
145
|
+
except (ValueError, AttributeError):
|
146
|
+
# 流已关闭
|
147
|
+
pass
|
148
|
+
|
149
|
+
def close(self) -> None:
|
150
|
+
"""关闭流"""
|
151
|
+
if hasattr(self.stream, 'close') and self.stream not in (sys.stdout, sys.stderr):
|
152
|
+
self.stream.close()
|
153
|
+
|
154
|
+
class FileHandler(LogHandler):
|
155
|
+
"""文件处理器"""
|
156
|
+
|
157
|
+
def __init__(
|
158
|
+
self,
|
159
|
+
filename: Union[str, Path],
|
160
|
+
mode: str = 'a',
|
161
|
+
encoding: str = 'utf-8',
|
162
|
+
level: LogLevel = LogLevel.INFO,
|
163
|
+
formatter: Optional[Callable[[LogRecord], str]] = None,
|
164
|
+
delay: bool = False
|
165
|
+
):
|
166
|
+
super().__init__(level)
|
167
|
+
self.filename = Path(filename)
|
168
|
+
self.mode = mode
|
169
|
+
self.encoding = encoding
|
170
|
+
self.delay = delay
|
171
|
+
self._file: Optional[TextIO] = None
|
172
|
+
if formatter is not None:
|
173
|
+
self._formatter = formatter
|
174
|
+
|
175
|
+
if not delay:
|
176
|
+
self._open_file()
|
177
|
+
|
178
|
+
def _open_file(self) -> None:
|
179
|
+
"""打开日志文件"""
|
180
|
+
self.filename.parent.mkdir(parents=True, exist_ok=True)
|
181
|
+
self._file = open(self.filename, self.mode, encoding=self.encoding)
|
182
|
+
|
183
|
+
def emit(self, record: LogRecord) -> None:
|
184
|
+
"""写入日志到文件"""
|
185
|
+
if not self.filter(record):
|
186
|
+
return
|
187
|
+
|
188
|
+
if self._file is None and self.delay:
|
189
|
+
self._open_file()
|
190
|
+
|
191
|
+
formatted = self.format(record)
|
192
|
+
|
193
|
+
with self._lock:
|
194
|
+
if self._file is not None:
|
195
|
+
try:
|
196
|
+
self._file.write(formatted + '\n')
|
197
|
+
self._file.flush()
|
198
|
+
except (OSError, ValueError):
|
199
|
+
# 文件已关闭或不可写
|
200
|
+
self._file = None
|
201
|
+
|
202
|
+
def close(self) -> None:
|
203
|
+
"""关闭文件"""
|
204
|
+
if self._file is not None:
|
205
|
+
with self._lock:
|
206
|
+
if self._file is not None:
|
207
|
+
self._file.close()
|
208
|
+
self._file = None
|
209
|
+
|
210
|
+
class RotatingFileHandler(FileHandler):
|
211
|
+
"""滚动文件处理器"""
|
212
|
+
|
213
|
+
def __init__(
|
214
|
+
self,
|
215
|
+
filename: Union[str, Path],
|
216
|
+
max_bytes: int = 10 * 1024 * 1024, # 10MB
|
217
|
+
backup_count: int = 5,
|
218
|
+
mode: str = 'a',
|
219
|
+
encoding: str = 'utf-8',
|
220
|
+
level: LogLevel = LogLevel.INFO,
|
221
|
+
formatter: Optional[Callable[[LogRecord], str]] = None,
|
222
|
+
delay: bool = False
|
223
|
+
):
|
224
|
+
super().__init__(filename, mode, encoding, level, formatter, delay)
|
225
|
+
self.max_bytes = max_bytes
|
226
|
+
self.backup_count = backup_count
|
227
|
+
|
228
|
+
def emit(self, record: LogRecord) -> None:
|
229
|
+
"""处理日志记录,必要时滚动文件"""
|
230
|
+
if not self.filter(record):
|
231
|
+
return
|
232
|
+
|
233
|
+
if self._file is None and self.delay:
|
234
|
+
self._open_file()
|
235
|
+
|
236
|
+
formatted = self.format(record)
|
237
|
+
|
238
|
+
with self._lock:
|
239
|
+
if self._file is not None:
|
240
|
+
try:
|
241
|
+
# 检查是否需要滚动
|
242
|
+
if self._file.tell() + len(formatted) > self.max_bytes:
|
243
|
+
self._do_rollover()
|
244
|
+
|
245
|
+
self._file.write(formatted + '\n')
|
246
|
+
self._file.flush()
|
247
|
+
except (OSError, ValueError):
|
248
|
+
# 文件已关闭或不可写
|
249
|
+
self._file = None
|
250
|
+
|
251
|
+
def _do_rollover(self) -> None:
|
252
|
+
"""执行文件滚动"""
|
253
|
+
if self._file is not None:
|
254
|
+
self._file.close()
|
255
|
+
self._file = None
|
256
|
+
|
257
|
+
# 重命名现有日志文件
|
258
|
+
for i in range(self.backup_count - 1, 0, -1):
|
259
|
+
src = self.filename.with_suffix(f'.{i}')
|
260
|
+
dst = self.filename.with_suffix(f'.{i+1}')
|
261
|
+
if src.exists():
|
262
|
+
if dst.exists():
|
263
|
+
dst.unlink()
|
264
|
+
src.rename(dst)
|
265
|
+
|
266
|
+
# 重命名主日志文件
|
267
|
+
dst = self.filename.with_suffix('.1')
|
268
|
+
if dst.exists():
|
269
|
+
dst.unlink()
|
270
|
+
if self.filename.exists():
|
271
|
+
self.filename.rename(dst)
|
272
|
+
|
273
|
+
# 重新打开主日志文件
|
274
|
+
self._open_file()
|
275
|
+
|
276
|
+
class JSONFormatter:
|
277
|
+
"""JSON格式化器"""
|
278
|
+
|
279
|
+
def __call__(self, record: LogRecord) -> str:
|
280
|
+
"""将日志记录转换为JSON字符串"""
|
281
|
+
record_dict = asdict(record)
|
282
|
+
record_dict['level'] = record.level.name
|
283
|
+
if record.exc_info:
|
284
|
+
exc_type, exc_value, exc_tb = record.exc_info
|
285
|
+
record_dict['exception'] = {
|
286
|
+
'type': exc_type.__name__,
|
287
|
+
'message': str(exc_value),
|
288
|
+
'traceback': traceback.format_exception(exc_type, exc_value, exc_tb)
|
289
|
+
}
|
290
|
+
return json.dumps(record_dict, ensure_ascii=False)
|
291
|
+
|
292
|
+
class Logger:
|
293
|
+
"""
|
294
|
+
终极版日志记录器
|
295
|
+
|
296
|
+
特性:
|
297
|
+
- 多级别日志记录
|
298
|
+
- 多处理器支持
|
299
|
+
- 线程安全
|
300
|
+
- 异步日志支持
|
301
|
+
- 上下文信息记录
|
302
|
+
- 结构化日志(JSON)
|
303
|
+
- 日志轮转
|
304
|
+
- 审计日志
|
305
|
+
"""
|
306
|
+
|
307
|
+
_loggers: Dict[str, 'Logger'] = {}
|
308
|
+
_lock = threading.Lock()
|
309
|
+
|
310
|
+
def __init__(
|
311
|
+
self,
|
312
|
+
name: str,
|
313
|
+
level: LogLevel = LogLevel.INFO,
|
314
|
+
handlers: Optional[List[LogHandler]] = None,
|
315
|
+
propagate: bool = True,
|
316
|
+
async_mode: bool = False,
|
317
|
+
queue_size: int = 1000
|
318
|
+
):
|
319
|
+
"""
|
320
|
+
初始化日志记录器
|
321
|
+
|
322
|
+
参数:
|
323
|
+
name: 日志器名称
|
324
|
+
level: 日志级别阈值
|
325
|
+
handlers: 日志处理器列表
|
326
|
+
propagate: 是否传播到父日志器
|
327
|
+
async_mode: 是否启用异步模式
|
328
|
+
queue_size: 异步队列大小
|
329
|
+
"""
|
330
|
+
self.name = name
|
331
|
+
self.level = level
|
332
|
+
self.handlers = handlers or []
|
333
|
+
self.propagate = propagate
|
334
|
+
self.parent: Optional['Logger'] = None
|
335
|
+
self._async_mode = async_mode
|
336
|
+
self._queue: Optional[Queue] = None
|
337
|
+
self._worker_thread: Optional[threading.Thread] = None
|
338
|
+
self._stop_event = threading.Event()
|
339
|
+
|
340
|
+
if async_mode:
|
341
|
+
self._init_async_logging(queue_size)
|
342
|
+
|
343
|
+
def _init_async_logging(self, queue_size: int) -> None:
|
344
|
+
"""初始化异步日志记录"""
|
345
|
+
self._queue = Queue(maxsize=queue_size)
|
346
|
+
self._worker_thread = threading.Thread(
|
347
|
+
target=self._process_log_records,
|
348
|
+
name=f"Logger-{self.name}-Worker",
|
349
|
+
daemon=True
|
350
|
+
)
|
351
|
+
self._worker_thread.start()
|
352
|
+
|
353
|
+
def _process_log_records(self) -> None:
|
354
|
+
"""处理日志记录的线程函数"""
|
355
|
+
while not self._stop_event.is_set():
|
356
|
+
try:
|
357
|
+
record = self._queue.get(timeout=0.1)
|
358
|
+
self._handle_record(record)
|
359
|
+
except Empty:
|
360
|
+
continue
|
361
|
+
except Exception:
|
362
|
+
# 防止工作线程因异常退出
|
363
|
+
continue
|
364
|
+
|
365
|
+
def _handle_record(self, record: LogRecord) -> None:
|
366
|
+
"""处理日志记录"""
|
367
|
+
for handler in self.handlers:
|
368
|
+
try:
|
369
|
+
handler.emit(record)
|
370
|
+
except Exception:
|
371
|
+
# 处理器错误不应中断日志系统
|
372
|
+
continue
|
373
|
+
|
374
|
+
# 传播到父日志器
|
375
|
+
if self.propagate and self.parent is not None:
|
376
|
+
self.parent._handle_record(record)
|
377
|
+
|
378
|
+
def _make_record(
|
379
|
+
self,
|
380
|
+
level: LogLevel,
|
381
|
+
message: str,
|
382
|
+
user: Optional[str] = None,
|
383
|
+
extra: Optional[Dict[str, Any]] = None,
|
384
|
+
exc_info: Optional[Tuple[Type[BaseException], BaseException, Any]] = None,
|
385
|
+
stack_level: int = 1
|
386
|
+
) -> LogRecord:
|
387
|
+
"""创建日志记录对象"""
|
388
|
+
# 获取调用者信息
|
389
|
+
frame = inspect.currentframe()
|
390
|
+
for _ in range(stack_level + 1):
|
391
|
+
if frame is None:
|
392
|
+
break
|
393
|
+
frame = frame.f_back
|
394
|
+
|
395
|
+
module = None
|
396
|
+
func_name = None
|
397
|
+
lineno = None
|
398
|
+
|
399
|
+
if frame is not None:
|
400
|
+
module = inspect.getmodule(frame)
|
401
|
+
if module is not None:
|
402
|
+
module = module.__name__
|
403
|
+
func_name = frame.f_code.co_name
|
404
|
+
lineno = frame.f_lineno
|
405
|
+
|
406
|
+
# 获取进程和线程信息
|
407
|
+
process_id = os.getpid()
|
408
|
+
thread_id = threading.get_ident()
|
409
|
+
|
410
|
+
# 获取主机名
|
411
|
+
try:
|
412
|
+
hostname = socket.gethostname()
|
413
|
+
except:
|
414
|
+
hostname = None
|
415
|
+
|
416
|
+
return LogRecord(
|
417
|
+
level=level,
|
418
|
+
message=message,
|
419
|
+
timestamp=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f'),
|
420
|
+
logger_name=self.name,
|
421
|
+
user=user,
|
422
|
+
module=module,
|
423
|
+
func_name=func_name,
|
424
|
+
lineno=lineno,
|
425
|
+
process_id=process_id,
|
426
|
+
thread_id=thread_id,
|
427
|
+
hostname=hostname,
|
428
|
+
request_id=Logger.get_request_id(),
|
429
|
+
extra=extra,
|
430
|
+
exc_info=exc_info
|
431
|
+
)
|
432
|
+
|
433
|
+
@staticmethod
|
434
|
+
def get_request_id() -> str:
|
435
|
+
"""获取当前请求ID(用于分布式追踪)"""
|
436
|
+
# 可以从线程局部存储或上下文获取
|
437
|
+
return str(uuid.uuid4())
|
438
|
+
|
439
|
+
def add_handler(self, handler: LogHandler) -> None:
|
440
|
+
"""添加日志处理器"""
|
441
|
+
with self._lock:
|
442
|
+
self.handlers.append(handler)
|
443
|
+
|
444
|
+
def remove_handler(self, handler: LogHandler) -> None:
|
445
|
+
"""移除日志处理器"""
|
446
|
+
with self._lock:
|
447
|
+
if handler in self.handlers:
|
448
|
+
self.handlers.remove(handler)
|
449
|
+
|
450
|
+
def set_level(self, level: LogLevel) -> None:
|
451
|
+
"""设置日志级别"""
|
452
|
+
with self._lock:
|
453
|
+
self.level = level
|
454
|
+
|
455
|
+
def log(
|
456
|
+
self,
|
457
|
+
level: LogLevel,
|
458
|
+
message: str,
|
459
|
+
user: Optional[str] = None,
|
460
|
+
extra: Optional[Dict[str, Any]] = None,
|
461
|
+
exc_info: Optional[Tuple[Type[BaseException], BaseException, Any]] = None,
|
462
|
+
stack_level: int = 1
|
463
|
+
) -> None:
|
464
|
+
"""记录日志"""
|
465
|
+
if level.value < self.level.value:
|
466
|
+
return
|
467
|
+
|
468
|
+
record = self._make_record(level, message, user, extra, exc_info, stack_level + 1)
|
469
|
+
|
470
|
+
if self._async_mode and self._queue is not None:
|
471
|
+
try:
|
472
|
+
self._queue.put_nowait(record)
|
473
|
+
except Full:
|
474
|
+
# 队列已满,同步处理
|
475
|
+
self._handle_record(record)
|
476
|
+
else:
|
477
|
+
self._handle_record(record)
|
478
|
+
|
479
|
+
def debug(
|
480
|
+
self,
|
481
|
+
message: str,
|
482
|
+
user: Optional[str] = None,
|
483
|
+
extra: Optional[Dict[str, Any]] = None,
|
484
|
+
stack_level: int = 0
|
485
|
+
) -> None:
|
486
|
+
"""记录DEBUG级别日志"""
|
487
|
+
self.log(LogLevel.DEBUG, message, user, extra, None, stack_level + 1)
|
488
|
+
|
489
|
+
def info(
|
490
|
+
self,
|
491
|
+
message: str,
|
492
|
+
user: Optional[str] = None,
|
493
|
+
extra: Optional[Dict[str, Any]] = None,
|
494
|
+
stack_level: int = 0
|
495
|
+
) -> None:
|
496
|
+
"""记录INFO级别日志"""
|
497
|
+
self.log(LogLevel.INFO, message, user, extra, None, stack_level + 1)
|
498
|
+
|
499
|
+
def warning(
|
500
|
+
self,
|
501
|
+
message: str,
|
502
|
+
user: Optional[str] = None,
|
503
|
+
extra: Optional[Dict[str, Any]] = None,
|
504
|
+
stack_level: int = 0
|
505
|
+
) -> None:
|
506
|
+
"""记录WARNING级别日志"""
|
507
|
+
self.log(LogLevel.WARNING, message, user, extra, None, stack_level + 1)
|
508
|
+
|
509
|
+
def error(
|
510
|
+
self,
|
511
|
+
message: str,
|
512
|
+
user: Optional[str] = None,
|
513
|
+
extra: Optional[Dict[str, Any]] = None,
|
514
|
+
exc_info: Optional[Tuple[Type[BaseException], BaseException, Any]] = None,
|
515
|
+
stack_level: int = 0
|
516
|
+
) -> None:
|
517
|
+
"""记录ERROR级别日志"""
|
518
|
+
self.log(LogLevel.ERROR, message, user, extra, exc_info, stack_level + 1)
|
519
|
+
|
520
|
+
def critical(
|
521
|
+
self,
|
522
|
+
message: str,
|
523
|
+
user: Optional[str] = None,
|
524
|
+
extra: Optional[Dict[str, Any]] = None,
|
525
|
+
exc_info: Optional[Tuple[Type[BaseException], BaseException, Any]] = None,
|
526
|
+
stack_level: int = 0
|
527
|
+
) -> None:
|
528
|
+
"""记录CRITICAL级别日志"""
|
529
|
+
self.log(LogLevel.CRITICAL, message, user, extra, exc_info, stack_level + 1)
|
530
|
+
|
531
|
+
def audit(
|
532
|
+
self,
|
533
|
+
message: str,
|
534
|
+
user: Optional[str] = None,
|
535
|
+
extra: Optional[Dict[str, Any]] = None,
|
536
|
+
stack_level: int = 0
|
537
|
+
) -> None:
|
538
|
+
"""记录审计日志"""
|
539
|
+
self.log(LogLevel.AUDIT, message, user, extra, None, stack_level + 1)
|
540
|
+
|
541
|
+
def exception(
|
542
|
+
self,
|
543
|
+
message: str,
|
544
|
+
user: Optional[str] = None,
|
545
|
+
extra: Optional[Dict[str, Any]] = None,
|
546
|
+
stack_level: int = 0
|
547
|
+
) -> None:
|
548
|
+
"""记录当前异常"""
|
549
|
+
exc_info = sys.exc_info()
|
550
|
+
if exc_info[0] is not None:
|
551
|
+
self.log(LogLevel.ERROR, message, user, extra, exc_info, stack_level + 1)
|
552
|
+
|
553
|
+
def close(self) -> None:
|
554
|
+
"""关闭日志记录器"""
|
555
|
+
if self._async_mode:
|
556
|
+
self._stop_event.set()
|
557
|
+
if self._worker_thread is not None:
|
558
|
+
self._worker_thread.join(timeout=5)
|
559
|
+
|
560
|
+
# 处理队列中剩余日志
|
561
|
+
if self._queue is not None:
|
562
|
+
while not self._queue.empty():
|
563
|
+
try:
|
564
|
+
record = self._queue.get_nowait()
|
565
|
+
self._handle_record(record)
|
566
|
+
except Empty:
|
567
|
+
break
|
568
|
+
|
569
|
+
for handler in self.handlers:
|
570
|
+
handler.close()
|
571
|
+
|
572
|
+
@classmethod
|
573
|
+
def get_logger(
|
574
|
+
cls,
|
575
|
+
name: str = 'root',
|
576
|
+
level: Optional[LogLevel] = None,
|
577
|
+
handlers: Optional[List[LogHandler]] = None,
|
578
|
+
propagate: bool = True,
|
579
|
+
async_mode: bool = False,
|
580
|
+
queue_size: int = 1000
|
581
|
+
) -> 'Logger':
|
582
|
+
"""获取或创建日志记录器"""
|
583
|
+
with cls._lock:
|
584
|
+
if name in cls._loggers:
|
585
|
+
logger = cls._loggers[name]
|
586
|
+
# 更新配置
|
587
|
+
if level is not None:
|
588
|
+
logger.set_level(level)
|
589
|
+
if handlers is not None:
|
590
|
+
logger.handlers = handlers
|
591
|
+
logger.propagate = propagate
|
592
|
+
return logger
|
593
|
+
else:
|
594
|
+
# 创建层次结构
|
595
|
+
parts = name.split('.')
|
596
|
+
parent = None
|
597
|
+
|
598
|
+
for i in range(1, len(parts)):
|
599
|
+
parent_name = '.'.join(parts[:i])
|
600
|
+
if parent_name not in cls._loggers:
|
601
|
+
cls._loggers[parent_name] = Logger(
|
602
|
+
name=parent_name,
|
603
|
+
level=LogLevel.INFO,
|
604
|
+
propagate=True
|
605
|
+
)
|
606
|
+
|
607
|
+
if len(parts) > 1:
|
608
|
+
parent_name = '.'.join(parts[:-1])
|
609
|
+
parent = cls._loggers.get(parent_name)
|
610
|
+
|
611
|
+
logger = Logger(
|
612
|
+
name=name,
|
613
|
+
level=level or LogLevel.INFO,
|
614
|
+
handlers=handlers,
|
615
|
+
propagate=propagate,
|
616
|
+
async_mode=async_mode,
|
617
|
+
queue_size=queue_size
|
618
|
+
)
|
619
|
+
logger.parent = parent
|
620
|
+
cls._loggers[name] = logger
|
621
|
+
return logger
|
622
|
+
|
623
|
+
@classmethod
|
624
|
+
def shutdown(cls) -> None:
|
625
|
+
"""关闭所有日志记录器"""
|
626
|
+
with cls._lock:
|
627
|
+
for logger in cls._loggers.values():
|
628
|
+
logger.close()
|
629
|
+
cls._loggers.clear()
|
630
|
+
|
631
|
+
# 全局默认日志记录器
|
632
|
+
_default_logger = Logger.get_logger('root')
|
633
|
+
|
634
|
+
# 便捷函数
|
635
|
+
def debug(message: str, user: Optional[str] = None, **kwargs) -> None:
|
636
|
+
"""记录DEBUG级别日志"""
|
637
|
+
_default_logger.debug(message, user, kwargs)
|
638
|
+
|
639
|
+
def info(message: str, user: Optional[str] = None, **kwargs) -> None:
|
640
|
+
"""记录INFO级别日志"""
|
641
|
+
_default_logger.info(message, user, kwargs)
|
642
|
+
|
643
|
+
def warning(message: str, user: Optional[str] = None, **kwargs) -> None:
|
644
|
+
"""记录WARNING级别日志"""
|
645
|
+
_default_logger.warning(message, user, kwargs)
|
646
|
+
|
647
|
+
def error(message: str, user: Optional[str] = None, **kwargs) -> None:
|
648
|
+
"""记录ERROR级别日志"""
|
649
|
+
_default_logger.error(message, user, kwargs)
|
650
|
+
|
651
|
+
def critical(message: str, user: Optional[str] = None, **kwargs) -> None:
|
652
|
+
"""记录CRITICAL级别日志"""
|
653
|
+
_default_logger.critical(message, user, kwargs)
|
654
|
+
|
655
|
+
def audit(message: str, user: Optional[str] = None, **kwargs) -> None:
|
656
|
+
"""记录审计日志"""
|
657
|
+
_default_logger.audit(message, user, kwargs)
|
658
|
+
|
659
|
+
def exception(message: str, user: Optional[str] = None, **kwargs) -> None:
|
660
|
+
"""记录当前异常"""
|
661
|
+
_default_logger.exception(message, user, kwargs)
|
662
|
+
|
663
|
+
def get_logger(name: str = 'root', **kwargs) -> Logger:
|
664
|
+
"""获取命名的日志记录器"""
|
665
|
+
return Logger.get_logger(name, **kwargs)
|
666
|
+
|
667
|
+
def setup_default_logger(
|
668
|
+
level: Union[LogLevel, str] = LogLevel.INFO,
|
669
|
+
filename: Optional[Union[str, Path]] = None,
|
670
|
+
console: bool = True,
|
671
|
+
json_format: bool = False,
|
672
|
+
max_bytes: Optional[int] = None,
|
673
|
+
backup_count: int = 5,
|
674
|
+
async_mode: bool = False,
|
675
|
+
queue_size: int = 1000
|
676
|
+
) -> None:
|
677
|
+
"""设置全局默认日志记录器"""
|
678
|
+
global _default_logger
|
679
|
+
|
680
|
+
if isinstance(level, str):
|
681
|
+
level = LogLevel.from_string(level)
|
682
|
+
|
683
|
+
handlers = []
|
684
|
+
formatter = JSONFormatter() if json_format else None
|
685
|
+
|
686
|
+
if console:
|
687
|
+
handlers.append(StreamHandler(formatter=formatter))
|
688
|
+
|
689
|
+
if filename is not None:
|
690
|
+
if max_bytes is not None:
|
691
|
+
handlers.append(
|
692
|
+
RotatingFileHandler(
|
693
|
+
filename,
|
694
|
+
max_bytes=max_bytes,
|
695
|
+
backup_count=backup_count,
|
696
|
+
formatter=formatter
|
697
|
+
)
|
698
|
+
)
|
699
|
+
else:
|
700
|
+
handlers.append(FileHandler(filename, formatter=formatter))
|
701
|
+
|
702
|
+
_default_logger = Logger.get_logger(
|
703
|
+
'root',
|
704
|
+
level=level,
|
705
|
+
handlers=handlers,
|
706
|
+
async_mode=async_mode,
|
707
|
+
queue_size=queue_size
|
708
|
+
)
|
709
|
+
|
710
|
+
def shutdown() -> None:
|
711
|
+
"""关闭日志系统"""
|
712
|
+
Logger.shutdown()
|