logger-36 2025.20__py3-none-any.whl → 2025.22__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.
@@ -8,11 +8,20 @@ try:
8
8
  import rich # noqa
9
9
  except ModuleNotFoundError:
10
10
  RICH_IS_AVAILABLE = False
11
- from logger_36.constant.error import MISSING_RICH_MESSAGE
11
+ from logger_36.constant.error import MISSING_RICH_MESSAGE # noqa
12
12
  else:
13
13
  RICH_IS_AVAILABLE = True
14
14
  MISSING_RICH_MESSAGE = None
15
15
 
16
+ from logger_36.task.measure.memory import CanCheckUsage as CanCheckMemoryUsage
17
+
18
+ if CanCheckMemoryUsage():
19
+ MEMORY_MEASURE_IS_AVAILABLE = True
20
+ MEMORY_MEASURE_ERROR = None
21
+ else:
22
+ MEMORY_MEASURE_IS_AVAILABLE = False
23
+ from logger_36.constant.error import MEMORY_MEASURE_ERROR # noqa
24
+
16
25
  """
17
26
  COPYRIGHT NOTICE
18
27
 
@@ -29,7 +29,7 @@ class console_handler_t(base_t):
29
29
  """"""
30
30
  s.__stdout__.write(self.MessageFromRecord(record)[0] + "\n")
31
31
 
32
- def EmitAsIs(self, message: str, /) -> None:
32
+ def EmitMessage(self, message: str, /) -> None:
33
33
  """"""
34
34
  s.__stdout__.write(message + "\n")
35
35
 
@@ -96,7 +96,7 @@ class console_rich_handler_t(base_t):
96
96
  """"""
97
97
  return cls(name, message_width, level, kwargs)
98
98
 
99
- def Rule(self, text: str | None, /, *, color: str = "black") -> str | rule_t:
99
+ def Rule(self, /, *, text: str | None = None, color: str = "black") -> str | rule_t:
100
100
  """"""
101
101
  if text is None:
102
102
  return rule_t(style=color)
@@ -116,7 +116,7 @@ class console_rich_handler_t(base_t):
116
116
  self.console.print(message, crop=False, overflow="ignore")
117
117
  self._log_parity = not self._log_parity
118
118
 
119
- def EmitAsIs(self, message: str | renderable_t, /) -> None:
119
+ def EmitMessage(self, message: str | renderable_t, /) -> None:
120
120
  """"""
121
121
  self.console.print(message, crop=False, overflow="ignore")
122
122
 
@@ -32,7 +32,7 @@ class file_handler_t(base_t):
32
32
  self.stream.write(output[0] + "\n")
33
33
  self.stream.flush()
34
34
 
35
- def EmitAsIs(self, message: str, /) -> None:
35
+ def EmitMessage(self, message: str, /) -> None:
36
36
  """"""
37
37
  self.stream.write(message + "\n")
38
38
  self.stream.flush()
@@ -38,10 +38,10 @@ class generic_handler_t(base_t):
38
38
  self, name: str | None, message_width: int, level: int, kwargs
39
39
  ) -> None:
40
40
  """
41
- EmitAsIs: By definition, the generic handler does not know how to output
41
+ EmitMessage: By definition, the generic handler does not know how to output
42
42
  messages. If not passed, it defaults to output-ing messages in the console.
43
43
  """
44
- EmitAsIs = kwargs.pop("EmitAsIs", None)
44
+ EmitMessage = kwargs.pop(base_t.EmitMessage.__name__, None)
45
45
  alternating_logs = kwargs.pop("alternating_logs", 0)
46
46
  supports_html = kwargs.pop("supports_html", False)
47
47
 
@@ -49,8 +49,8 @@ class generic_handler_t(base_t):
49
49
 
50
50
  base_t.__init__(self, name, message_width, None, level, kwargs)
51
51
 
52
- if EmitAsIs is not None:
53
- self.EmitAsIs = EmitAsIs
52
+ if EmitMessage is not None:
53
+ self.EmitMessage = EmitMessage
54
54
  self.is_rich = False
55
55
  self.console = None # console_t | None.
56
56
  self.console_options = None # rich.console.ConsoleOptions | None.
@@ -82,14 +82,14 @@ class generic_handler_t(base_t):
82
82
  """"""
83
83
  return cls(name, message_width, level, kwargs)
84
84
 
85
- def Rule(self, text: str | None, /, *, color: str = "black") -> str | rule_t:
85
+ def Rule(self, /, *, text: str | None = None, color: str = "black") -> str | rule_t:
86
86
  """"""
87
87
  if self.is_rich:
88
88
  if text is None:
89
89
  return rule_t(style=color)
90
90
  return rule_t(title=text_t(text, style=f"bold {color}"), style=color)
91
91
 
92
- return base_t.Rule(self, text, color=color)
92
+ return base_t.Rule(self, text=text, color=color)
93
93
 
94
94
  def emit(self, record: l.LogRecord, /) -> None:
95
95
  """"""
@@ -131,7 +131,7 @@ class generic_handler_t(base_t):
131
131
  else:
132
132
  message = self.MessageFromRecord(record)[0]
133
133
 
134
- self.EmitAsIs(message)
134
+ self.EmitMessage(message)
135
135
  self._log_parity = not self._log_parity
136
136
 
137
137
 
@@ -4,8 +4,11 @@ Contributor(s): Eric Debreuve (eric.debreuve@cnrs.fr) since 2023
4
4
  SEE COPYRIGHT NOTICE BELOW
5
5
  """
