logger-36 2025.6__py3-none-any.whl → 2025.7__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.
@@ -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: message_from_record_p = d.field(init=False)
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 ShowMessage(self, message: str, /, *, indented: bool = False) -> None:
63
+ def LogAsIs(self, message: str, /) -> None:
67
64
  """
68
65
  See documentation of
69
- logger_36.catalog.handler.generic.generic_handler_t.ShowMessage.
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.ShowMessage(RuleAsText(text))
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, LINE_INDENT
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, rule_t
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: message_from_record_p = d.field(init=False)
63
+ MessageFromRecord: MessageFromRecordPreprocessed_p = d.field(init=False)
68
64
  alternating_lines: int = 0
69
65
  background_is_light: bool = True
70
66
 
@@ -152,18 +148,16 @@ class console_rich_handler_t(l.Handler):
152
148
  )
153
149
  self.console.print(richer, crop=False, overflow="ignore")
154
150
 
155
- def ShowMessage(self, message: str | rule_t, /, *, indented: bool = False) -> None:
151
+ def LogAsIs(self, message: str | renderable_t, /) -> None:
156
152
  """
157
153
  See documentation of
158
- logger_36.catalog.handler.generic.generic_handler_t.ShowMessage.
154
+ logger_36.catalog.handler.generic.generic_handler_t.LogAsIs.
159
155
  """
160
- if isinstance(message, str) and indented:
161
- message = txt_.indent(message, LINE_INDENT)
162
156
  self.console.print(message, crop=False, overflow="ignore")
163
157
 
164
158
  def DisplayRule(self, /, *, text: str | None = None, color: str = "white") -> None:
165
159
  """"""
166
- self.ShowMessage(Rule(text, color))
160
+ self.LogAsIs(Rule(text, color))
167
161
 
168
162
 
169
163
  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: message_from_record_p = d.field(init=False)
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 ShowMessage(self, message: str, /, *, indented: bool = False) -> None:
72
+ def LogAsIs(self, message: str, /) -> None:
76
73
  """
77
74
  See documentation of
78
- logger_36.catalog.handler.generic.generic_handler_t.ShowMessage.
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.ShowMessage(RuleAsText(text))
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 handler_extension_t, message_from_record_h
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 display_rule_p(h.Protocol):
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
- ShowMessage:
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
- ShowMessage: show_message_p
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: display_rule_p = d.field(init=False)
69
+ DisplayRule: DisplayRule_p = d.field(init=False)
73
70
  extension: handler_extension_t = d.field(init=False)
74
- MessageFromRecord: message_from_record_h = d.field(init=False)
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.ShowMessage(message)
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.ShowMessage(RuleAsText(text))
184
+ self.LogAsIs(RuleAsText(text))
188
185
 
189
186
  def _DisplayRule(self, /, *, text: str | None = None, color: str = "white") -> None:
190
187
  """"""
191
- self.ShowMessage(Rule(text, color))
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
- home = path_t.home()
42
- if module.is_relative_to(home):
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 "):
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 generic_handler_t, show_message_p
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
- ShowMessage: show_message_p,
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
- ShowMessage=ShowMessage,
52
+ LogAsIs=LogAsIs,
53
53
  )
54
54
  logger.AddHandler(handler, should_hold_messages=should_hold_messages)
55
55
 
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 message_from_record_preprocessed_p(h.Protocol):
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
- message_from_record_h = message_from_record_raw_p | message_from_record_preprocessed_p
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: message_from_record_h = d.field(init=False)
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,39 @@ 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 max_memory_usage(self) -> int:
107
+ """"""
108
+ if self.memory_usages.__len__() > 0:
109
+ return max(tuple(zip(*self.memory_usages))[1])
110
+ return UNKNOWN_MEMORY_USAGE
111
+
112
+ @property
113
+ def max_memory_usage_full(self) -> tuple[str, int]:
114
+ """"""
115
+ if self.memory_usages.__len__() > 0:
116
+ where_s, usages = zip(*self.memory_usages)
117
+ max_usage = max(usages)
118
+
119
+ return where_s[usages.index(max_usage)], max_usage
120
+
121
+ return "?", UNKNOWN_MEMORY_USAGE
122
+
87
123
  def __post_init__(
88
124
  self, name_: str, level_: int, activate_wrn_interceptions: bool
89
125
  ) -> None:
