absfuyu 5.6.1__py3-none-any.whl → 6.1.2__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.
Potentially problematic release.
This version of absfuyu might be problematic. Click here for more details.
- absfuyu/__init__.py +5 -3
- absfuyu/__main__.py +2 -2
- absfuyu/cli/__init__.py +13 -2
- absfuyu/cli/audio_group.py +98 -0
- absfuyu/cli/color.py +2 -2
- absfuyu/cli/config_group.py +2 -2
- absfuyu/cli/do_group.py +2 -2
- absfuyu/cli/game_group.py +20 -2
- absfuyu/cli/tool_group.py +68 -4
- absfuyu/config/__init__.py +3 -3
- absfuyu/core/__init__.py +10 -6
- absfuyu/core/baseclass.py +104 -34
- absfuyu/core/baseclass2.py +43 -2
- absfuyu/core/decorator.py +2 -2
- absfuyu/core/docstring.py +4 -2
- absfuyu/core/dummy_cli.py +3 -3
- absfuyu/core/dummy_func.py +2 -2
- absfuyu/dxt/__init__.py +2 -2
- absfuyu/dxt/base_type.py +93 -0
- absfuyu/dxt/dictext.py +188 -6
- absfuyu/dxt/dxt_support.py +2 -2
- absfuyu/dxt/intext.py +72 -4
- absfuyu/dxt/listext.py +495 -23
- absfuyu/dxt/strext.py +2 -2
- absfuyu/extra/__init__.py +2 -2
- absfuyu/extra/audio/__init__.py +8 -0
- absfuyu/extra/audio/_util.py +57 -0
- absfuyu/extra/audio/convert.py +192 -0
- absfuyu/extra/audio/lossless.py +281 -0
- absfuyu/extra/beautiful.py +2 -2
- absfuyu/extra/da/__init__.py +39 -3
- absfuyu/extra/da/dadf.py +436 -29
- absfuyu/extra/da/dadf_base.py +2 -2
- absfuyu/extra/da/df_func.py +89 -5
- absfuyu/extra/da/mplt.py +2 -2
- absfuyu/extra/ggapi/__init__.py +8 -0
- absfuyu/extra/ggapi/gdrive.py +223 -0
- absfuyu/extra/ggapi/glicense.py +148 -0
- absfuyu/extra/ggapi/glicense_df.py +186 -0
- absfuyu/extra/ggapi/gsheet.py +88 -0
- absfuyu/extra/img/__init__.py +30 -0
- absfuyu/extra/img/converter.py +402 -0
- absfuyu/extra/img/dup_check.py +291 -0
- absfuyu/extra/pdf.py +4 -6
- absfuyu/extra/rclone.py +253 -0
- absfuyu/extra/xml.py +90 -0
- absfuyu/fun/__init__.py +2 -20
- absfuyu/fun/rubik.py +2 -2
- absfuyu/fun/tarot.py +2 -2
- absfuyu/game/__init__.py +2 -2
- absfuyu/game/game_stat.py +2 -2
- absfuyu/game/schulte.py +78 -0
- absfuyu/game/sudoku.py +2 -2
- absfuyu/game/tictactoe.py +2 -2
- absfuyu/game/wordle.py +6 -4
- absfuyu/general/__init__.py +2 -2
- absfuyu/general/content.py +2 -2
- absfuyu/general/human.py +2 -2
- absfuyu/general/resrel.py +213 -0
- absfuyu/general/shape.py +3 -8
- absfuyu/general/tax.py +344 -0
- absfuyu/logger.py +806 -59
- absfuyu/numbers/__init__.py +13 -0
- absfuyu/numbers/number_to_word.py +321 -0
- absfuyu/numbers/shorten_number.py +303 -0
- absfuyu/numbers/time_duration.py +217 -0
- absfuyu/pkg_data/__init__.py +2 -2
- absfuyu/pkg_data/deprecated.py +2 -2
- absfuyu/pkg_data/logo.py +1462 -0
- absfuyu/sort.py +4 -4
- absfuyu/tools/__init__.py +2 -2
- absfuyu/tools/checksum.py +119 -4
- absfuyu/tools/converter.py +2 -2
- absfuyu/tools/generator.py +24 -7
- absfuyu/tools/inspector.py +2 -2
- absfuyu/tools/keygen.py +2 -2
- absfuyu/tools/obfuscator.py +2 -2
- absfuyu/tools/passwordlib.py +2 -2
- absfuyu/tools/shutdownizer.py +3 -8
- absfuyu/tools/sw.py +213 -10
- absfuyu/tools/web.py +10 -13
- absfuyu/typings.py +5 -8
- absfuyu/util/__init__.py +31 -2
- absfuyu/util/api.py +7 -4
- absfuyu/util/cli.py +119 -0
- absfuyu/util/gui.py +91 -0
- absfuyu/util/json_method.py +2 -2
- absfuyu/util/lunar.py +2 -2
- absfuyu/util/package.py +124 -0
- absfuyu/util/path.py +313 -4
- absfuyu/util/performance.py +2 -2
- absfuyu/util/shorten_number.py +206 -13
- absfuyu/util/text_table.py +2 -2
- absfuyu/util/zipped.py +2 -2
- absfuyu/version.py +22 -19
- {absfuyu-5.6.1.dist-info → absfuyu-6.1.2.dist-info}/METADATA +37 -8
- absfuyu-6.1.2.dist-info/RECORD +105 -0
- {absfuyu-5.6.1.dist-info → absfuyu-6.1.2.dist-info}/WHEEL +1 -1
- absfuyu/extra/data_analysis.py +0 -21
- absfuyu-5.6.1.dist-info/RECORD +0 -79
- {absfuyu-5.6.1.dist-info → absfuyu-6.1.2.dist-info}/entry_points.txt +0 -0
- {absfuyu-5.6.1.dist-info → absfuyu-6.1.2.dist-info}/licenses/LICENSE +0 -0
absfuyu/logger.py
CHANGED
|
@@ -3,14 +3,8 @@ Absfuyu: Logger
|
|
|
3
3
|
---------------
|
|
4
4
|
Custom Logger Module
|
|
5
5
|
|
|
6
|
-
Version:
|
|
7
|
-
Date updated: 12/
|
|
8
|
-
|
|
9
|
-
Usage:
|
|
10
|
-
------
|
|
11
|
-
>>> from absfuyu.logger import logger, LogLevel
|
|
12
|
-
>>> logger.setLevel(LogLevel.DEBUG)
|
|
13
|
-
>>> logger.debug("This logs!")
|
|
6
|
+
Version: 6.1.1
|
|
7
|
+
Date updated: 30/12/2025 (dd/mm/yyyy)
|
|
14
8
|
"""
|
|
15
9
|
|
|
16
10
|
# Module level
|
|
@@ -18,65 +12,86 @@ Usage:
|
|
|
18
12
|
__all__ = [
|
|
19
13
|
# logger
|
|
20
14
|
"logger",
|
|
15
|
+
"AbsfuyuLogger",
|
|
21
16
|
"compress_for_log",
|
|
22
17
|
# log level
|
|
23
18
|
"LogLevel",
|
|
19
|
+
# Mixin
|
|
20
|
+
"LoggerMixin",
|
|
21
|
+
"AbsfuyuLoggerMixin",
|
|
24
22
|
]
|
|
25
23
|
|
|
26
24
|
|
|
27
25
|
# Library
|
|
28
26
|
# ---------------------------------------------------------------------------
|
|
27
|
+
import atexit
|
|
28
|
+
import datetime
|
|
29
|
+
import json
|
|
29
30
|
import logging
|
|
30
31
|
import math
|
|
31
|
-
|
|
32
|
-
from
|
|
32
|
+
import sys
|
|
33
|
+
from collections import Counter
|
|
34
|
+
from collections.abc import Callable
|
|
35
|
+
from logging.handlers import (
|
|
36
|
+
QueueHandler,
|
|
37
|
+
QueueListener,
|
|
38
|
+
RotatingFileHandler,
|
|
39
|
+
TimedRotatingFileHandler,
|
|
40
|
+
)
|
|
33
41
|
from pathlib import Path
|
|
34
|
-
from
|
|
42
|
+
from queue import Queue
|
|
43
|
+
from typing import Any, ClassVar, Self, override
|
|
35
44
|
|
|
36
45
|
|
|
37
46
|
# Setup
|
|
38
47
|
# ---------------------------------------------------------------------------
|
|
39
48
|
class LogLevel:
|
|
40
49
|
"""
|
|
41
|
-
``logging``
|
|
50
|
+
Python's ``logging`` module log level wrapper
|
|
42
51
|
"""
|
|
43
52
|
|
|
44
|
-
|
|
53
|
+
NOTSET: int = logging.NOTSET
|
|
45
54
|
DEBUG: int = logging.DEBUG
|
|
46
55
|
INFO: int = logging.INFO
|
|
47
56
|
WARNING: int = logging.WARNING
|
|
48
57
|
ERROR: int = logging.ERROR
|
|
49
58
|
CRITICAL: int = logging.CRITICAL
|
|
50
|
-
EXTREME: int = logging.CRITICAL + 10
|
|
51
|
-
|
|
52
59
|
|
|
53
|
-
|
|
54
|
-
""
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
_LOG_RECORD_BUILTIN_ATTRS = {
|
|
61
|
+
"args",
|
|
62
|
+
"asctime",
|
|
63
|
+
"created",
|
|
64
|
+
"exc_info",
|
|
65
|
+
"exc_text",
|
|
66
|
+
"filename",
|
|
67
|
+
"funcName",
|
|
68
|
+
"levelname",
|
|
69
|
+
"levelno",
|
|
70
|
+
"lineno",
|
|
71
|
+
"module",
|
|
72
|
+
"msecs",
|
|
73
|
+
"message",
|
|
74
|
+
"msg",
|
|
75
|
+
"name",
|
|
76
|
+
"pathname",
|
|
77
|
+
"process",
|
|
78
|
+
"processName",
|
|
79
|
+
"relativeCreated",
|
|
80
|
+
"stack_info",
|
|
81
|
+
"thread",
|
|
82
|
+
"threadName",
|
|
83
|
+
"taskName",
|
|
84
|
+
}
|
|
60
85
|
|
|
61
86
|
|
|
62
87
|
# Create a custom logger
|
|
88
|
+
# Temp logger - delete later
|
|
63
89
|
logger = logging.getLogger(__name__)
|
|
64
|
-
logger.setLevel(logging.WARNING)
|
|
65
|
-
|
|
66
|
-
# Create handlers
|
|
67
|
-
## Console log handler
|
|
68
|
-
console_handler = logging.StreamHandler()
|
|
69
|
-
console_handler.setLevel(LogLevel.TRACE) # Minimum log level
|
|
70
|
-
console_handler.setFormatter(
|
|
71
|
-
logging.Formatter(_LogFormat.CONSOLE, datefmt="%Y-%m-%d %H:%M:%S")
|
|
72
|
-
)
|
|
73
|
-
# logger.addHandler(console_handler)
|
|
74
|
-
logger.addHandler(logging.NullHandler())
|
|
75
90
|
|
|
76
91
|
|
|
77
92
|
# Functions
|
|
78
93
|
# ---------------------------------------------------------------------------
|
|
79
|
-
def _compress_list_for_print(iterable: list, max_visible:
|
|
94
|
+
def _compress_list_for_print(iterable: list, max_visible: int | None = 5) -> str:
|
|
80
95
|
"""
|
|
81
96
|
Compress the list to be more log-readable
|
|
82
97
|
|
|
@@ -106,7 +121,7 @@ def _compress_list_for_print(iterable: list, max_visible: Optional[int] = 5) ->
|
|
|
106
121
|
return f"{out} [Len: {len(iterable)}]"
|
|
107
122
|
|
|
108
123
|
|
|
109
|
-
def _compress_string_for_print(text: str, max_visible:
|
|
124
|
+
def _compress_string_for_print(text: str, max_visible: int | None = 120) -> str:
|
|
110
125
|
"""
|
|
111
126
|
Compress the string to be more log-readable
|
|
112
127
|
|
|
@@ -128,7 +143,7 @@ def _compress_string_for_print(text: str, max_visible: Optional[int] = 120) -> s
|
|
|
128
143
|
return f"{temp} [Len: {len(text)}]"
|
|
129
144
|
|
|
130
145
|
|
|
131
|
-
def compress_for_log(object_: Any, max_visible:
|
|
146
|
+
def compress_for_log(object_: Any, max_visible: int | None = None) -> str:
|
|
132
147
|
"""
|
|
133
148
|
Compress the object to be more log-readable
|
|
134
149
|
|
|
@@ -158,26 +173,29 @@ def compress_for_log(object_: Any, max_visible: Optional[int] = None) -> str:
|
|
|
158
173
|
return object_ # type: ignore
|
|
159
174
|
|
|
160
175
|
|
|
161
|
-
# Class
|
|
176
|
+
# Unused Class
|
|
162
177
|
# ---------------------------------------------------------------------------
|
|
163
|
-
class
|
|
178
|
+
class __CustomLogger:
|
|
164
179
|
"""
|
|
165
|
-
|
|
180
|
+
DO NOT USE
|
|
181
|
+
|
|
182
|
+
Custom logger [Incompleted, Remove soon]
|
|
166
183
|
|
|
167
184
|
Create a custom logger
|
|
185
|
+
|
|
168
186
|
*Useable but maybe unstable*
|
|
169
187
|
"""
|
|
170
188
|
|
|
171
189
|
def __init__(
|
|
172
190
|
self,
|
|
173
191
|
name: str,
|
|
174
|
-
cwd:
|
|
175
|
-
log_format:
|
|
192
|
+
cwd: str | Path = ".",
|
|
193
|
+
log_format: str | None = None,
|
|
176
194
|
*,
|
|
177
195
|
save_log_file: bool = False,
|
|
178
196
|
separated_error_file: bool = False,
|
|
179
197
|
timed_log: bool = False,
|
|
180
|
-
date_log_format:
|
|
198
|
+
date_log_format: str | None = None,
|
|
181
199
|
error_log_size: int = 1_000_000, # 1 MB
|
|
182
200
|
) -> None:
|
|
183
201
|
"""
|
|
@@ -192,9 +210,7 @@ class _CustomLogger:
|
|
|
192
210
|
"""
|
|
193
211
|
self._cwd = Path(cwd)
|
|
194
212
|
self.log_folder = self._cwd.joinpath("logs")
|
|
195
|
-
self.log_folder.mkdir(
|
|
196
|
-
exist_ok=True, parents=True
|
|
197
|
-
) # Does not throw exception when folder existed
|
|
213
|
+
self.log_folder.mkdir(exist_ok=True, parents=True) # Does not throw exception when folder existed
|
|
198
214
|
self.name = name
|
|
199
215
|
self.log_file = self.log_folder.joinpath(f"{name}.log")
|
|
200
216
|
|
|
@@ -216,17 +232,13 @@ class _CustomLogger:
|
|
|
216
232
|
## Console log handler
|
|
217
233
|
if log_format is None:
|
|
218
234
|
# Time|LogType|Function|LineNumber|Message
|
|
219
|
-
_log_format = (
|
|
220
|
-
"%(asctime)s [%(levelname)5s] %(funcName)s:%(lineno)3d: %(message)s"
|
|
221
|
-
)
|
|
235
|
+
_log_format = "%(asctime)s [%(levelname)5s] %(funcName)s:%(lineno)3d: %(message)s"
|
|
222
236
|
else:
|
|
223
237
|
_log_format = log_format
|
|
224
238
|
console_handler = logging.StreamHandler()
|
|
225
239
|
console_handler.setLevel(logging.DEBUG) # Minimum log level
|
|
226
240
|
_console_log_format = _log_format # Create formatters and add it to handlers
|
|
227
|
-
_console_formatter = logging.Formatter(
|
|
228
|
-
_console_log_format, datefmt=_date_format
|
|
229
|
-
)
|
|
241
|
+
_console_formatter = logging.Formatter(_console_log_format, datefmt=_date_format)
|
|
230
242
|
console_handler.setFormatter(_console_formatter)
|
|
231
243
|
self._console_handler = console_handler
|
|
232
244
|
self.logger.addHandler(self._console_handler) # Add handlers to the logger
|
|
@@ -238,9 +250,7 @@ class _CustomLogger:
|
|
|
238
250
|
_log_format = "%(asctime)s [%(levelname)5s] %(filename)s:%(funcName)s:%(lineno)3d: %(message)s"
|
|
239
251
|
else:
|
|
240
252
|
_log_format = log_format
|
|
241
|
-
file_handler = logging.FileHandler(
|
|
242
|
-
self.log_file, mode="a", encoding="utf-8"
|
|
243
|
-
)
|
|
253
|
+
file_handler = logging.FileHandler(self.log_file, mode="a", encoding="utf-8")
|
|
244
254
|
file_handler.setLevel(logging.DEBUG)
|
|
245
255
|
_file_log_format = _log_format
|
|
246
256
|
_file_formatter = logging.Formatter(_file_log_format, datefmt=_date_format)
|
|
@@ -250,7 +260,7 @@ class _CustomLogger:
|
|
|
250
260
|
|
|
251
261
|
if timed_log:
|
|
252
262
|
## Time handler (split log every day)
|
|
253
|
-
time_handler =
|
|
263
|
+
time_handler = TimedRotatingFileHandler(
|
|
254
264
|
self.log_folder.joinpath(f"{self.name}_timed.log"),
|
|
255
265
|
when="midnight",
|
|
256
266
|
interval=1,
|
|
@@ -276,7 +286,7 @@ class _CustomLogger:
|
|
|
276
286
|
_log_format = "%(asctime)s [%(levelname)5s] %(filename)s:%(funcName)s:%(lineno)3d: %(message)s"
|
|
277
287
|
else:
|
|
278
288
|
_log_format = log_format
|
|
279
|
-
error_handler =
|
|
289
|
+
error_handler = RotatingFileHandler(
|
|
280
290
|
self.log_folder.joinpath(f"{self.name}_error.log"),
|
|
281
291
|
maxBytes=error_log_size,
|
|
282
292
|
backupCount=1,
|
|
@@ -294,9 +304,7 @@ class _CustomLogger:
|
|
|
294
304
|
return self.__str__()
|
|
295
305
|
|
|
296
306
|
@staticmethod
|
|
297
|
-
def _add_logging_level(
|
|
298
|
-
level_name: str, level_num: int, method_name: Optional[str] = None
|
|
299
|
-
):
|
|
307
|
+
def _add_logging_level(level_name: str, level_num: int, method_name: str | None = None):
|
|
300
308
|
"""
|
|
301
309
|
Comprehensively adds a new logging level to the `logging` module and the
|
|
302
310
|
currently configured logging class.
|
|
@@ -342,3 +350,742 @@ class _CustomLogger:
|
|
|
342
350
|
if level_num < logging.DEBUG:
|
|
343
351
|
self._console_handler.setLevel(level_num)
|
|
344
352
|
self.logger.setLevel(level_num)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
# Class
|
|
356
|
+
# ---------------------------------------------------------------------------
|
|
357
|
+
class _BasicJsonFormatter(logging.Formatter):
|
|
358
|
+
"""
|
|
359
|
+
.json LogRecord
|
|
360
|
+
|
|
361
|
+
*deprecated*
|
|
362
|
+
"""
|
|
363
|
+
|
|
364
|
+
@override
|
|
365
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
366
|
+
data = {
|
|
367
|
+
"timestamp": self.formatTime(record, datefmt="%Y-%m-%d %H:%M:%S"),
|
|
368
|
+
"logger": record.name,
|
|
369
|
+
"level": record.levelname,
|
|
370
|
+
"message": record.getMessage(),
|
|
371
|
+
"file": record.filename,
|
|
372
|
+
"line": record.lineno,
|
|
373
|
+
}
|
|
374
|
+
return json.dumps(data)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
class LoggingJSONFormatter(logging.Formatter):
|
|
378
|
+
"""
|
|
379
|
+
JSONify log record to ``.jsonl`` file
|
|
380
|
+
"""
|
|
381
|
+
|
|
382
|
+
def __init__(
|
|
383
|
+
self,
|
|
384
|
+
*,
|
|
385
|
+
fmt_keys: dict[str, str] | None = None,
|
|
386
|
+
) -> None:
|
|
387
|
+
super().__init__()
|
|
388
|
+
self.fmt_keys = fmt_keys if fmt_keys is not None else {}
|
|
389
|
+
|
|
390
|
+
@override
|
|
391
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
392
|
+
message = self._prepare_log_dict(record)
|
|
393
|
+
return json.dumps(message, default=str)
|
|
394
|
+
|
|
395
|
+
def _prepare_log_dict(self, record: logging.LogRecord):
|
|
396
|
+
always_fields = {
|
|
397
|
+
"message": record.getMessage(),
|
|
398
|
+
"timestamp": datetime.datetime.fromtimestamp(record.created, tz=datetime.timezone.utc).isoformat(),
|
|
399
|
+
}
|
|
400
|
+
if record.exc_info is not None:
|
|
401
|
+
always_fields["exc_info"] = self.formatException(record.exc_info)
|
|
402
|
+
|
|
403
|
+
if record.stack_info is not None:
|
|
404
|
+
always_fields["stack_info"] = self.formatStack(record.stack_info)
|
|
405
|
+
|
|
406
|
+
message = {
|
|
407
|
+
key: (msg_val if (msg_val := always_fields.pop(val, None)) is not None else getattr(record, val))
|
|
408
|
+
for key, val in self.fmt_keys.items()
|
|
409
|
+
}
|
|
410
|
+
message.update(always_fields)
|
|
411
|
+
|
|
412
|
+
# Get data from LogRecord
|
|
413
|
+
# for key, val in record.__dict__.items():
|
|
414
|
+
# if key not in _LOG_RECORD_BUILTIN_ATTRS:
|
|
415
|
+
# message[key] = val
|
|
416
|
+
for x in _LOG_RECORD_BUILTIN_ATTRS:
|
|
417
|
+
v = getattr(record, x, None)
|
|
418
|
+
if x not in message and v is not None:
|
|
419
|
+
message[x] = v
|
|
420
|
+
|
|
421
|
+
return message
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
class LogLevelUpperFilter(logging.Filter):
|
|
425
|
+
"""
|
|
426
|
+
Filter ``LogRecord`` that <= ``filter_level``, by default: ``logging.WARNING``
|
|
427
|
+
"""
|
|
428
|
+
|
|
429
|
+
def __init__(self, name: str = "", filter_level: int | str = LogLevel.WARNING) -> None:
|
|
430
|
+
"""
|
|
431
|
+
Log level upper filter
|
|
432
|
+
|
|
433
|
+
Parameters
|
|
434
|
+
----------
|
|
435
|
+
name : str, optional
|
|
436
|
+
Name of the filter, by default ``""``
|
|
437
|
+
|
|
438
|
+
filter_level : int | str, optional
|
|
439
|
+
Log level to to filter, by default ``logging.WARNING``
|
|
440
|
+
|
|
441
|
+
Raises
|
|
442
|
+
------
|
|
443
|
+
ValueError
|
|
444
|
+
When type of ``filter_level`` is not ``<str>`` or ``<int>``
|
|
445
|
+
"""
|
|
446
|
+
super().__init__(name)
|
|
447
|
+
|
|
448
|
+
if isinstance(filter_level, str):
|
|
449
|
+
log_level = {
|
|
450
|
+
"DEBUG": logging.DEBUG,
|
|
451
|
+
"INFO": logging.INFO,
|
|
452
|
+
"WARNING": logging.WARNING,
|
|
453
|
+
"ERROR": logging.ERROR,
|
|
454
|
+
"CRITICAL": logging.CRITICAL,
|
|
455
|
+
}
|
|
456
|
+
new_filter_level = log_level.get(filter_level.strip().upper())
|
|
457
|
+
self._filter_level = new_filter_level if new_filter_level is not None else logging.WARNING
|
|
458
|
+
elif isinstance(filter_level, int):
|
|
459
|
+
self._filter_level = filter_level
|
|
460
|
+
else:
|
|
461
|
+
raise ValueError("filter_level must type <str> or <int>")
|
|
462
|
+
|
|
463
|
+
@override
|
|
464
|
+
def filter(self, record: logging.LogRecord) -> bool | logging.LogRecord:
|
|
465
|
+
return record.levelno <= self._filter_level
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
class AbsfuyuLogger(logging.Logger):
|
|
469
|
+
"""
|
|
470
|
+
Custom ``logging.Logger`` with prebuilt add handler method, and queue logging enabler
|
|
471
|
+
|
|
472
|
+
Available handlers:
|
|
473
|
+
- Stream
|
|
474
|
+
- File
|
|
475
|
+
- Rotating file
|
|
476
|
+
- Timed file
|
|
477
|
+
- Json
|
|
478
|
+
|
|
479
|
+
Note:
|
|
480
|
+
- Add format for formater with: ``add_log_format(...)``
|
|
481
|
+
- Enable queue with: ``enable_queue``
|
|
482
|
+
- Properties: ``available_log_format_style``, ``handlers_name_list``
|
|
483
|
+
"""
|
|
484
|
+
|
|
485
|
+
_LOG_FORMAT: ClassVar[dict[str, str]] = {
|
|
486
|
+
# Time|LogType|Function|LineNumber|Message
|
|
487
|
+
"console": "%(asctime)s [%(levelname)8s] %(funcName)s:%(lineno)3d: %(message)s",
|
|
488
|
+
# Time|LogType|FileName|Function|LineNumber|Message
|
|
489
|
+
"file": "%(asctime)s [%(levelname)s] %(filename)s:%(module)s:%(funcName)s:%(lineno)3d: %(message)s",
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
def __init__(
|
|
493
|
+
self,
|
|
494
|
+
name: str,
|
|
495
|
+
level: int = LogLevel.NOTSET,
|
|
496
|
+
*,
|
|
497
|
+
date_format: str | None = None,
|
|
498
|
+
date_format_file: str | None = None,
|
|
499
|
+
) -> None:
|
|
500
|
+
"""
|
|
501
|
+
Initialize the logger with a name and an optional level.
|
|
502
|
+
(Custom version)
|
|
503
|
+
|
|
504
|
+
Parameters
|
|
505
|
+
----------
|
|
506
|
+
name : str
|
|
507
|
+
Name of the logger
|
|
508
|
+
|
|
509
|
+
level : int, optional
|
|
510
|
+
Log level, by default ``logging.NOTSET``
|
|
511
|
+
|
|
512
|
+
date_format : str | None, optional
|
|
513
|
+
| Date format in log handler, by default ``None``
|
|
514
|
+
| If ``date_format`` is not specified, ``"%Y-%m-%d %H:%M:%S"`` is used
|
|
515
|
+
|
|
516
|
+
date_format_file : str | None, optional
|
|
517
|
+
| Date format in log file handler, by default ``None``
|
|
518
|
+
| If ``date_format`` is not specified, ``"%Y-%m-%dT%H:%M:%S%z"`` is used
|
|
519
|
+
"""
|
|
520
|
+
super().__init__(name, level)
|
|
521
|
+
|
|
522
|
+
# Extra
|
|
523
|
+
self._cl_date_fmt = "%Y-%m-%d %H:%M:%S" if date_format is None else date_format
|
|
524
|
+
self._cl_date_fmt_file = "%Y-%m-%dT%H:%M:%S%z" if date_format_file is None else date_format_file
|
|
525
|
+
|
|
526
|
+
# Class method
|
|
527
|
+
# --------------------------------
|
|
528
|
+
@classmethod
|
|
529
|
+
def default_config(cls, name: str, level: int = LogLevel.WARNING, /) -> Self:
|
|
530
|
+
"""
|
|
531
|
+
Default configuration for this custom logger
|
|
532
|
+
- 1 debug stream handler
|
|
533
|
+
- 1 info-warning stream handler
|
|
534
|
+
- 1 error-critical stream handler
|
|
535
|
+
|
|
536
|
+
Parameters
|
|
537
|
+
----------
|
|
538
|
+
name : str
|
|
539
|
+
Name of the logger
|
|
540
|
+
|
|
541
|
+
level : int, optional
|
|
542
|
+
Log level, by default ``logging.NOTSET``
|
|
543
|
+
|
|
544
|
+
Returns
|
|
545
|
+
-------
|
|
546
|
+
Self
|
|
547
|
+
Custom logger
|
|
548
|
+
"""
|
|
549
|
+
logger = cls(name=name, level=level)
|
|
550
|
+
|
|
551
|
+
# Debug handler
|
|
552
|
+
logger.add_stream_handler(
|
|
553
|
+
name="handler_stream_debug",
|
|
554
|
+
level=LogLevel.DEBUG,
|
|
555
|
+
stream=sys.stdout,
|
|
556
|
+
upper_bound_level=LogLevel.DEBUG,
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
# Info-Warning handler
|
|
560
|
+
logger.add_stream_handler(
|
|
561
|
+
name="handler_stream_normal",
|
|
562
|
+
level=LogLevel.INFO,
|
|
563
|
+
stream=sys.stdout,
|
|
564
|
+
upper_bound_level=LogLevel.WARNING,
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
# Error-Critical handler
|
|
568
|
+
logger.add_stream_handler(name="handler_stream_error", level=LogLevel.ERROR, stream=sys.stderr)
|
|
569
|
+
return logger
|
|
570
|
+
|
|
571
|
+
# Formater
|
|
572
|
+
# --------------------------------
|
|
573
|
+
@classmethod
|
|
574
|
+
def add_log_format(cls, name: str, format: str) -> None:
|
|
575
|
+
"""
|
|
576
|
+
Add a log format to use in ``logging.Formater`` for ``logging.Handler``
|
|
577
|
+
|
|
578
|
+
Parameters
|
|
579
|
+
----------
|
|
580
|
+
name : str
|
|
581
|
+
Name of the format
|
|
582
|
+
|
|
583
|
+
format : str
|
|
584
|
+
Format string
|
|
585
|
+
"""
|
|
586
|
+
cls._LOG_FORMAT[name] = format
|
|
587
|
+
|
|
588
|
+
@property
|
|
589
|
+
def available_log_format_style(self) -> list[str]:
|
|
590
|
+
"""
|
|
591
|
+
Available log format style for ``logging.Formatter``
|
|
592
|
+
"""
|
|
593
|
+
return list(self._LOG_FORMAT.keys())
|
|
594
|
+
|
|
595
|
+
def _get_log_format(self, format_name: str, /) -> str:
|
|
596
|
+
default = "%(asctime)s [%(levelname)5s] %(funcName)s:%(lineno)3d: %(message)s"
|
|
597
|
+
return self._LOG_FORMAT.get(format_name, default)
|
|
598
|
+
|
|
599
|
+
# Handlers
|
|
600
|
+
# --------------------------------
|
|
601
|
+
def _handle_log_handler(
|
|
602
|
+
self,
|
|
603
|
+
handler: logging.Handler,
|
|
604
|
+
name: str | None,
|
|
605
|
+
format_name: str,
|
|
606
|
+
level: int,
|
|
607
|
+
upper_bound_level: int | None = None,
|
|
608
|
+
overwrite_date_format: str | None = None,
|
|
609
|
+
) -> logging.Handler:
|
|
610
|
+
"""
|
|
611
|
+
Handle handler configuration
|
|
612
|
+
|
|
613
|
+
Parameters
|
|
614
|
+
----------
|
|
615
|
+
handler : logging.Handler
|
|
616
|
+
Handler to config
|
|
617
|
+
|
|
618
|
+
name : str | None
|
|
619
|
+
Name of the handler
|
|
620
|
+
|
|
621
|
+
format_name : str
|
|
622
|
+
| LogFormater to use
|
|
623
|
+
| Default options: ``console``, ``file``
|
|
624
|
+
|
|
625
|
+
level : int
|
|
626
|
+
Log level
|
|
627
|
+
|
|
628
|
+
upper_bound_level : int | None, optional
|
|
629
|
+
| Log level to cut off, by default ``None``
|
|
630
|
+
| Eg: ``upper_bound_level=logging.ERROR`` to show logs upto ERROR level
|
|
631
|
+
|
|
632
|
+
overwrite_date_format : str | None, optional
|
|
633
|
+
Overwrite the date format for formater, by default ``None``
|
|
634
|
+
|
|
635
|
+
Returns
|
|
636
|
+
-------
|
|
637
|
+
logging.Handler
|
|
638
|
+
Handler
|
|
639
|
+
"""
|
|
640
|
+
|
|
641
|
+
# Set name
|
|
642
|
+
if name is not None:
|
|
643
|
+
handler.set_name(name)
|
|
644
|
+
|
|
645
|
+
# Set level
|
|
646
|
+
handler.setLevel(level)
|
|
647
|
+
|
|
648
|
+
# Filter
|
|
649
|
+
dt_fmt = self._cl_date_fmt
|
|
650
|
+
if isinstance(
|
|
651
|
+
handler,
|
|
652
|
+
(RotatingFileHandler, TimedRotatingFileHandler, logging.FileHandler),
|
|
653
|
+
):
|
|
654
|
+
dt_fmt = self._cl_date_fmt_file
|
|
655
|
+
if overwrite_date_format is not None:
|
|
656
|
+
dt_fmt = overwrite_date_format
|
|
657
|
+
fmt = logging.Formatter(self._get_log_format(format_name), datefmt=dt_fmt)
|
|
658
|
+
handler.setFormatter(fmt)
|
|
659
|
+
|
|
660
|
+
# Upperbound
|
|
661
|
+
if upper_bound_level is not None:
|
|
662
|
+
log_filter = LogLevelUpperFilter(filter_level=upper_bound_level)
|
|
663
|
+
handler.addFilter(log_filter)
|
|
664
|
+
|
|
665
|
+
return handler
|
|
666
|
+
|
|
667
|
+
@property
|
|
668
|
+
def handlers_name_list(self) -> Counter:
|
|
669
|
+
"""
|
|
670
|
+
List of handlers available in the logger
|
|
671
|
+
|
|
672
|
+
Returns
|
|
673
|
+
-------
|
|
674
|
+
Counter
|
|
675
|
+
List of handlers
|
|
676
|
+
"""
|
|
677
|
+
# out = {}
|
|
678
|
+
# for x in self.handlers:
|
|
679
|
+
# out.setdefault(x.name, 0)
|
|
680
|
+
# out[x.name] += 1
|
|
681
|
+
# return out
|
|
682
|
+
return Counter(x.name for x in self.handlers)
|
|
683
|
+
|
|
684
|
+
def add_stream_handler(
|
|
685
|
+
self,
|
|
686
|
+
name: str | None = None,
|
|
687
|
+
format_name: str = "console",
|
|
688
|
+
level: int = LogLevel.NOTSET,
|
|
689
|
+
*,
|
|
690
|
+
stream: Any | None = None,
|
|
691
|
+
upper_bound_level: int | None = None,
|
|
692
|
+
overwrite_date_format: str | None = None,
|
|
693
|
+
) -> None:
|
|
694
|
+
"""
|
|
695
|
+
Add stream handler for logger
|
|
696
|
+
|
|
697
|
+
Parameters
|
|
698
|
+
----------
|
|
699
|
+
name : str | None, optional
|
|
700
|
+
Name of the handler, by default ``None``
|
|
701
|
+
|
|
702
|
+
format_name : str, optional
|
|
703
|
+
| LogFormater to use, by default ``"console"``
|
|
704
|
+
| Default options: ``console``, ``file``
|
|
705
|
+
|
|
706
|
+
level : int, optional
|
|
707
|
+
Log level, by default ``logging.NOTSET``
|
|
708
|
+
|
|
709
|
+
stream : Any | None, optional
|
|
710
|
+
| Stream to use, by default ``None``
|
|
711
|
+
| If ``stream`` is not specified, ``sys.stderr`` is used.
|
|
712
|
+
| Options: ``sys.stdout``, ``sys.stderr``, ...
|
|
713
|
+
|
|
714
|
+
upper_bound_level : int | None, optional
|
|
715
|
+
| Log level to cut off, by default ``None``
|
|
716
|
+
| Eg: ``upper_bound_level=logging.ERROR`` to show logs upto ERROR level
|
|
717
|
+
|
|
718
|
+
overwrite_date_format : str | None, optional
|
|
719
|
+
Overwrite the date format for formater, by default ``None``
|
|
720
|
+
"""
|
|
721
|
+
st = sys.stderr if stream is None else stream
|
|
722
|
+
handler = logging.StreamHandler(st)
|
|
723
|
+
handler = self._handle_log_handler(
|
|
724
|
+
handler=handler,
|
|
725
|
+
name=name,
|
|
726
|
+
format_name=format_name,
|
|
727
|
+
level=level,
|
|
728
|
+
upper_bound_level=upper_bound_level,
|
|
729
|
+
overwrite_date_format=overwrite_date_format,
|
|
730
|
+
)
|
|
731
|
+
self.addHandler(handler)
|
|
732
|
+
|
|
733
|
+
def add_file_handler(
|
|
734
|
+
self,
|
|
735
|
+
file_path: str | Path,
|
|
736
|
+
name: str | None = None,
|
|
737
|
+
format_name: str = "file",
|
|
738
|
+
level: int = LogLevel.ERROR,
|
|
739
|
+
*,
|
|
740
|
+
upper_bound_level: int | None = None,
|
|
741
|
+
overwrite_date_format: str | None = None,
|
|
742
|
+
) -> None:
|
|
743
|
+
"""
|
|
744
|
+
Add file handler for logger
|
|
745
|
+
|
|
746
|
+
Parameters
|
|
747
|
+
----------
|
|
748
|
+
file_path : str | Path
|
|
749
|
+
Path to log file
|
|
750
|
+
|
|
751
|
+
name : str | None, optional
|
|
752
|
+
Name of the handler, by default ``None``
|
|
753
|
+
|
|
754
|
+
format_name : str, optional
|
|
755
|
+
| LogFormater to use, by default ``"file"``
|
|
756
|
+
| Default options: ``console``, ``file``
|
|
757
|
+
|
|
758
|
+
level : int, optional
|
|
759
|
+
Log level, by default ``logging.ERROR``
|
|
760
|
+
|
|
761
|
+
upper_bound_level : int | None, optional
|
|
762
|
+
| Log level to cut off, by default ``None``
|
|
763
|
+
| Eg: ``upper_bound_level=logging.ERROR`` to show logs upto ERROR level
|
|
764
|
+
|
|
765
|
+
overwrite_date_format : str | None, optional
|
|
766
|
+
Overwrite the date format for formater, by default ``None``
|
|
767
|
+
"""
|
|
768
|
+
path = Path(file_path)
|
|
769
|
+
handler = logging.FileHandler(str(path.resolve()), mode="a", encoding="utf-8")
|
|
770
|
+
handler = self._handle_log_handler(
|
|
771
|
+
handler=handler,
|
|
772
|
+
name=name,
|
|
773
|
+
format_name=format_name,
|
|
774
|
+
level=level,
|
|
775
|
+
upper_bound_level=upper_bound_level,
|
|
776
|
+
overwrite_date_format=overwrite_date_format,
|
|
777
|
+
)
|
|
778
|
+
self.addHandler(handler)
|
|
779
|
+
|
|
780
|
+
def add_rotating_file_handler(
|
|
781
|
+
self,
|
|
782
|
+
file_path: str | Path,
|
|
783
|
+
name: str | None = None,
|
|
784
|
+
format_name: str = "file",
|
|
785
|
+
level: int = LogLevel.DEBUG,
|
|
786
|
+
*,
|
|
787
|
+
max_bytes: int = 5_000_000,
|
|
788
|
+
backup_count: int = 5,
|
|
789
|
+
upper_bound_level: int | None = None,
|
|
790
|
+
overwrite_date_format: str | None = None,
|
|
791
|
+
) -> None:
|
|
792
|
+
"""
|
|
793
|
+
Add rotating file handler for logger
|
|
794
|
+
|
|
795
|
+
Parameters
|
|
796
|
+
----------
|
|
797
|
+
file_path : str | Path
|
|
798
|
+
Path to log file
|
|
799
|
+
|
|
800
|
+
name : str | None, optional
|
|
801
|
+
Name of the handler, by default ``None``
|
|
802
|
+
|
|
803
|
+
format_name : str, optional
|
|
804
|
+
| LogFormater to use, by default ``"file"``
|
|
805
|
+
| Default options: ``console``, ``file``
|
|
806
|
+
|
|
807
|
+
level : int, optional
|
|
808
|
+
Log level, by default ``logging.DEBUG``
|
|
809
|
+
|
|
810
|
+
max_bytes : int, optional
|
|
811
|
+
| Max byte to rollover, by default ``0``
|
|
812
|
+
| If ``max_bytes`` is zero, rollover never occurs.
|
|
813
|
+
| Set to ``1_000_000`` for 1 MB
|
|
814
|
+
|
|
815
|
+
backup_count : int, optional
|
|
816
|
+
Number of backup file, by default ``0``
|
|
817
|
+
|
|
818
|
+
upper_bound_level : int | None, optional
|
|
819
|
+
| Log level to cut off, by default ``None``
|
|
820
|
+
| Eg: ``upper_bound_level=logging.ERROR`` to show logs upto ERROR level
|
|
821
|
+
|
|
822
|
+
overwrite_date_format : str | None, optional
|
|
823
|
+
Overwrite the date format for formater, by default ``None``
|
|
824
|
+
"""
|
|
825
|
+
path = Path(file_path)
|
|
826
|
+
handler = RotatingFileHandler(
|
|
827
|
+
str(path.resolve()),
|
|
828
|
+
mode="a",
|
|
829
|
+
encoding="utf-8",
|
|
830
|
+
maxBytes=max_bytes,
|
|
831
|
+
backupCount=backup_count,
|
|
832
|
+
)
|
|
833
|
+
handler = self._handle_log_handler(
|
|
834
|
+
handler=handler,
|
|
835
|
+
name=name,
|
|
836
|
+
format_name=format_name,
|
|
837
|
+
level=level,
|
|
838
|
+
upper_bound_level=upper_bound_level,
|
|
839
|
+
overwrite_date_format=overwrite_date_format,
|
|
840
|
+
)
|
|
841
|
+
self.addHandler(handler)
|
|
842
|
+
|
|
843
|
+
def add_timed_rotating_file_handler(
|
|
844
|
+
self,
|
|
845
|
+
file_path: str | Path,
|
|
846
|
+
name: str | None = None,
|
|
847
|
+
format_name: str = "file",
|
|
848
|
+
level: int = LogLevel.DEBUG,
|
|
849
|
+
*,
|
|
850
|
+
when: str = "midnight",
|
|
851
|
+
interval: int = 1,
|
|
852
|
+
backup_count: int = 5,
|
|
853
|
+
upper_bound_level: int | None = None,
|
|
854
|
+
overwrite_date_format: str | None = None,
|
|
855
|
+
) -> None:
|
|
856
|
+
"""
|
|
857
|
+
Add timed rotating file handler for logger
|
|
858
|
+
|
|
859
|
+
Parameters
|
|
860
|
+
----------
|
|
861
|
+
file_path : str | Path
|
|
862
|
+
Path to log file
|
|
863
|
+
|
|
864
|
+
name : str | None, optional
|
|
865
|
+
Name of the handler, by default ``None``
|
|
866
|
+
|
|
867
|
+
format_name : str, optional
|
|
868
|
+
| LogFormater to use, by default ``"file"``
|
|
869
|
+
| Default options: ``console``, ``file``
|
|
870
|
+
|
|
871
|
+
level : int, optional
|
|
872
|
+
Log level, by default ``logging.DEBUG``
|
|
873
|
+
|
|
874
|
+
when : Literal["S", "M", "H", "D", "midnight", "W"] | str, optional
|
|
875
|
+
When to rollover, by default ``"midnight"``
|
|
876
|
+
- S - Seconds
|
|
877
|
+
- M - Minutes
|
|
878
|
+
- H - Hours
|
|
879
|
+
- D - Days
|
|
880
|
+
- midnight - roll over at midnight
|
|
881
|
+
- W{0-6} - roll over on a certain day; 0 - Monday
|
|
882
|
+
|
|
883
|
+
interval : int, optional
|
|
884
|
+
Interval, by default ``1``
|
|
885
|
+
|
|
886
|
+
backup_count : int, optional
|
|
887
|
+
Number of backup file, by default ``0``
|
|
888
|
+
|
|
889
|
+
upper_bound_level : int | None, optional
|
|
890
|
+
| Log level to cut off, by default ``None``
|
|
891
|
+
| Eg: ``upper_bound_level=logging.ERROR`` to show logs upto ERROR level
|
|
892
|
+
|
|
893
|
+
overwrite_date_format : str | None, optional
|
|
894
|
+
Overwrite the date format for formater, by default ``None``
|
|
895
|
+
"""
|
|
896
|
+
path = Path(file_path)
|
|
897
|
+
handler = TimedRotatingFileHandler(
|
|
898
|
+
str(path.resolve()),
|
|
899
|
+
encoding="utf-8",
|
|
900
|
+
when=when,
|
|
901
|
+
interval=interval,
|
|
902
|
+
backupCount=backup_count,
|
|
903
|
+
)
|
|
904
|
+
handler = self._handle_log_handler(
|
|
905
|
+
handler=handler,
|
|
906
|
+
name=name,
|
|
907
|
+
format_name=format_name,
|
|
908
|
+
level=level,
|
|
909
|
+
upper_bound_level=upper_bound_level,
|
|
910
|
+
overwrite_date_format=overwrite_date_format,
|
|
911
|
+
)
|
|
912
|
+
self.addHandler(handler)
|
|
913
|
+
|
|
914
|
+
def add_json_file_handler(
|
|
915
|
+
self,
|
|
916
|
+
file_path: str | Path,
|
|
917
|
+
name: str | None = None,
|
|
918
|
+
level: int = LogLevel.ERROR,
|
|
919
|
+
*,
|
|
920
|
+
max_bytes: int = 0,
|
|
921
|
+
backup_count: int = 0,
|
|
922
|
+
upper_bound_level: int | None = None,
|
|
923
|
+
) -> None:
|
|
924
|
+
"""
|
|
925
|
+
Add ``.jsonl`` file log handler for logger
|
|
926
|
+
|
|
927
|
+
Parameters
|
|
928
|
+
----------
|
|
929
|
+
file_path : str | Path
|
|
930
|
+
Path to ``.jsonl`` log file
|
|
931
|
+
|
|
932
|
+
name : str | None, optional
|
|
933
|
+
Name of the handler, by default ``None``
|
|
934
|
+
|
|
935
|
+
level : int, optional
|
|
936
|
+
Log level, by default ``logging.ERROR``
|
|
937
|
+
|
|
938
|
+
max_bytes : int, optional
|
|
939
|
+
| Max byte to rollover, by default ``0``
|
|
940
|
+
| If ``max_bytes`` is zero, rollover never occurs.
|
|
941
|
+
| Set to ``1_000_000`` for 1 MB
|
|
942
|
+
|
|
943
|
+
backup_count : int, optional
|
|
944
|
+
Number of backup file, by default ``0``
|
|
945
|
+
|
|
946
|
+
upper_bound_level : int | None, optional
|
|
947
|
+
| Log level to cut off, by default ``None``
|
|
948
|
+
| Eg: ``upper_bound_level=logging.ERROR`` to show logs upto ERROR level
|
|
949
|
+
"""
|
|
950
|
+
path = Path(file_path)
|
|
951
|
+
handler = RotatingFileHandler(
|
|
952
|
+
str(path.resolve()),
|
|
953
|
+
mode="a",
|
|
954
|
+
encoding="utf-8",
|
|
955
|
+
maxBytes=max_bytes,
|
|
956
|
+
backupCount=backup_count,
|
|
957
|
+
)
|
|
958
|
+
handler.setLevel(level)
|
|
959
|
+
if name is not None:
|
|
960
|
+
handler.set_name(name)
|
|
961
|
+
handler.setFormatter(LoggingJSONFormatter())
|
|
962
|
+
if upper_bound_level is not None:
|
|
963
|
+
log_filter = LogLevelUpperFilter(filter_level=upper_bound_level)
|
|
964
|
+
handler.addFilter(log_filter)
|
|
965
|
+
self.addHandler(handler)
|
|
966
|
+
|
|
967
|
+
def enable_queue(self, listener_handlers: list[logging.Handler] | None = None) -> None:
|
|
968
|
+
"""
|
|
969
|
+
Convert logger to async queue mode.
|
|
970
|
+
|
|
971
|
+
Parameters
|
|
972
|
+
----------
|
|
973
|
+
listener_handlers : list[logging.Handler] | None, optional
|
|
974
|
+
| List of handlers the listener should write to, by default ``None``
|
|
975
|
+
| If ``listener_handlers`` is not specified, all handlers are used.
|
|
976
|
+
"""
|
|
977
|
+
|
|
978
|
+
# Get all available handlers
|
|
979
|
+
if listener_handlers is None:
|
|
980
|
+
listener_handlers = [h for h in self.handlers]
|
|
981
|
+
|
|
982
|
+
self._queue = Queue()
|
|
983
|
+
|
|
984
|
+
# Make listener
|
|
985
|
+
self._listener = QueueListener(self._queue, *listener_handlers, respect_handler_level=True)
|
|
986
|
+
|
|
987
|
+
# Replace existing handlers with Queue handler in self.handlers
|
|
988
|
+
self._queue_handler = QueueHandler(self._queue)
|
|
989
|
+
self._queue_handler.listener = self._listener
|
|
990
|
+
self.remove_all_hanlders()
|
|
991
|
+
self.addHandler(self._queue_handler) # self.handlers = [self._queue_handler]
|
|
992
|
+
|
|
993
|
+
# Log listener
|
|
994
|
+
# self._listener.start()
|
|
995
|
+
# atexit.register(self._listener.stop) # Register to stop listening when exit
|
|
996
|
+
self._queue_handler.listener.start()
|
|
997
|
+
atexit.register(self._queue_handler.listener.stop)
|
|
998
|
+
|
|
999
|
+
def remove_all_hanlders(self) -> None:
|
|
1000
|
+
"""
|
|
1001
|
+
Remove all handlers for this logger
|
|
1002
|
+
"""
|
|
1003
|
+
for x in self.handlers[:]:
|
|
1004
|
+
x.close()
|
|
1005
|
+
self.removeHandler(x)
|
|
1006
|
+
|
|
1007
|
+
def remove_hander_by_name(self, handler_name: str | None, /) -> None:
|
|
1008
|
+
"""
|
|
1009
|
+
Remove an existing handler by its name
|
|
1010
|
+
|
|
1011
|
+
Parameters
|
|
1012
|
+
----------
|
|
1013
|
+
handler_name : str | None
|
|
1014
|
+
Name of the handler
|
|
1015
|
+
"""
|
|
1016
|
+
for x in self.handlers[:]:
|
|
1017
|
+
if x.name == handler_name:
|
|
1018
|
+
x.close()
|
|
1019
|
+
self.removeHandler(x)
|
|
1020
|
+
|
|
1021
|
+
# Test
|
|
1022
|
+
# --------------------------------
|
|
1023
|
+
def test_logger(self) -> None:
|
|
1024
|
+
"""
|
|
1025
|
+
Test the logger by logging message in every log level
|
|
1026
|
+
"""
|
|
1027
|
+
# test = ["debug", "info", "warning", "error", "exception", "critical"]
|
|
1028
|
+
# for x in test:
|
|
1029
|
+
# log_func = getattr(self, x)
|
|
1030
|
+
# log_func(f"This is {'an'if x.startswith('e')else 'a'} {x} message")
|
|
1031
|
+
|
|
1032
|
+
self.debug("This is a debug message")
|
|
1033
|
+
self.info("This is a info message")
|
|
1034
|
+
self.warning("This is a warning message")
|
|
1035
|
+
self.error("This is an error message")
|
|
1036
|
+
# self.exception("This is an exception message")
|
|
1037
|
+
self.critical("This is a critical message")
|
|
1038
|
+
|
|
1039
|
+
|
|
1040
|
+
# Mixin
|
|
1041
|
+
# ---------------------------------------------------------------------------
|
|
1042
|
+
class LoggerMixin[LoggerLike: logging.Logger]:
|
|
1043
|
+
"""
|
|
1044
|
+
Mixin providing a lazily-initialized logger.
|
|
1045
|
+
|
|
1046
|
+
Attributes
|
|
1047
|
+
----------
|
|
1048
|
+
CUSTOM_LOGGER : Callable[[str], LoggerLike] | None, optional
|
|
1049
|
+
Optional factory for creating a custom logger.
|
|
1050
|
+
|
|
1051
|
+
LOGGER_NAME : str | None, optional
|
|
1052
|
+
Override default logger name.
|
|
1053
|
+
|
|
1054
|
+
|
|
1055
|
+
Example:
|
|
1056
|
+
--------
|
|
1057
|
+
>>> class Test(LoggerMixin[logging.Logger]):
|
|
1058
|
+
... pass
|
|
1059
|
+
|
|
1060
|
+
>>> class Test2(LoggerMixin[AbsfuyuLogger]):
|
|
1061
|
+
... CUSTOM_LOGGER = AbsfuyuLogger
|
|
1062
|
+
... LOGGER_NAME = "App"
|
|
1063
|
+
... pass
|
|
1064
|
+
"""
|
|
1065
|
+
|
|
1066
|
+
CUSTOM_LOGGER: Callable[[str], LoggerLike] | None = None
|
|
1067
|
+
LOGGER_NAME: str | None = None
|
|
1068
|
+
|
|
1069
|
+
# def __init__(self) -> None:
|
|
1070
|
+
# self._logger: LoggerLike
|
|
1071
|
+
|
|
1072
|
+
@property
|
|
1073
|
+
def logger(self) -> LoggerLike:
|
|
1074
|
+
if not hasattr(self, "_logger"):
|
|
1075
|
+
logger_name = self.__class__.__name__ if self.LOGGER_NAME is None else self.LOGGER_NAME
|
|
1076
|
+
if self.CUSTOM_LOGGER is None:
|
|
1077
|
+
self._logger = logging.getLogger(logger_name)
|
|
1078
|
+
else:
|
|
1079
|
+
self._logger = self.CUSTOM_LOGGER(logger_name)
|
|
1080
|
+
return self._logger
|
|
1081
|
+
|
|
1082
|
+
|
|
1083
|
+
class AbsfuyuLoggerMixin(LoggerMixin[AbsfuyuLogger]):
|
|
1084
|
+
CUSTOM_LOGGER = AbsfuyuLogger
|
|
1085
|
+
|
|
1086
|
+
|
|
1087
|
+
class _AbsfuyuLoggerLib(AbsfuyuLoggerMixin):
|
|
1088
|
+
"""Logger for this library"""
|
|
1089
|
+
|
|
1090
|
+
CUSTOM_LOGGER = AbsfuyuLogger.default_config
|
|
1091
|
+
LOGGER_NAME = "absfuyu"
|