6
6
 
7
- SHOW_W_RULE_ATTR = "should_show_w_rule"
7
+ INTERNAL_INFO_ATTR = "internal_info"
8
8
  SHOW_WHERE_ATTR = "should_show_where"
9
+ SHOW_W_RULE_ATTR = "should_show_w_rule"
10
+ WHEN_OR_ELAPSED_ATTR = "when_or_elapsed"
11
+ WHERE_ATTR = "where"
9
12
 
10
13
  """
11
14
  COPYRIGHT NOTICE
logger_36/type/handler.py CHANGED
@@ -16,7 +16,7 @@ from logger_36.config.message import (
16
16
  WHERE_SEPARATOR,
17
17
  )
18
18
  from logger_36.constant.message import NEXT_LINE_PROLOGUE
19
- from logger_36.constant.record import SHOW_W_RULE_ATTR
19
+ from logger_36.constant.record import SHOW_W_RULE_ATTR, WHEN_OR_ELAPSED_ATTR, WHERE_ATTR
20
20
  from logger_36.constant.rule import (
21
21
  DEFAULT_RULE,
22
22
  DEFAULT_RULE_LENGTH,
@@ -66,7 +66,7 @@ class extension_t:
66
66
  message = self.PreProcessedMessage(message)
67
67
 
68
68
  if hasattr(record, SHOW_W_RULE_ATTR):
69
- return self.Rule(message, color=rule_color), False
69
+ return self.Rule(text=message, color=rule_color), False
70
70
 
71
71
  if (self.message_width <= 0) or (message.__len__() <= self.message_width):
72
72
  if "\n" in message:
@@ -78,11 +78,11 @@ class extension_t:
78
78
  lines = WrappedLines([message], self.message_width)
79
79
  message = NEXT_LINE_PROLOGUE.join(lines)
80
80
 
81
- when_or_elapsed = getattr(record, "when_or_elapsed", None)
81
+ when_or_elapsed = getattr(record, WHEN_OR_ELAPSED_ATTR, None)
82
82
  if when_or_elapsed is None:
83
83
  return message, True
84
84
 
85
- if (where := getattr(record, "where", None)) is None:
85
+ if (where := getattr(record, WHERE_ATTR, None)) is None:
86
86
  where = ""
87
87
  else:
88
88
  where = f"{NEXT_LINE_PROLOGUE}{WHERE_SEPARATOR} {where}"
@@ -93,7 +93,7 @@ class extension_t:
93
93
  f"{MESSAGE_MARKER} {message}{where}"
94
94
  ), True
95
95
 
96
- def Rule(self, text: str | None, /, *, color: str = "black") -> str | h.Any:
96
+ def Rule(self, /, *, text: str | None = None, color: str = "black") -> str | h.Any:
97
97
  """
98
98
  Return type hint h.Any: For Rich, for example.