@@ -95,11 +131,92 @@ class logger_t(base_t):
95
131
  for level in l.getLevelNamesMapping().values():
96
132
  self.events[level] = 0
97
133
 
134
+ self.info(f'New logger "{self.name}" for "{PROJECT_FILE_RELATIVE}"')
135
+
98
136
  if activate_wrn_interceptions:
99
- self._ActivateWarningInterceptions()
137
+ self.ToggleWarningInterceptions(True)
100
138
  if self.exit_on_error:
101
139
  self.exit_on_critical = True
102
140
 
141
+ def handle(self, record: l.LogRecord, /) -> None:
142
+ """"""
143
+ elapsed_time, now = ElapsedTime(should_return_now=True)
144
+
145
+ if (self.on_hold.__len__() > 0) and not self.should_hold_messages:
146
+ for held in self.on_hold:
147
+ base_t.handle(self, held)
148
+ self.on_hold.clear()
149
+
150
+ if (date := now.date()) != self.last_message_date:
151
+ self.last_message_date = date
152
+ # levelno: Added for management by logging.Logger.handle.
153
+ date_record = l.makeLogRecord(
154
+ {
155
+ "name": self.name,
156
+ "levelno": l.INFO,
157
+ "msg": f"DATE: {date.strftime(DATE_FORMAT)}",
158
+ SHOW_W_RULE_ATTR: True,
159
+ }
160
+ )
161
+ if self.should_hold_messages:
162
+ self.on_hold.append(date_record)
163
+ else:
164
+ base_t.handle(self, date_record)
165
+
166
+ # When.
167
+ if now - self.last_message_now > LONG_ENOUGH:
168
+ record.when_or_elapsed = now.strftime(TIME_FORMAT)
169
+ else:
170
+ record.when_or_elapsed = (
171
+ f"{ELAPSED_TIME_SEPARATOR}{elapsed_time:.<{TIME_LENGTH_m_1}}"
172
+ )
173
+ self.last_message_now = now
174
+
175
+ # Where.
176
+ # Memory usage is also stored if there are no handlers yet, just in case.
177
+ should_store_where = self.any_handler_stores_memory or not self.hasHandlers()
178
+ should_show_where = (record.levelno != l.INFO) and not hasattr(
179
+ record, HIDE_WHERE_ATTR
180
+ )
181
+ if should_store_where or should_show_where:
182
+ module = path_t(record.pathname)
183
+ for path in s.path:
184
+ if module.is_relative_to(path):
185
+ module = module.relative_to(path).with_suffix("")
186
+ module = str(module).replace(FOLDER_SEPARATOR, ".")
187
+ break
188
+ else:
189
+ module = record.module
190
+ where = f"{module}:{record.funcName}:{record.lineno}"
191
+ if should_show_where:
192
+ record.where = where
193
+ else:
194
+ where = None
195
+
196
+ # How.
197
+ record.level_first_letter = record.levelname[0]
198
+
199
+ # What.
200
+ if not isinstance(record.msg, str):
201
+ record.msg = str(record.msg)
202
+
203
+ if self.should_hold_messages:
204
+ self.on_hold.append(record)
205
+ else:
206
+ base_t.handle(self, record)
207
+
208
+ if (self.exit_on_critical and (record.levelno is l.CRITICAL)) or (
209
+ self.exit_on_error and (record.levelno is l.ERROR)
210
+ ):
211
+ # Also works if self.exit_on_error and record.levelno is l.CRITICAL since
212
+ # __post_init__ set self.exit_on_critical if self.exit_on_error.
213
+ s.exit(1)
214
+
215
+ self.events[record.levelno] += 1
216
+
217
+ if should_store_where:
218
+ self.memory_usages.append((where, CurrentMemoryUsage()))
219
+
103
220
  def SetLevel(
104
221
  self,
105
222
  level: int,
@@ -133,32 +250,82 @@ class logger_t(base_t):
133
250
  )
134
251
  )
135
252
 
