logger-36 2023.13__py3-none-any.whl → 2025.3__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- logger_36/__init__.py +65 -41
- logger_36/api/logger.py +53 -0
- logger_36/api/storage.py +53 -0
- logger_36/catalog/config/console_rich.py +76 -0
- logger_36/catalog/handler/console.py +117 -0
- logger_36/catalog/handler/console_rich.py +235 -0
- logger_36/catalog/handler/file.py +128 -0
- logger_36/catalog/handler/generic.py +228 -0
- logger_36/catalog/logger/chronos.py +61 -0
- logger_36/catalog/logger/gpu.py +90 -0
- logger_36/catalog/logger/memory.py +129 -0
- logger_36/catalog/logger/system.py +84 -0
- logger_36/config/issue.py +56 -0
- logger_36/config/logger.py +103 -0
- logger_36/config/memory.py +54 -0
- logger_36/config/message.py +66 -0
- logger_36/config/system.py +70 -0
- logger_36/constant/error.py +70 -0
- logger_36/constant/generic.py +58 -0
- logger_36/constant/handler.py +58 -0
- logger_36/constant/issue.py +58 -0
- logger_36/constant/logger.py +67 -0
- logger_36/constant/memory.py +58 -0
- logger_36/constant/message.py +72 -0
- logger_36/constant/record.py +55 -0
- logger_36/constant/system.py +60 -0
- logger_36/content.py +55 -0
- logger_36/exception.py +105 -0
- logger_36/gpu.py +53 -0
- logger_36/handler.py +209 -0
- logger_36/instance/logger.py +55 -0
- logger_36/instance/loggers.py +56 -0
- logger_36/memory.py +60 -0
- logger_36/storage.py +53 -0
- logger_36/system.py +53 -0
- logger_36/task/format/memory.py +132 -0
- logger_36/task/format/message.py +111 -0
- logger_36/task/format/rule.py +74 -0
- logger_36/task/inspection.py +70 -48
- logger_36/task/measure/chronos.py +84 -0
- logger_36/task/measure/memory.py +72 -0
- logger_36/task/storage.py +164 -0
- logger_36/time.py +54 -0
- logger_36/type/handler.py +184 -0
- logger_36/type/issue.py +91 -0
- logger_36/type/logger.py +542 -0
- logger_36/type/loggers.py +78 -0
- logger_36/version.py +53 -32
- logger_36-2025.3.dist-info/METADATA +154 -0
- logger_36-2025.3.dist-info/RECORD +52 -0
- {logger_36-2023.13.dist-info → logger_36-2025.3.dist-info}/WHEEL +1 -1
- logger_36/config.py +0 -66
- logger_36/constant.py +0 -57
- logger_36/main.py +0 -185
- logger_36/measure/chronos.py +0 -55
- logger_36/measure/memory.py +0 -102
- logger_36/type/console.py +0 -122
- logger_36/type/extension.py +0 -122
- logger_36/type/file.py +0 -52
- logger_36/type/generic.py +0 -116
- logger_36-2023.13.dist-info/METADATA +0 -106
- logger_36-2023.13.dist-info/RECORD +0 -16
- {logger_36-2023.13.dist-info → logger_36-2025.3.dist-info}/top_level.txt +0 -0
logger_36/type/logger.py
ADDED
@@ -0,0 +1,542 @@
|
|
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 logging as lggg
|
9
|
+
import sys as sstm
|
10
|
+
import traceback as tcbk
|
11
|
+
import types as t
|
12
|
+
import typing as h
|
13
|
+
from datetime import date as date_t
|
14
|
+
from datetime import datetime as date_time_t
|
15
|
+
from os import sep as FOLDER_SEPARATOR
|
16
|
+
from pathlib import Path as path_t
|
17
|
+
from traceback import TracebackException as traceback_t
|
18
|
+
|
19
|
+
from logger_36.config.issue import ISSUE_CONTEXT_END, ISSUE_CONTEXT_SEPARATOR
|
20
|
+
from logger_36.config.message import (
|
21
|
+
DATE_FORMAT,
|
22
|
+
ELAPSED_TIME_SEPARATOR,
|
23
|
+
LONG_ENOUGH,
|
24
|
+
TIME_FORMAT,
|
25
|
+
)
|
26
|
+
from logger_36.constant.generic import NOT_PASSED
|
27
|
+
from logger_36.constant.handler import ANONYMOUS
|
28
|
+
from logger_36.constant.issue import ISSUE_LEVEL_SEPARATOR, ORDER, order_h
|
29
|
+
from logger_36.constant.logger import (
|
30
|
+
LOGGER_NAME,
|
31
|
+
WARNING_LOGGER_NAME,
|
32
|
+
WARNING_TYPE_COMPILED_PATTERN,
|
33
|
+
logger_handle_h,
|
34
|
+
)
|
35
|
+
from logger_36.constant.memory import UNKNOWN_MEMORY_USAGE
|
36
|
+
from logger_36.constant.message import TIME_LENGTH_m_1, expected_op_h
|
37
|
+
from logger_36.constant.record import (
|
38
|
+
HIDE_WHERE_ATTR,
|
39
|
+
SHOW_W_RULE_ATTR,
|
40
|
+
STORE_MEMORY_ATTR,
|
41
|
+
)
|
42
|
+
from logger_36.task.format.message import MessageWithActualExpected
|
43
|
+
from logger_36.task.measure.chronos import ElapsedTime
|
44
|
+
from logger_36.task.measure.memory import CurrentUsage as CurrentMemoryUsage
|
45
|
+
from logger_36.type.issue import NewIssue, issue_t
|
46
|
+
|
47
|
+
logger_base_t = lggg.Logger
|
48
|
+
|
49
|
+
_DATE_TIME_ORIGIN = date_time_t.fromtimestamp(1970, None)
|
50
|
+
_DATE_ORIGIN = _DATE_TIME_ORIGIN.date()
|
51
|
+
|
52
|
+
|
53
|
+
@d.dataclass(slots=True, repr=False, eq=False)
|
54
|
+
class logger_t(logger_base_t):
|
55
|
+
"""
|
56
|
+
intercepted_wrn_handle: When warning interception is on, this stores the original
|
57
|
+
"handle" method of the Python warning logger.
|
58
|
+
"""
|
59
|
+
name_: d.InitVar[str] = LOGGER_NAME
|
60
|
+
level_: d.InitVar[int] = lggg.NOTSET
|
61
|
+
activate_wrn_interceptions: d.InitVar[bool] = True
|
62
|
+
|
63
|
+
# Must not be False until at least one handler has been added.
|
64
|
+
should_hold_messages: bool = True
|
65
|
+
exit_on_error: bool = False # Implies exit_on_critical.
|
66
|
+
exit_on_critical: bool = False
|
67
|
+
|
68
|
+
on_hold: list[lggg.LogRecord] = d.field(init=False, default_factory=list)
|
69
|
+
events: dict[int, int] = d.field(init=False, default_factory=dict)
|
70
|
+
last_message_now: date_time_t = d.field(init=False, default=_DATE_TIME_ORIGIN)
|
71
|
+
last_message_date: date_t = d.field(init=False, default=_DATE_ORIGIN)
|
72
|
+
any_handler_stores_memory: bool = d.field(init=False, default=False)
|
73
|
+
memory_usages: list[tuple[str, int]] = d.field(init=False, default_factory=list)
|
74
|
+
context_levels: list[str] = d.field(init=False, default_factory=list)
|
75
|
+
staged_issues: list[issue_t] = d.field(init=False, default_factory=list)
|
76
|
+
intercepted_wrn_handle: logger_handle_h | None = d.field(init=False, default=None)
|
77
|
+
intercepted_log_handles: dict[str, logger_handle_h] = d.field(
|
78
|
+
init=False, default_factory=dict
|
79
|
+
)
|
80
|
+
|
81
|
+
def __post_init__(
|
82
|
+
self, name_: str, level_: int, activate_wrn_interceptions: bool
|
83
|
+
) -> None:
|
84
|
+
""""""
|
85
|
+
logger_base_t.__init__(self, name_)
|
86
|
+
self.setLevel(level_)
|
87
|
+
self.propagate = False # Part of logger_base_t.
|
88
|
+
|
89
|
+
for level in lggg.getLevelNamesMapping().values():
|
90
|
+
self.events[level] = 0
|
91
|
+
|
92
|
+
if activate_wrn_interceptions:
|
93
|
+
self._ActivateWarningInterceptions()
|
94
|
+
if self.exit_on_error:
|
95
|
+
self.exit_on_critical = True
|
96
|
+
|
97
|
+
def ResetEventCounts(self) -> None:
|
98
|
+
""""""
|
99
|
+
for level in self.events:
|
100
|
+
self.events[level] = 0
|
101
|
+
|
102
|
+
def _ActivateWarningInterceptions(self) -> None:
|
103
|
+
"""
|
104
|
+
The log message will not appear if called from __post_init__ since there are no
|
105
|
+
handlers yet.
|
106
|
+
"""
|
107
|
+
if self.intercepted_wrn_handle is None:
|
108
|
+
logger = lggg.getLogger(WARNING_LOGGER_NAME)
|
109
|
+
self.intercepted_wrn_handle = logger.handle
|
110
|
+
logger.handle = t.MethodType(_HandleForWarnings(self), logger)
|
111
|
+
|
112
|
+
lggg.captureWarnings(True)
|
113
|
+
self.info("Warning Interception: ON")
|
114
|
+
|
115
|
+
def _DeactivateWarningInterceptions(self) -> None:
|
116
|
+
""""""
|
117
|
+
if self.intercepted_wrn_handle is not None:
|
118
|
+
logger = lggg.getLogger(WARNING_LOGGER_NAME)
|
119
|
+
logger.handle = self.intercepted_wrn_handle
|
120
|
+
self.intercepted_wrn_handle = None
|
121
|
+
|
122
|
+
lggg.captureWarnings(False)
|
123
|
+
self.info("Warning Interception: OFF")
|
124
|
+
|
125
|
+
def ToggleWarningInterceptions(self, state: bool, /) -> None:
|
126
|
+
""""""
|
127
|
+
if state:
|
128
|
+
self._ActivateWarningInterceptions()
|
129
|
+
else:
|
130
|
+
self._DeactivateWarningInterceptions()
|
131
|
+
|
132
|
+
def ToggleLogInterceptions(self, state: bool, /) -> None:
|
133
|
+
""""""
|
134
|
+
if state:
|
135
|
+
self.ToggleLogInterceptions(False)
|
136
|
+
|
137
|
+
all_loggers = [lggg.getLogger()] + [
|
138
|
+
lggg.getLogger(_nme)
|
139
|
+
for _nme in self.manager.loggerDict
|
140
|
+
if _nme not in (self.name, WARNING_LOGGER_NAME)
|
141
|
+
]
|
142
|
+
for logger in all_loggers:
|
143
|
+
self.intercepted_log_handles[logger.name] = logger.handle
|
144
|
+
logger.handle = t.MethodType(
|
145
|
+
_HandleForInterceptions(logger, self), logger
|
146
|
+
)
|
147
|
+
|
148
|
+
intercepted = sorted(self.intercepted_log_handles.keys())
|
149
|
+
if intercepted.__len__() > 0:
|
150
|
+
as_str = ", ".join(intercepted)
|
151
|
+
self.info(f"Now Intercepting LOGs from: {as_str}")
|
152
|
+
elif self.intercepted_log_handles.__len__() > 0:
|
153
|
+
for name, handle in self.intercepted_log_handles.items():
|
154
|
+
logger = lggg.getLogger(name)
|
155
|
+
logger.handle = handle
|
156
|
+
self.intercepted_log_handles.clear()
|
157
|
+
self.info("Log Interception: OFF")
|
158
|
+
|
159
|
+
@property
|
160
|
+
def max_memory_usage(self) -> int:
|
161
|
+
""""""
|
162
|
+
if self.memory_usages.__len__() > 0:
|
163
|
+
return max(tuple(zip(*self.memory_usages))[1])
|
164
|
+
return UNKNOWN_MEMORY_USAGE
|
165
|
+
|
166
|
+
@property
|
167
|
+
def max_memory_usage_full(self) -> tuple[str, int]:
|
168
|
+
""""""
|
169
|
+
if self.memory_usages.__len__() > 0:
|
170
|
+
where_s, usages = zip(*self.memory_usages)
|
171
|
+
max_usage = max(usages)
|
172
|
+
|
173
|
+
return where_s[usages.index(max_usage)], max_usage
|
174
|
+
|
175
|
+
return "?", UNKNOWN_MEMORY_USAGE
|
176
|
+
|
177
|
+
def AddHandler(self, handler: lggg.Handler, should_hold_messages: bool, /) -> None:
|
178
|
+
""""""
|
179
|
+
self.should_hold_messages = should_hold_messages
|
180
|
+
logger_base_t.addHandler(self, handler)
|
181
|
+
|
182
|
+
extension = getattr(handler, "extension", None)
|
183
|
+
if extension is None:
|
184
|
+
name = handler.name
|
185
|
+
if (name is None) or (name.__len__() == 0):
|
186
|
+
name = ANONYMOUS
|
187
|
+
else:
|
188
|
+
name = getattr(extension, "name", ANONYMOUS)
|
189
|
+
if getattr(extension, STORE_MEMORY_ATTR, False):
|
190
|
+
self.any_handler_stores_memory = True
|
191
|
+
|
192
|
+
path = getattr(handler, "baseFilename", "")
|
193
|
+
if isinstance(path, path_t) or (path.__len__() > 0):
|
194
|
+
path = f"\nPath: {path}"
|
195
|
+
|
196
|
+
self.info(
|
197
|
+
f'New handler "{name}" of type "{type(handler).__name__}" and '
|
198
|
+
f"level {handler.level}={lggg.getLevelName(handler.level)}{path}",
|
199
|
+
)
|
200
|
+
|
201
|
+
def handle(self, record: lggg.LogRecord, /) -> None:
|
202
|
+
""""""
|
203
|
+
elapsed_time, now = ElapsedTime(should_return_now=True)
|
204
|
+
|
205
|
+
if (self.on_hold.__len__() > 0) and not self.should_hold_messages:
|
206
|
+
for held in self.on_hold:
|
207
|
+
logger_base_t.handle(self, held)
|
208
|
+
self.on_hold.clear()
|
209
|
+
|
210
|
+
if (date := now.date()) != self.last_message_date:
|
211
|
+
self.last_message_date = date
|
212
|
+
# levelno: Added for management by logging.Logger.handle.
|
213
|
+
date_record = lggg.makeLogRecord(
|
214
|
+
{
|
215
|
+
"name": self.name,
|
216
|
+
"levelno": lggg.INFO,
|
217
|
+
"msg": f"DATE: {date.strftime(DATE_FORMAT)}",
|
218
|
+
SHOW_W_RULE_ATTR: True,
|
219
|
+
}
|
220
|
+
)
|
221
|
+
if self.should_hold_messages:
|
222
|
+
self.on_hold.append(date_record)
|
223
|
+
else:
|
224
|
+
logger_base_t.handle(self, date_record)
|
225
|
+
|
226
|
+
# When.
|
227
|
+
if now - self.last_message_now > LONG_ENOUGH:
|
228
|
+
record.when_or_elapsed = now.strftime(TIME_FORMAT)
|
229
|
+
else:
|
230
|
+
record.when_or_elapsed = (
|
231
|
+
f"{ELAPSED_TIME_SEPARATOR}{elapsed_time:.<{TIME_LENGTH_m_1}}"
|
232
|
+
)
|
233
|
+
self.last_message_now = now
|
234
|
+
|
235
|
+
# Where.
|
236
|
+
# Memory usage is also stored if there are no handlers yet, just in case.
|
237
|
+
should_store_where = self.any_handler_stores_memory or not self.hasHandlers()
|
238
|
+
should_show_where = (record.levelno != lggg.INFO) and not hasattr(
|
239
|
+
record, HIDE_WHERE_ATTR
|
240
|
+
)
|
241
|
+
if should_store_where or should_show_where:
|
242
|
+
module = path_t(record.pathname)
|
243
|
+
for path in sstm.path:
|
244
|
+
if module.is_relative_to(path):
|
245
|
+
module = module.relative_to(path).with_suffix("")
|
246
|
+
module = str(module).replace(FOLDER_SEPARATOR, ".")
|
247
|
+
break
|
248
|
+
else:
|
249
|
+
module = record.module
|
250
|
+
where = f"{module}:{record.funcName}:{record.lineno}"
|
251
|
+
if should_show_where:
|
252
|
+
record.where = where
|
253
|
+
else:
|
254
|
+
where = None
|
255
|
+
|
256
|
+
# How.
|
257
|
+
record.level_first_letter = record.levelname[0]
|
258
|
+
|
259
|
+
# What.
|
260
|
+
if not isinstance(record.msg, str):
|
261
|
+
record.msg = str(record.msg)
|
262
|
+
|
263
|
+
if self.should_hold_messages:
|
264
|
+
self.on_hold.append(record)
|
265
|
+
else:
|
266
|
+
logger_base_t.handle(self, record)
|
267
|
+
|
268
|
+
if (self.exit_on_critical and (record.levelno is lggg.CRITICAL)) or (
|
269
|
+
self.exit_on_error and (record.levelno is lggg.ERROR)
|
270
|
+
):
|
271
|
+
# Also works if self.exit_on_error and record.levelno is lggg.CRITICAL since
|
272
|
+
# __post_init__ set self.exit_on_critical if self.exit_on_error.
|
273
|
+
sstm.exit(1)
|
274
|
+
|
275
|
+
self.events[record.levelno] += 1
|
276
|
+
|
277
|
+
if should_store_where:
|
278
|
+
self.memory_usages.append((where, CurrentMemoryUsage()))
|
279
|
+
|
280
|
+
def Log(
|
281
|
+
self,
|
282
|
+
message: str,
|
283
|
+
/,
|
284
|
+
*,
|
285
|
+
level: int | str = lggg.ERROR,
|
286
|
+
actual: h.Any = NOT_PASSED,
|
287
|
+
expected: h.Any | None = None,
|
288
|
+
expected_is_choices: bool = False,
|
289
|
+
expected_op: expected_op_h = "=",
|
290
|
+
with_final_dot: bool = True,
|
291
|
+
) -> None:
|
292
|
+
""""""
|
293
|
+
if isinstance(level, str):
|
294
|
+
level = lggg.getLevelNamesMapping()[level.upper()]
|
295
|
+
message = MessageWithActualExpected(
|
296
|
+
message,
|
297
|
+
actual=actual,
|
298
|
+
expected=expected,
|
299
|
+
expected_is_choices=expected_is_choices,
|
300
|
+
expected_op=expected_op,
|
301
|
+
with_final_dot=with_final_dot,
|
302
|
+
)
|
303
|
+
self.log(level, message)
|
304
|
+
|
305
|
+
def LogException(
|
306
|
+
self,
|
307
|
+
exception: Exception,
|
308
|
+
/,
|
309
|
+
*,
|
310
|
+
level: int | str = lggg.ERROR,
|
311
|
+
should_remove_caller: bool = False,
|
312
|
+
) -> None:
|
313
|
+
""""""
|
314
|
+
if isinstance(level, str):
|
315
|
+
level = lggg.getLevelNamesMapping()[level.upper()]
|
316
|
+
lines = tcbk.format_exception(exception)
|
317
|
+
if should_remove_caller:
|
318
|
+
message = "\n".join(lines[:1] + lines[2:])
|
319
|
+
else:
|
320
|
+
# TODO: Explain:
|
321
|
+
# - Why it's not: "\n".join(lines)?
|
322
|
+
# - Why adding exception name here and not when removing caller?
|
323
|
+
formatted = "".join(lines)
|
324
|
+
message = f"{type(exception).__name__}:\n{formatted}"
|
325
|
+
self.log(level, message)
|
326
|
+
|
327
|
+
def ShowMessage(self, message: str, /, *, indented: bool = False) -> None:
|
328
|
+
"""
|
329
|
+
See documentation of
|
330
|
+
logger_36.catalog.handler.generic.generic_handler_t.ShowMessage.
|
331
|
+
"""
|
332
|
+
for handler in self.handlers:
|
333
|
+
ShowMessage = getattr(handler, "ShowMessage", None)
|
334
|
+
if ShowMessage is not None:
|
335
|
+
ShowMessage(message, indented=indented)
|
336
|
+
|
337
|
+
def DisplayRule(self, /, *, text: str | None = None, color: str = "white") -> None:
|
338
|
+
""""""
|
339
|
+
for handler in self.handlers:
|
340
|
+
DisplayRule = getattr(handler, "DisplayRule", None)
|
341
|
+
if DisplayRule is not None:
|
342
|
+
DisplayRule(text=text, color=color)
|
343
|
+
|
344
|
+
def AddContextLevel(self, new_level: str, /) -> None:
|
345
|
+
""""""
|
346
|
+
self.context_levels.append(new_level)
|
347
|
+
|
348
|
+
def AddedContextLevel(self, new_level: str, /) -> h.Self:
|
349
|
+
"""
|
350
|
+
Meant to be used as:
|
351
|
+
with self.AddedContextLevel("new level"):
|
352
|
+
...
|
353
|
+
"""
|
354
|
+
self.AddContextLevel(new_level)
|
355
|
+
return self
|
356
|
+
|
357
|
+
def StageIssue(
|
358
|
+
self,
|
359
|
+
message: str,
|
360
|
+
/,
|
361
|
+
*,
|
362
|
+
level: int = lggg.ERROR,
|
363
|
+
actual: h.Any = NOT_PASSED,
|
364
|
+
expected: h.Any | None = None,
|
365
|
+
expected_is_choices: bool = False,
|
366
|
+
expected_op: expected_op_h = "=",
|
367
|
+
with_final_dot: bool = False,
|
368
|
+
) -> None:
|
369
|
+
""""""
|
370
|
+
context = ISSUE_CONTEXT_SEPARATOR.join(self.context_levels)
|
371
|
+
issue = NewIssue(
|
372
|
+
context,
|
373
|
+
ISSUE_CONTEXT_END,
|
374
|
+
message,
|
375
|
+
level=level,
|
376
|
+
actual=actual,
|
377
|
+
expected=expected,
|
378
|
+
expected_is_choices=expected_is_choices,
|
379
|
+
expected_op=expected_op,
|
380
|
+
with_final_dot=with_final_dot,
|
381
|
+
)
|
382
|
+
self.staged_issues.append(issue)
|
383
|
+
|
384
|
+
@property
|
385
|
+
def has_staged_issues(self) -> bool:
|
386
|
+
""""""
|
387
|
+
return self.staged_issues.__len__() > 0
|
388
|
+
|
389
|
+
def CommitIssues(
|
390
|
+
self,
|
391
|
+
/,
|
392
|
+
*,
|
393
|
+
order: order_h = "when",
|
394
|
+
unified: bool = False,
|
395
|
+
) -> None:
|
396
|
+
"""
|
397
|
+
Note that issues after an issue with a level triggering process exit will not be
|
398
|
+
logged.
|
399
|
+
"""
|
400
|
+
if not self.has_staged_issues:
|
401
|
+
return
|
402
|
+
|
403
|
+
if order not in ORDER:
|
404
|
+
raise ValueError(
|
405
|
+
MessageWithActualExpected(
|
406
|
+
"Invalid commit order",
|
407
|
+
actual=order,
|
408
|
+
expected=f"One of {str(ORDER)[1:-1]}",
|
409
|
+
)
|
410
|
+
)
|
411
|
+
|
412
|
+
if order == "when":
|
413
|
+
issues = self.staged_issues
|
414
|
+
else: # order == "context"
|
415
|
+
issues = sorted(self.staged_issues, key=lambda _elm: _elm.context)
|
416
|
+
"""
|
417
|
+
Format issues as an exception:
|
418
|
+
try:
|
419
|
+
raise ValueError("\n" + "\n".join(issues))
|
420
|
+
except ValueError as exception:
|
421
|
+
lines = ["Traceback (most recent call last):"] + tcbk.format_stack()[:-1]
|
422
|
+
lines[-1] = lines[-1][:-1]
|
423
|
+
lines.extend(tcbk.format_exception_only(exception))
|
424
|
+
formatted = "\n".join(lines)
|
425
|
+
"""
|
426
|
+
|
427
|
+
hide_where = {HIDE_WHERE_ATTR: None}
|
428
|
+
if unified:
|
429
|
+
level, _ = issues[0].split(ISSUE_LEVEL_SEPARATOR, maxsplit=1)
|
430
|
+
wo_level = []
|
431
|
+
for issue in issues:
|
432
|
+
_, issue = issue.split(ISSUE_LEVEL_SEPARATOR, maxsplit=1)
|
433
|
+
wo_level.append(issue)
|
434
|
+
self.log(int(level), "\n".join(wo_level), stacklevel=2, extra=hide_where)
|
435
|
+
else:
|
436
|
+
for issue in issues:
|
437
|
+
level, issue = issue.split(ISSUE_LEVEL_SEPARATOR, maxsplit=1)
|
438
|
+
self.log(int(level), issue, stacklevel=2, extra=hide_where)
|
439
|
+
self.staged_issues.clear()
|
440
|
+
|
441
|
+
def __enter__(self) -> None:
|
442
|
+
""""""
|
443
|
+
pass
|
444
|
+
|
445
|
+
def __exit__(
|
446
|
+
self,
|
447
|
+
exc_type: Exception | None,
|
448
|
+
exc_value: str | None,
|
449
|
+
traceback: traceback_t | None,
|
450
|
+
/,
|
451
|
+
) -> bool:
|
452
|
+
""""""
|
453
|
+
_ = self.context_levels.pop()
|
454
|
+
return False
|
455
|
+
|
456
|
+
|
457
|
+
def _HandleForWarnings(interceptor: logger_base_t, /) -> logger_handle_h:
|
458
|
+
""""""
|
459
|
+
|
460
|
+
def handle_p(_: logger_base_t, record: lggg.LogRecord, /) -> None:
|
461
|
+
pieces = WARNING_TYPE_COMPILED_PATTERN.match(record.msg)
|
462
|
+
if pieces is None:
|
463
|
+
# The warning message does not follow the default format.
|
464
|
+
interceptor.handle(record)
|
465
|
+
return
|
466
|
+
|
467
|
+
GetPiece = pieces.group
|
468
|
+
path = GetPiece(1)
|
469
|
+
line = GetPiece(2)
|
470
|
+
kind = GetPiece(3)
|
471
|
+
message = GetPiece(4).strip()
|
472
|
+
|
473
|
+
duplicate = lggg.makeLogRecord(record.__dict__)
|
474
|
+
duplicate.msg = f"{kind}: {message}"
|
475
|
+
duplicate.pathname = path
|
476
|
+
duplicate.module = path_t(path).stem
|
477
|
+
duplicate.funcName = "?"
|
478
|
+
duplicate.lineno = line
|
479
|
+
|
480
|
+
interceptor.handle(duplicate)
|
481
|
+
|
482
|
+
return handle_p
|
483
|
+
|
484
|
+
|
485
|
+
def _HandleForInterceptions(
|
486
|
+
intercepted: logger_base_t, interceptor: logger_base_t, /
|
487
|
+
) -> logger_handle_h:
|
488
|
+
""""""
|
489
|
+
|
490
|
+
def handle_p(_: logger_base_t, record: lggg.LogRecord, /) -> None:
|
491
|
+
duplicate = lggg.makeLogRecord(record.__dict__)
|
492
|
+
duplicate.msg = f"{record.msg} :{intercepted.name}:"
|
493
|
+
interceptor.handle(duplicate)
|
494
|
+
|
495
|
+
return handle_p
|
496
|
+
|
497
|
+
|
498
|
+
"""
|
499
|
+
COPYRIGHT NOTICE
|
500
|
+
|
501
|
+
This software is governed by the CeCILL license under French law and
|
502
|
+
abiding by the rules of distribution of free software. You can use,
|
503
|
+
modify and/ or redistribute the software under the terms of the CeCILL
|
504
|
+
license as circulated by CEA, CNRS and INRIA at the following URL
|
505
|
+
"http://www.cecill.info".
|
506
|
+
|
507
|
+
As a counterpart to the access to the source code and rights to copy,
|
508
|
+
modify and redistribute granted by the license, users are provided only
|
509
|
+
with a limited warranty and the software's author, the holder of the
|
510
|
+
economic rights, and the successive licensors have only limited
|
511
|
+
liability.
|
512
|
+
|
513
|
+
In this respect, the user's attention is drawn to the risks associated
|
514
|
+
with loading, using, modifying and/or developing or reproducing the
|
515
|
+
software by the user in light of its specific status of free software,
|
516
|
+
that may mean that it is complicated to manipulate, and that also
|
517
|
+
therefore means that it is reserved for developers and experienced
|
518
|
+
professionals having in-depth computer knowledge. Users are therefore
|
519
|
+
encouraged to load and test the software's suitability as regards their
|
520
|
+
requirements in conditions enabling the security of their systems and/or
|
521
|
+
data to be ensured and, more generally, to use and operate it in the
|
522
|
+
same conditions as regards security.
|
523
|
+
|
524
|
+
The fact that you are presently reading this means that you have had
|
525
|
+
knowledge of the CeCILL license and that you accept its terms.
|
526
|
+
|
527
|
+
SEE LICENCE NOTICE: file README-LICENCE-utf8.txt at project source root.
|
528
|
+
|
529
|
+
This software is being developed by Eric Debreuve, a CNRS employee and
|
530
|
+
member of team Morpheme.
|
531
|
+
Team Morpheme is a joint team between Inria, CNRS, and UniCA.
|
532
|
+
It is hosted by the Centre Inria d'Université Côte d'Azur, Laboratory
|
533
|
+
I3S, and Laboratory iBV.
|
534
|
+
|
535
|
+
CNRS: https://www.cnrs.fr/index.php/en
|
536
|
+
Inria: https://www.inria.fr/en/
|
537
|
+
UniCA: https://univ-cotedazur.eu/
|
538
|
+
Centre Inria d'Université Côte d'Azur: https://www.inria.fr/en/centre/sophia/
|
539
|
+
I3S: https://www.i3s.unice.fr/en/
|
540
|
+
iBV: http://ibv.unice.fr/
|
541
|
+
Team Morpheme: https://team.inria.fr/morpheme/
|
542
|
+
"""
|
@@ -0,0 +1,78 @@
|
|
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 typing as h
|
9
|
+
|
10
|
+
from logger_36.type.logger import logger_t
|
11
|
+
|
12
|
+
|
13
|
+
@d.dataclass(slots=True, repr=False, eq=False)
|
14
|
+
class loggers_t(dict[h.Hashable, logger_t]):
|
15
|
+
active: logger_t | None = d.field(init=False, default=None)
|
16
|
+
|
17
|
+
def AddNew(self, uid: h.Hashable, /) -> None:
|
18
|
+
""""""
|
19
|
+
self.Add(uid, logger_t())
|
20
|
+
|
21
|
+
def Add(self, uid: h.Hashable, logger: logger_t, /) -> None:
|
22
|
+
""""""
|
23
|
+
if uid in self:
|
24
|
+
raise NameError(f"Logger with name/identity {uid} already exists.")
|
25
|
+
|
26
|
+
self[uid] = logger
|
27
|
+
self.active = logger
|
28
|
+
|
29
|
+
def SetActive(self, uid: h.Hashable, /) -> None:
|
30
|
+
""""""
|
31
|
+
self.active = self[uid]
|
32
|
+
|
33
|
+
|
34
|
+
"""
|
35
|
+
COPYRIGHT NOTICE
|
36
|
+
|
37
|
+
This software is governed by the CeCILL license under French law and
|
38
|
+
abiding by the rules of distribution of free software. You can use,
|
39
|
+
modify and/ or redistribute the software under the terms of the CeCILL
|
40
|
+
license as circulated by CEA, CNRS and INRIA at the following URL
|
41
|
+
"http://www.cecill.info".
|
42
|
+
|
43
|
+
As a counterpart to the access to the source code and rights to copy,
|
44
|
+
modify and redistribute granted by the license, users are provided only
|
45
|
+
with a limited warranty and the software's author, the holder of the
|
46
|
+
economic rights, and the successive licensors have only limited
|
47
|
+
liability.
|
48
|
+
|
49
|
+
In this respect, the user's attention is drawn to the risks associated
|
50
|
+
with loading, using, modifying and/or developing or reproducing the
|
51
|
+
software by the user in light of its specific status of free software,
|
52
|
+
that may mean that it is complicated to manipulate, and that also
|
53
|
+
therefore means that it is reserved for developers and experienced
|
54
|
+
professionals having in-depth computer knowledge. Users are therefore
|
55
|
+
encouraged to load and test the software's suitability as regards their
|
56
|
+
requirements in conditions enabling the security of their systems and/or
|
57
|
+
data to be ensured and, more generally, to use and operate it in the
|
58
|
+
same conditions as regards security.
|
59
|
+
|
60
|
+
The fact that you are presently reading this means that you have had
|
61
|
+
knowledge of the CeCILL license and that you accept its terms.
|
62
|
+
|
63
|
+
SEE LICENCE NOTICE: file README-LICENCE-utf8.txt at project source root.
|
64
|
+
|
65
|
+
This software is being developed by Eric Debreuve, a CNRS employee and
|
66
|
+
member of team Morpheme.
|
67
|
+
Team Morpheme is a joint team between Inria, CNRS, and UniCA.
|
68
|
+
It is hosted by the Centre Inria d'Université Côte d'Azur, Laboratory
|
69
|
+
I3S, and Laboratory iBV.
|
70
|
+
|
71
|
+
CNRS: https://www.cnrs.fr/index.php/en
|
72
|
+
Inria: https://www.inria.fr/en/
|
73
|
+
UniCA: https://univ-cotedazur.eu/
|
74
|
+
Centre Inria d'Université Côte d'Azur: https://www.inria.fr/en/centre/sophia/
|
75
|
+
I3S: https://www.i3s.unice.fr/en/
|
76
|
+
iBV: http://ibv.unice.fr/
|
77
|
+
Team Morpheme: https://team.inria.fr/morpheme/
|
78
|
+
"""
|