99
99
  """
@@ -113,13 +113,13 @@ class extension_t:
113
113
 
114
114
  return f"{half_rule} {text} {half_rule}"
115
115
 
116
- def EmitAsIs(self, message: str, /) -> None:
116
+ def EmitMessage(self, message: str, /) -> None:
117
117
  """"""
118
118
  raise NotImplementedError
119
119
 
120
120
  def EmitRule(self, /, *, text: str | None = None, color: str = "black") -> None:
121
121
  """"""
122
- self.EmitAsIs(self.Rule(text, color=color))
122
+ self.EmitMessage(self.Rule(text=text, color=color))
123
123
 
124
124
 
125
125
  class handler_t(l.Handler, extension_t):
logger_36/type/logger.py CHANGED
@@ -19,7 +19,12 @@ from os import sep as FOLDER_SEPARATOR
19
19
  from pathlib import Path as path_t
20
20
  from traceback import TracebackException as traceback_t
21
21
 
22
- from logger_36.catalog.config.optional import MISSING_RICH_MESSAGE, RICH_IS_AVAILABLE
22
+ from logger_36.catalog.config.optional import (
23
+ MEMORY_MEASURE_ERROR,
24
+ MEMORY_MEASURE_IS_AVAILABLE,
25
+ MISSING_RICH_MESSAGE,
26
+ RICH_IS_AVAILABLE,
27
+ )
23
28
  from logger_36.catalog.handler.console import console_handler_t
24
29
  from logger_36.catalog.handler.file import file_handler_t
25
30
  from logger_36.config.issue import ISSUE_CONTEXT_END, ISSUE_CONTEXT_SEPARATOR
@@ -30,17 +35,19 @@ from logger_36.config.message import (
30
35
  TIME_FORMAT,
31
36
  WHERE_SEPARATOR,
32
37
  )
33
- from logger_36.constant.error import MEMORY_MEASURE_ERROR
34
38
  from logger_36.constant.generic import NOT_PASSED
35
39
  from logger_36.constant.issue import ISSUE_LEVEL_SEPARATOR, ORDER, order_h
36
40
  from logger_36.constant.logger import WARNING_LOGGER_NAME, WARNING_TYPE_COMPILED_PATTERN
37
41
  from logger_36.constant.memory import UNKNOWN_MEMORY_USAGE
38
42
  from logger_36.constant.message import LINE_INDENT, TIME_LENGTH_m_1, expected_op_h
39
43
  from logger_36.constant.path import PROJECT_FILE_RELATIVE, USER_FOLDER
40
- from logger_36.constant.record import SHOW_W_RULE_ATTR, SHOW_WHERE_ATTR
44
+ from logger_36.constant.record import (
45
+ INTERNAL_INFO_ATTR,
46
+ SHOW_W_RULE_ATTR,
47
+ SHOW_WHERE_ATTR,
48
+ )
41
49
  from logger_36.task.format.message import MessageWithActualExpected
42
50
  from logger_36.task.measure.chronos import ElapsedTime
43
- from logger_36.task.measure.memory import CanCheckUsage as CanCheckMemoryUsage
44
51
  from logger_36.task.measure.memory import CurrentUsage as CurrentMemoryUsage
45
52
  from logger_36.type.handler import any_handler_t as base_handler_t
46
53
  from logger_36.type.handler import extension_t as handler_extension_t
@@ -61,7 +68,6 @@ logger_handle_h = logger_handle_raw_h | logger_handle_with_self_h
61
68
 
62
69
  _DATE_TIME_ORIGIN = date_time_t.fromtimestamp(1970, None)
63
70
  _DATE_ORIGIN = _DATE_TIME_ORIGIN.date()
64
- _MEMORY_MEASURE_ERROR = MEMORY_MEASURE_ERROR
65
71
 
66
72
 
67
73
  @d.dataclass(slots=True, repr=False, eq=False)
@@ -70,20 +76,21 @@ class logger_t(base_t):
70
76
  intercepted_wrn_handle: When warning interception is on, this stores the original
71
77
  "handle" method of the Python warning logger.
72
78
 
73
- _should_activate_log_interceptions: Loggers instantiated after a logger_t logger will
74
- be missed by an early call of ToggleLogInterceptions. Therefore, passing True for
75
- activate_log_interceptions only sets _should_activate_log_interceptions to True,
79
+ _should_activate_log_interceptions: Loggers instantiated after a logger_t logger
80
+ will be missed by an early call of ToggleLogInterceptions. Therefore, passing True
81
+ for activate_log_interceptions only sets _should_activate_log_interceptions to True,
76
82
  which is later checked in AddHandler to effectively call ToggleLogInterceptions if
77
- _should_hold_messages is False (which normally indicates that the handler about to be
78
- added is the last one).
83
+ _warming_up is False (which normally indicates that the handler about to
84
+ be added is the last one).
79
85
 
80
- _should_hold_messages: Must not be False until at least one handler has been added.
86
+ _warming_up: Must not be False until at least one handler has been added.
81
87
  """
82
88
 
83
89
  should_record_messages: bool = False
84
- should_watch_memory_usage: bool = False
90
+ should_monitor_memory_usage: bool = False
85
91
  exit_on_error: bool = False # Implies exit_on_critical.
86
92
  exit_on_critical: bool = False
93
+ verbose: bool = False
87
94
 
88
95
  events: dict[int, int] = d.field(init=False, default_factory=dict)
89
96
  recorded: list[tuple[int, str]] = d.field(init=False, default_factory=list)
@@ -98,11 +105,13 @@ class logger_t(base_t):
98
105
  init=False, default_factory=dict
99
106
  )
100
107
  intercepts_exceptions: bool = d.field(init=False, default=False)
101
- _should_hold_messages: bool = d.field(init=False, default=True)
102
- _should_activate_log_interceptions: bool = d.field(init=False, default=False)
103
- _on_hold: list[l.LogRecord] = d.field(init=False, default_factory=list)
104
108
  _recording_handler: handler_extension_t | None = d.field(init=False, default=None)
105
109
 
110
+ # Used only until the last handler is added (see AddHandler).
111
+ _should_activate_log_interceptions: bool = d.field(init=False, default=False)
112
+ _warming_up: bool = d.field(init=False, default=True)
113
+ _on_hold: list[l.LogRecord] | None = d.field(init=False, default_factory=list)
114
+
106
115
  name_: d.InitVar[str | None] = None
107
116
  level_: d.InitVar[int] = l.NOTSET
108
117
  activate_wrn_interceptions: d.InitVar[bool] = True
@@ -156,21 +165,28 @@ class logger_t(base_t):
156
165
  activate_exc_interceptions: bool,
