logger-36 2025.24__py3-none-any.whl → 2025.26__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.
- logger_36/api/{time.py → chronos.py} +1 -1
- logger_36/catalog/handler/console_rich.py +39 -29
- logger_36/catalog/handler/file.py +1 -2
- logger_36/catalog/handler/generic.py +24 -10
- logger_36/catalog/handler/memory.py +24 -2
- logger_36/catalog/logger/chronos.py +10 -2
- logger_36/catalog/logger/memory.py +1 -1
- logger_36/config/message.py +1 -4
- logger_36/constant/{date_time.py → chronos.py} +4 -0
- logger_36/constant/error.py +2 -0
- logger_36/constant/html.py +2 -0
- logger_36/constant/message.py +7 -7
- logger_36/constant/record.py +2 -0
- logger_36/extension/{record.py → file.py} +11 -21
- logger_36/extension/line.py +1 -3
- logger_36/task/format/memory.py +1 -1
- logger_36/task/format/message.py +7 -5
- logger_36/task/measure/chronos.py +21 -11
- logger_36/task/storage.py +30 -55
- logger_36/type/handler.py +22 -24
- logger_36/type/issue.py +7 -4
- logger_36/type/logger.py +157 -51
- logger_36/type/loggers.py +0 -2
- logger_36/version.py +1 -1
- {logger_36-2025.24.dist-info → logger_36-2025.26.dist-info}/METADATA +30 -44
- logger_36-2025.26.dist-info/RECORD +53 -0
- logger_36-2025.24.dist-info/RECORD +0 -53
- /logger_36/{constant/generic.py → extension/sentinel.py} +0 -0
- {logger_36-2025.24.dist-info → logger_36-2025.26.dist-info}/WHEEL +0 -0
- {logger_36-2025.24.dist-info → logger_36-2025.26.dist-info}/top_level.txt +0 -0
logger_36/task/storage.py
CHANGED
@@ -5,12 +5,17 @@ SEE COPYRIGHT NOTICE BELOW
|
|
5
5
|
"""
|
6
6
|
|
7
7
|
import logging as l
|
8
|
-
import os as o
|
9
|
-
import tempfile as tmps
|
10
8
|
from io import IOBase as io_base_t
|
11
9
|
from pathlib import Path as path_t
|
12
10
|
|
13
|
-
from logger_36.constant.
|
11
|
+
from logger_36.constant.error import CANNOT_SAVE_RECORDS
|
12
|
+
from logger_36.constant.html import (
|
13
|
+
BODY_PLACEHOLDER,
|
14
|
+
HTML_SUFFIX,
|
15
|
+
MINIMAL_HTML,
|
16
|
+
TITLE_PLACEHOLDER,
|
17
|
+
)
|
18
|
+
from logger_36.extension.file import NewTemporaryFile
|
14
19
|
from logger_36.instance.logger import L
|
15
20
|
from logger_36.type.logger import logger_t
|
16
21
|
|
@@ -21,60 +26,17 @@ def SaveLOGasHTML(
|
|
21
26
|
"""
|
22
27
|
From first console handler found.
|
23
28
|
"""
|
24
|
-
|
25
|
-
|
26
|
-
if path is None:
|
27
|
-
for handler in logger.handlers:
|
28
|
-
if isinstance(handler, l.FileHandler):
|
29
|
-
path = path_t(handler.baseFilename).with_suffix(".htm")
|
30
|
-
break
|
31
|
-
else:
|
32
|
-
logger.warning(f"{cannot_save}: No file handler to build a filename from.")
|
33
|
-
return
|
34
|
-
|
35
|
-
if path.exists():
|
36
|
-
logger.warning(
|
37
|
-
f'{cannot_save}: Automatically generated path "{path}" already exists.'
|
38
|
-
)
|
39
|
-
return
|
40
|
-
elif isinstance(path, str):
|
41
|
-
path = path_t(path)
|
42
|
-
|
43
|
-
actual_file = isinstance(path, path_t)
|
44
|
-
|
45
|
-
if actual_file and path.exists():
|
46
|
-
existing = path
|
47
|
-
|
48
|
-
accessor, path = tmps.mkstemp(suffix=".htm")
|
49
|
-
o.close(accessor)
|
50
|
-
path = path_t(path)
|
51
|
-
|
29
|
+
records = logger_t.Records(logger)
|
30
|
+
if records is None:
|
52
31
|
logger.warning(
|
53
|
-
f
|
32
|
+
f"{CANNOT_SAVE_RECORDS}: No handlers with recording capability found"
|
54
33
|
)
|
55
|
-
|
56
|
-
for handler in logger.handlers:
|
57
|
-
records = getattr(handler, "records", None)
|
58
|
-
if isinstance(records, list) and (
|
59
|
-
(records.__len__() == 0)
|
60
|
-
or all(
|
61
|
-
isinstance(_, tuple)
|
62
|
-
and isinstance(_[0], int)
|
63
|
-
and isinstance(_[1], dict | str | l.LogRecord)
|
64
|
-
and isinstance(_[2], bool)
|
65
|
-
for _ in records
|
66
|
-
)
|
67
|
-
):
|
68
|
-
break
|
69
|
-
else:
|
70
|
-
logger.warning(f"{cannot_save}: No handlers with recording capability found.")
|
71
34
|
return
|
72
|
-
|
73
35
|
if records.__len__() == 0:
|
74
36
|
return
|
75
37
|
|
76
38
|
if isinstance(records[0][1], str):
|
77
|
-
records = map(
|
39
|
+
records = map(_HighlightedRecord, records)
|
78
40
|
else:
|
79
41
|
records = map(lambda _: str(_[1]), records)
|
80
42
|
body = "\n".join(records)
|
@@ -82,16 +44,29 @@ def SaveLOGasHTML(
|
|
82
44
|
BODY_PLACEHOLDER, body
|
83
45
|
)
|
84
46
|
|
85
|
-
if
|
86
|
-
|
87
|
-
|
47
|
+
if path is None:
|
48
|
+
path = logger_t.StoragePath(logger, HTML_SUFFIX)
|
49
|
+
logger.info(f'Saving LOG as HTML in "{path}"')
|
50
|
+
elif isinstance(path, str):
|
51
|
+
path = path_t(path)
|
52
|
+
if path.exists():
|
53
|
+
existing = path
|
54
|
+
path = NewTemporaryFile(HTML_SUFFIX)
|
55
|
+
logger.warning(
|
56
|
+
f'File "{existing}" already exists: '
|
57
|
+
f'Saving LOG as HTML in "{path}" instead'
|
58
|
+
)
|
88
59
|
else:
|
89
60
|
path.write(html)
|
61
|
+
return
|
62
|
+
|
63
|
+
with open(path, "w") as accessor:
|
64
|
+
accessor.write(html)
|
90
65
|
|
91
66
|
|
92
|
-
def
|
67
|
+
def _HighlightedRecord(record: tuple[int, str, bool], /) -> str:
|
93
68
|
""""""
|
94
|
-
level, message, is_not_a_rule =
|
69
|
+
level, message, is_not_a_rule = record
|
95
70
|
|
96
71
|
if is_not_a_rule:
|
97
72
|
if level == l.DEBUG:
|
logger_36/type/handler.py
CHANGED
@@ -8,15 +8,14 @@ import logging as l
|
|
8
8
|
import typing as h
|
9
9
|
from pathlib import Path as path_t
|
10
10
|
|
11
|
-
from logger_36.config.message import
|
12
|
-
FALLBACK_MESSAGE_WIDTH,
|
13
|
-
LEVEL_CLOSING,
|
14
|
-
LEVEL_OPENING,
|
15
|
-
MESSAGE_MARKER,
|
16
|
-
WHERE_SEPARATOR,
|
17
|
-
)
|
11
|
+
from logger_36.config.message import FALLBACK_MESSAGE_WIDTH
|
18
12
|
from logger_36.config.rule import DEFAULT_RULE_LENGTH, RULE_CHARACTER
|
19
|
-
from logger_36.constant.message import
|
13
|
+
from logger_36.constant.message import (
|
14
|
+
NEXT_LINE_PROLOGUE,
|
15
|
+
TIME_PLACEHOLDER,
|
16
|
+
WHERE_PROLOGUE,
|
17
|
+
CONTEXT_LENGTH_p_1,
|
18
|
+
)
|
20
19
|
from logger_36.constant.record import SHOW_W_RULE_ATTR, WHEN_OR_ELAPSED_ATTR, WHERE_ATTR
|
21
20
|
from logger_36.constant.rule import DEFAULT_RULE, MIN_HALF_RULE_LENGTH
|
22
21
|
from logger_36.extension.line import WrappedLines
|
@@ -53,47 +52,46 @@ class extension_t:
|
|
53
52
|
|
54
53
|
def MessageFromRecord(
|
55
54
|
self, record: l.LogRecord, /, *, rule_color: str = "black"
|
56
|
-
) -> tuple[str, bool]:
|
55
|
+
) -> tuple[str, bool, int | None]:
|
57
56
|
"""
|
58
|
-
|
57
|
+
Arguments from second on: is_not_a_rule, where_location.
|
59
58
|
"""
|
60
59
|
message = record.msg # See logger_36.catalog.handler.README.txt.
|
61
60
|
if self.PreProcessedMessage is not None:
|
62
61
|
message = self.PreProcessedMessage(message)
|
63
62
|
|
64
63
|
if hasattr(record, SHOW_W_RULE_ATTR):
|
65
|
-
return self.Rule(text=message, color=rule_color), False
|
64
|
+
return self.Rule(text=message, color=rule_color), False, None
|
66
65
|
|
67
66
|
if (self.message_width <= 0) or (message.__len__() <= self.message_width):
|
68
67
|
if "\n" in message:
|
69
68
|
message = NEXT_LINE_PROLOGUE.join(message.splitlines())
|
70
69
|
else:
|
71
70
|
if "\n" in message:
|
72
|
-
lines =
|
71
|
+
lines = message.splitlines()
|
73
72
|
else:
|
74
|
-
lines =
|
75
|
-
message = NEXT_LINE_PROLOGUE.join(lines)
|
76
|
-
|
77
|
-
when_or_elapsed = getattr(record, WHEN_OR_ELAPSED_ATTR, None)
|
78
|
-
if when_or_elapsed is None:
|
79
|
-
return message, True
|
73
|
+
lines = [message]
|
74
|
+
message = NEXT_LINE_PROLOGUE.join(WrappedLines(lines, self.message_width))
|
80
75
|
|
76
|
+
when_or_elapsed = getattr(record, WHEN_OR_ELAPSED_ATTR, TIME_PLACEHOLDER)
|
81
77
|
if (where := getattr(record, WHERE_ATTR, None)) is None:
|
78
|
+
where_location = None
|
82
79
|
where = ""
|
83
80
|
else:
|
84
|
-
|
81
|
+
where_location = CONTEXT_LENGTH_p_1 + message.__len__()
|
82
|
+
where = f"{WHERE_PROLOGUE}{where}"
|
85
83
|
|
86
84
|
return (
|
87
|
-
f"{when_or_elapsed}"
|
88
|
-
|
89
|
-
|
90
|
-
)
|
85
|
+
f"{when_or_elapsed}_{record.levelname[0].lower()} {message}{where}",
|
86
|
+
True,
|
87
|
+
where_location,
|
88
|
+
)
|
91
89
|
|
92
90
|
def Rule(self, /, *, text: str | None = None, color: str = "black") -> str | h.Any:
|
93
91
|
"""
|
94
92
|
Return type hint h.Any: For Rich, for example.
|
95
93
|
"""
|
96
|
-
if text
|
94
|
+
if text in (None, ""):
|
97
95
|
if self.message_width > 0:
|
98
96
|
return self.message_width * RULE_CHARACTER
|
99
97
|
return DEFAULT_RULE
|
logger_36/type/issue.py
CHANGED
@@ -8,9 +8,9 @@ import logging as l
|
|
8
8
|
import typing as h
|
9
9
|
|
10
10
|
from logger_36.config.issue import ISSUE_BASE_CONTEXT
|
11
|
-
from logger_36.constant.generic import NOT_PASSED
|
12
11
|
from logger_36.constant.issue import ISSUE_LEVEL_SEPARATOR
|
13
12
|
from logger_36.constant.message import expected_op_h
|
13
|
+
from logger_36.extension.sentinel import NOT_PASSED
|
14
14
|
from logger_36.task.format.message import MessageWithActualExpected
|
15
15
|
|
16
16
|
issue_t = str
|
@@ -28,11 +28,11 @@ def NewIssue(
|
|
28
28
|
expected_is_choices: bool = False,
|
29
29
|
expected_op: expected_op_h = "=",
|
30
30
|
with_final_dot: bool = True,
|
31
|
-
) -> issue_t:
|
31
|
+
) -> tuple[issue_t, bool]:
|
32
32
|
""""""
|
33
33
|
if context.__len__() == 0:
|
34
34
|
context = ISSUE_BASE_CONTEXT
|
35
|
-
message = MessageWithActualExpected(
|
35
|
+
message, has_actual_expected = MessageWithActualExpected(
|
36
36
|
message,
|
37
37
|
actual=actual,
|
38
38
|
expected=expected,
|
@@ -41,7 +41,10 @@ def NewIssue(
|
|
41
41
|
with_final_dot=with_final_dot,
|
42
42
|
)
|
43
43
|
|
44
|
-
return
|
44
|
+
return (
|
45
|
+
f"{level}{ISSUE_LEVEL_SEPARATOR}{context}{separator}{message}",
|
46
|
+
has_actual_expected,
|
47
|
+
)
|
45
48
|
|
46
49
|
|
47
50
|
"""
|
logger_36/type/logger.py
CHANGED
@@ -7,6 +7,7 @@ SEE COPYRIGHT NOTICE BELOW
|
|
7
7
|
import dataclasses as d
|
8
8
|
import inspect as e
|
9
9
|
import logging as l
|
10
|
+
import multiprocessing as prll
|
10
11
|
import sys as s
|
11
12
|
import textwrap as text
|
12
13
|
import threading as thrd
|
@@ -15,6 +16,8 @@ import types as t
|
|
15
16
|
import typing as h
|
16
17
|
from datetime import date as date_t
|
17
18
|
from datetime import datetime as date_time_t
|
19
|
+
from logging.handlers import QueueHandler as queue_handler_t
|
20
|
+
from logging.handlers import QueueListener as log_server_t
|
18
21
|
from pathlib import Path as path_t
|
19
22
|
from traceback import TracebackException as traceback_t
|
20
23
|
|
@@ -26,25 +29,32 @@ from logger_36.catalog.config.optional import (
|
|
26
29
|
)
|
27
30
|
from logger_36.catalog.handler.console import console_handler_t
|
28
31
|
from logger_36.catalog.handler.file import file_handler_t
|
32
|
+
from logger_36.catalog.handler.memory import memory_handler_t, records_h
|
29
33
|
from logger_36.config.issue import ISSUE_CONTEXT_END, ISSUE_CONTEXT_SEPARATOR
|
30
34
|
from logger_36.config.message import (
|
31
35
|
DATE_FORMAT,
|
32
|
-
ELAPSED_TIME_SEPARATOR,
|
33
36
|
LONG_ENOUGH,
|
34
37
|
TIME_FORMAT,
|
35
38
|
WHERE_SEPARATOR,
|
36
39
|
)
|
37
|
-
from logger_36.constant.
|
38
|
-
from logger_36.constant.generic import NOT_PASSED
|
40
|
+
from logger_36.constant.chronos import DATE_ORIGIN, DATE_TIME_ORIGIN
|
39
41
|
from logger_36.constant.issue import ISSUE_LEVEL_SEPARATOR, ORDER, order_h
|
40
42
|
from logger_36.constant.logger import WARNING_LOGGER_NAME, WARNING_TYPE_COMPILED_PATTERN
|
41
43
|
from logger_36.constant.memory import UNKNOWN_MEMORY_USAGE
|
42
|
-
from logger_36.constant.message import LINE_INDENT,
|
44
|
+
from logger_36.constant.message import LINE_INDENT, expected_op_h
|
43
45
|
from logger_36.constant.path import USER_FOLDER, LAUNCH_ROOT_FILE_relative
|
44
|
-
from logger_36.constant.record import
|
45
|
-
|
46
|
+
from logger_36.constant.record import (
|
47
|
+
HAS_ACTUAL_EXPECTED_ATTR,
|
48
|
+
SHOW_W_RULE_ATTR,
|
49
|
+
SHOW_WHEN_ATTR,
|
50
|
+
SHOW_WHERE_ATTR,
|
51
|
+
WHEN_OR_ELAPSED_ATTR,
|
52
|
+
WHERE_ATTR,
|
53
|
+
)
|
54
|
+
from logger_36.extension.file import NewTemporaryFile
|
55
|
+
from logger_36.extension.sentinel import NOT_PASSED
|
46
56
|
from logger_36.task.format.message import MessageWithActualExpected
|
47
|
-
from logger_36.task.measure.chronos import
|
57
|
+
from logger_36.task.measure.chronos import FormattedElapsedTime
|
48
58
|
from logger_36.task.measure.memory import CurrentUsage as CurrentMemoryUsage
|
49
59
|
from logger_36.type.handler import extension_t as handler_extension_t
|
50
60
|
from logger_36.type.handler import handler_h as base_handler_h
|
@@ -63,6 +73,8 @@ logger_handle_raw_h = h.Callable[[l.LogRecord], None]
|
|
63
73
|
logger_handle_with_self_h = h.Callable[[l.Logger, l.LogRecord], None]
|
64
74
|
logger_handle_h = logger_handle_raw_h | logger_handle_with_self_h
|
65
75
|
|
76
|
+
MAIN_PROCESS_NAME = "MainProcess"
|
77
|
+
|
66
78
|
|
67
79
|
@d.dataclass(slots=True, repr=False, eq=False)
|
68
80
|
class logger_t(base_t):
|
@@ -87,7 +99,9 @@ class logger_t(base_t):
|
|
87
99
|
last_message_date: date_t = d.field(init=False, default=DATE_ORIGIN)
|
88
100
|
memory_usages: list[tuple[str, int]] = d.field(init=False, default_factory=list)
|
89
101
|
context_levels: list[str] = d.field(init=False, default_factory=list)
|
90
|
-
staged_issues: list[issue_t] = d.field(
|
102
|
+
staged_issues: list[tuple[issue_t, bool]] = d.field(
|
103
|
+
init=False, default_factory=list
|
104
|
+
)
|
91
105
|
intercepted_wrn_handle: logger_handle_h | None = d.field(init=False, default=None)
|
92
106
|
intercepted_log_handles: dict[str, logger_handle_h] = d.field(
|
93
107
|
init=False, default_factory=dict
|
@@ -97,6 +111,8 @@ class logger_t(base_t):
|
|
97
111
|
# Used only until the first handler is added (see AddHandler).
|
98
112
|
_should_activate_log_interceptions: bool = d.field(init=False, default=False)
|
99
113
|
|
114
|
+
log_server: log_server_t | None = d.field(init=False, default=None)
|
115
|
+
|
100
116
|
name_: d.InitVar[str | None] = None
|
101
117
|
level_: d.InitVar[int] = l.NOTSET
|
102
118
|
activate_wrn_interceptions: d.InitVar[bool] = True
|
@@ -109,6 +125,21 @@ class logger_t(base_t):
|
|
109
125
|
FormattedEntry = lambda _: f"{_[0]}: {_[1].replace('\n', '↲ ')}"
|
110
126
|
return "\n".join(map(FormattedEntry, self.history.items()))
|
111
127
|
|
128
|
+
@property
|
129
|
+
def records(self) -> records_h | None:
|
130
|
+
""""""
|
131
|
+
return logger_t.Records(self)
|
132
|
+
|
133
|
+
@staticmethod
|
134
|
+
def Records(logger: base_t | l.Logger, /) -> records_h | None:
|
135
|
+
""""""
|
136
|
+
for handler in logger.handlers:
|
137
|
+
output = getattr(handler, "records", None)
|
138
|
+
if memory_handler_t.AreRecords(output):
|
139
|
+
return output
|
140
|
+
|
141
|
+
return None
|
142
|
+
|
112
143
|
@property
|
113
144
|
def intercepts_warnings(self) -> bool:
|
114
145
|
""""""
|
@@ -156,6 +187,8 @@ class logger_t(base_t):
|
|
156
187
|
activate_exc_interceptions: bool,
|
157
188
|
) -> None:
|
158
189
|
""""""
|
190
|
+
assert prll.current_process().name == MAIN_PROCESS_NAME
|
191
|
+
|
159
192
|
if name_ is None:
|
160
193
|
name_ = f"{type(self).__name__}:{hex(id(self))[2:]}"
|
161
194
|
|
@@ -185,38 +218,39 @@ class logger_t(base_t):
|
|
185
218
|
|
186
219
|
def handle(self, record: l.LogRecord, /) -> None:
|
187
220
|
""""""
|
188
|
-
|
189
|
-
|
221
|
+
now = date_time_t.now()
|
190
222
|
if (date := now.date()) != self.last_message_date:
|
191
223
|
self._AcknowledgeDateChange(date)
|
192
224
|
|
225
|
+
level = record.levelno
|
226
|
+
|
193
227
|
# When.
|
194
|
-
if
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
228
|
+
if getattr(record, SHOW_WHEN_ATTR, True):
|
229
|
+
if now - self.last_message_now > LONG_ENOUGH:
|
230
|
+
w_or_e = f"{now:{TIME_FORMAT}}"
|
231
|
+
else:
|
232
|
+
w_or_e = FormattedElapsedTime(now) # or: f"{[...]:.<{TIME_LENGTH}}".
|
233
|
+
setattr(record, WHEN_OR_ELAPSED_ATTR, w_or_e)
|
199
234
|
self.last_message_now = now
|
200
235
|
|
201
236
|
# Where.
|
202
|
-
should_show_where = getattr(record, SHOW_WHERE_ATTR,
|
237
|
+
should_show_where = getattr(record, SHOW_WHERE_ATTR, level != l.INFO)
|
203
238
|
if should_show_where or self.should_monitor_memory_usage:
|
204
|
-
where =
|
205
|
-
|
206
|
-
|
239
|
+
where = f"{record.pathname}:{record.funcName}:{record.lineno}"
|
240
|
+
if should_show_where:
|
241
|
+
setattr(record, WHERE_ATTR, where)
|
242
|
+
if self.should_monitor_memory_usage:
|
243
|
+
self.memory_usages.append((where, CurrentMemoryUsage()))
|
207
244
|
|
208
245
|
# What.
|
209
246
|
if not isinstance(record.msg, str):
|
210
247
|
record.msg = str(record.msg)
|
211
248
|
|
212
249
|
base_t.handle(self, record)
|
213
|
-
self.n_events[
|
250
|
+
self.n_events[level] += 1
|
214
251
|
|
215
|
-
if self.
|
216
|
-
self.
|
217
|
-
|
218
|
-
if (self.exit_on_critical and (record.levelno is l.CRITICAL)) or (
|
219
|
-
self.exit_on_error and (record.levelno is l.ERROR)
|
252
|
+
if (self.exit_on_critical and (level is l.CRITICAL)) or (
|
253
|
+
self.exit_on_error and (level is l.ERROR)
|
220
254
|
):
|
221
255
|
# Also works if self.exit_on_error and record.levelno is l.CRITICAL since
|
222
256
|
# __post_init__ set self.exit_on_critical if self.exit_on_error.
|
@@ -230,7 +264,7 @@ class logger_t(base_t):
|
|
230
264
|
{
|
231
265
|
"name": self.name,
|
232
266
|
"levelno": l.INFO, # For management by logging.Logger.handle.
|
233
|
-
"msg": f"DATE: {date
|
267
|
+
"msg": f"DATE: {date:{DATE_FORMAT}}",
|
234
268
|
SHOW_W_RULE_ATTR: True,
|
235
269
|
}
|
236
270
|
)
|
@@ -390,6 +424,27 @@ class logger_t(base_t):
|
|
390
424
|
""""""
|
391
425
|
self.AddHandler(file_handler_t, path=path)
|
392
426
|
|
427
|
+
def MakeMultiSafe(self) -> None:
|
428
|
+
"""
|
429
|
+
Should not be called until after all desired handlers have been added (as a
|
430
|
+
better-then-nothing check, it is checked that the logger has at least one
|
431
|
+
handler). If handlers are added passed this call, execution might freeze or
|
432
|
+
crash.
|
433
|
+
"""
|
434
|
+
assert self.log_server is None
|
435
|
+
assert self.hasHandlers()
|
436
|
+
|
437
|
+
handlers = tuple(self.handlers) # Making a copy is necessary.
|
438
|
+
for handler in handlers:
|
439
|
+
self.removeHandler(handler)
|
440
|
+
|
441
|
+
queue = prll.Queue()
|
442
|
+
|
443
|
+
self.addHandler(queue_handler_t(queue))
|
444
|
+
|
445
|
+
self.log_server = log_server_t(queue, *handlers)
|
446
|
+
self.log_server.start()
|
447
|
+
|
393
448
|
def __call__(self, *args, **kwargs) -> None:
|
394
449
|
"""
|
395
450
|
For a print-like calling for print-based debugging.
|
@@ -401,7 +456,7 @@ class logger_t(base_t):
|
|
401
456
|
path = path_t(details.filename)
|
402
457
|
if path.is_relative_to(USER_FOLDER):
|
403
458
|
path = path.relative_to(USER_FOLDER)
|
404
|
-
where = f"{str(path.with_suffix(''))}
|
459
|
+
where = f"{str(path.with_suffix(''))}:{details.function}:{details.lineno}"
|
405
460
|
|
406
461
|
self.info(separator.join(map(str, args)) + f"\n{WHERE_SEPARATOR} " + where)
|
407
462
|
|
@@ -420,7 +475,7 @@ class logger_t(base_t):
|
|
420
475
|
""""""
|
421
476
|
if isinstance(level, str):
|
422
477
|
level = l.getLevelNamesMapping()[level.upper()]
|
423
|
-
message = MessageWithActualExpected(
|
478
|
+
message, has_actual_expected = MessageWithActualExpected(
|
424
479
|
message,
|
425
480
|
actual=actual,
|
426
481
|
expected=expected,
|
@@ -428,19 +483,25 @@ class logger_t(base_t):
|
|
428
483
|
expected_op=expected_op,
|
429
484
|
with_final_dot=with_final_dot,
|
430
485
|
)
|
431
|
-
|
486
|
+
if has_actual_expected:
|
487
|
+
extra = {HAS_ACTUAL_EXPECTED_ATTR: True}
|
488
|
+
else:
|
489
|
+
extra = {}
|
490
|
+
self.log(level, message, extra=extra)
|
432
491
|
|
433
492
|
def LogAsIs(self, message: str, /, *, indented: bool = False) -> None:
|
434
493
|
""""""
|
435
494
|
if indented:
|
436
495
|
message = text.indent(message, LINE_INDENT)
|
437
496
|
|
497
|
+
emit_message_name = handler_extension_t.EmitMessage.__name__
|
498
|
+
FallbackEmitMessage = print
|
438
499
|
for handler in self.handlers:
|
439
|
-
EmitMessage = getattr(
|
440
|
-
handler, handler_extension_t.EmitMessage.__name__, None
|
441
|
-
)
|
500
|
+
EmitMessage = getattr(handler, emit_message_name, FallbackEmitMessage)
|
442
501
|
if EmitMessage is not None:
|
443
502
|
EmitMessage(message)
|
503
|
+
if EmitMessage is print:
|
504
|
+
FallbackEmitMessage = None
|
444
505
|
|
445
506
|
info_raw = LogAsIs # To follow the convention of the logging methods info, error...
|
446
507
|
|
@@ -472,20 +533,30 @@ class logger_t(base_t):
|
|
472
533
|
self.LogException(exception, level=l.CRITICAL)
|
473
534
|
s.exit(1)
|
474
535
|
|
475
|
-
def DealWithExceptionInThread(
|
476
|
-
self, exc_type, exc_value, exc_traceback, _, /
|
477
|
-
) -> None:
|
536
|
+
def DealWithExceptionInThread(self, args, /) -> None:
|
478
537
|
""""""
|
479
|
-
self.DealWithException(exc_type, exc_value, exc_traceback)
|
538
|
+
self.DealWithException(args.exc_type, args.exc_value, args.exc_traceback)
|
480
539
|
|
481
540
|
def DisplayRule(
|
482
541
|
self, /, *, message: str | None = None, color: str = "white"
|
483
542
|
) -> None:
|
484
543
|
""""""
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
544
|
+
if message is None:
|
545
|
+
message = ""
|
546
|
+
record = l.makeLogRecord(
|
547
|
+
{
|
548
|
+
"name": self.name,
|
549
|
+
"levelno": l.INFO, # For management by logging.Logger.handle.
|
550
|
+
"msg": message,
|
551
|
+
SHOW_W_RULE_ATTR: True,
|
552
|
+
}
|
553
|
+
)
|
554
|
+
base_t.handle(self, record)
|
555
|
+
# emit_rule_name = handler_extension_t.EmitRule.__name__
|
556
|
+
# for handler in self.handlers:
|
557
|
+
# EmitRule = getattr(handler, emit_rule_name, None)
|
558
|
+
# if EmitRule is not None:
|
559
|
+
# EmitRule(text=message, color=color)
|
489
560
|
|
490
561
|
def AddContextLevel(self, new_level: str, /) -> None:
|
491
562
|
""""""
|
@@ -527,7 +598,9 @@ class logger_t(base_t):
|
|
527
598
|
)
|
528
599
|
self.staged_issues.append(issue)
|
529
600
|
|
530
|
-
def PopIssues(
|
601
|
+
def PopIssues(
|
602
|
+
self, /, *, should_remove_context: bool = False
|
603
|
+
) -> list[tuple[str, bool]]:
|
531
604
|
""""""
|
532
605
|
if not self.has_staged_issues:
|
533
606
|
return []
|
@@ -539,10 +612,10 @@ class logger_t(base_t):
|
|
539
612
|
else:
|
540
613
|
separator = ISSUE_LEVEL_SEPARATOR
|
541
614
|
separator_length = separator.__len__()
|
542
|
-
for issue in self.staged_issues:
|
615
|
+
for issue, has_actual_expected in self.staged_issues:
|
543
616
|
start_idx = issue.find(separator)
|
544
617
|
issue = issue[(start_idx + separator_length) :]
|
545
|
-
output.append(issue)
|
618
|
+
output.append((issue, has_actual_expected))
|
546
619
|
|
547
620
|
self.staged_issues.clear()
|
548
621
|
|
@@ -564,13 +637,16 @@ class logger_t(base_t):
|
|
564
637
|
"Invalid commit order",
|
565
638
|
actual=order,
|
566
639
|
expected=f"One of {str(ORDER)[1:-1]}",
|
567
|
-
)
|
640
|
+
)[0]
|
568
641
|
)
|
569
642
|
|
570
643
|
if order == "when":
|
571
644
|
issues = self.staged_issues
|
572
645
|
else: # order == "context"
|
573
|
-
issues = sorted(
|
646
|
+
issues = sorted(
|
647
|
+
self.staged_issues,
|
648
|
+
key=lambda _: _[0].split(ISSUE_LEVEL_SEPARATOR, maxsplit=1)[1],
|
649
|
+
)
|
574
650
|
"""
|
575
651
|
Format issues as an exception:
|
576
652
|
try:
|
@@ -582,20 +658,43 @@ class logger_t(base_t):
|
|
582
658
|
formatted = "\n".join(lines)
|
583
659
|
"""
|
584
660
|
|
585
|
-
|
661
|
+
extra = {SHOW_WHERE_ATTR: False}
|
586
662
|
if unified:
|
587
|
-
level, _ = issues[0].split(ISSUE_LEVEL_SEPARATOR, maxsplit=1)
|
663
|
+
level, _ = issues[0][0].split(ISSUE_LEVEL_SEPARATOR, maxsplit=1)
|
588
664
|
wo_level = []
|
589
|
-
|
665
|
+
any_has_actual_expected = False
|
666
|
+
for issue, has_actual_expected in issues:
|
590
667
|
_, issue = issue.split(ISSUE_LEVEL_SEPARATOR, maxsplit=1)
|
668
|
+
if has_actual_expected:
|
669
|
+
any_has_actual_expected = True
|
591
670
|
wo_level.append(issue)
|
592
|
-
|
671
|
+
if any_has_actual_expected:
|
672
|
+
extra[HAS_ACTUAL_EXPECTED_ATTR] = True
|
673
|
+
self.log(int(level), "\n".join(wo_level), stacklevel=2, extra=extra)
|
593
674
|
else:
|
594
|
-
for issue in issues:
|
675
|
+
for issue, has_actual_expected in issues:
|
595
676
|
level, issue = issue.split(ISSUE_LEVEL_SEPARATOR, maxsplit=1)
|
596
|
-
|
677
|
+
if has_actual_expected:
|
678
|
+
extra[HAS_ACTUAL_EXPECTED_ATTR] = True
|
679
|
+
self.log(int(level), issue, stacklevel=2, extra=extra)
|
680
|
+
if has_actual_expected:
|
681
|
+
del extra[HAS_ACTUAL_EXPECTED_ATTR]
|
597
682
|
self.staged_issues.clear()
|
598
683
|
|
684
|
+
def StoragePath(self, suffix: str, /) -> path_t:
|
685
|
+
"""
|
686
|
+
Use as staticmethod if needed.
|
687
|
+
"""
|
688
|
+
for handler in self.handlers:
|
689
|
+
if (path := getattr(handler, "baseFilename", None)) is not None:
|
690
|
+
output = path_t(path).with_suffix(suffix)
|
691
|
+
if output.exists():
|
692
|
+
output = NewTemporaryFile(suffix)
|
693
|
+
|
694
|
+
return output
|
695
|
+
|
696
|
+
return NewTemporaryFile(suffix)
|
697
|
+
|
599
698
|
def __enter__(self) -> None:
|
600
699
|
""""""
|
601
700
|
pass
|
@@ -611,6 +710,13 @@ class logger_t(base_t):
|
|
611
710
|
_ = self.context_levels.pop()
|
612
711
|
return False
|
613
712
|
|
713
|
+
def __del__(self) -> None:
|
714
|
+
""""""
|
715
|
+
if (prll.current_process().name == MAIN_PROCESS_NAME) and (
|
716
|
+
self.log_server is not None
|
717
|
+
):
|
718
|
+
self.log_server.stop()
|
719
|
+
|
614
720
|
|
615
721
|
def _HandleForWarnings(interceptor: base_t, /) -> logger_handle_h:
|
616
722
|
""""""
|
logger_36/type/loggers.py
CHANGED
@@ -22,7 +22,6 @@ class loggers_t(dict[h.Hashable, logger_t]):
|
|
22
22
|
*,
|
23
23
|
name: str | None = None,
|
24
24
|
level: int = l.NOTSET,
|
25
|
-
should_record_messages: bool = False,
|
26
25
|
exit_on_error: bool = False,
|
27
26
|
exit_on_critical: bool = False,
|
28
27
|
activate_wrn_interceptions: bool = True,
|
@@ -31,7 +30,6 @@ class loggers_t(dict[h.Hashable, logger_t]):
|
|
31
30
|
) -> None:
|
32
31
|
""""""
|
33
32
|
logger = logger_t(
|
34
|
-
should_record_messages=should_record_messages,
|
35
33
|
exit_on_error=exit_on_error,
|
36
34
|
exit_on_critical=exit_on_critical,
|
37
35
|
name_=name,
|