reykit 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.
- reykit/__init__.py +41 -0
- reykit/rall.py +33 -0
- reykit/rcomm.py +431 -0
- reykit/rdata.py +395 -0
- reykit/rdll/__init__.py +17 -0
- reykit/rdll/rdll_inject.py +41 -0
- reykit/rdll/rdll_inject_core.py +202 -0
- reykit/remail.py +276 -0
- reykit/rexception.py +339 -0
- reykit/rimage.py +261 -0
- reykit/rlog.py +1061 -0
- reykit/rmonkey.py +341 -0
- reykit/rmultitask.py +871 -0
- reykit/rnumber.py +161 -0
- reykit/ros.py +1917 -0
- reykit/rrandom.py +351 -0
- reykit/rregex.py +293 -0
- reykit/rschedule.py +272 -0
- reykit/rstdout.py +356 -0
- reykit/rsystem.py +1180 -0
- reykit/rtable.py +511 -0
- reykit/rtext.py +458 -0
- reykit/rtime.py +678 -0
- reykit/rtype.py +106 -0
- reykit/rwrap.py +613 -0
- reykit/rzip.py +137 -0
- reykit-1.0.0.dist-info/METADATA +29 -0
- reykit-1.0.0.dist-info/RECORD +30 -0
- reykit-1.0.0.dist-info/WHEEL +5 -0
- reykit-1.0.0.dist-info/top_level.txt +1 -0
reykit/rlog.py
ADDED
@@ -0,0 +1,1061 @@
|
|
1
|
+
# !/usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
"""
|
5
|
+
@Time : 2023-10-08 21:26:43
|
6
|
+
@Author : Rey
|
7
|
+
@Contact : reyxbo@163.com
|
8
|
+
@Explain : Log methods.
|
9
|
+
"""
|
10
|
+
|
11
|
+
|
12
|
+
from __future__ import annotations
|
13
|
+
from typing import Any, Optional, Union, Literal, Final, NoReturn, overload, override
|
14
|
+
from collections.abc import Callable
|
15
|
+
from queue import Queue
|
16
|
+
from os.path import abspath as os_abspath
|
17
|
+
from logging import (
|
18
|
+
getLogger,
|
19
|
+
Handler,
|
20
|
+
StreamHandler,
|
21
|
+
Formatter,
|
22
|
+
Filter,
|
23
|
+
LogRecord,
|
24
|
+
DEBUG as LDEBUG,
|
25
|
+
INFO as LINFO,
|
26
|
+
WARNING as LWARNING,
|
27
|
+
ERROR as LERROR,
|
28
|
+
CRITICAL as LCRITICAL
|
29
|
+
)
|
30
|
+
from logging.handlers import QueueHandler
|
31
|
+
from concurrent_log_handler import ConcurrentRotatingFileHandler, ConcurrentTimedRotatingFileHandler
|
32
|
+
|
33
|
+
from .rexception import throw, catch_exc
|
34
|
+
from .ros import RFile
|
35
|
+
from .rregex import search, sub
|
36
|
+
from .rstdout import RConfigStdout, modify_print, reset_print
|
37
|
+
from .rsystem import get_first_notnull, get_stack_param
|
38
|
+
from .rtext import to_text
|
39
|
+
from .rtime import now, time_to
|
40
|
+
from .rtype import RConfigMeta
|
41
|
+
from .rwrap import wrap_thread
|
42
|
+
|
43
|
+
|
44
|
+
__all__ = (
|
45
|
+
'RConfigLog',
|
46
|
+
'RLog',
|
47
|
+
'RRecord'
|
48
|
+
)
|
49
|
+
|
50
|
+
|
51
|
+
class RConfigLog(object, metaclass=RConfigMeta):
|
52
|
+
"""
|
53
|
+
Rey's `config log` type.
|
54
|
+
"""
|
55
|
+
|
56
|
+
# Module path.
|
57
|
+
path_rlog: Final[str] = os_abspath(__file__)
|
58
|
+
|
59
|
+
|
60
|
+
class RLog(object):
|
61
|
+
"""
|
62
|
+
Rey's `log` type.
|
63
|
+
"""
|
64
|
+
|
65
|
+
# Status.
|
66
|
+
print_replaced: bool = False
|
67
|
+
|
68
|
+
# Default value.
|
69
|
+
default_format = (
|
70
|
+
'%(format_time)s | '
|
71
|
+
'%(format_levelname)s | '
|
72
|
+
'%(format_path)s | '
|
73
|
+
'%(format_message)s'
|
74
|
+
)
|
75
|
+
default_format_date = '%Y-%m-%d %H:%M:%S'
|
76
|
+
default_format_width = 100
|
77
|
+
|
78
|
+
# Whether print colour.
|
79
|
+
print_colour: bool = True
|
80
|
+
|
81
|
+
# Level.
|
82
|
+
DEBUG = LDEBUG
|
83
|
+
INFO = LINFO
|
84
|
+
WARNING = LWARNING
|
85
|
+
ERROR = LERROR
|
86
|
+
CRITICAL = LCRITICAL
|
87
|
+
|
88
|
+
|
89
|
+
def __init__(
|
90
|
+
self,
|
91
|
+
name: str = 'Log'
|
92
|
+
) -> None:
|
93
|
+
"""
|
94
|
+
Build `log` attributes.
|
95
|
+
|
96
|
+
Parameters
|
97
|
+
----------
|
98
|
+
name : Log name. When log name existed, then direct return, otherwise build.
|
99
|
+
"""
|
100
|
+
|
101
|
+
# Set attribute.
|
102
|
+
self.name: Final[str] = name
|
103
|
+
self.stoped = False
|
104
|
+
|
105
|
+
# Get logger.
|
106
|
+
self.logger = getLogger(name)
|
107
|
+
|
108
|
+
# Set level.
|
109
|
+
self.logger.setLevel(self.DEBUG)
|
110
|
+
|
111
|
+
|
112
|
+
def _get_message_stack(self) -> dict:
|
113
|
+
"""
|
114
|
+
Get message stack parameters.
|
115
|
+
|
116
|
+
Returns
|
117
|
+
-------
|
118
|
+
Stack parameters.
|
119
|
+
"""
|
120
|
+
|
121
|
+
# Get parameter.
|
122
|
+
stack_params = get_stack_param('full', 12)
|
123
|
+
stack_param = stack_params[-1]
|
124
|
+
|
125
|
+
# Compatible.
|
126
|
+
|
127
|
+
## Compatible '__call__'.
|
128
|
+
if (
|
129
|
+
stack_param['filename'] == RConfigLog.path_rlog
|
130
|
+
and stack_param['name'] in ('debug', 'info', 'warning', 'error', 'critical')
|
131
|
+
):
|
132
|
+
stack_param = stack_params[-2]
|
133
|
+
|
134
|
+
## Compatible 'print'.
|
135
|
+
if (
|
136
|
+
stack_param['filename'] == RConfigLog.path_rlog
|
137
|
+
and stack_param['name'] == 'preprocess'
|
138
|
+
):
|
139
|
+
stack_param = stack_params[-3]
|
140
|
+
|
141
|
+
## Compatible 'echo'.
|
142
|
+
if (
|
143
|
+
stack_param['filename'] == RConfigStdout._path_rstdout
|
144
|
+
and stack_param['name'] == 'echo'
|
145
|
+
):
|
146
|
+
stack_param = stack_params[-4]
|
147
|
+
|
148
|
+
return stack_param
|
149
|
+
|
150
|
+
|
151
|
+
def _supply_format_standard(
|
152
|
+
self,
|
153
|
+
format_: str,
|
154
|
+
record: LogRecord
|
155
|
+
) -> None:
|
156
|
+
"""
|
157
|
+
Supply format standard parameters.
|
158
|
+
|
159
|
+
Parameters
|
160
|
+
----------
|
161
|
+
format_ : Record format.
|
162
|
+
record : Log record instance.
|
163
|
+
"""
|
164
|
+
|
165
|
+
# Format 'format_time'.
|
166
|
+
if '%(format_time)s' in format_:
|
167
|
+
datetime = now()
|
168
|
+
datetime_str = time_to(datetime, True)
|
169
|
+
record.format_time = datetime_str[:-3]
|
170
|
+
|
171
|
+
# Format 'format_levelname'.
|
172
|
+
if '%(format_levelname)s' in format_:
|
173
|
+
record.format_levelname = record.levelname.ljust(8)
|
174
|
+
|
175
|
+
# Format 'format_path'.
|
176
|
+
if '%(format_path)s' in format_:
|
177
|
+
message_stack = self._get_message_stack()
|
178
|
+
record.format_path = '%s:%s' % (
|
179
|
+
message_stack['filename'],
|
180
|
+
message_stack['lineno']
|
181
|
+
)
|
182
|
+
|
183
|
+
# Format 'format_message'.
|
184
|
+
if '%(format_message)s' in format_:
|
185
|
+
record.format_message = record.getMessage()
|
186
|
+
|
187
|
+
|
188
|
+
def get_level_color_ansi(
|
189
|
+
self,
|
190
|
+
level: int
|
191
|
+
) -> str:
|
192
|
+
"""
|
193
|
+
Get level color `ANSI` code.
|
194
|
+
|
195
|
+
Parameters
|
196
|
+
----------
|
197
|
+
level : Record level.
|
198
|
+
|
199
|
+
Returns
|
200
|
+
-------
|
201
|
+
Level color ansi code.
|
202
|
+
"""
|
203
|
+
|
204
|
+
# Set parameters.
|
205
|
+
color_code_dict = {
|
206
|
+
10: '\033[1;34m',
|
207
|
+
20: '\033[1;37m',
|
208
|
+
30: '\033[1;33m',
|
209
|
+
40: '\033[1;31m',
|
210
|
+
50: '\033[1;37;41m'
|
211
|
+
}
|
212
|
+
|
213
|
+
# Get.
|
214
|
+
color_code = color_code_dict.get(level, '')
|
215
|
+
|
216
|
+
return color_code
|
217
|
+
|
218
|
+
|
219
|
+
def _supply_format_print(
|
220
|
+
self,
|
221
|
+
format_: str,
|
222
|
+
record: LogRecord
|
223
|
+
) -> None:
|
224
|
+
"""
|
225
|
+
Supply format print parameters.
|
226
|
+
|
227
|
+
Parameters
|
228
|
+
----------
|
229
|
+
format_ : Record format.
|
230
|
+
record : Log record instance.
|
231
|
+
"""
|
232
|
+
|
233
|
+
# Break.
|
234
|
+
|
235
|
+
## Switch.
|
236
|
+
if not self.print_colour: return
|
237
|
+
|
238
|
+
## Added.
|
239
|
+
pattern = '\033\\[[\\d;]+?m'
|
240
|
+
result = search(pattern, format_)
|
241
|
+
if result is not None: return
|
242
|
+
|
243
|
+
# 'format_time'.
|
244
|
+
if '%(format_time)s' in format_:
|
245
|
+
record.format_time = '\033[32m%s\033[0m' % record.format_time
|
246
|
+
|
247
|
+
# 'format_levelname'.
|
248
|
+
if '%(format_levelname)s' in format_:
|
249
|
+
level_color_code = self.get_level_color_ansi(record.levelno)
|
250
|
+
record.format_levelname = '%s%s\033[0m' % (
|
251
|
+
level_color_code,
|
252
|
+
record.format_levelname
|
253
|
+
)
|
254
|
+
|
255
|
+
# 'format_path'.
|
256
|
+
if '%(format_path)s' in format_:
|
257
|
+
record.format_path = '\033[36m%s\033[0m' % record.format_path
|
258
|
+
|
259
|
+
# 'format_message'.
|
260
|
+
if (
|
261
|
+
'%(format_message)s' in format_
|
262
|
+
and search('\033\\[[\\d;]+?m', record.format_message) is None
|
263
|
+
):
|
264
|
+
level_color_code = self.get_level_color_ansi(record.levelno)
|
265
|
+
record.format_message = '%s%s\033[0m' % (
|
266
|
+
level_color_code,
|
267
|
+
record.format_message
|
268
|
+
)
|
269
|
+
|
270
|
+
|
271
|
+
def _supply_format_file(
|
272
|
+
self,
|
273
|
+
format_: str,
|
274
|
+
record: LogRecord
|
275
|
+
) -> None:
|
276
|
+
"""
|
277
|
+
Supply format file parameters.
|
278
|
+
|
279
|
+
Parameters
|
280
|
+
----------
|
281
|
+
format_ : Record format.
|
282
|
+
record : Log record instance.
|
283
|
+
"""
|
284
|
+
|
285
|
+
# Format 'format_message'.
|
286
|
+
if '%(format_message)s' in format_:
|
287
|
+
pattern = '\033\\[[\\d;]+?m'
|
288
|
+
record.format_message = sub(pattern, record.format_message)
|
289
|
+
|
290
|
+
|
291
|
+
def get_default_filter_method(
|
292
|
+
self,
|
293
|
+
format_: str,
|
294
|
+
mode : Optional[Literal['print', 'file']] = None
|
295
|
+
) -> Callable[[LogRecord], Literal[True]]:
|
296
|
+
"""
|
297
|
+
Get default filter method of handler.
|
298
|
+
|
299
|
+
Parameters
|
300
|
+
----------
|
301
|
+
format_ : Record format.
|
302
|
+
mode : Handler mode.
|
303
|
+
- `None`: Standard filter method.
|
304
|
+
- `Literal['print']`: Print handler filter method.
|
305
|
+
- `Literal['file']`: File handler filter method.
|
306
|
+
|
307
|
+
Returns
|
308
|
+
-------
|
309
|
+
Filter method.
|
310
|
+
"""
|
311
|
+
|
312
|
+
|
313
|
+
# Define.
|
314
|
+
def default_filter_method(
|
315
|
+
record: LogRecord
|
316
|
+
) -> Literal[True]:
|
317
|
+
"""
|
318
|
+
Default filter method of handler.
|
319
|
+
|
320
|
+
Parameters
|
321
|
+
----------
|
322
|
+
record : Log record instance.
|
323
|
+
|
324
|
+
Returns
|
325
|
+
-------
|
326
|
+
Whether pass.
|
327
|
+
"""
|
328
|
+
|
329
|
+
# Format standard.
|
330
|
+
self._supply_format_standard(format_, record)
|
331
|
+
|
332
|
+
match mode:
|
333
|
+
|
334
|
+
# Format print.
|
335
|
+
case 'print':
|
336
|
+
self._supply_format_print(format_, record)
|
337
|
+
|
338
|
+
# Format file.
|
339
|
+
case 'file':
|
340
|
+
self._supply_format_file(format_, record)
|
341
|
+
|
342
|
+
return True
|
343
|
+
|
344
|
+
|
345
|
+
return default_filter_method
|
346
|
+
|
347
|
+
|
348
|
+
def get_filter(
|
349
|
+
self,
|
350
|
+
method: Callable[[LogRecord], bool]
|
351
|
+
) -> Filter:
|
352
|
+
"""
|
353
|
+
Get filter.
|
354
|
+
|
355
|
+
Parameters
|
356
|
+
----------
|
357
|
+
method : Filter method.
|
358
|
+
|
359
|
+
Returns
|
360
|
+
-------
|
361
|
+
Filter.
|
362
|
+
"""
|
363
|
+
|
364
|
+
|
365
|
+
# Define.
|
366
|
+
class RFilter(Filter):
|
367
|
+
"""
|
368
|
+
Rey's filter type.
|
369
|
+
"""
|
370
|
+
|
371
|
+
|
372
|
+
@override
|
373
|
+
def filter(
|
374
|
+
record: LogRecord
|
375
|
+
) -> Literal[True]:
|
376
|
+
"""
|
377
|
+
Filter method.
|
378
|
+
|
379
|
+
Parameters
|
380
|
+
----------
|
381
|
+
record : Log record instance.
|
382
|
+
|
383
|
+
Returns
|
384
|
+
-------
|
385
|
+
Whether pass.
|
386
|
+
"""
|
387
|
+
|
388
|
+
# Filter.
|
389
|
+
result = method(record)
|
390
|
+
|
391
|
+
return result
|
392
|
+
|
393
|
+
|
394
|
+
return RFilter
|
395
|
+
|
396
|
+
|
397
|
+
def add_print(
|
398
|
+
self,
|
399
|
+
level: int = DEBUG,
|
400
|
+
format_: Optional[str] = None,
|
401
|
+
filter_: Optional[Callable[[LogRecord], bool]] = None
|
402
|
+
) -> StreamHandler:
|
403
|
+
"""
|
404
|
+
Add print output record handler.
|
405
|
+
|
406
|
+
Parameters
|
407
|
+
----------
|
408
|
+
level : Handler level.
|
409
|
+
format_ : Record format.
|
410
|
+
- `None`: Use attribute `default_format`.
|
411
|
+
- `str`: Use this value.
|
412
|
+
`Contain 'format_time'`: Date and time and millisecond, print output with color.
|
413
|
+
`Contain 'format_levelname'`: Level name and fixed width, print output with color.
|
414
|
+
`Contain 'format_path'`: Record code path, print output with color.
|
415
|
+
`Contain 'format_message'`: message content, file output delete ANSI code, print outputwith color.
|
416
|
+
filter_ : Filter method. The parameter is the `LogRecord` instance, return is `bool`.
|
417
|
+
- `None`: Use default filter method.
|
418
|
+
- `Callable`: Use this method.
|
419
|
+
|
420
|
+
Returns
|
421
|
+
-------
|
422
|
+
Handler.
|
423
|
+
"""
|
424
|
+
|
425
|
+
# Get parameter.
|
426
|
+
format_ = get_first_notnull(format_, self.default_format, default='exception')
|
427
|
+
filter_ = filter_ or self.get_default_filter_method(format_, 'print')
|
428
|
+
|
429
|
+
# Create handler.
|
430
|
+
handler = StreamHandler()
|
431
|
+
handler.setLevel(level)
|
432
|
+
formatter = Formatter(format_, self.default_format_date)
|
433
|
+
handler.setFormatter(formatter)
|
434
|
+
handler_filter = self.get_filter(filter_)
|
435
|
+
handler.addFilter(handler_filter)
|
436
|
+
|
437
|
+
# Add.
|
438
|
+
self.logger.addHandler(handler)
|
439
|
+
|
440
|
+
return handler
|
441
|
+
|
442
|
+
|
443
|
+
@overload
|
444
|
+
def add_file(
|
445
|
+
self,
|
446
|
+
path: Optional[str] = None,
|
447
|
+
mb: Optional[float] = None,
|
448
|
+
time: None = None,
|
449
|
+
level: int = DEBUG,
|
450
|
+
format_: Optional[str] = None,
|
451
|
+
filter_: Optional[Callable[[LogRecord], bool]] = None
|
452
|
+
) -> ConcurrentRotatingFileHandler: ...
|
453
|
+
|
454
|
+
@overload
|
455
|
+
def add_file(
|
456
|
+
self,
|
457
|
+
path: Optional[str] = None,
|
458
|
+
mb: None = None,
|
459
|
+
time: Union[float, Literal['m', 'w0', 'w1', 'w2', 'w3', 'w4', 'w5', 'w6']] = None,
|
460
|
+
level: int = DEBUG,
|
461
|
+
format_: Optional[str] = None,
|
462
|
+
filter_: Optional[Callable[[LogRecord], bool]] = None
|
463
|
+
) -> ConcurrentTimedRotatingFileHandler: ...
|
464
|
+
|
465
|
+
@overload
|
466
|
+
def add_file(
|
467
|
+
self,
|
468
|
+
path: Optional[str] = None,
|
469
|
+
mb: None = None,
|
470
|
+
time: Any = None,
|
471
|
+
level: int = DEBUG,
|
472
|
+
format_: Optional[str] = None,
|
473
|
+
filter_: Optional[Callable[[LogRecord], bool]] = None
|
474
|
+
) -> NoReturn: ...
|
475
|
+
|
476
|
+
@overload
|
477
|
+
def add_file(
|
478
|
+
self,
|
479
|
+
path: Optional[str] = None,
|
480
|
+
mb: float = None,
|
481
|
+
time: Union[float, Literal['m', 'w0', 'w1', 'w2', 'w3', 'w4', 'w5', 'w6']] = None,
|
482
|
+
level: int = DEBUG,
|
483
|
+
format_: Optional[str] = None,
|
484
|
+
filter_: Optional[Callable[[LogRecord], bool]] = None
|
485
|
+
) -> NoReturn: ...
|
486
|
+
|
487
|
+
def add_file(
|
488
|
+
self,
|
489
|
+
path: Optional[str] = None,
|
490
|
+
mb: Optional[float] = None,
|
491
|
+
time: Optional[Union[float, Literal['m', 'w0', 'w1', 'w2', 'w3', 'w4', 'w5', 'w6']]] = None,
|
492
|
+
level: int = DEBUG,
|
493
|
+
format_: Optional[str] = None,
|
494
|
+
filter_: Optional[Callable[[LogRecord], bool]] = None
|
495
|
+
) -> Union[ConcurrentRotatingFileHandler, ConcurrentTimedRotatingFileHandler]:
|
496
|
+
"""
|
497
|
+
Add file output record handler, can split files based on size or time.
|
498
|
+
|
499
|
+
Parameters
|
500
|
+
----------
|
501
|
+
path : File path.
|
502
|
+
- `None`: Use attribute `self.name`.
|
503
|
+
- `str`: Use this value.
|
504
|
+
mb : File split condition, max megabyte. Conflict with parameter `time`. Cannot be less than 1, prevent infinite split file.
|
505
|
+
time : File split condition, interval time. Conflict with parameter `mb`.
|
506
|
+
- `float`: Interval hours.
|
507
|
+
- `Literal['m']`: Everyday midnight.
|
508
|
+
- `Literal['w0', 'w1', 'w2', 'w3', 'w4', 'w5', 'w6']`: Weekly midnight, 'w0' is monday, 'w6' is sunday, and so on.
|
509
|
+
level : Handler level.
|
510
|
+
format_ : Record format.
|
511
|
+
- `None`: Use attribute `default_format`.
|
512
|
+
- `str`: Use this value.
|
513
|
+
`Contain 'format_time'`: Date and time and millisecond, print output with color.
|
514
|
+
`Contain 'format_levelname'`: Level name and fixed width, print output with color.
|
515
|
+
`Contain 'format_path'`: Record code path, print output with color.
|
516
|
+
`Contain 'format_message'`: message content, file output delete ANSI code, print outputwith color.
|
517
|
+
filter_ : Filter method. The parameter is the `LogRecord` instance, return is `bool`.
|
518
|
+
- `None`: Use default filter method.
|
519
|
+
- `Callable`: Use this method.
|
520
|
+
|
521
|
+
Returns
|
522
|
+
-------
|
523
|
+
Handler.
|
524
|
+
"""
|
525
|
+
|
526
|
+
# Get parameter.
|
527
|
+
format_ = get_first_notnull(format_, self.default_format, default='exception')
|
528
|
+
path = path or self.name
|
529
|
+
filter_ = filter_ or self.get_default_filter_method(format_, 'file')
|
530
|
+
|
531
|
+
# Create handler.
|
532
|
+
|
533
|
+
## Throw exception.
|
534
|
+
if (
|
535
|
+
mb is not None
|
536
|
+
and time is not None
|
537
|
+
):
|
538
|
+
raise AssertionError('parameter "mb" and "time" cannot be used together')
|
539
|
+
|
540
|
+
## By size split.
|
541
|
+
elif mb is not None:
|
542
|
+
|
543
|
+
### Check.
|
544
|
+
if mb < 1:
|
545
|
+
throw(ValueError, mb)
|
546
|
+
|
547
|
+
byte = int(mb * 1024 * 1024)
|
548
|
+
handler = ConcurrentRotatingFileHandler(
|
549
|
+
path,
|
550
|
+
'a',
|
551
|
+
byte,
|
552
|
+
1_0000_0000,
|
553
|
+
delay=True
|
554
|
+
)
|
555
|
+
|
556
|
+
## By time split.
|
557
|
+
elif time is not None:
|
558
|
+
match time:
|
559
|
+
|
560
|
+
### Interval hours.
|
561
|
+
case int() | float():
|
562
|
+
second = int(time * 60 * 60)
|
563
|
+
handler = ConcurrentTimedRotatingFileHandler(
|
564
|
+
path,
|
565
|
+
'S',
|
566
|
+
second,
|
567
|
+
1_0000_0000,
|
568
|
+
delay=True
|
569
|
+
)
|
570
|
+
|
571
|
+
### Everyday midnight.
|
572
|
+
case 'm':
|
573
|
+
handler = ConcurrentTimedRotatingFileHandler(
|
574
|
+
path,
|
575
|
+
'MIDNIGHT',
|
576
|
+
backupCount=1_0000_0000,
|
577
|
+
delay=True
|
578
|
+
)
|
579
|
+
|
580
|
+
### Weekly midnight
|
581
|
+
case 'w0' | 'w1' | 'w2' | 'w3' | 'w4' | 'w5' | 'w6':
|
582
|
+
handler = ConcurrentTimedRotatingFileHandler(
|
583
|
+
path,
|
584
|
+
time,
|
585
|
+
backupCount=1_0000_0000,
|
586
|
+
delay=True
|
587
|
+
)
|
588
|
+
|
589
|
+
### Throw exception.
|
590
|
+
case _:
|
591
|
+
throw(ValueError, time)
|
592
|
+
|
593
|
+
## Not split.
|
594
|
+
else:
|
595
|
+
handler = ConcurrentRotatingFileHandler(
|
596
|
+
path,
|
597
|
+
'a',
|
598
|
+
delay=True
|
599
|
+
)
|
600
|
+
|
601
|
+
# Set handler.
|
602
|
+
handler.setLevel(level)
|
603
|
+
formatter = Formatter(format_, self.default_format_date)
|
604
|
+
handler.setFormatter(formatter)
|
605
|
+
handler_filter = self.get_filter(filter_)
|
606
|
+
handler.addFilter(handler_filter)
|
607
|
+
|
608
|
+
# Add.
|
609
|
+
self.logger.addHandler(handler)
|
610
|
+
|
611
|
+
return handler
|
612
|
+
|
613
|
+
|
614
|
+
def add_queue(
|
615
|
+
self,
|
616
|
+
queue: Optional[Queue] = None,
|
617
|
+
level: int = DEBUG,
|
618
|
+
filter_: Optional[Callable[[LogRecord], bool]] = None
|
619
|
+
) -> tuple[QueueHandler, Queue[LogRecord]]:
|
620
|
+
"""
|
621
|
+
Add queue output record handler.
|
622
|
+
|
623
|
+
Parameters
|
624
|
+
----------
|
625
|
+
queue : Queue instance.
|
626
|
+
- `None`: Create queue and use.
|
627
|
+
- `Queue`: Use this queue.
|
628
|
+
level : Handler level.
|
629
|
+
filter_ : Filter method. The parameter is the `LogRecord` instance, return is `bool`.
|
630
|
+
|
631
|
+
Returns
|
632
|
+
-------
|
633
|
+
Handler and queue.
|
634
|
+
"""
|
635
|
+
|
636
|
+
## Create queue.
|
637
|
+
queue = queue or Queue()
|
638
|
+
|
639
|
+
# Create handler.
|
640
|
+
handler = QueueHandler(queue)
|
641
|
+
|
642
|
+
# Set handler.
|
643
|
+
handler.setLevel(level)
|
644
|
+
if filter_ is not None:
|
645
|
+
handler_filter = self.get_filter(filter_)
|
646
|
+
handler.addFilter(handler_filter)
|
647
|
+
|
648
|
+
# Add.
|
649
|
+
self.logger.addHandler(handler)
|
650
|
+
|
651
|
+
return handler, queue
|
652
|
+
|
653
|
+
|
654
|
+
def add_handler(
|
655
|
+
self,
|
656
|
+
method: Callable[[LogRecord], Any],
|
657
|
+
level: int = DEBUG,
|
658
|
+
filter_: Optional[Callable[[LogRecord], bool]] = None
|
659
|
+
) -> None:
|
660
|
+
"""
|
661
|
+
Add method record handler.
|
662
|
+
|
663
|
+
Parameters
|
664
|
+
----------
|
665
|
+
method : Handler method. The parameter is the `LogRecord` instance.
|
666
|
+
level : Handler level.
|
667
|
+
filter_ : Filter method. The parameter is the `LogRecord` instance, return is `bool`.
|
668
|
+
"""
|
669
|
+
|
670
|
+
# Add queue out.
|
671
|
+
_, queue = self.add_queue(level=level, filter_=filter_)
|
672
|
+
|
673
|
+
|
674
|
+
# Define.
|
675
|
+
@wrap_thread
|
676
|
+
def execute() -> None:
|
677
|
+
"""
|
678
|
+
Execute method.
|
679
|
+
"""
|
680
|
+
|
681
|
+
while True:
|
682
|
+
record = queue.get()
|
683
|
+
method(record)
|
684
|
+
|
685
|
+
|
686
|
+
# Execute.
|
687
|
+
execute()
|
688
|
+
|
689
|
+
|
690
|
+
def delete_handler(
|
691
|
+
self,
|
692
|
+
handler: Handler
|
693
|
+
) -> None:
|
694
|
+
"""
|
695
|
+
Delete record handler.
|
696
|
+
|
697
|
+
Parameters
|
698
|
+
----------
|
699
|
+
handler : Handler.
|
700
|
+
"""
|
701
|
+
|
702
|
+
# Delete.
|
703
|
+
self.logger.removeHandler(handler)
|
704
|
+
|
705
|
+
|
706
|
+
def clear_handler(self) -> None:
|
707
|
+
"""
|
708
|
+
Delete all record handler.
|
709
|
+
"""
|
710
|
+
|
711
|
+
# Delete.
|
712
|
+
for handle in self.logger.handlers:
|
713
|
+
self.logger.removeHandler(handle)
|
714
|
+
|
715
|
+
|
716
|
+
def catch_print(self, printing: bool = True) -> None:
|
717
|
+
"""
|
718
|
+
Catch print to log.
|
719
|
+
|
720
|
+
Parameters
|
721
|
+
----------
|
722
|
+
printing : Whether to still print.
|
723
|
+
"""
|
724
|
+
|
725
|
+
|
726
|
+
# Define.
|
727
|
+
def preprocess(__s: str) -> str:
|
728
|
+
"""
|
729
|
+
Preprocess function.
|
730
|
+
|
731
|
+
Parameters
|
732
|
+
----------
|
733
|
+
__s : Standard ouput text.
|
734
|
+
|
735
|
+
Returns
|
736
|
+
-------
|
737
|
+
Preprocessed text.
|
738
|
+
"""
|
739
|
+
|
740
|
+
# Log.
|
741
|
+
if __s not in ('\n', ' ', '[0m'):
|
742
|
+
self(__s, level=self.INFO, catch=False)
|
743
|
+
|
744
|
+
# Print.
|
745
|
+
if printing:
|
746
|
+
return __s
|
747
|
+
|
748
|
+
|
749
|
+
# Modify.
|
750
|
+
modify_print(preprocess)
|
751
|
+
|
752
|
+
# Update status.
|
753
|
+
self.print_replaced = True
|
754
|
+
|
755
|
+
|
756
|
+
def reset_print(self) -> None:
|
757
|
+
"""
|
758
|
+
Reset log replace print.
|
759
|
+
"""
|
760
|
+
|
761
|
+
# Break.
|
762
|
+
if not self.print_replaced: return
|
763
|
+
|
764
|
+
# Reset.
|
765
|
+
reset_print()
|
766
|
+
|
767
|
+
# Update status.
|
768
|
+
self.print_replaced = False
|
769
|
+
|
770
|
+
|
771
|
+
def log(
|
772
|
+
self,
|
773
|
+
*messages: Any,
|
774
|
+
level: Optional[int] = None,
|
775
|
+
catch: bool = True,
|
776
|
+
**params: Any
|
777
|
+
) -> None:
|
778
|
+
"""
|
779
|
+
Record log.
|
780
|
+
|
781
|
+
Parameters
|
782
|
+
----------
|
783
|
+
messages : Record content.
|
784
|
+
level : Record level.
|
785
|
+
- `None`: Automatic judge.
|
786
|
+
`in 'except' syntax`: Use 'ERROR' level.
|
787
|
+
`Other`: Use 'INFO' level.
|
788
|
+
- `int`: Use this value.
|
789
|
+
catch : Whether catch and append exception stack.
|
790
|
+
params : Record Format parameters.
|
791
|
+
"""
|
792
|
+
|
793
|
+
# Get parameter.
|
794
|
+
if (
|
795
|
+
level is None
|
796
|
+
or catch
|
797
|
+
):
|
798
|
+
exc_report, exc_type, *_ = catch_exc()
|
799
|
+
|
800
|
+
## Messages.
|
801
|
+
messages_len = len(messages)
|
802
|
+
if messages_len == 0:
|
803
|
+
messages = [None]
|
804
|
+
|
805
|
+
## Level.
|
806
|
+
if level is None:
|
807
|
+
if exc_type is None:
|
808
|
+
level = self.INFO
|
809
|
+
else:
|
810
|
+
level = self.ERROR
|
811
|
+
|
812
|
+
## Messages.
|
813
|
+
messages = '\n'.join(
|
814
|
+
[
|
815
|
+
to_text(message, self.default_format_width)
|
816
|
+
for message in messages
|
817
|
+
]
|
818
|
+
)
|
819
|
+
if '\n' in messages:
|
820
|
+
messages = '\n' + messages
|
821
|
+
|
822
|
+
### Exception.
|
823
|
+
if (
|
824
|
+
catch
|
825
|
+
and exc_type is not None
|
826
|
+
):
|
827
|
+
messages = '%s\n%s' % (
|
828
|
+
messages,
|
829
|
+
exc_report
|
830
|
+
)
|
831
|
+
|
832
|
+
# Record.
|
833
|
+
self.logger.log(level, messages, extra=params)
|
834
|
+
|
835
|
+
|
836
|
+
def debug(
|
837
|
+
self,
|
838
|
+
*messages: Any,
|
839
|
+
**params: Any
|
840
|
+
) -> None:
|
841
|
+
"""
|
842
|
+
Record `debug` level log.
|
843
|
+
|
844
|
+
Parameters
|
845
|
+
----------
|
846
|
+
messages : Record content.
|
847
|
+
params : Record Format parameters.
|
848
|
+
"""
|
849
|
+
|
850
|
+
# Record.
|
851
|
+
self.log(*messages, level=self.DEBUG, **params)
|
852
|
+
|
853
|
+
|
854
|
+
def info(
|
855
|
+
self,
|
856
|
+
*messages: Any,
|
857
|
+
**params: Any
|
858
|
+
) -> None:
|
859
|
+
"""
|
860
|
+
Record `info` level log.
|
861
|
+
|
862
|
+
Parameters
|
863
|
+
----------
|
864
|
+
messages : Record content.
|
865
|
+
params : Record Format parameters.
|
866
|
+
"""
|
867
|
+
|
868
|
+
# Record.
|
869
|
+
self.log(*messages, level=self.INFO, **params)
|
870
|
+
|
871
|
+
|
872
|
+
def warning(
|
873
|
+
self,
|
874
|
+
*messages: Any,
|
875
|
+
**params: Any
|
876
|
+
) -> None:
|
877
|
+
"""
|
878
|
+
Record `warning` level log.
|
879
|
+
|
880
|
+
Parameters
|
881
|
+
----------
|
882
|
+
messages : Record content.
|
883
|
+
params : Record Format parameters.
|
884
|
+
"""
|
885
|
+
|
886
|
+
# Record.
|
887
|
+
self.log(*messages, level=self.WARNING, **params)
|
888
|
+
|
889
|
+
|
890
|
+
def error(
|
891
|
+
self,
|
892
|
+
*messages: Any,
|
893
|
+
**params: Any
|
894
|
+
) -> None:
|
895
|
+
"""
|
896
|
+
Record `error` level log.
|
897
|
+
|
898
|
+
Parameters
|
899
|
+
----------
|
900
|
+
messages : Record content.
|
901
|
+
params : Record Format parameters.
|
902
|
+
"""
|
903
|
+
|
904
|
+
# Record.
|
905
|
+
self.log(*messages, level=self.ERROR, **params)
|
906
|
+
|
907
|
+
|
908
|
+
def critical(
|
909
|
+
self,
|
910
|
+
*messages: Any,
|
911
|
+
**params: Any
|
912
|
+
) -> None:
|
913
|
+
"""
|
914
|
+
Record `critical` level log.
|
915
|
+
|
916
|
+
Parameters
|
917
|
+
----------
|
918
|
+
messages : Record content.
|
919
|
+
params : Record Format parameters.
|
920
|
+
"""
|
921
|
+
|
922
|
+
# Record.
|
923
|
+
self.log(*messages, level=self.CRITICAL, **params)
|
924
|
+
|
925
|
+
|
926
|
+
def stop(self) -> None:
|
927
|
+
"""
|
928
|
+
Stop record.
|
929
|
+
"""
|
930
|
+
|
931
|
+
# Set level.
|
932
|
+
self.logger.setLevel(100)
|
933
|
+
|
934
|
+
# Update status.
|
935
|
+
self.stoped = True
|
936
|
+
|
937
|
+
|
938
|
+
def start(self) -> None:
|
939
|
+
"""
|
940
|
+
Start record.
|
941
|
+
"""
|
942
|
+
|
943
|
+
# Set level.
|
944
|
+
self.logger.setLevel(self.DEBUG)
|
945
|
+
|
946
|
+
# Update status.
|
947
|
+
self.stoped = False
|
948
|
+
|
949
|
+
|
950
|
+
def __del__(self) -> None:
|
951
|
+
"""
|
952
|
+
Delete handle.
|
953
|
+
"""
|
954
|
+
|
955
|
+
# Reset.
|
956
|
+
self.reset_print()
|
957
|
+
|
958
|
+
# Delete handler.
|
959
|
+
self.clear_handler()
|
960
|
+
|
961
|
+
|
962
|
+
__call__ = log
|
963
|
+
|
964
|
+
|
965
|
+
class RRecord(object):
|
966
|
+
"""
|
967
|
+
Rey's `record` type.
|
968
|
+
"""
|
969
|
+
|
970
|
+
|
971
|
+
def __init__(
|
972
|
+
self,
|
973
|
+
path: Optional[str] = '_rrecord'
|
974
|
+
) -> None:
|
975
|
+
"""
|
976
|
+
Build `record` attributes.
|
977
|
+
|
978
|
+
Parameters
|
979
|
+
----------
|
980
|
+
path : File path.
|
981
|
+
- `None`: Record to variable.
|
982
|
+
- `path`: Record to file.
|
983
|
+
"""
|
984
|
+
|
985
|
+
# Set attribute.
|
986
|
+
self.path = path
|
987
|
+
if path is None:
|
988
|
+
self.records = []
|
989
|
+
|
990
|
+
|
991
|
+
def record(
|
992
|
+
self,
|
993
|
+
value: Any
|
994
|
+
) -> None:
|
995
|
+
"""
|
996
|
+
Record value.
|
997
|
+
|
998
|
+
Parameters
|
999
|
+
----------
|
1000
|
+
value : Value.
|
1001
|
+
"""
|
1002
|
+
|
1003
|
+
# To variable.
|
1004
|
+
if self.path is None:
|
1005
|
+
self.records.append(value)
|
1006
|
+
|
1007
|
+
# To file.
|
1008
|
+
else:
|
1009
|
+
rfile = RFile(self.path)
|
1010
|
+
|
1011
|
+
## Convert.
|
1012
|
+
if value.__class__ != str:
|
1013
|
+
value = str(value)
|
1014
|
+
if rfile:
|
1015
|
+
value += ':'
|
1016
|
+
else:
|
1017
|
+
value = ':%s:' % value
|
1018
|
+
|
1019
|
+
## Record.
|
1020
|
+
rfile(value, True)
|
1021
|
+
|
1022
|
+
|
1023
|
+
def is_record(
|
1024
|
+
self,
|
1025
|
+
value: Any
|
1026
|
+
) -> bool:
|
1027
|
+
"""
|
1028
|
+
Judge if has been recorded.
|
1029
|
+
|
1030
|
+
Parameters
|
1031
|
+
----------
|
1032
|
+
value : Record value.
|
1033
|
+
|
1034
|
+
Returns
|
1035
|
+
-------
|
1036
|
+
Judge result.
|
1037
|
+
"""
|
1038
|
+
|
1039
|
+
# To variable.
|
1040
|
+
if self.path is None:
|
1041
|
+
judge = value in self.records
|
1042
|
+
|
1043
|
+
# To file.
|
1044
|
+
else:
|
1045
|
+
rfile = RFile(self.path)
|
1046
|
+
|
1047
|
+
## Convert.
|
1048
|
+
if value.__class__ != str:
|
1049
|
+
value = str(value)
|
1050
|
+
value = ':%s:' % value
|
1051
|
+
|
1052
|
+
## Judge.
|
1053
|
+
judge = value in rfile
|
1054
|
+
|
1055
|
+
return judge
|
1056
|
+
|
1057
|
+
|
1058
|
+
__call__ = record
|
1059
|
+
|
1060
|
+
|
1061
|
+
__contains__ = is_record
|