157
166
  ) -> None:
158
167
  """"""
159
- global _MEMORY_MEASURE_ERROR
160
-
161
168
  if name_ is None:
162
169
  name_ = f"{type(self).__name__}:{hex(id(self))[2:]}"
163
170
 
171
+ self.handle = self._HandleWarmUp
164
172
  base_t.__init__(self, name_)
165
173
  self.setLevel(level_)
166
174
  self.propagate = False # Part of base_t.
167
175
 
168
- if self.should_record_messages:
169
- self.ActivateMessageRecording()
176
+ if self.exit_on_error:
177
+ self.exit_on_critical = True
170
178
 
171
179
  for level_id in l.getLevelNamesMapping().values():
172
180
  self.events[level_id] = 0
173
181
 
182
+ if self.should_record_messages:
183
+ self.ActivateMessageRecording()
184
+
185
+ self.info(
186
+ f'New logger "{self.name}" for "{PROJECT_FILE_RELATIVE}"',
187
+ extra={INTERNAL_INFO_ATTR: True},
188
+ )
189
+
174
190
  if activate_wrn_interceptions:
175
191
  self.ToggleWarningInterceptions(True)
176
192
  if activate_log_interceptions:
@@ -178,23 +194,33 @@ class logger_t(base_t):
178
194
  if activate_exc_interceptions:
179
195
  self.ToggleExceptionInterceptions(True)
180
196
 
181
- if self.exit_on_error:
182
- self.exit_on_critical = True
197
+ if self.should_monitor_memory_usage:
198
+ self.ActivateMemoryUsageMonitoring()
183
199
 
184
- if self.should_watch_memory_usage and not CanCheckMemoryUsage():
185
- self.should_watch_memory_usage = False
186
- if _MEMORY_MEASURE_ERROR is not None:
187
- s.__stderr__.write(_MEMORY_MEASURE_ERROR + "\n")
188
- _MEMORY_MEASURE_ERROR = None
200
+ def _HandleWarmUp(self, record: l.LogRecord, /) -> None:
201
+ """"""
202
+ now = ElapsedTime(should_return_now=True)[1]
203
+ if (date := now.date()) != self.last_message_date:
204
+ self._AcknowledgeDateChange(date)
205
+
206
+ # When.
207
+ record.when_or_elapsed = (TIME_LENGTH_m_1 + 1) * "."
208
+ # Where.
209
+ if record.levelno != l.INFO:
210
+ _ = _RecordLocation(record, True)
211
+ # What.
212
+ if not isinstance(record.msg, str):
213
+ record.msg = str(record.msg)
189
214
 
190
- self.info(f'New logger "{self.name}" for "{PROJECT_FILE_RELATIVE}"')
215
+ self._on_hold.append(record)
191
216
 
192
- def handle(self, record: l.LogRecord, /) -> None:
217
+ def _HandleRoutine(self, record: l.LogRecord, /) -> None:
193
218
  """"""
194
- elapsed_time, now = ElapsedTime(should_return_now=True)
219
+ is_internal_info = getattr(record, INTERNAL_INFO_ATTR, False)
220
+ if is_internal_info and not self.verbose:
221
+ return
195
222
 
196
- if (self._on_hold.__len__() > 0) and not self._should_hold_messages:
197
- self._FlushRecordsOnHold()
223
+ elapsed_time, now = ElapsedTime(should_return_now=True)
198
224
 
199
225
  if (date := now.date()) != self.last_message_date:
200
226
  self._AcknowledgeDateChange(date)
@@ -209,19 +235,8 @@ class logger_t(base_t):
209
235
 
210
236
  # Where.
211
237
  should_show_where = getattr(record, SHOW_WHERE_ATTR, record.levelno != l.INFO)
212
- if should_show_where or self.should_watch_memory_usage:
213
- module = path_t(record.pathname)
214
- for path in s.path:
215
- if module.is_relative_to(path):
216
- module = module.relative_to(path).with_suffix("")
217
- module = str(module).replace(FOLDER_SEPARATOR, ".")
218
- break
219
- else:
220
- if module.is_relative_to(USER_FOLDER):
221
- module = module.relative_to(USER_FOLDER)
222
- where = f"{module}:{record.funcName}:{record.lineno}"
223
- if should_show_where:
224
- record.where = where
238
+ if should_show_where or self.should_monitor_memory_usage:
239
+ where = _RecordLocation(record, should_show_where)
225
240
  else:
226
241
  where = None
227
242
 
@@ -229,13 +244,9 @@ class logger_t(base_t):
229
244
  if not isinstance(record.msg, str):
230
245
  record.msg = str(record.msg)
231
246
 
232
- if self._should_hold_messages:
233
- self._on_hold.append(record)
234
- else:
235
- self._HandleRecord(record)
236
- self.events[record.levelno] += 1
247
+ self._HandleRaw(record)
237
248
 
238
- if self.should_watch_memory_usage:
249
+ if self.should_monitor_memory_usage:
239
250
  self.memory_usages.append((where, CurrentMemoryUsage()))