136
- def MakeRich(self, *, alternating_lines: int = 2) -> None:
253
+ def MakeMonochrome(
254
+ self,
255
+ *,
256
+ should_intercept_logs: bool = True,
257
+ should_override_exceptions: bool = True,
258
+ ) -> None:
259
+ """"""
260
+ self._MakePreamble(
261
+ should_intercept_logs=should_intercept_logs,
262
+ should_override_exceptions=should_override_exceptions,
263
+ )
264
+ AddConsoleHandler(self)
265
+
266
+ def MakeRich(
267
+ self,
268
+ *,
269
+ alternating_lines: int = 2,
270
+ should_intercept_logs: bool = True,
271
+ should_override_exceptions: bool = True,
272
+ ) -> None:
137
273
  """"""
138
- OverrideExceptionFormat()
274
+ self._MakePreamble(
275
+ should_intercept_logs=should_intercept_logs,
276
+ should_override_exceptions=should_override_exceptions,
277
+ )
139
278
  AddRichConsoleHandler(self, alternating_lines=alternating_lines)
140
279
 
280
+ def MakePermanent(
281
+ self,
282
+ path: str | path_t,
283
+ /,
284
+ *,
285
+ should_intercept_logs: bool = True,
286
+ should_override_exceptions: bool = True,
287
+ ) -> None:
288
+ """"""
289
+ self._MakePreamble(
290
+ should_intercept_logs=should_intercept_logs,
291
+ should_override_exceptions=should_override_exceptions,
292
+ )
293
+ AddFileHandler(self, path)
294
+
295
+ def _MakePreamble(
296
+ self,
297
+ *,
298
+ should_intercept_logs: bool = True,
299
+ should_override_exceptions: bool = True,
300
+ ) -> None:
301
+ """"""
302
+ if should_override_exceptions:
303
+ OverrideExceptionFormat()
304
+ if should_intercept_logs:
305
+ self.ToggleLogInterceptions(True)
306
+
141
307
  def ResetEventCounts(self) -> None:
142
308
  """"""
143
309
  for level in self.events:
144
310
  self.events[level] = 0
145
311
 
146
- def _ActivateWarningInterceptions(self) -> None:
312
+ def ToggleWarningInterceptions(self, state: bool, /) -> None:
147
313
  """
148
314
  The log message will not appear if called from __post_init__ since there are no
149
315
  handlers yet.
150
316
  """
151
- if self.intercepted_wrn_handle is None:
317
+ if state:
318
+ assert not self.intercepts_warnings
319
+
152
320
  logger = l.getLogger(WARNING_LOGGER_NAME)
153
321
  self.intercepted_wrn_handle = logger.handle
154
322
  logger.handle = t.MethodType(_HandleForWarnings(self), logger)
155
323
 
156
324
  l.captureWarnings(True)
157
325
  self.info("Warning Interception: ON")
326
+ else:
327
+ assert self.intercepts_warnings
158
328
 
159
- def _DeactivateWarningInterceptions(self) -> None:
160
- """"""
161
- if self.intercepted_wrn_handle is not None:
162
329
  logger = l.getLogger(WARNING_LOGGER_NAME)
163
330
  logger.handle = self.intercepted_wrn_handle
164
331
  self.intercepted_wrn_handle = None
@@ -166,17 +333,10 @@ class logger_t(base_t):
166
333
  l.captureWarnings(False)
167
334
  self.info("Warning Interception: OFF")
168
335
 
169
- def ToggleWarningInterceptions(self, state: bool, /) -> None:
170
- """"""
171
- if state:
172
- self._ActivateWarningInterceptions()
173
- else:
174
- self._DeactivateWarningInterceptions()
175
-
176
336
  def ToggleLogInterceptions(self, state: bool, /) -> None:
177
337
  """"""
178
338
  if state:
179
- self.ToggleLogInterceptions(False)
339
+ assert not self.intercepts_logs
180
340
 
