logger-36 2025.6__py3-none-any.whl → 2025.8__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/storage.py +1 -1
- logger_36/catalog/handler/console.py +5 -10
- logger_36/catalog/handler/console_rich.py +16 -13
- logger_36/catalog/handler/file.py +5 -10
- logger_36/catalog/handler/generic.py +10 -13
- logger_36/constant/path.py +69 -0
- logger_36/exception.py +4 -3
- logger_36/extension/html_.py +107 -0
- logger_36/handler.py +3 -3
- logger_36/task/storage.py +10 -77
- logger_36/type/handler.py +4 -7
- logger_36/type/logger.py +212 -130
- logger_36/version.py +1 -1
- {logger_36-2025.6.dist-info → logger_36-2025.8.dist-info}/METADATA +1 -1
- {logger_36-2025.6.dist-info → logger_36-2025.8.dist-info}/RECORD +17 -15
- {logger_36-2025.6.dist-info → logger_36-2025.8.dist-info}/WHEEL +0 -0
- {logger_36-2025.6.dist-info → logger_36-2025.8.dist-info}/top_level.txt +0 -0
logger_36/api/storage.py
CHANGED
@@ -6,14 +6,11 @@ SEE COPYRIGHT NOTICE BELOW
|
|
6
6
|
|
7
7
|
import dataclasses as d
|
8
8
|
import logging as l
|
9
|
-
import textwrap as txt_
|
10
9
|
import typing as h
|
11
10
|
|
12
|
-
from logger_36.constant.message import LINE_INDENT
|
13
11
|
from logger_36.constant.record import SHOW_W_RULE_ATTR
|
14
12
|
from logger_36.task.format.rule import RuleAsText
|
15
|
-
from logger_36.type.handler import handler_extension_t
|
16
|
-
from logger_36.type.handler import message_from_record_raw_p as message_from_record_p
|
13
|
+
from logger_36.type.handler import MessageFromRecordRaw_h, handler_extension_t
|
17
14
|
|
18
15
|
|
19
16
|
@d.dataclass(slots=True, repr=False, eq=False)
|
@@ -25,7 +22,7 @@ class console_handler_t(l.Handler):
|
|
25
22
|
kind: h.ClassVar[str] = "c"
|
26
23
|
|
27
24
|
extension: handler_extension_t = d.field(init=False)
|
28
|
-
MessageFromRecord:
|
25
|
+
MessageFromRecord: MessageFromRecordRaw_h = d.field(init=False)
|
29
26
|
|
30
27
|
name: d.InitVar[str | None] = None
|
31
28
|
level: d.InitVar[int] = l.NOTSET
|
@@ -63,18 +60,16 @@ class console_handler_t(l.Handler):
|
|
63
60
|
message = self.MessageFromRecord(record)
|
64
61
|
print(message)
|
65
62
|
|
66
|
-
def
|
63
|
+
def LogAsIs(self, message: str, /) -> None:
|
67
64
|
"""
|
68
65
|
See documentation of
|
69
|
-
logger_36.catalog.handler.generic.generic_handler_t.
|
66
|
+
logger_36.catalog.handler.generic.generic_handler_t.LogAsIs.
|
70
67
|
"""
|
71
|
-
if indented:
|
72
|
-
message = txt_.indent(message, LINE_INDENT)
|
73
68
|
print(message)
|
74
69
|
|
75
70
|
def DisplayRule(self, /, *, text: str | None = None, color: str = "white") -> None:
|
76
71
|
""""""
|
77
|
-
self.
|
72
|
+
self.LogAsIs(RuleAsText(text))
|
78
73
|
|
79
74
|
|
80
75
|
"""
|
@@ -6,7 +6,6 @@ SEE COPYRIGHT NOTICE BELOW
|
|
6
6
|
|
7
7
|
import dataclasses as d
|
8
8
|
import logging as l
|
9
|
-
import textwrap as txt_
|
10
9
|
import typing as h
|
11
10
|
|
12
11
|
from logger_36.catalog.config.console_rich import (
|
@@ -20,13 +19,10 @@ from logger_36.catalog.config.console_rich import (
|
|
20
19
|
WHITE_COLOR,
|
21
20
|
)
|
22
21
|
from logger_36.config.message import ACTUAL_PATTERNS, EXPECTED_PATTERNS, WHERE_SEPARATOR
|
23
|
-
from logger_36.constant.message import CONTEXT_LENGTH
|
22
|
+
from logger_36.constant.message import CONTEXT_LENGTH
|
24
23
|
from logger_36.constant.record import SHOW_W_RULE_ATTR
|
25
|
-
from logger_36.task.format.rule import Rule
|
26
|
-
from logger_36.type.handler import handler_extension_t
|
27
|
-
from logger_36.type.handler import (
|
28
|
-
message_from_record_preprocessed_p as message_from_record_p,
|
29
|
-
)
|
24
|
+
from logger_36.task.format.rule import Rule
|
25
|
+
from logger_36.type.handler import MessageFromRecordPreprocessed_p, handler_extension_t
|
30
26
|
from rich.console import Console as console_t # noqa
|
31
27
|
from rich.console import RenderableType as renderable_t # noqa
|
32
28
|
from rich.markup import escape as EscapedVersion # noqa
|
@@ -64,7 +60,7 @@ class console_rich_handler_t(l.Handler):
|
|
64
60
|
|
65
61
|
extension: handler_extension_t = d.field(init=False)
|
66
62
|
console: console_t = d.field(init=False)
|
67
|
-
MessageFromRecord:
|
63
|
+
MessageFromRecord: MessageFromRecordPreprocessed_p = d.field(init=False)
|
68
64
|
alternating_lines: int = 0
|
69
65
|
background_is_light: bool = True
|
70
66
|
|
@@ -78,6 +74,15 @@ class console_rich_handler_t(l.Handler):
|
|
78
74
|
|
79
75
|
rich_kwargs: d.InitVar[dict[str, h.Any] | None] = None
|
80
76
|
|
77
|
+
@property
|
78
|
+
def past_logs_as_HTML(self) -> str | None:
|
79
|
+
""""""
|
80
|
+
console = self.console
|
81
|
+
if console.record:
|
82
|
+
return console.export_html()
|
83
|
+
|
84
|
+
return None
|
85
|
+
|
81
86
|
def __post_init__(
|
82
87
|
self,
|
83
88
|
name: str | None,
|
@@ -152,18 +157,16 @@ class console_rich_handler_t(l.Handler):
|
|
152
157
|
)
|
153
158
|
self.console.print(richer, crop=False, overflow="ignore")
|
154
159
|
|
155
|
-
def
|
160
|
+
def LogAsIs(self, message: str | renderable_t, /) -> None:
|
156
161
|
"""
|
157
162
|
See documentation of
|
158
|
-
logger_36.catalog.handler.generic.generic_handler_t.
|
163
|
+
logger_36.catalog.handler.generic.generic_handler_t.LogAsIs.
|
159
164
|
"""
|
160
|
-
if isinstance(message, str) and indented:
|
161
|
-
message = txt_.indent(message, LINE_INDENT)
|
162
165
|
self.console.print(message, crop=False, overflow="ignore")
|
163
166
|
|
164
167
|
def DisplayRule(self, /, *, text: str | None = None, color: str = "white") -> None:
|
165
168
|
""""""
|
166
|
-
self.
|
169
|
+
self.LogAsIs(Rule(text, color))
|
167
170
|
|
168
171
|
|
169
172
|
def HighlightedVersion(
|
@@ -6,15 +6,12 @@ SEE COPYRIGHT NOTICE BELOW
|
|
6
6
|
|
7
7
|
import dataclasses as d
|
8
8
|
import logging as l
|
9
|
-
import textwrap as txt_
|
10
9
|
import typing as h
|
11
10
|
from pathlib import Path as path_t
|
12
11
|
|
13
|
-
from logger_36.constant.message import LINE_INDENT
|
14
12
|
from logger_36.constant.record import SHOW_W_RULE_ATTR
|
15
13
|
from logger_36.task.format.rule import RuleAsText
|
16
|
-
from logger_36.type.handler import handler_extension_t
|
17
|
-
from logger_36.type.handler import message_from_record_raw_p as message_from_record_p
|
14
|
+
from logger_36.type.handler import MessageFromRecordRaw_h, handler_extension_t
|
18
15
|
|
19
16
|
|
20
17
|
@d.dataclass(slots=True, repr=False, eq=False)
|
@@ -26,7 +23,7 @@ class file_handler_t(l.FileHandler):
|
|
26
23
|
kind: h.ClassVar[str] = "f"
|
27
24
|
|
28
25
|
extension: handler_extension_t = d.field(init=False)
|
29
|
-
MessageFromRecord:
|
26
|
+
MessageFromRecord: MessageFromRecordRaw_h = d.field(init=False)
|
30
27
|
|
31
28
|
name: d.InitVar[str | None] = None
|
32
29
|
level: d.InitVar[int] = l.NOTSET
|
@@ -72,19 +69,17 @@ class file_handler_t(l.FileHandler):
|
|
72
69
|
print(message, file=self.stream)
|
73
70
|
self.stream.flush()
|
74
71
|
|
75
|
-
def
|
72
|
+
def LogAsIs(self, message: str, /) -> None:
|
76
73
|
"""
|
77
74
|
See documentation of
|
78
|
-
logger_36.catalog.handler.generic.generic_handler_t.
|
75
|
+
logger_36.catalog.handler.generic.generic_handler_t.LogAsIs.
|
79
76
|
"""
|
80
|
-
if indented:
|
81
|
-
message = txt_.indent(message, LINE_INDENT)
|
82
77
|
print(message, file=self.stream)
|
83
78
|
self.stream.flush()
|
84
79
|
|
85
80
|
def DisplayRule(self, /, *, text: str | None = None, color: str = "white") -> None:
|
86
81
|
""""""
|
87
|
-
self.
|
82
|
+
self.LogAsIs(RuleAsText(text))
|
88
83
|
|
89
84
|
|
90
85
|
"""
|
@@ -24,16 +24,13 @@ else:
|
|
24
24
|
|
25
25
|
from logger_36.constant.record import SHOW_W_RULE_ATTR
|
26
26
|
from logger_36.task.format.rule import Rule, RuleAsText
|
27
|
-
from logger_36.type.handler import
|
27
|
+
from logger_36.type.handler import MessageFromRecord_h, handler_extension_t
|
28
28
|
|
29
|
-
|
30
|
-
@h.runtime_checkable
|
31
|
-
class show_message_p(h.Protocol):
|
32
|
-
def __call__(self, message: str, /, *, indented: bool = False) -> None: ...
|
29
|
+
LogAsIs_h = h.Callable[[str | h.Any], None]
|
33
30
|
|
34
31
|
|
35
32
|
@h.runtime_checkable
|
36
|
-
class
|
33
|
+
class DisplayRule_p(h.Protocol):
|
37
34
|
def __call__(self, /, *, text: str | None = None, color: str = "white") -> None: ...
|
38
35
|
|
39
36
|
|
@@ -49,7 +46,7 @@ class generic_handler_t(l.Handler):
|
|
49
46
|
- anything else: disabled
|
50
47
|
- Runtime value: 0/1=do not/do highlight next time.
|
51
48
|
|
52
|
-
|
49
|
+
LogAsIs:
|
53
50
|
Log a message as is, i.e. without formatting. If this is a method, it should
|
54
51
|
contain the same call(s) as the final ones in the emit methods that are used to
|
55
52
|
output the formatted log messages. This means that there is some code
|
@@ -62,16 +59,16 @@ class generic_handler_t(l.Handler):
|
|
62
59
|
|
63
60
|
kind: h.ClassVar[str] = "g"
|
64
61
|
|
65
|
-
|
62
|
+
LogAsIs: LogAsIs_h
|
66
63
|
# "None -> h.Any" (twice below) since None | None is invalid.
|
67
64
|
console: console_t | h.Any = None
|
68
65
|
console_options: console_options_t | h.Any = None
|
69
66
|
alternating_lines: int = 0
|
70
67
|
background_is_light: bool = True
|
71
68
|
|
72
|
-
DisplayRule:
|
69
|
+
DisplayRule: DisplayRule_p = d.field(init=False)
|
73
70
|
extension: handler_extension_t = d.field(init=False)
|
74
|
-
MessageFromRecord:
|
71
|
+
MessageFromRecord: MessageFromRecord_h = d.field(init=False)
|
75
72
|
|
76
73
|
name: d.InitVar[str | None] = None
|
77
74
|
level: d.InitVar[int] = l.NOTSET
|
@@ -178,17 +175,17 @@ class generic_handler_t(l.Handler):
|
|
178
175
|
"<pre style='margin-bottom:0px'>" + "".join(html_segments) + "</pre>"
|
179
176
|
)
|
180
177
|
|
181
|
-
self.
|
178
|
+
self.LogAsIs(message)
|
182
179
|
|
183
180
|
def _DisplayRuleAsText(
|
184
181
|
self, /, *, text: str | None = None, color: str = "white"
|
185
182
|
) -> None:
|
186
183
|
""""""
|
187
|
-
self.
|
184
|
+
self.LogAsIs(RuleAsText(text))
|
188
185
|
|
189
186
|
def _DisplayRule(self, /, *, text: str | None = None, color: str = "white") -> None:
|
190
187
|
""""""
|
191
|
-
self.
|
188
|
+
self.LogAsIs(Rule(text, color))
|
192
189
|
|
193
190
|
|
194
191
|
"""
|
@@ -0,0 +1,69 @@
|
|
1
|
+
"""
|
2
|
+
Copyright CNRS/Inria/UniCA
|
3
|
+
Contributor(s): Eric Debreuve (eric.debreuve@cnrs.fr) since 2023
|
4
|
+
SEE COPYRIGHT NOTICE BELOW
|
5
|
+
"""
|
6
|
+
|
7
|
+
import inspect as nspt
|
8
|
+
import tempfile as tmps
|
9
|
+
from pathlib import Path as path_t
|
10
|
+
|
11
|
+
USER_FOLDER = path_t.home()
|
12
|
+
|
13
|
+
frame = nspt.stack()[-1]
|
14
|
+
if path_t(frame.filename).exists():
|
15
|
+
PROJECT_FILE = path_t(frame.filename)
|
16
|
+
if PROJECT_FILE.is_relative_to(USER_FOLDER):
|
17
|
+
PROJECT_FILE_RELATIVE = path_t("~") / PROJECT_FILE.relative_to(USER_FOLDER)
|
18
|
+
else:
|
19
|
+
PROJECT_FILE_RELATIVE = PROJECT_FILE
|
20
|
+
PROJECT_FOLDER = PROJECT_FILE.parent
|
21
|
+
else:
|
22
|
+
PROJECT_FILE = PROJECT_FILE_RELATIVE = "<unknown>"
|
23
|
+
PROJECT_FOLDER = path_t(tmps.mkdtemp())
|
24
|
+
|
25
|
+
"""
|
26
|
+
COPYRIGHT NOTICE
|
27
|
+
|
28
|
+
This software is governed by the CeCILL license under French law and
|
29
|
+
abiding by the rules of distribution of free software. You can use,
|
30
|
+
modify and/ or redistribute the software under the terms of the CeCILL
|
31
|
+
license as circulated by CEA, CNRS and INRIA at the following URL
|
32
|
+
"http://www.cecill.info".
|
33
|
+
|
34
|
+
As a counterpart to the access to the source code and rights to copy,
|
35
|
+
modify and redistribute granted by the license, users are provided only
|
36
|
+
with a limited warranty and the software's author, the holder of the
|
37
|
+
economic rights, and the successive licensors have only limited
|
38
|
+
liability.
|
39
|
+
|
40
|
+
In this respect, the user's attention is drawn to the risks associated
|
41
|
+
with loading, using, modifying and/or developing or reproducing the
|
42
|
+
software by the user in light of its specific status of free software,
|
43
|
+
that may mean that it is complicated to manipulate, and that also
|
44
|
+
therefore means that it is reserved for developers and experienced
|
45
|
+
professionals having in-depth computer knowledge. Users are therefore
|
46
|
+
encouraged to load and test the software's suitability as regards their
|
47
|
+
requirements in conditions enabling the security of their systems and/or
|
48
|
+
data to be ensured and, more generally, to use and operate it in the
|
49
|
+
same conditions as regards security.
|
50
|
+
|
51
|
+
The fact that you are presently reading this means that you have had
|
52
|
+
knowledge of the CeCILL license and that you accept its terms.
|
53
|
+
|
54
|
+
SEE LICENCE NOTICE: file README-LICENCE-utf8.txt at project source root.
|
55
|
+
|
56
|
+
This software is being developed by Eric Debreuve, a CNRS employee and
|
57
|
+
member of team Morpheme.
|
58
|
+
Team Morpheme is a joint team between Inria, CNRS, and UniCA.
|
59
|
+
It is hosted by the Centre Inria d'Université Côte d'Azur, Laboratory
|
60
|
+
I3S, and Laboratory iBV.
|
61
|
+
|
62
|
+
CNRS: https://www.cnrs.fr/index.php/en
|
63
|
+
Inria: https://www.inria.fr/en/
|
64
|
+
UniCA: https://univ-cotedazur.eu/
|
65
|
+
Centre Inria d'Université Côte d'Azur: https://www.inria.fr/en/centre/sophia/
|
66
|
+
I3S: https://www.i3s.unice.fr/en/
|
67
|
+
iBV: http://ibv.unice.fr/
|
68
|
+
Team Morpheme: https://team.inria.fr/morpheme/
|
69
|
+
"""
|
logger_36/exception.py
CHANGED
@@ -11,6 +11,8 @@ import traceback as tcbk
|
|
11
11
|
import types as t
|
12
12
|
from pathlib import Path as path_t
|
13
13
|
|
14
|
+
from logger_36.constant.path import USER_FOLDER
|
15
|
+
|
14
16
|
_ORIGINAL_EXCEPTION_HANDLER = s.excepthook
|
15
17
|
|
16
18
|
|
@@ -38,9 +40,8 @@ def _HandleException(
|
|
38
40
|
line_content = module.read_text().splitlines()[line_number - 1].strip()
|
39
41
|
|
40
42
|
# Format module.
|
41
|
-
|
42
|
-
|
43
|
-
module = path_t("~") / module.relative_to(home)
|
43
|
+
if module.is_relative_to(USER_FOLDER):
|
44
|
+
module = path_t("~") / module.relative_to(USER_FOLDER)
|
44
45
|
|
45
46
|
# Format line content.
|
46
47
|
if line_content.startswith("raise "):
|
@@ -0,0 +1,107 @@
|
|
1
|
+
"""
|
2
|
+
Copyright CNRS/Inria/UniCA
|
3
|
+
Contributor(s): Eric Debreuve (eric.debreuve@cnrs.fr) since 2023
|
4
|
+
SEE COPYRIGHT NOTICE BELOW
|
5
|
+
"""
|
6
|
+
|
7
|
+
import dataclasses as d
|
8
|
+
import re as r
|
9
|
+
from html.parser import HTMLParser as base_t
|
10
|
+
|
11
|
+
_BODY_END_PATTERN = r"</[bB][oO][dD][yY]>(.|\n)*$"
|
12
|
+
|
13
|
+
|
14
|
+
@d.dataclass(slots=True, repr=False, eq=False)
|
15
|
+
class html_content_t(base_t):
|
16
|
+
source: str = ""
|
17
|
+
inside_body: bool = d.field(init=False, default=False)
|
18
|
+
body_position_start: tuple[int, int] = d.field(init=False, default=(-1, -1))
|
19
|
+
body_position_end: tuple[int, int] = d.field(init=False, default=(-1, -1))
|
20
|
+
pieces: list[str] = d.field(init=False, default_factory=list)
|
21
|
+
|
22
|
+
@property
|
23
|
+
def body(self) -> str:
|
24
|
+
""""""
|
25
|
+
output = self.source.splitlines()
|
26
|
+
output = "\n".join(
|
27
|
+
output[self.body_position_start[0] : (self.body_position_end[0] + 1)]
|
28
|
+
)
|
29
|
+
output = output[self.body_position_start[1] :]
|
30
|
+
output = r.sub(_BODY_END_PATTERN, "", output, count=1)
|
31
|
+
|
32
|
+
return output.strip()
|
33
|
+
|
34
|
+
@property
|
35
|
+
def body_as_text(self) -> str:
|
36
|
+
""""""
|
37
|
+
return "".join(self.pieces).strip()
|
38
|
+
|
39
|
+
def __post_init__(self) -> None:
|
40
|
+
""""""
|
41
|
+
base_t.__init__(self)
|
42
|
+
self.source = self.source.strip()
|
43
|
+
self.feed(self.source)
|
44
|
+
|
45
|
+
def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]], /) -> None:
|
46
|
+
""""""
|
47
|
+
if tag == "body":
|
48
|
+
self.body_position_start = self.getpos()
|
49
|
+
self.inside_body = True
|
50
|
+
|
51
|
+
def handle_endtag(self, tag: str, /) -> None:
|
52
|
+
""""""
|
53
|
+
if tag == "body":
|
54
|
+
self.body_position_end = self.getpos()
|
55
|
+
self.inside_body = False
|
56
|
+
|
57
|
+
def handle_data(self, data: str, /) -> None:
|
58
|
+
""""""
|
59
|
+
if self.inside_body:
|
60
|
+
self.pieces.append(data)
|
61
|
+
|
62
|
+
|
63
|
+
"""
|
64
|
+
COPYRIGHT NOTICE
|
65
|
+
|
66
|
+
This software is governed by the CeCILL license under French law and
|
67
|
+
abiding by the rules of distribution of free software. You can use,
|
68
|
+
modify and/ or redistribute the software under the terms of the CeCILL
|
69
|
+
license as circulated by CEA, CNRS and INRIA at the following URL
|
70
|
+
"http://www.cecill.info".
|
71
|
+
|
72
|
+
As a counterpart to the access to the source code and rights to copy,
|
73
|
+
modify and redistribute granted by the license, users are provided only
|
74
|
+
with a limited warranty and the software's author, the holder of the
|
75
|
+
economic rights, and the successive licensors have only limited
|
76
|
+
liability.
|
77
|
+
|
78
|
+
In this respect, the user's attention is drawn to the risks associated
|
79
|
+
with loading, using, modifying and/or developing or reproducing the
|
80
|
+
software by the user in light of its specific status of free software,
|
81
|
+
that may mean that it is complicated to manipulate, and that also
|
82
|
+
therefore means that it is reserved for developers and experienced
|
83
|
+
professionals having in-depth computer knowledge. Users are therefore
|
84
|
+
encouraged to load and test the software's suitability as regards their
|
85
|
+
requirements in conditions enabling the security of their systems and/or
|
86
|
+
data to be ensured and, more generally, to use and operate it in the
|
87
|
+
same conditions as regards security.
|
88
|
+
|
89
|
+
The fact that you are presently reading this means that you have had
|
90
|
+
knowledge of the CeCILL license and that you accept its terms.
|
91
|
+
|
92
|
+
SEE LICENCE NOTICE: file README-LICENCE-utf8.txt at project source root.
|
93
|
+
|
94
|
+
This software is being developed by Eric Debreuve, a CNRS employee and
|
95
|
+
member of team Morpheme.
|
96
|
+
Team Morpheme is a joint team between Inria, CNRS, and UniCA.
|
97
|
+
It is hosted by the Centre Inria d'Université Côte d'Azur, Laboratory
|
98
|
+
I3S, and Laboratory iBV.
|
99
|
+
|
100
|
+
CNRS: https://www.cnrs.fr/index.php/en
|
101
|
+
Inria: https://www.inria.fr/en/
|
102
|
+
UniCA: https://univ-cotedazur.eu/
|
103
|
+
Centre Inria d'Université Côte d'Azur: https://www.inria.fr/en/centre/sophia/
|
104
|
+
I3S: https://www.i3s.unice.fr/en/
|
105
|
+
iBV: http://ibv.unice.fr/
|
106
|
+
Team Morpheme: https://team.inria.fr/morpheme/
|
107
|
+
"""
|
logger_36/handler.py
CHANGED
@@ -11,7 +11,7 @@ from pathlib import Path as path_t
|
|
11
11
|
from logger_36.catalog.config.optional import MISSING_RICH_MESSAGE, RICH_IS_AVAILABLE
|
12
12
|
from logger_36.catalog.handler.console import console_handler_t
|
13
13
|
from logger_36.catalog.handler.file import file_handler_t
|
14
|
-
from logger_36.catalog.handler.generic import
|
14
|
+
from logger_36.catalog.handler.generic import LogAsIs_h, generic_handler_t
|
15
15
|
|
16
16
|
if RICH_IS_AVAILABLE:
|
17
17
|
from logger_36.catalog.handler.console_rich import console_rich_handler_t
|
@@ -24,7 +24,7 @@ _MISSING_RICH_MESSAGE = MISSING_RICH_MESSAGE
|
|
24
24
|
|
25
25
|
def AddGenericHandler(
|
26
26
|
logger: l.Logger,
|
27
|
-
|
27
|
+
LogAsIs: LogAsIs_h,
|
28
28
|
/,
|
29
29
|
*,
|
30
30
|
name: str | None = None,
|
@@ -49,7 +49,7 @@ def AddGenericHandler(
|
|
49
49
|
alternating_lines=alternating_lines,
|
50
50
|
should_record=should_record,
|
51
51
|
rich_kwargs=kwargs,
|
52
|
-
|
52
|
+
LogAsIs=LogAsIs,
|
53
53
|
)
|
54
54
|
logger.AddHandler(handler, should_hold_messages=should_hold_messages)
|
55
55
|
|
logger_36/task/storage.py
CHANGED
@@ -4,73 +4,12 @@ Contributor(s): Eric Debreuve (eric.debreuve@cnrs.fr) since 2023
|
|
4
4
|
SEE COPYRIGHT NOTICE BELOW
|
5
5
|
"""
|
6
6
|
|
7
|
-
import dataclasses as d
|
8
7
|
import logging as l
|
9
|
-
import re as r
|
10
|
-
from html.parser import HTMLParser as html_parser_t
|
11
8
|
from io import IOBase as io_base_t
|
12
9
|
from pathlib import Path as path_t
|
13
10
|
|
14
|
-
from logger_36.catalog.config.optional import RICH_IS_AVAILABLE
|
15
11
|
from logger_36.instance.logger import L
|
16
12
|
|
17
|
-
if RICH_IS_AVAILABLE:
|
18
|
-
from rich.console import Console as console_t # noqa
|
19
|
-
else:
|
20
|
-
console_t = None
|
21
|
-
|
22
|
-
|
23
|
-
_BODY_END_PATTERN = r"</[bB][oO][dD][yY]>(.|\n)*$"
|
24
|
-
|
25
|
-
|
26
|
-
@d.dataclass(slots=True, repr=False, eq=False)
|
27
|
-
class html_reader_t(html_parser_t):
|
28
|
-
source: str = ""
|
29
|
-
inside_body: bool = d.field(init=False, default=False)
|
30
|
-
body_position_start: tuple[int, int] = d.field(init=False, default=(-1, -1))
|
31
|
-
body_position_end: tuple[int, int] = d.field(init=False, default=(-1, -1))
|
32
|
-
pieces: list[str] = d.field(init=False, default_factory=list)
|
33
|
-
|
34
|
-
def __post_init__(self) -> None:
|
35
|
-
""""""
|
36
|
-
html_parser_t.__init__(self)
|
37
|
-
self.source = self.source.strip()
|
38
|
-
self.feed(self.source)
|
39
|
-
|
40
|
-
def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]], /) -> None:
|
41
|
-
""""""
|
42
|
-
if tag == "body":
|
43
|
-
self.body_position_start = self.getpos()
|
44
|
-
self.inside_body = True
|
45
|
-
|
46
|
-
def handle_endtag(self, tag: str, /) -> None:
|
47
|
-
""""""
|
48
|
-
if tag == "body":
|
49
|
-
self.body_position_end = self.getpos()
|
50
|
-
self.inside_body = False
|
51
|
-
|
52
|
-
def handle_data(self, data: str, /) -> None:
|
53
|
-
""""""
|
54
|
-
if self.inside_body:
|
55
|
-
self.pieces.append(data)
|
56
|
-
|
57
|
-
@property
|
58
|
-
def body(self) -> str:
|
59
|
-
""""""
|
60
|
-
output = self.source.splitlines()
|
61
|
-
output = "\n".join(
|
62
|
-
output[self.body_position_start[0] : (self.body_position_end[0] + 1)]
|
63
|
-
)
|
64
|
-
output = output[self.body_position_start[1] :]
|
65
|
-
output = r.sub(_BODY_END_PATTERN, "", output, count=1)
|
66
|
-
|
67
|
-
return output.strip()
|
68
|
-
|
69
|
-
@property
|
70
|
-
def body_as_text(self) -> str:
|
71
|
-
""""""
|
72
|
-
return "".join(self.pieces).strip()
|
73
|
-
|
74
13
|
|
75
14
|
def SaveLOGasHTML(path: str | path_t | io_base_t | None = None) -> None:
|
76
15
|
"""
|
@@ -78,18 +17,15 @@ def SaveLOGasHTML(path: str | path_t | io_base_t | None = None) -> None:
|
|
78
17
|
"""
|
79
18
|
cannot_save = "Cannot save logging record as HTML"
|
80
19
|
|
81
|
-
if console_t is None:
|
82
|
-
L.warning(f"{cannot_save}: The Rich console cannot be imported.")
|
83
|
-
return
|
84
|
-
|
85
20
|
if path is None:
|
86
21
|
for handler in L.handlers:
|
87
22
|
if isinstance(handler, l.FileHandler):
|
88
23
|
path = path_t(handler.baseFilename).with_suffix(".htm")
|
89
24
|
break
|
90
|
-
|
25
|
+
else:
|
91
26
|
L.warning(f"{cannot_save}: No file handler to build a filename from.")
|
92
27
|
return
|
28
|
+
|
93
29
|
if path.exists():
|
94
30
|
L.warning(
|
95
31
|
f'{cannot_save}: Automatically generated path "{path}" already exists.'
|
@@ -103,18 +39,15 @@ def SaveLOGasHTML(path: str | path_t | io_base_t | None = None) -> None:
|
|
103
39
|
L.warning(f'{cannot_save}: File "{path}" already exists.')
|
104
40
|
return
|
105
41
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
html = console.export_html()
|
110
|
-
if actual_file:
|
111
|
-
with open(path, "w") as accessor:
|
112
|
-
accessor.write(html)
|
113
|
-
else:
|
114
|
-
path.write(html)
|
115
|
-
break
|
42
|
+
html = L.past_logs_as_HTML
|
43
|
+
if html is None:
|
44
|
+
L.warning(f"{cannot_save}: No handler could provide an HTML output.")
|
116
45
|
else:
|
117
|
-
|
46
|
+
if actual_file:
|
47
|
+
with open(path, "w") as accessor:
|
48
|
+
accessor.write(html)
|
49
|
+
else:
|
50
|
+
path.write(html)
|
118
51
|
|
119
52
|
|
120
53
|
"""
|
logger_36/type/handler.py
CHANGED
@@ -22,14 +22,11 @@ from logger_36.task.format.message import MessageWithActualExpected
|
|
22
22
|
from logger_36.task.measure.chronos import TimeStamp
|
23
23
|
from logger_36.task.measure.memory import CanCheckUsage as CanCheckMemoryUsage
|
24
24
|
|
25
|
-
|
26
|
-
@h.runtime_checkable
|
27
|
-
class message_from_record_raw_p(h.Protocol):
|
28
|
-
def __call__(self, record: l.LogRecord, /) -> str: ...
|
25
|
+
MessageFromRecordRaw_h = h.Callable[[l.LogRecord], str]
|
29
26
|
|
30
27
|
|
31
28
|
@h.runtime_checkable
|
32
|
-
class
|
29
|
+
class MessageFromRecordPreprocessed_p(h.Protocol):
|
33
30
|
def __call__(
|
34
31
|
self,
|
35
32
|
record: l.LogRecord,
|
@@ -39,7 +36,7 @@ class message_from_record_preprocessed_p(h.Protocol):
|
|
39
36
|
) -> str: ...
|
40
37
|
|
41
38
|
|
42
|
-
|
39
|
+
MessageFromRecord_h = MessageFromRecordRaw_h | MessageFromRecordPreprocessed_p
|
43
40
|
|
44
41
|
_MEMORY_MEASURE_ERROR = MEMORY_MEASURE_ERROR
|
45
42
|
|
@@ -49,7 +46,7 @@ class handler_extension_t:
|
|
49
46
|
name: str | None = None
|
50
47
|
should_store_memory_usage: bool = False
|
51
48
|
message_width: int = -1
|
52
|
-
MessageFromRecord:
|
49
|
+
MessageFromRecord: MessageFromRecord_h = d.field(init=False)
|
53
50
|
|
54
51
|
handler: d.InitVar[l.Handler | None] = None
|
55
52
|
level: d.InitVar[int] = l.NOTSET
|
logger_36/type/logger.py
CHANGED
@@ -7,6 +7,7 @@ SEE COPYRIGHT NOTICE BELOW
|
|
7
7
|
import dataclasses as d
|
8
8
|
import logging as l
|
9
9
|
import sys as s
|
10
|
+
import textwrap as text
|
10
11
|
import traceback as tcbk
|
11
12
|
import types as t
|
12
13
|
import typing as h
|
@@ -32,15 +33,17 @@ from logger_36.constant.logger import (
|
|
32
33
|
WARNING_TYPE_COMPILED_PATTERN,
|
33
34
|
)
|
34
35
|
from logger_36.constant.memory import UNKNOWN_MEMORY_USAGE
|
35
|
-
from logger_36.constant.message import TIME_LENGTH_m_1, expected_op_h
|
36
|
+
from logger_36.constant.message import LINE_INDENT, TIME_LENGTH_m_1, expected_op_h
|
37
|
+
from logger_36.constant.path import PROJECT_FILE_RELATIVE
|
36
38
|
from logger_36.constant.record import (
|
37
39
|
HIDE_WHERE_ATTR,
|
38
40
|
SHOW_W_RULE_ATTR,
|
39
41
|
STORE_MEMORY_ATTR,
|
40
42
|
)
|
41
43
|
from logger_36.exception import OverrideExceptionFormat
|
42
|
-
from logger_36.handler import AddRichConsoleHandler
|
44
|
+
from logger_36.handler import AddConsoleHandler, AddFileHandler, AddRichConsoleHandler
|
43
45
|
from logger_36.task.format.message import MessageWithActualExpected
|
46
|
+
from logger_36.task.format.rule import RuleAsText
|
44
47
|
from logger_36.task.measure.chronos import ElapsedTime
|
45
48
|
from logger_36.task.measure.memory import CurrentUsage as CurrentMemoryUsage
|
46
49
|
from logger_36.type.issue import NewIssue, issue_t
|
@@ -84,6 +87,51 @@ class logger_t(base_t):
|
|
84
87
|
init=False, default_factory=dict
|
85
88
|
)
|
86
89
|
|
90
|
+
@property
|
91
|
+
def intercepts_warnings(self) -> bool:
|
92
|
+
""""""
|
93
|
+
return self.intercepted_wrn_handle is not None
|
94
|
+
|
95
|
+
@property
|
96
|
+
def intercepts_logs(self) -> bool:
|
97
|
+
""""""
|
98
|
+
return self.intercepted_log_handles.__len__() > 0
|
99
|
+
|
100
|
+
@property
|
101
|
+
def has_staged_issues(self) -> bool:
|
102
|
+
""""""
|
103
|
+
return self.staged_issues.__len__() > 0
|
104
|
+
|
105
|
+
@property
|
106
|
+
def past_logs_as_HTML(self) -> str | None:
|
107
|
+
"""
|
108
|
+
From the first handler found with the given functionality, if any.
|
109
|
+
"""
|
110
|
+
for handler in self.handlers:
|
111
|
+
past_logs_as_HTML = getattr(handler, "past_logs_as_HTML", None)
|
112
|
+
if past_logs_as_HTML is not None:
|
113
|
+
return past_logs_as_HTML
|
114
|
+
|
115
|
+
return None
|
116
|
+
|
117
|
+
@property
|
118
|
+
def max_memory_usage(self) -> int:
|
119
|
+
""""""
|
120
|
+
if self.memory_usages.__len__() > 0:
|
121
|
+
return max(tuple(zip(*self.memory_usages))[1])
|
122
|
+
return UNKNOWN_MEMORY_USAGE
|
123
|
+
|
124
|
+
@property
|
125
|
+
def max_memory_usage_full(self) -> tuple[str, int]:
|
126
|
+
""""""
|
127
|
+
if self.memory_usages.__len__() > 0:
|
128
|
+
where_s, usages = zip(*self.memory_usages)
|
129
|
+
max_usage = max(usages)
|
130
|
+
|
131
|
+
return where_s[usages.index(max_usage)], max_usage
|
132
|
+
|
133
|
+
return "?", UNKNOWN_MEMORY_USAGE
|
134
|
+
|
87
135
|
def __post_init__(
|
88
136
|
self, name_: str, level_: int, activate_wrn_interceptions: bool
|
89
137
|
) -> None:
|
@@ -95,11 +143,92 @@ class logger_t(base_t):
|
|
95
143
|
for level in l.getLevelNamesMapping().values():
|
96
144
|
self.events[level] = 0
|
97
145
|
|
146
|
+
self.info(f'New logger "{self.name}" for "{PROJECT_FILE_RELATIVE}"')
|
147
|
+
|
98
148
|
if activate_wrn_interceptions:
|
99
|
-
self.
|
149
|
+
self.ToggleWarningInterceptions(True)
|
100
150
|
if self.exit_on_error:
|
101
151
|
self.exit_on_critical = True
|
102
152
|
|
153
|
+
def handle(self, record: l.LogRecord, /) -> None:
|
154
|
+
""""""
|
155
|
+
elapsed_time, now = ElapsedTime(should_return_now=True)
|
156
|
+
|
157
|
+
if (self.on_hold.__len__() > 0) and not self.should_hold_messages:
|
158
|
+
for held in self.on_hold:
|
159
|
+
base_t.handle(self, held)
|
160
|
+
self.on_hold.clear()
|
161
|
+
|
162
|
+
if (date := now.date()) != self.last_message_date:
|
163
|
+
self.last_message_date = date
|
164
|
+
# levelno: Added for management by logging.Logger.handle.
|
165
|
+
date_record = l.makeLogRecord(
|
166
|
+
{
|
167
|
+
"name": self.name,
|
168
|
+
"levelno": l.INFO,
|
169
|
+
"msg": f"DATE: {date.strftime(DATE_FORMAT)}",
|
170
|
+
SHOW_W_RULE_ATTR: True,
|
171
|
+
}
|
172
|
+
)
|
173
|
+
if self.should_hold_messages:
|
174
|
+
self.on_hold.append(date_record)
|
175
|
+
else:
|
176
|
+
base_t.handle(self, date_record)
|
177
|
+
|
178
|
+
# When.
|
179
|
+
if now - self.last_message_now > LONG_ENOUGH:
|
180
|
+
record.when_or_elapsed = now.strftime(TIME_FORMAT)
|
181
|
+
else:
|
182
|
+
record.when_or_elapsed = (
|
183
|
+
f"{ELAPSED_TIME_SEPARATOR}{elapsed_time:.<{TIME_LENGTH_m_1}}"
|
184
|
+
)
|
185
|
+
self.last_message_now = now
|
186
|
+
|
187
|
+
# Where.
|
188
|
+
# Memory usage is also stored if there are no handlers yet, just in case.
|
189
|
+
should_store_where = self.any_handler_stores_memory or not self.hasHandlers()
|
190
|
+
should_show_where = (record.levelno != l.INFO) and not hasattr(
|
191
|
+
record, HIDE_WHERE_ATTR
|
192
|
+
)
|
193
|
+
if should_store_where or should_show_where:
|
194
|
+
module = path_t(record.pathname)
|
195
|
+
for path in s.path:
|
196
|
+
if module.is_relative_to(path):
|
197
|
+
module = module.relative_to(path).with_suffix("")
|
198
|
+
module = str(module).replace(FOLDER_SEPARATOR, ".")
|
199
|
+
break
|
200
|
+
else:
|
201
|
+
module = record.module
|
202
|
+
where = f"{module}:{record.funcName}:{record.lineno}"
|
203
|
+
if should_show_where:
|
204
|
+
record.where = where
|
205
|
+
else:
|
206
|
+
where = None
|
207
|
+
|
208
|
+
# How.
|
209
|
+
record.level_first_letter = record.levelname[0]
|
210
|
+
|
211
|
+
# What.
|
212
|
+
if not isinstance(record.msg, str):
|
213
|
+
record.msg = str(record.msg)
|
214
|
+
|
215
|
+
if self.should_hold_messages:
|
216
|
+
self.on_hold.append(record)
|
217
|
+
else:
|
218
|
+
base_t.handle(self, record)
|
219
|
+
|
220
|
+
if (self.exit_on_critical and (record.levelno is l.CRITICAL)) or (
|
221
|
+
self.exit_on_error and (record.levelno is l.ERROR)
|
222
|
+
):
|
223
|
+
# Also works if self.exit_on_error and record.levelno is l.CRITICAL since
|
224
|
+
# __post_init__ set self.exit_on_critical if self.exit_on_error.
|
225
|
+
s.exit(1)
|
226
|
+
|
227
|
+
self.events[record.levelno] += 1
|
228
|
+
|
229
|
+
if should_store_where:
|
230
|
+
self.memory_usages.append((where, CurrentMemoryUsage()))
|
231
|
+
|
103
232
|
def SetLevel(
|
104
233
|
self,
|
105
234
|
level: int,
|
@@ -133,32 +262,82 @@ class logger_t(base_t):
|
|
133
262
|
)
|
134
263
|
)
|
135
264
|
|
136
|
-
def
|
265
|
+
def MakeMonochrome(
|
266
|
+
self,
|
267
|
+
*,
|
268
|
+
should_intercept_logs: bool = True,
|
269
|
+
should_override_exceptions: bool = True,
|
270
|
+
) -> None:
|
271
|
+
""""""
|
272
|
+
self._MakePreamble(
|
273
|
+
should_intercept_logs=should_intercept_logs,
|
274
|
+
should_override_exceptions=should_override_exceptions,
|
275
|
+
)
|
276
|
+
AddConsoleHandler(self)
|
277
|
+
|
278
|
+
def MakeRich(
|
279
|
+
self,
|
280
|
+
*,
|
281
|
+
alternating_lines: int = 2,
|
282
|
+
should_intercept_logs: bool = True,
|
283
|
+
should_override_exceptions: bool = True,
|
284
|
+
) -> None:
|
137
285
|
""""""
|
138
|
-
|
286
|
+
self._MakePreamble(
|
287
|
+
should_intercept_logs=should_intercept_logs,
|
288
|
+
should_override_exceptions=should_override_exceptions,
|
289
|
+
)
|
139
290
|
AddRichConsoleHandler(self, alternating_lines=alternating_lines)
|
140
291
|
|
292
|
+
def MakePermanent(
|
293
|
+
self,
|
294
|
+
path: str | path_t,
|
295
|
+
/,
|
296
|
+
*,
|
297
|
+
should_intercept_logs: bool = True,
|
298
|
+
should_override_exceptions: bool = True,
|
299
|
+
) -> None:
|
300
|
+
""""""
|
301
|
+
self._MakePreamble(
|
302
|
+
should_intercept_logs=should_intercept_logs,
|
303
|
+
should_override_exceptions=should_override_exceptions,
|
304
|
+
)
|
305
|
+
AddFileHandler(self, path)
|
306
|
+
|
307
|
+
def _MakePreamble(
|
308
|
+
self,
|
309
|
+
*,
|
310
|
+
should_intercept_logs: bool = True,
|
311
|
+
should_override_exceptions: bool = True,
|
312
|
+
) -> None:
|
313
|
+
""""""
|
314
|
+
if should_override_exceptions:
|
315
|
+
OverrideExceptionFormat()
|
316
|
+
if should_intercept_logs:
|
317
|
+
self.ToggleLogInterceptions(True)
|
318
|
+
|
141
319
|
def ResetEventCounts(self) -> None:
|
142
320
|
""""""
|
143
321
|
for level in self.events:
|
144
322
|
self.events[level] = 0
|
145
323
|
|
146
|
-
def
|
324
|
+
def ToggleWarningInterceptions(self, state: bool, /) -> None:
|
147
325
|
"""
|
148
326
|
The log message will not appear if called from __post_init__ since there are no
|
149
327
|
handlers yet.
|
150
328
|
"""
|
151
|
-
if
|
329
|
+
if state:
|
330
|
+
assert not self.intercepts_warnings
|
331
|
+
|
152
332
|
logger = l.getLogger(WARNING_LOGGER_NAME)
|
153
333
|
self.intercepted_wrn_handle = logger.handle
|
154
334
|
logger.handle = t.MethodType(_HandleForWarnings(self), logger)
|
155
335
|
|
156
336
|
l.captureWarnings(True)
|
157
337
|
self.info("Warning Interception: ON")
|
338
|
+
else:
|
339
|
+
assert self.intercepts_warnings
|
158
340
|
|
159
|
-
def _DeactivateWarningInterceptions(self) -> None:
|
160
|
-
""""""
|
161
|
-
if self.intercepted_wrn_handle is not None:
|
162
341
|
logger = l.getLogger(WARNING_LOGGER_NAME)
|
163
342
|
logger.handle = self.intercepted_wrn_handle
|
164
343
|
self.intercepted_wrn_handle = None
|
@@ -166,18 +345,12 @@ class logger_t(base_t):
|
|
166
345
|
l.captureWarnings(False)
|
167
346
|
self.info("Warning Interception: OFF")
|
168
347
|
|
169
|
-
def ToggleWarningInterceptions(self, state: bool, /) -> None:
|
170
|
-
""""""
|
171
|
-
if state:
|
172
|
-
self._ActivateWarningInterceptions()
|
173
|
-
else:
|
174
|
-
self._DeactivateWarningInterceptions()
|
175
|
-
|
176
348
|
def ToggleLogInterceptions(self, state: bool, /) -> None:
|
177
349
|
""""""
|
178
350
|
if state:
|
179
|
-
self.
|
351
|
+
assert not self.intercepts_logs
|
180
352
|
|
353
|
+
# Note: Alternative to self.manager is logging.root.manager.
|
181
354
|
all_loggers = [l.getLogger()] + [
|
182
355
|
l.getLogger(_nme)
|
183
356
|
for _nme in self.manager.loggerDict
|
@@ -193,31 +366,15 @@ class logger_t(base_t):
|
|
193
366
|
if intercepted.__len__() > 0:
|
194
367
|
as_str = ", ".join(intercepted)
|
195
368
|
self.info(f"Now Intercepting LOGs from: {as_str}")
|
196
|
-
|
369
|
+
else:
|
370
|
+
assert self.intercepts_logs
|
371
|
+
|
197
372
|
for name, handle in self.intercepted_log_handles.items():
|
198
373
|
logger = l.getLogger(name)
|
199
374
|
logger.handle = handle
|
200
375
|
self.intercepted_log_handles.clear()
|
201
376
|
self.info("Log Interception: OFF")
|
202
377
|
|
203
|
-
@property
|
204
|
-
def max_memory_usage(self) -> int:
|
205
|
-
""""""
|
206
|
-
if self.memory_usages.__len__() > 0:
|
207
|
-
return max(tuple(zip(*self.memory_usages))[1])
|
208
|
-
return UNKNOWN_MEMORY_USAGE
|
209
|
-
|
210
|
-
@property
|
211
|
-
def max_memory_usage_full(self) -> tuple[str, int]:
|
212
|
-
""""""
|
213
|
-
if self.memory_usages.__len__() > 0:
|
214
|
-
where_s, usages = zip(*self.memory_usages)
|
215
|
-
max_usage = max(usages)
|
216
|
-
|
217
|
-
return where_s[usages.index(max_usage)], max_usage
|
218
|
-
|
219
|
-
return "?", UNKNOWN_MEMORY_USAGE
|
220
|
-
|
221
378
|
def AddHandler(
|
222
379
|
self, handler: l.Handler, /, *, should_hold_messages: bool = False
|
223
380
|
) -> None:
|
@@ -244,85 +401,6 @@ class logger_t(base_t):
|
|
244
401
|
f"level {handler.level}={l.getLevelName(handler.level)}{path}",
|
245
402
|
)
|
246
403
|
|
247
|
-
def handle(self, record: l.LogRecord, /) -> None:
|
248
|
-
""""""
|
249
|
-
elapsed_time, now = ElapsedTime(should_return_now=True)
|
250
|
-
|
251
|
-
if (self.on_hold.__len__() > 0) and not self.should_hold_messages:
|
252
|
-
for held in self.on_hold:
|
253
|
-
base_t.handle(self, held)
|
254
|
-
self.on_hold.clear()
|
255
|
-
|
256
|
-
if (date := now.date()) != self.last_message_date:
|
257
|
-
self.last_message_date = date
|
258
|
-
# levelno: Added for management by logging.Logger.handle.
|
259
|
-
date_record = l.makeLogRecord(
|
260
|
-
{
|
261
|
-
"name": self.name,
|
262
|
-
"levelno": l.INFO,
|
263
|
-
"msg": f"DATE: {date.strftime(DATE_FORMAT)}",
|
264
|
-
SHOW_W_RULE_ATTR: True,
|
265
|
-
}
|
266
|
-
)
|
267
|
-
if self.should_hold_messages:
|
268
|
-
self.on_hold.append(date_record)
|
269
|
-
else:
|
270
|
-
base_t.handle(self, date_record)
|
271
|
-
|
272
|
-
# When.
|
273
|
-
if now - self.last_message_now > LONG_ENOUGH:
|
274
|
-
record.when_or_elapsed = now.strftime(TIME_FORMAT)
|
275
|
-
else:
|
276
|
-
record.when_or_elapsed = (
|
277
|
-
f"{ELAPSED_TIME_SEPARATOR}{elapsed_time:.<{TIME_LENGTH_m_1}}"
|
278
|
-
)
|
279
|
-
self.last_message_now = now
|
280
|
-
|
281
|
-
# Where.
|
282
|
-
# Memory usage is also stored if there are no handlers yet, just in case.
|
283
|
-
should_store_where = self.any_handler_stores_memory or not self.hasHandlers()
|
284
|
-
should_show_where = (record.levelno != l.INFO) and not hasattr(
|
285
|
-
record, HIDE_WHERE_ATTR
|
286
|
-
)
|
287
|
-
if should_store_where or should_show_where:
|
288
|
-
module = path_t(record.pathname)
|
289
|
-
for path in s.path:
|
290
|
-
if module.is_relative_to(path):
|
291
|
-
module = module.relative_to(path).with_suffix("")
|
292
|
-
module = str(module).replace(FOLDER_SEPARATOR, ".")
|
293
|
-
break
|
294
|
-
else:
|
295
|
-
module = record.module
|
296
|
-
where = f"{module}:{record.funcName}:{record.lineno}"
|
297
|
-
if should_show_where:
|
298
|
-
record.where = where
|
299
|
-
else:
|
300
|
-
where = None
|
301
|
-
|
302
|
-
# How.
|
303
|
-
record.level_first_letter = record.levelname[0]
|
304
|
-
|
305
|
-
# What.
|
306
|
-
if not isinstance(record.msg, str):
|
307
|
-
record.msg = str(record.msg)
|
308
|
-
|
309
|
-
if self.should_hold_messages:
|
310
|
-
self.on_hold.append(record)
|
311
|
-
else:
|
312
|
-
base_t.handle(self, record)
|
313
|
-
|
314
|
-
if (self.exit_on_critical and (record.levelno is l.CRITICAL)) or (
|
315
|
-
self.exit_on_error and (record.levelno is l.ERROR)
|
316
|
-
):
|
317
|
-
# Also works if self.exit_on_error and record.levelno is l.CRITICAL since
|
318
|
-
# __post_init__ set self.exit_on_critical if self.exit_on_error.
|
319
|
-
s.exit(1)
|
320
|
-
|
321
|
-
self.events[record.levelno] += 1
|
322
|
-
|
323
|
-
if should_store_where:
|
324
|
-
self.memory_usages.append((where, CurrentMemoryUsage()))
|
325
|
-
|
326
404
|
def Log(
|
327
405
|
self,
|
328
406
|
message: str,
|
@@ -370,22 +448,31 @@ class logger_t(base_t):
|
|
370
448
|
message = f"{type(exception).__name__}:\n{formatted}"
|
371
449
|
self.log(level, message)
|
372
450
|
|
373
|
-
def
|
451
|
+
def LogAsIs(self, message: str, /, *, indented: bool = False) -> None:
|
374
452
|
"""
|
375
453
|
See documentation of
|
376
|
-
logger_36.catalog.handler.generic.generic_handler_t.
|
454
|
+
logger_36.catalog.handler.generic.generic_handler_t.LogAsIs.
|
377
455
|
"""
|
456
|
+
if indented:
|
457
|
+
message = text.indent(message, LINE_INDENT)
|
458
|
+
|
378
459
|
for handler in self.handlers:
|
379
|
-
|
380
|
-
|
381
|
-
|
460
|
+
if (LogAsIs := getattr(handler, "LogAsIs", None)) is None:
|
461
|
+
self.info(message)
|
462
|
+
else:
|
463
|
+
LogAsIs(message)
|
382
464
|
|
383
|
-
|
465
|
+
raw_info = LogAsIs # To follow the convention of the logging methods info, error...
|
466
|
+
|
467
|
+
def DisplayRule(
|
468
|
+
self, /, *, message: str | None = None, color: str = "white"
|
469
|
+
) -> None:
|
384
470
|
""""""
|
385
471
|
for handler in self.handlers:
|
386
|
-
DisplayRule
|
387
|
-
|
388
|
-
|
472
|
+
if (DisplayRule := getattr(handler, "DisplayRule", None)) is None:
|
473
|
+
self.info(RuleAsText(message))
|
474
|
+
else:
|
475
|
+
DisplayRule(text=message, color=color)
|
389
476
|
|
390
477
|
def AddContextLevel(self, new_level: str, /) -> None:
|
391
478
|
""""""
|
@@ -427,11 +514,6 @@ class logger_t(base_t):
|
|
427
514
|
)
|
428
515
|
self.staged_issues.append(issue)
|
429
516
|
|
430
|
-
@property
|
431
|
-
def has_staged_issues(self) -> bool:
|
432
|
-
""""""
|
433
|
-
return self.staged_issues.__len__() > 0
|
434
|
-
|
435
517
|
def CommitIssues(
|
436
518
|
self,
|
437
519
|
/,
|
logger_36/version.py
CHANGED
@@ -1,21 +1,21 @@
|
|
1
1
|
logger_36/__init__.py,sha256=3BtAgxFb14e9zzC5fXwqSQxstsd3BO0b_KVu3_wbLwg,2592
|
2
2
|
logger_36/content.py,sha256=clHYYUKa8n4qef6PVlUV4mFHRRf6fnm9wEd2fu9oagA,2381
|
3
|
-
logger_36/exception.py,sha256=
|
3
|
+
logger_36/exception.py,sha256=BQm9maX9CMjpqN26cEsArPCcX37LqYtQSrUZ3fN8tbU,4613
|
4
4
|
logger_36/gpu.py,sha256=BOumedCAPWvCo7J-KJ3XE-jr5S0KSmgcFv_S4QKRPO8,2252
|
5
|
-
logger_36/handler.py,sha256=
|
5
|
+
logger_36/handler.py,sha256=pIwunW-_aSB-SrdlvVmq61nOTH03deKIVcJa4Sz_hkc,6304
|
6
6
|
logger_36/memory.py,sha256=szJVk4UTXsbYv3B-W9LFttf1F3j86GXHsKgEUOsXKl4,2743
|
7
7
|
logger_36/storage.py,sha256=sCxkHQH4xMaseweK1p2M1j0j2PxNPpy9MytPdg1sKiQ,2239
|
8
8
|
logger_36/system.py,sha256=cgOMF_OneYeIJDMbIbIDx96EZss2uAdkk8QofOC7O1U,2251
|
9
9
|
logger_36/time.py,sha256=Uw1jQtY1njsRuIPRAXX44v4nPOo84MSBu_WK_YCRzQs,2324
|
10
|
-
logger_36/version.py,sha256=
|
10
|
+
logger_36/version.py,sha256=dIKiLoaPWfEeTZKKzEklIyLlE8cFbHUq2h72W5GmOiI,2205
|
11
11
|
logger_36/api/logger.py,sha256=TE3ATbymeWX-wBKBFkVz2FxUyJnaqY7vzFwAONVsp2o,2233
|
12
|
-
logger_36/api/storage.py,sha256=
|
12
|
+
logger_36/api/storage.py,sha256=v1iywLEItJCz18F_nJ20OnlpCpLdA-7EhlvqdLu42js,2243
|
13
13
|
logger_36/catalog/config/console_rich.py,sha256=lAa5Ev5BhXvmQzfIt1FNihMNUQJFlXaIzNanAMdgtd0,2861
|
14
14
|
logger_36/catalog/config/optional.py,sha256=HaN6mbx7gHBBppNvUw1ckhYTOrlYqb-b_r0mzPcHPjM,2398
|
15
|
-
logger_36/catalog/handler/console.py,sha256=
|
16
|
-
logger_36/catalog/handler/console_rich.py,sha256=
|
17
|
-
logger_36/catalog/handler/file.py,sha256=
|
18
|
-
logger_36/catalog/handler/generic.py,sha256=
|
15
|
+
logger_36/catalog/handler/console.py,sha256=s2DBcDK9To-wVS228RsDDPmOPxlIbVnQbZINfIK2TP0,4150
|
16
|
+
logger_36/catalog/handler/console_rich.py,sha256=uYCoxPBPypaioSibC68Vw9r1XoY8AB5pAq2-RV1a4wQ,8544
|
17
|
+
logger_36/catalog/handler/file.py,sha256=2qbsI3UHxqEm9WiCMkAm20hA2qXth2wKnakazVbwrBs,4613
|
18
|
+
logger_36/catalog/handler/generic.py,sha256=y-f6HY5xppoHYYnej0qOQT3BI0Gam_0W1_bIHCk5nn0,9212
|
19
19
|
logger_36/catalog/logger/chronos.py,sha256=ocY13f98EfknU7wZCv0FS9Xb7pTNaWCPSusXFIEvEd4,2437
|
20
20
|
logger_36/catalog/logger/gpu.py,sha256=lzrkqrMnXsszRB_TiHFqnNNI7JhNat8qL2OSlnHDe5c,3412
|
21
21
|
logger_36/catalog/logger/memory.py,sha256=CWhr2J4BqArJxzH6tS-ZThr-rYPAQGtuLn0pP7Iryfg,4685
|
@@ -31,22 +31,24 @@ logger_36/constant/issue.py,sha256=01l8itRPWGS5F6gXtsXUJgGR-4lS1Eu3_YeKC-khKLw,2
|
|
31
31
|
logger_36/constant/logger.py,sha256=2qRkteblpbHrq9x0aiw9MPquyXrSRd6_yMQnPEhFp2U,2468
|
32
32
|
logger_36/constant/memory.py,sha256=ZL1MwbdtNsrCrOwzEyfTsfOoOsRBTJtbbf3otHGnxXo,2343
|
33
33
|
logger_36/constant/message.py,sha256=Ys_CAyhENlT8Z3rr-AxO4hjdl1jLsKzVSPQ8wqLOCPQ,2838
|
34
|
+
logger_36/constant/path.py,sha256=fKJn2vGj012BU5DFRetDFus_tKMty2q_WL0J2KrXdCo,2731
|
34
35
|
logger_36/constant/record.py,sha256=9Q28lVH_s0og4v74delgwIPAJ9G28I5rBM-brXcoY80,2308
|
35
36
|
logger_36/constant/system.py,sha256=G2mzBTxRXoJMxb53TnmBaceMJC_q3WonoCG7y6nC_R8,2430
|
37
|
+
logger_36/extension/html_.py,sha256=J9EX8-Rotq9i8bZ9U-dIpXv5gKLLnLmWqdDy4XayT1Q,3868
|
36
38
|
logger_36/instance/logger.py,sha256=oTw5svRzKRJKvGrrZUtutJIOjp5UISft3fl0Ze7DOBE,2241
|
37
39
|
logger_36/instance/loggers.py,sha256=RCWpC1NPAf6vXnFc9NqsSALv-x-FEzcH6k_OlxTxeQk,2251
|
38
40
|
logger_36/task/inspection.py,sha256=KZzmQyREQ6VmBWCLyNIYIOOISW9C_fC9TWTSX90zGDk,5019
|
39
|
-
logger_36/task/storage.py,sha256=
|
41
|
+
logger_36/task/storage.py,sha256=T96JZT5Tmrt_-Kqf_WKweTvJYPX6lmPZZkJzCqyVPcI,3502
|
40
42
|
logger_36/task/format/memory.py,sha256=jpQS8tAdxy7GM_FzqEIJUU3m-6O9iX-jiyO7gx5YwR8,4266
|
41
43
|
logger_36/task/format/message.py,sha256=T2V2gUlUQqSojyRrz4I4uAHwNe6eBEsuAe6V-LTyx0k,3867
|
42
44
|
logger_36/task/format/rule.py,sha256=vkf-HivFb4VqV2GeOPVqMAp99krtziI-kXhox3UVnzw,2873
|
43
45
|
logger_36/task/measure/chronos.py,sha256=1kVhu6jZlNAtNWQQh8ZVuRwZIAC9gGz3_ul1tn0t4Yw,3055
|
44
46
|
logger_36/task/measure/memory.py,sha256=OjU5EYFH8SnzlCQKAoiXvauUlwQYOrH34jFXTVYF0jE,2517
|
45
|
-
logger_36/type/handler.py,sha256
|
47
|
+
logger_36/type/handler.py,sha256=-myl7uBMOzkwCs1u4ehuYlQa9F6909jmnL2v_eQN5ag,6819
|
46
48
|
logger_36/type/issue.py,sha256=2rGsFqaQJCbeml9xN08mN_nK79L8qscaS_0ws36Y0bI,3214
|
47
|
-
logger_36/type/logger.py,sha256=
|
49
|
+
logger_36/type/logger.py,sha256=J6J87-RkH0RwiROeDQBnoTxLO5eXz32-GKjtxqyRXwk,22743
|
48
50
|
logger_36/type/loggers.py,sha256=znqxWBnfQxvkg3VUfbTUvt3S6Kq0DAzWWepxQDt9suI,2871
|
49
|
-
logger_36-2025.
|
50
|
-
logger_36-2025.
|
51
|
-
logger_36-2025.
|
52
|
-
logger_36-2025.
|
51
|
+
logger_36-2025.8.dist-info/METADATA,sha256=R6yfIOmsKd4dJgf9EwdP7Mv1TtSlLD04g5liwcKXPOU,6505
|
52
|
+
logger_36-2025.8.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
53
|
+
logger_36-2025.8.dist-info/top_level.txt,sha256=sM95BTMWmslEEgR_1pzwZsOeSp8C_QBiu8ImbFr0XLc,10
|
54
|
+
logger_36-2025.8.dist-info/RECORD,,
|
File without changes
|
File without changes
|