240
251
 
241
252
  if (self.exit_on_critical and (record.levelno is l.CRITICAL)) or (
@@ -245,12 +256,28 @@ class logger_t(base_t):
245
256
  # __post_init__ set self.exit_on_critical if self.exit_on_error.
246
257
  s.exit(1)
247
258
 
248
- def _FlushRecordsOnHold(self) -> None:
259
+ def _HandleRaw(self, record: l.LogRecord, /) -> None:
249
260
  """"""
261
+ if self.should_record_messages:
262
+ message = self._recording_handler.MessageFromRecord(record)[0]
263
+ self.recorded.append((record.levelno, message))
264
+
265
+ base_t.handle(self, record)
266
+ self.events[record.levelno] += 1
267
+
268
+ def _FlushRecordsOnHold(self) -> None:
269
+ """
270
+ should_record_messages and verbose must have been set by now.
271
+ """
250
272
  for held in self._on_hold:
251
- self._HandleRecord(held)
273
+ if self.verbose or not getattr(held, INTERNAL_INFO_ATTR, False):
274
+ self._HandleRaw(held)
252
275
 
253
276
  self._on_hold.clear()
277
+ self._on_hold = None
278
+ self._warming_up = False
279
+
280
+ self.handle = self._HandleRoutine
254
281
 
255
282
  def _AcknowledgeDateChange(self, date: date_t, /) -> None:
256
283
  """"""
@@ -265,24 +292,32 @@ class logger_t(base_t):
265
292
  }
266
293
  )
267
294
 
268
- if self._should_hold_messages:
295
+ if self._warming_up:
269
296
  self._on_hold.append(record)
270
297
  else:
271
- self._HandleRecord(record)
272
-
273
- def _HandleRecord(self, record: l.LogRecord, /) -> None:
274
- """"""
275
- if self.should_record_messages:
276
- message = self._recording_handler.MessageFromRecord(record)[0]
277
- self.recorded.append((record.levelno, message))
278
-
279
- base_t.handle(self, record)
298
+ self._HandleRaw(record)
280
299
 
281
300
  def ActivateMessageRecording(self) -> None:
282
301
  """"""
283
302
  self._recording_handler = handler_extension_t("recording_handler", 0, None)
284
303
  self.should_record_messages = True # Useless if called from __post_init__.
285
- self.info(f'Message recording activated for logger "{self.name}"')
304
+ self.info(
305
+ f'Message recording activated for logger "{self.name}"',
306
+ extra={INTERNAL_INFO_ATTR: True},
307
+ )
308
+
309
+ def ActivateMemoryUsageMonitoring(self) -> None:
310
+ """"""
311
+ if MEMORY_MEASURE_IS_AVAILABLE:
312
+ # Useless if called from __post_init__.
313
+ self.should_monitor_memory_usage = True
314
+ self.info(
315
+ f'Memory usage monitoring activated for logger "{self.name}"',
316
+ extra={INTERNAL_INFO_ATTR: True},
317
+ )
318
+ else:
319
+ self.should_monitor_memory_usage = False
320
+ self.error(MEMORY_MEASURE_ERROR)
286
321
 
287
322
  def ResetEventCounts(self) -> None:
288
323
  """"""
@@ -300,7 +335,7 @@ class logger_t(base_t):
300
335
  logger.handle = t.MethodType(_HandleForWarnings(self), logger)
301
336
 
302
337
  l.captureWarnings(True)
303
- self.info("Warning Interception: ON")
338
+ self.info("Warning Interception: ON", extra={INTERNAL_INFO_ATTR: True})
304
339
  else:
305
340
  if not self.intercepts_warnings:
306
341
  return
@@ -310,7 +345,7 @@ class logger_t(base_t):
310
345
  self.intercepted_wrn_handle = None
311
346
 
312
347
  l.captureWarnings(False)
313
- self.info("Warning Interception: OFF")
348
+ self.info("Warning Interception: OFF", extra={INTERNAL_INFO_ATTR: True})
314
349
 
315
350
  def ToggleLogInterceptions(self, state: bool, /) -> None:
316
351
  """"""
@@ -334,7 +369,10 @@ class logger_t(base_t):
334
369
  intercepted = sorted(self.intercepted_log_handles.keys())
335
370
  if intercepted.__len__() > 0:
336
371
  as_str = ", ".join(intercepted)
337
- self.info(f"Now Intercepting LOGs from: {as_str}")
372
+ self.info(
373
+ f"Now Intercepting LOGs from: {as_str}",
374
+ extra={INTERNAL_INFO_ATTR: True},
375
+ )
338
376
  else:
339
377
  if not self.intercepts_logs:
340
378
  return
@@ -343,7 +381,7 @@ class logger_t(base_t):
343
381
  logger = l.getLogger(name)
344
382
  logger.handle = handle
345
383
  self.intercepted_log_handles.clear()
346
- self.info("Log Interception: OFF")
384
+ self.info("Log Interception: OFF", extra={INTERNAL_INFO_ATTR: True})
347
385
 