181
341
  all_loggers = [l.getLogger()] + [
182
342
  l.getLogger(_nme)
@@ -193,31 +353,15 @@ class logger_t(base_t):
193
353
  if intercepted.__len__() > 0:
194
354
  as_str = ", ".join(intercepted)
195
355
  self.info(f"Now Intercepting LOGs from: {as_str}")
196
- elif self.intercepted_log_handles.__len__() > 0:
356
+ else:
357
+ assert self.intercepts_logs
358
+
197
359
  for name, handle in self.intercepted_log_handles.items():
198
360
  logger = l.getLogger(name)
199
361
  logger.handle = handle
200
362
  self.intercepted_log_handles.clear()
201
363
  self.info("Log Interception: OFF")
202
364
 
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
365
  def AddHandler(
222
366
  self, handler: l.Handler, /, *, should_hold_messages: bool = False
223
367
  ) -> None:
@@ -244,85 +388,6 @@ class logger_t(base_t):
244
388
  f"level {handler.level}={l.getLevelName(handler.level)}{path}",
245
389
  )
246
390
 
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
391
  def Log(
327
392
  self,
328
393
  message: str,
@@ -370,22 +435,31 @@ class logger_t(base_t):
370
435
  message = f"{type(exception).__name__}:\n{formatted}"
371
436
  self.log(level, message)
372
437
 
373
- def ShowMessage(self, message: str, /, *, indented: bool = False) -> None:
438
+ def LogAsIs(self, message: str, /, *, indented: bool = False) -> None:
374
439
  """
375
440
  See documentation of
376
- logger_36.catalog.handler.generic.generic_handler_t.ShowMessage.
441
+ logger_36.catalog.handler.generic.generic_handler_t.LogAsIs.
377
442
  """
443
+ if indented:
444
+ message = text.indent(message, LINE_INDENT)
445
+
378
446
  for handler in self.handlers:
379
- ShowMessage = getattr(handler, "ShowMessage", None)
380
- if ShowMessage is not None:
381
- ShowMessage(message, indented=indented)
447
+ if (LogAsIs := getattr(handler, "LogAsIs", None)) is None:
448
+ self.info(message)
449
+ else:
450
+ LogAsIs(message)
451
+
452
+ raw_info = LogAsIs # To follow the convention of the logging methods info, error...
382
453
 
383
- def DisplayRule(self, /, *, text: str | None = None, color: str = "white") -> None:
454
+ def DisplayRule(
455
+ self, /, *, message: str | None = None, color: str = "white"
456
+ ) -> None:
384
457
  """"""
385
458
  for handler in self.handlers:
386
- DisplayRule = getattr(handler, "DisplayRule", None)
387
- if DisplayRule is not None:
388
- DisplayRule(text=text, color=color)
459
+ if (DisplayRule := getattr(handler, "DisplayRule", None)) is None:
460
+ self.info(RuleAsText(message))
461
+ else:
462
+ DisplayRule(text=message, color=color)
389
463
 
390
464
  def AddContextLevel(self, new_level: str, /) -> None:
391
465
  """"""
@@ -427,11 +501,6 @@ class logger_t(base_t):
427
501
  )
428
502
  self.staged_issues.append(issue)
429
503
 