348
386
  def ToggleExceptionInterceptions(self, state: bool, /) -> None:
349
387
  """"""
@@ -354,7 +392,7 @@ class logger_t(base_t):
354
392
  s.excepthook = self.DealWithException
355
393
  thrd.excepthook = self.DealWithExceptionInThread
356
394
  self.intercepts_exceptions = True
357
- self.info("Exception Interception: ON")
395
+ self.info("Exception Interception: ON", extra={INTERNAL_INFO_ATTR: True})
358
396
  else:
359
397
  if not self.intercepts_exceptions:
360
398
  return
@@ -362,7 +400,7 @@ class logger_t(base_t):
362
400
  s.excepthook = s.__excepthook__
363
401
  thrd.excepthook = thrd.__excepthook__
364
402
  self.intercepts_exceptions = False
365
- self.info("Exception Interception: OFF")
403
+ self.info("Exception Interception: OFF", extra={INTERNAL_INFO_ATTR: True})
366
404
 
367
405
  def AddHandler(
368
406
  self,
@@ -375,15 +413,21 @@ class logger_t(base_t):
375
413
  name: str | None = None,
376
414
  level: int = l.INFO,
377
415
  message_width: int = -1,
378
- should_still_hold_messages: bool = False,
416
+ this_is_last_handler: bool = True,
379
417
  **kwargs,
380
418
  ) -> None:
381
- """"""
382
- if (not should_still_hold_messages) and self._should_activate_log_interceptions:
383
- self.ToggleLogInterceptions(True)
384
- self._should_activate_log_interceptions = False
419
+ """
420
+ Silently ignores re-holding request after un-holding.
421
+ """
422
+ should_flush_on_hold = False
423
+ new_handler_warning = ""
424
+ if self._warming_up and this_is_last_handler:
425
+ if self._should_activate_log_interceptions:
426
+ self.ToggleLogInterceptions(True)
427
+ self._should_activate_log_interceptions = False
385
428
 
386
- self._should_hold_messages = should_still_hold_messages
429
+ should_flush_on_hold = True
430
+ new_handler_warning = "\n(Handlers added from now on will miss above logs.)"
387
431
 
388
432
  if isinstance(handler_t_or_handler, type):
389
433
  handler = handler_t_or_handler.New(
@@ -393,13 +437,20 @@ class logger_t(base_t):
393
437
  handler = handler_t_or_handler
394
438
  base_t.addHandler(self, handler)
395
439
 
396
- path = getattr(handler, "baseFilename", "")
397
- if isinstance(path, path_t) or (path.__len__() > 0):
398
- path = f"\nPath: {path}"
399
- self.info(
400
- f'New handler "{handler.name}" of type "{type(handler).__name__}" and '
401
- f"level {handler.level}={l.getLevelName(handler.level)}{path}"
402
- )
440
+ if self.verbose:
441
+ path = getattr(handler, "baseFilename", "")
442
+ if isinstance(path, path_t) or (path.__len__() > 0):
443
+ path = f"\nPath: {path}"
444
+ self.info(
445
+ f'New handler "{handler.name}" of type "{type(handler).__name__}" and '
446
+ f"level {handler.level}={l.getLevelName(handler.level)}{path}"
447
+ f"{new_handler_warning}",
448
+ extra={INTERNAL_INFO_ATTR: True},
449
+ )
450
+
451
+ # Wait until after the handler has been added to flush messages on hold.
452
+ if should_flush_on_hold:
453
+ self._FlushRecordsOnHold()
403
454
 
404
455
  def MakeMonochrome(self) -> None:
405
456
  """"""
@@ -407,13 +458,12 @@ class logger_t(base_t):
407
458
 
408
459
  def MakeRich(self, *, alternating_logs: int = 0) -> None:
409
460
  """"""
410
- if MISSING_RICH_MESSAGE is not None:
411
- s.__stderr__.write(MISSING_RICH_MESSAGE + "\n")
412
-
413
- if console_rich_handler_t is console_handler_t:
414
- handler_kwargs = {}
415
- else:
461
+ if RICH_IS_AVAILABLE:
416
462
  handler_kwargs = {"alternating_logs": alternating_logs}
463
+ else:
464
+ handler_kwargs = {}
465
+ self.error(MISSING_RICH_MESSAGE)
466
+
417
467
  self.AddHandler(console_rich_handler_t, **handler_kwargs)
418
468
 
419
469
  def MakePermanent(self, path: str | path_t, /) -> None:
@@ -500,8 +550,11 @@ class logger_t(base_t):
500
550
  message = text.indent(message, LINE_INDENT)
501
551
 
502
552
  for handler in self.handlers:
503
- if (EmitAsIs := getattr(handler, "EmitAsIs", None)) is not None:
504
- EmitAsIs(message)
553
+ EmitMessage = getattr(
554
+ handler, handler_extension_t.EmitMessage.__name__, None
555
+ )
556
+ if EmitMessage is not None:
557
+ EmitMessage(message)
505
558
 
506
559
  info_raw = LogAsIs # To follow the convention of the logging methods info, error...
507
560
 
@@ -510,7 +563,8 @@ class logger_t(base_t):
510
563
  ) -> None:
511
564
  """"""
512
565
  for handler in self.handlers:
513
- if (EmitRule := getattr(handler, "EmitRule", None)) is not None:
566
+ EmitRule = getattr(handler, handler_extension_t.EmitRule.__name__, None)
567
+ if EmitRule is not None:
514
568
  EmitRule(text=message, color=color)
515
569
 
516
570
  def AddContextLevel(self, new_level: str, /) -> None:
@@ -638,6 +692,26 @@ class logger_t(base_t):
638
692
  return False
639
693
 
640
694
 
695
+ def _RecordLocation(record: l.LogRecord, should_also_store: bool, /) -> str:
696
+ """"""
697
+ module = path_t(record.pathname)
698
+ for path in s.path:
699
+ if module.is_relative_to(path):
700
+ module = module.relative_to(path).with_suffix("")
701
+ module = str(module).replace(FOLDER_SEPARATOR, ".")
702
+ break
703
+ else:
704
+ if module.is_relative_to(USER_FOLDER):
705
+ module = module.relative_to(USER_FOLDER)
706
+
707
+ output = f"{module}:{record.funcName}:{record.lineno}"
708
+
709
+ if should_also_store:
710
+ record.where = output
711
+
712
+ return output
713
+
714
+
641
715
  def _HandleForWarnings(interceptor: base_t, /) -> logger_handle_h:
642
716
  """"""
643
717
 
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.20"
7
+ __version__ = "2025.22"
8
8
 