430
- @property
431
- def has_staged_issues(self) -> bool:
432
- """"""
433
- return self.staged_issues.__len__() > 0
434
-
435
504
  def CommitIssues(
436
505
  self,
437
506
  /,
logger_36/version.py CHANGED
@@ -4,7 +4,7 @@ Contributor(s): Eric Debreuve (eric.debreuve@cnrs.fr) since 2023
4
4
  SEE COPYRIGHT NOTICE BELOW
5
5
  """
6
6
 
7
- __version__ = "2025.6"
7
+ __version__ = "2025.7"
8
8
 
9
9
  """
10
10
  COPYRIGHT NOTICE
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: logger-36
3
- Version: 2025.6
3
+ Version: 2025.7
4
4
  Summary: Simple logger with a catalog of handlers
5
5
  Home-page: https://src.koda.cnrs.fr/eric.debreuve/logger-36/
6
6
  Author: Eric Debreuve
@@ -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=YNT4S_gvz7WQUSWILFAwI01h5-BVw1d0oT97zjnw3dU,4575
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=hEKhfsKHhg8UqSrBTVe7w0lubf9SSkuGMi81oXwjPoo,6326
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=eAXepMLNtMw0muvMucofZ1LJ6BBOIZKujqvtOs93Qho,2205
10
+ logger_36/version.py,sha256=QWIy4Jr-I6cIfY2uS0OZkwf54A1Oh3Rxgb14HI6W6bE,2205
11
11
  logger_36/api/logger.py,sha256=TE3ATbymeWX-wBKBFkVz2FxUyJnaqY7vzFwAONVsp2o,2233
12
12
  logger_36/api/storage.py,sha256=KT52AGR37nsMrhKTVfG8R-Dc7lmCXjWML18cOqqCXZY,2239
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=tLMroj95xt_IC9Vra4M5TCDRKrU4mSkfvUg9GliQ_l8,4402
16
- logger_36/catalog/handler/console_rich.py,sha256=gkRnbhwyFFrS4nGvgUn9lElh7YNCAaC2-7dAex5bHzI,8592
17
- logger_36/catalog/handler/file.py,sha256=hCb21GahjeRbBJGtPsa2PPme7LmlZ8ftF3Q_BMKRiA8,4865
18
- logger_36/catalog/handler/generic.py,sha256=Y6rxfKkWe9cA196ntt6p0_rcjEeESGFCKWVlo1WIzW0,9335
15
+ logger_36/catalog/handler/console.py,sha256=s2DBcDK9To-wVS228RsDDPmOPxlIbVnQbZINfIK2TP0,4150
16
+ logger_36/catalog/handler/console_rich.py,sha256=pI0OE0c5I19-ycvOJjdG5vdtDXTZadNNRbKCJD-2oL4,8347
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,6 +31,7 @@ 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
36
37
  logger_36/instance/logger.py,sha256=oTw5svRzKRJKvGrrZUtutJIOjp5UISft3fl0Ze7DOBE,2241
@@ -42,11 +43,11 @@ logger_36/task/format/message.py,sha256=T2V2gUlUQqSojyRrz4I4uAHwNe6eBEsuAe6V-LTy
42
43
  logger_36/task/format/rule.py,sha256=vkf-HivFb4VqV2GeOPVqMAp99krtziI-kXhox3UVnzw,2873
43
44
  logger_36/task/measure/chronos.py,sha256=1kVhu6jZlNAtNWQQh8ZVuRwZIAC9gGz3_ul1tn0t4Yw,3055
44
45
  logger_36/task/measure/memory.py,sha256=OjU5EYFH8SnzlCQKAoiXvauUlwQYOrH34jFXTVYF0jE,2517
45
- logger_36/type/handler.py,sha256=7M8f8U-zedzo-1AR7iIxHnzRHsuDj9IM0WtrqG2GMY0,6902
46
+ logger_36/type/handler.py,sha256=-myl7uBMOzkwCs1u4ehuYlQa9F6909jmnL2v_eQN5ag,6819
46
47
  logger_36/type/issue.py,sha256=2rGsFqaQJCbeml9xN08mN_nK79L8qscaS_0ws36Y0bI,3214
47
- logger_36/type/logger.py,sha256=4cCa-u1ktDdlj96aOWz6OsDJ_WhuN7eKEEALIHDS9NY,20451
48
+ logger_36/type/logger.py,sha256=nGNWJ5xcyA79E45qcZ-gZ3ZuY7j1aDg5NXk7VwbQVFU,22287
48
49
  logger_36/type/loggers.py,sha256=znqxWBnfQxvkg3VUfbTUvt3S6Kq0DAzWWepxQDt9suI,2871
49
- logger_36-2025.6.dist-info/METADATA,sha256=AQ37dDMn1ZG7E3X5x29OR4_GaeG_FWcVkjew0B9hbzw,6505
50
- logger_36-2025.6.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
51
- logger_36-2025.6.dist-info/top_level.txt,sha256=sM95BTMWmslEEgR_1pzwZsOeSp8C_QBiu8ImbFr0XLc,10
52
- logger_36-2025.6.dist-info/RECORD,,
50
+ logger_36-2025.7.dist-info/METADATA,sha256=E708bS17nu3wkJZ7YTNfvezYSBUbfwXgUCwiiXwOkf0,6505
51
+ logger_36-2025.7.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
52
+ logger_36-2025.7.dist-info/top_level.txt,sha256=sM95BTMWmslEEgR_1pzwZsOeSp8C_QBiu8ImbFr0XLc,10
53
+ logger_36-2025.7.dist-info/RECORD,,