9
9
  """
10
10
  COPYRIGHT NOTICE
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: logger-36
3
- Version: 2025.20
3
+ Version: 2025.22
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,5 +1,5 @@
1
1
  logger_36/__init__.py,sha256=mK6AD0eWI2Sk42oxleTvsxzYJ28FbHK5WNkpLgAhnNE,2129
2
- logger_36/version.py,sha256=vDO1d9-QYkQ8yZUGGG46kdG9NRQx6ONSvh6-E6JGHu4,1680
2
+ logger_36/version.py,sha256=WM8W-3pzA_JWd1DQWpdL-uJTAxV5u8_nbyWIlGaizjg,1680
3
3
  logger_36/api/content.py,sha256=DuT4UX4r_1DTXzuuRD-tvsTZk5X-Nj11loBKhuWOMw0,1791
4
4
  logger_36/api/gpu.py,sha256=NNs1IvQ7bh8Dppm8O8K2YLWbm4rogc3Ie_-D6xzkX3g,1726
5
5
  logger_36/api/memory.py,sha256=vOY4cTTrC3u7L0OrKXdPNlsCahYjCrY4h7iqpGZv9kU,2217
@@ -8,11 +8,11 @@ logger_36/api/system.py,sha256=h-3GfhZPwawv0UKBWKkT1LzxSCZwpA2VIsy03lLYi6w,1725
8
8
  logger_36/api/time.py,sha256=JG0vgzPSRZ7UWQyoihnVu4sjPC-okFIKA3ZyNh2GaZo,1798
9
9
  logger_36/api/type.py,sha256=eLZ2yuH-sYeh4Z2KnAwTRJEbmkmgzBPMncdqXfFUTG8,1760
10
10
  logger_36/catalog/config/console_rich.py,sha256=t9p9-AkSgPiLAsm1evAdbz77g7JcVLePhUJ1FzNi3cY,2330
11
- logger_36/catalog/config/optional.py,sha256=5vabOlEQIFxoT_y4AjP19rpOjBuUJplpuBkLoCIKImA,1872
12
- logger_36/catalog/handler/console.py,sha256=OKtRG3PseT06_UwZBtbragNIKvCqa7ZsLphYEsgeq2c,2308
13
- logger_36/catalog/handler/console_rich.py,sha256=b3B_pQefv0IeQFEj-tZ5-tH8RrcessZI8sI4Tyf17bg,6180
14
- logger_36/catalog/handler/file.py,sha256=JReN8KruN8eKhBfG9sj72kkHfukHK4-8mdSxNpgFhLU,2455
15
- logger_36/catalog/handler/generic.py,sha256=0Z4Vm-AAc92szghYEzihkm9CBRzCvqgdUTXH_p98S4o,6314
11
+ logger_36/catalog/config/optional.py,sha256=8d8HdpE07gHfsdoL8mVAlRlh9AgLcb4z7I7h6ob7CfU,2174
12
+ logger_36/catalog/handler/console.py,sha256=YEQN8fw7ra9w-5sJACNC4pktv9einm5ASP9TncU8TQo,2311
13
+ logger_36/catalog/handler/console_rich.py,sha256=gPpq8ij1ZFTXaLgISaCTXDDwv7uWPivLeq2ttpiuKlk,6190
14
+ logger_36/catalog/handler/file.py,sha256=yg8GnsV6AmDsx1R5iZlsK-5idqD733gc09Syl02TG7Y,2458
15
+ logger_36/catalog/handler/generic.py,sha256=L8mLc-iQlBWKBqEN3-wIKy2yk_TVgqIWYn_P1hXDsQA,6361
16
16
  logger_36/catalog/logger/chronos.py,sha256=S4m9TMPQy_Ju500mpE1jNzu2gZG-QKdVuvX9RVRKHR8,1911
17
17
  logger_36/catalog/logger/gpu.py,sha256=Py5YY0nD_pqJzJsEKQYoOGHcPqyNVJ3J2noOS3hDL6g,2890
18
18
  logger_36/catalog/logger/memory.py,sha256=J0ZGKO7j1FZA_aDGxpABtvzDy1RjCDiDmWYh4U98fEI,4253
@@ -29,7 +29,7 @@ logger_36/constant/logger.py,sha256=ZQYX9JiPsoivwRgYNtdEqRKCagSKD88lRqvxP8MX1ZE,
29
29
  logger_36/constant/memory.py,sha256=Q_E5tTWa-cGaNwrE_xmKa3BxQG6oJO6DHczrxc_M4sE,1817
30
30
  logger_36/constant/message.py,sha256=YJOEzdI0ZjUOdHo3CsiS56FVPhrfNoQYvXuUkprH61g,2312
31
31
  logger_36/constant/path.py,sha256=OfLh70Jyc8po9Ls34nQh_bRr3PXyQ3kF9ciR9QPhiqI,2213
32
- logger_36/constant/record.py,sha256=gQCGLxq8Vs789Ty_qaRNKy18mqlyMT_4kyN-T9r_rGE,1734
32
+ logger_36/constant/record.py,sha256=IGWnKA4TGv1oU6ezzNrGocZlB7Ie7nTr4l3IOtwdWOg,1833
33
33
  logger_36/constant/rule.py,sha256=tBKQgPTt6G_p5eInDdWoEEAvQFz4WMSt5THsS5jvk14,1779
34
34
  logger_36/constant/system.py,sha256=pLlLXG5sepQlSUOo3TphaGrHg8xzJBp-GxpL2NPP47k,1904
35
35
  logger_36/extension/html_.py,sha256=W9SyiYsaaYHUrHLGAAN2wiJGXUlwOBJ5gzdjmEcnF18,3342
@@ -42,11 +42,11 @@ logger_36/task/format/memory.py,sha256=J1Oy3jw8wjSp2kuiRUm_VFpzXOHX2FOc7nuRrCyrs
42
42
  logger_36/task/format/message.py,sha256=Rm6zymVEEGcgKfmxMPXP7q3PtwZJKlXGhqZ5tnvlwxA,3502
43
43
  logger_36/task/measure/chronos.py,sha256=7ijMZgP4EP18HbLV2yCxpNpRS9724Wyk523f-nkbhUM,2529
44
44
  logger_36/task/measure/memory.py,sha256=kkPHEIUTUhkCOLrAt01eLJLnsnkl0nFPNhFZdIB_JAw,1991
45
- logger_36/type/handler.py,sha256=ChxP1j9PXLmoiNcsOdxI4bYVdr75v7HeCWp_iYJ2WNY,6602
45
+ logger_36/type/handler.py,sha256=4LCFZzI4OEH6lVBrBQ09ZIvF0nq-_j7OOVnujBDyDWA,6665
46
46
  logger_36/type/issue.py,sha256=QHAYf7QgrjJUtF2D46z6X630qTgeP_0FE5hIwf54RsE,2688
47
- logger_36/type/logger.py,sha256=tWdexeJT9IrxKK2NQmnHi1WVmN16xR8RNZPMc__SWBs,25447
47
+ logger_36/type/logger.py,sha256=b2F21M5z1hwPdDsyHgBIlC06X9-_7zsjqyGEsVaIJgM,27536
48
48
  logger_36/type/loggers.py,sha256=7EX7Sg_RlduBjdfFlNZmUfNeDloH1xU30Rdkg_-rXh8,3172
49
- logger_36-2025.20.dist-info/METADATA,sha256=On7rI0r81M6ehWSyX7ZPiBKYVZoLqMPj-1VJWRuSd50,6529
50
- logger_36-2025.20.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
51
- logger_36-2025.20.dist-info/top_level.txt,sha256=sM95BTMWmslEEgR_1pzwZsOeSp8C_QBiu8ImbFr0XLc,10
52
- logger_36-2025.20.dist-info/RECORD,,
49
+ logger_36-2025.22.dist-info/METADATA,sha256=xcPkxJFWNYEIXgspK2VCcU4LbsME8hlmFQjASmR-3KM,6529
50
+ logger_36-2025.22.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
51
+ logger_36-2025.22.dist-info/top_level.txt,sha256=sM95BTMWmslEEgR_1pzwZsOeSp8C_QBiu8ImbFr0XLc,10
52
+ logger_36-2025.22.dist-info/RECORD,,