haiway 0.7.2__py3-none-any.whl → 0.8.1__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.
- haiway/__init__.py +14 -2
- haiway/context/__init__.py +14 -2
- haiway/context/access.py +119 -57
- haiway/context/identifier.py +75 -0
- haiway/context/logging.py +176 -0
- haiway/context/metrics.py +61 -329
- haiway/context/state.py +6 -6
- haiway/context/tasks.py +4 -5
- haiway/helpers/__init__.py +2 -0
- haiway/helpers/metrics.py +321 -0
- {haiway-0.7.2.dist-info → haiway-0.8.1.dist-info}/METADATA +1 -1
- {haiway-0.7.2.dist-info → haiway-0.8.1.dist-info}/RECORD +15 -12
- {haiway-0.7.2.dist-info → haiway-0.8.1.dist-info}/LICENSE +0 -0
- {haiway-0.7.2.dist-info → haiway-0.8.1.dist-info}/WHEEL +0 -0
- {haiway-0.7.2.dist-info → haiway-0.8.1.dist-info}/top_level.txt +0 -0
haiway/__init__.py
CHANGED
@@ -1,13 +1,19 @@
|
|
1
1
|
from haiway.context import (
|
2
2
|
Disposable,
|
3
3
|
Disposables,
|
4
|
+
MetricsContext,
|
5
|
+
MetricsHandler,
|
6
|
+
MetricsRecording,
|
7
|
+
MetricsScopeEntering,
|
8
|
+
MetricsScopeExiting,
|
4
9
|
MissingContext,
|
5
10
|
MissingState,
|
6
|
-
|
11
|
+
ScopeIdentifier,
|
7
12
|
ctx,
|
8
13
|
)
|
9
14
|
from haiway.helpers import (
|
10
15
|
ArgumentsTrace,
|
16
|
+
MetricsLogger,
|
11
17
|
ResultTrace,
|
12
18
|
asynchronous,
|
13
19
|
cache,
|
@@ -50,11 +56,17 @@ __all__ = [
|
|
50
56
|
"AttributeRequirement",
|
51
57
|
"Disposable",
|
52
58
|
"Disposables",
|
59
|
+
"MetricsContext",
|
60
|
+
"MetricsHandler",
|
61
|
+
"MetricsLogger",
|
62
|
+
"MetricsRecording",
|
63
|
+
"MetricsScopeEntering",
|
64
|
+
"MetricsScopeExiting",
|
53
65
|
"Missing",
|
54
66
|
"MissingContext",
|
55
67
|
"MissingState",
|
56
68
|
"ResultTrace",
|
57
|
-
"
|
69
|
+
"ScopeIdentifier",
|
58
70
|
"State",
|
59
71
|
"always",
|
60
72
|
"async_always",
|
haiway/context/__init__.py
CHANGED
@@ -1,13 +1,25 @@
|
|
1
1
|
from haiway.context.access import ctx
|
2
2
|
from haiway.context.disposables import Disposable, Disposables
|
3
|
-
from haiway.context.
|
3
|
+
from haiway.context.identifier import ScopeIdentifier
|
4
|
+
from haiway.context.metrics import (
|
5
|
+
MetricsContext,
|
6
|
+
MetricsHandler,
|
7
|
+
MetricsRecording,
|
8
|
+
MetricsScopeEntering,
|
9
|
+
MetricsScopeExiting,
|
10
|
+
)
|
4
11
|
from haiway.context.types import MissingContext, MissingState
|
5
12
|
|
6
13
|
__all__ = [
|
7
14
|
"Disposable",
|
8
15
|
"Disposables",
|
16
|
+
"MetricsContext",
|
17
|
+
"MetricsHandler",
|
18
|
+
"MetricsRecording",
|
19
|
+
"MetricsScopeEntering",
|
20
|
+
"MetricsScopeExiting",
|
9
21
|
"MissingContext",
|
10
22
|
"MissingState",
|
11
|
-
"
|
23
|
+
"ScopeIdentifier",
|
12
24
|
"ctx",
|
13
25
|
]
|
haiway/context/access.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
from asyncio import CancelledError, Task, current_task
|
1
|
+
from asyncio import CancelledError, Task, current_task, iscoroutinefunction
|
2
2
|
from collections.abc import (
|
3
3
|
AsyncGenerator,
|
4
4
|
AsyncIterator,
|
@@ -9,14 +9,16 @@ from collections.abc import (
|
|
9
9
|
from contextvars import Context, copy_context
|
10
10
|
from logging import Logger
|
11
11
|
from types import TracebackType
|
12
|
-
from typing import Any, final
|
12
|
+
from typing import Any, final, overload
|
13
13
|
|
14
14
|
from haiway.context.disposables import Disposable, Disposables
|
15
|
-
from haiway.context.
|
15
|
+
from haiway.context.identifier import ScopeIdentifier
|
16
|
+
from haiway.context.logging import LoggerContext
|
17
|
+
from haiway.context.metrics import MetricsContext, MetricsHandler
|
16
18
|
from haiway.context.state import StateContext
|
17
19
|
from haiway.context.tasks import TaskGroupContext
|
18
20
|
from haiway.state import State
|
19
|
-
from haiway.utils import freeze
|
21
|
+
from haiway.utils import freeze, mimic_function
|
20
22
|
|
21
23
|
__all__ = [
|
22
24
|
"ctx",
|
@@ -25,38 +27,47 @@ __all__ = [
|
|
25
27
|
|
26
28
|
@final
|
27
29
|
class ScopeContext:
|
28
|
-
def __init__(
|
30
|
+
def __init__(
|
29
31
|
self,
|
30
|
-
|
31
|
-
name: str,
|
32
|
+
label: str,
|
32
33
|
logger: Logger | None,
|
33
34
|
state: tuple[State, ...],
|
34
35
|
disposables: Disposables | None,
|
35
|
-
|
36
|
-
| Callable[[ScopeMetrics], None]
|
37
|
-
| None,
|
36
|
+
metrics: MetricsHandler | None,
|
38
37
|
) -> None:
|
38
|
+
self._identifier: ScopeIdentifier = ScopeIdentifier.scope(label)
|
39
|
+
self._logger_context: LoggerContext = LoggerContext(
|
40
|
+
self._identifier,
|
41
|
+
logger=logger,
|
42
|
+
)
|
39
43
|
self._task_group_context: TaskGroupContext = TaskGroupContext()
|
40
|
-
# postponing state creation to include disposables
|
44
|
+
# postponing state creation to include disposables state when prepared
|
41
45
|
self._state_context: StateContext
|
42
46
|
self._state: tuple[State, ...] = state
|
43
47
|
self._disposables: Disposables | None = disposables
|
44
48
|
# pre-building metrics context to ensure nested context registering
|
49
|
+
if __debug__:
|
50
|
+
if self._identifier.is_root and metrics is None:
|
51
|
+
from haiway.helpers import MetricsLogger
|
52
|
+
|
53
|
+
metrics = MetricsLogger.handler()
|
45
54
|
self._metrics_context: MetricsContext = MetricsContext.scope(
|
46
|
-
|
47
|
-
|
48
|
-
trace_id=trace_id,
|
49
|
-
completion=completion,
|
55
|
+
self._identifier,
|
56
|
+
metrics=metrics,
|
50
57
|
)
|
51
58
|
|
52
59
|
freeze(self)
|
53
60
|
|
54
|
-
def __enter__(self) ->
|
61
|
+
def __enter__(self) -> str:
|
55
62
|
assert self._disposables is None, "Can't enter synchronous context with disposables" # nosec: B101
|
63
|
+
self._identifier.__enter__()
|
64
|
+
self._logger_context.__enter__()
|
56
65
|
self._state_context = StateContext.updated(self._state)
|
57
66
|
self._state_context.__enter__()
|
58
67
|
self._metrics_context.__enter__()
|
59
68
|
|
69
|
+
return self._identifier.trace_id
|
70
|
+
|
60
71
|
def __exit__(
|
61
72
|
self,
|
62
73
|
exc_type: type[BaseException] | None,
|
@@ -75,7 +86,21 @@ class ScopeContext:
|
|
75
86
|
exc_tb=exc_tb,
|
76
87
|
)
|
77
88
|
|
78
|
-
|
89
|
+
self._logger_context.__exit__(
|
90
|
+
exc_type=exc_type,
|
91
|
+
exc_val=exc_val,
|
92
|
+
exc_tb=exc_tb,
|
93
|
+
)
|
94
|
+
|
95
|
+
self._identifier.__exit__(
|
96
|
+
exc_type=exc_type,
|
97
|
+
exc_val=exc_val,
|
98
|
+
exc_tb=exc_tb,
|
99
|
+
)
|
100
|
+
|
101
|
+
async def __aenter__(self) -> str:
|
102
|
+
self._identifier.__enter__()
|
103
|
+
self._logger_context.__enter__()
|
79
104
|
await self._task_group_context.__aenter__()
|
80
105
|
|
81
106
|
if self._disposables is not None:
|
@@ -89,6 +114,8 @@ class ScopeContext:
|
|
89
114
|
self._state_context.__enter__()
|
90
115
|
self._metrics_context.__enter__()
|
91
116
|
|
117
|
+
return self._identifier.trace_id
|
118
|
+
|
92
119
|
async def __aexit__(
|
93
120
|
self,
|
94
121
|
exc_type: type[BaseException] | None,
|
@@ -120,35 +147,82 @@ class ScopeContext:
|
|
120
147
|
exc_tb=exc_tb,
|
121
148
|
)
|
122
149
|
|
150
|
+
self._logger_context.__exit__(
|
151
|
+
exc_type=exc_type,
|
152
|
+
exc_val=exc_val,
|
153
|
+
exc_tb=exc_tb,
|
154
|
+
)
|
155
|
+
|
156
|
+
self._identifier.__exit__(
|
157
|
+
exc_type=exc_type,
|
158
|
+
exc_val=exc_val,
|
159
|
+
exc_tb=exc_tb,
|
160
|
+
)
|
161
|
+
|
162
|
+
@overload
|
163
|
+
def __call__[Result, **Arguments](
|
164
|
+
self,
|
165
|
+
function: Callable[Arguments, Coroutine[None, None, Result]],
|
166
|
+
) -> Callable[Arguments, Coroutine[None, None, Result]]: ...
|
167
|
+
|
168
|
+
@overload
|
169
|
+
def __call__[Result, **Arguments](
|
170
|
+
self,
|
171
|
+
function: Callable[Arguments, Result],
|
172
|
+
) -> Callable[Arguments, Result]: ...
|
173
|
+
|
174
|
+
def __call__[Result, **Arguments](
|
175
|
+
self,
|
176
|
+
function: Callable[Arguments, Coroutine[None, None, Result]] | Callable[Arguments, Result],
|
177
|
+
) -> Callable[Arguments, Coroutine[None, None, Result]] | Callable[Arguments, Result]:
|
178
|
+
if iscoroutinefunction(function):
|
179
|
+
|
180
|
+
async def async_context(
|
181
|
+
*args: Arguments.args,
|
182
|
+
**kwargs: Arguments.kwargs,
|
183
|
+
) -> Result:
|
184
|
+
async with self:
|
185
|
+
return await function(*args, **kwargs)
|
186
|
+
|
187
|
+
return mimic_function(function, within=async_context)
|
188
|
+
|
189
|
+
else:
|
190
|
+
|
191
|
+
def sync_context(
|
192
|
+
*args: Arguments.args,
|
193
|
+
**kwargs: Arguments.kwargs,
|
194
|
+
) -> Result:
|
195
|
+
with self:
|
196
|
+
return function(*args, **kwargs) # pyright: ignore[reportReturnType]
|
197
|
+
|
198
|
+
return mimic_function(function, within=sync_context) # pyright: ignore[reportReturnType]
|
199
|
+
|
123
200
|
|
124
201
|
@final
|
125
202
|
class ctx:
|
126
203
|
@staticmethod
|
127
204
|
def scope(
|
128
|
-
|
205
|
+
label: str,
|
129
206
|
/,
|
130
207
|
*state: State,
|
131
208
|
disposables: Disposables | Iterable[Disposable] | None = None,
|
132
209
|
logger: Logger | None = None,
|
133
|
-
|
134
|
-
completion: Callable[[ScopeMetrics], Coroutine[None, None, None]]
|
135
|
-
| Callable[[ScopeMetrics], None]
|
136
|
-
| None = None,
|
210
|
+
metrics: MetricsHandler | None = None,
|
137
211
|
) -> ScopeContext:
|
138
212
|
"""
|
139
|
-
|
140
|
-
it becomes nested with current context as its
|
213
|
+
Prepare scope context with given parameters. When called within an existing context\
|
214
|
+
it becomes nested with current context as its parent.
|
141
215
|
|
142
216
|
Parameters
|
143
217
|
----------
|
144
|
-
|
218
|
+
label: str
|
145
219
|
name of the scope context
|
146
220
|
|
147
221
|
*state: State | Disposable
|
148
222
|
state propagated within the scope context, will be merged with current state by\
|
149
223
|
replacing current with provided on conflict.
|
150
224
|
|
151
|
-
disposables: Disposables |
|
225
|
+
disposables: Disposables | Iterable[Disposable] | None
|
152
226
|
disposables consumed within the context when entered. Produced state will automatically\
|
153
227
|
be added to the scope state. Using asynchronous context is required if any disposables\
|
154
228
|
were provided.
|
@@ -157,21 +231,17 @@ class ctx:
|
|
157
231
|
logger used within the scope context, when not provided current logger will be used\
|
158
232
|
if any, otherwise the logger with the scope name will be requested.
|
159
233
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
completion: Callable[[ScopeMetrics], Coroutine[None, None, None]] | Callable[[ScopeMetrics], None] | None = None
|
166
|
-
completion callback called on exit from the scope granting access to finished\
|
167
|
-
scope metrics. Completion is called outside of the context when its metrics is\
|
168
|
-
already finished. Make sure to avoid any long operations within the completion.
|
234
|
+
metrics_store: MetricsStore | None = None
|
235
|
+
metrics storage solution responsible for recording and storing metrics.\
|
236
|
+
Metrics recroding will be ignored if storage is not provided.
|
237
|
+
Assigning metrics_store within existing context will result in an error.
|
169
238
|
|
170
239
|
Returns
|
171
240
|
-------
|
172
241
|
ScopeContext
|
173
|
-
context object intended to enter context manager with
|
174
|
-
|
242
|
+
context object intended to enter context manager with.\
|
243
|
+
context manager will provide trace_id of current context.
|
244
|
+
"""
|
175
245
|
|
176
246
|
resolved_disposables: Disposables | None
|
177
247
|
match disposables:
|
@@ -185,12 +255,11 @@ class ctx:
|
|
185
255
|
resolved_disposables = Disposables(*iterable)
|
186
256
|
|
187
257
|
return ScopeContext(
|
188
|
-
|
189
|
-
name=name,
|
258
|
+
label=label,
|
190
259
|
logger=logger,
|
191
260
|
state=state,
|
192
261
|
disposables=resolved_disposables,
|
193
|
-
|
262
|
+
metrics=metrics,
|
194
263
|
)
|
195
264
|
|
196
265
|
@staticmethod
|
@@ -339,32 +408,25 @@ class ctx:
|
|
339
408
|
)
|
340
409
|
|
341
410
|
@staticmethod
|
342
|
-
def record
|
343
|
-
metric:
|
411
|
+
def record(
|
412
|
+
metric: State,
|
344
413
|
/,
|
345
|
-
merge: Callable[[Metric, Metric], Metric] = lambda lhs, rhs: rhs,
|
346
414
|
) -> None:
|
347
415
|
"""
|
348
416
|
Record metric within current scope context.
|
349
417
|
|
350
418
|
Parameters
|
351
419
|
----------
|
352
|
-
metric:
|
353
|
-
value of metric to be recorded
|
354
|
-
|
355
|
-
merge: Callable[[MetricType, MetricType], MetricType] = lambda lhs, rhs: rhs
|
356
|
-
merge method used on to resolve conflicts when a metric of the same type\
|
357
|
-
was already recorded. When not provided value will be override current if any.
|
420
|
+
metric: State
|
421
|
+
value of metric to be recorded. When a metric implements __add__ it will be added to\
|
422
|
+
current value if any, otherwise subsequent calls may replace existing value.
|
358
423
|
|
359
424
|
Returns
|
360
425
|
-------
|
361
426
|
None
|
362
427
|
"""
|
363
428
|
|
364
|
-
MetricsContext.record(
|
365
|
-
metric,
|
366
|
-
merge=merge,
|
367
|
-
)
|
429
|
+
MetricsContext.record(metric)
|
368
430
|
|
369
431
|
@staticmethod
|
370
432
|
def log_error(
|
@@ -393,7 +455,7 @@ class ctx:
|
|
393
455
|
None
|
394
456
|
"""
|
395
457
|
|
396
|
-
|
458
|
+
LoggerContext.log_error(
|
397
459
|
message,
|
398
460
|
*args,
|
399
461
|
exception=exception,
|
@@ -426,7 +488,7 @@ class ctx:
|
|
426
488
|
None
|
427
489
|
"""
|
428
490
|
|
429
|
-
|
491
|
+
LoggerContext.log_warning(
|
430
492
|
message,
|
431
493
|
*args,
|
432
494
|
exception=exception,
|
@@ -455,7 +517,7 @@ class ctx:
|
|
455
517
|
None
|
456
518
|
"""
|
457
519
|
|
458
|
-
|
520
|
+
LoggerContext.log_info(
|
459
521
|
message,
|
460
522
|
*args,
|
461
523
|
)
|
@@ -487,7 +549,7 @@ class ctx:
|
|
487
549
|
None
|
488
550
|
"""
|
489
551
|
|
490
|
-
|
552
|
+
LoggerContext.log_debug(
|
491
553
|
message,
|
492
554
|
*args,
|
493
555
|
exception=exception,
|
@@ -0,0 +1,75 @@
|
|
1
|
+
from contextvars import ContextVar, Token
|
2
|
+
from types import TracebackType
|
3
|
+
from typing import Self, final
|
4
|
+
from uuid import uuid4
|
5
|
+
|
6
|
+
__all__ = [
|
7
|
+
"ScopeIdentifier",
|
8
|
+
]
|
9
|
+
|
10
|
+
|
11
|
+
@final
|
12
|
+
class ScopeIdentifier:
|
13
|
+
_context = ContextVar[Self]("ScopeIdentifier")
|
14
|
+
|
15
|
+
@classmethod
|
16
|
+
def scope(
|
17
|
+
cls,
|
18
|
+
label: str,
|
19
|
+
/,
|
20
|
+
) -> Self:
|
21
|
+
current: Self
|
22
|
+
try: # check for current scope
|
23
|
+
current = cls._context.get()
|
24
|
+
|
25
|
+
except LookupError:
|
26
|
+
# create root scope when missing
|
27
|
+
trace_id: str = uuid4().hex
|
28
|
+
return cls(
|
29
|
+
label=label,
|
30
|
+
scope_id=uuid4().hex,
|
31
|
+
parent_id=trace_id, # trace_id is parent_id for root
|
32
|
+
trace_id=trace_id,
|
33
|
+
)
|
34
|
+
|
35
|
+
# create nested scope otherwise
|
36
|
+
return cls(
|
37
|
+
label=label,
|
38
|
+
scope_id=uuid4().hex,
|
39
|
+
parent_id=current.scope_id,
|
40
|
+
trace_id=current.trace_id,
|
41
|
+
)
|
42
|
+
|
43
|
+
def __init__(
|
44
|
+
self,
|
45
|
+
trace_id: str,
|
46
|
+
parent_id: str,
|
47
|
+
scope_id: str,
|
48
|
+
label: str,
|
49
|
+
) -> None:
|
50
|
+
self.trace_id: str = trace_id
|
51
|
+
self.parent_id: str = parent_id
|
52
|
+
self.scope_id: str = scope_id
|
53
|
+
self.label: str = label
|
54
|
+
self.unique_name: str = f"[{trace_id}] [{label}] [{scope_id}]"
|
55
|
+
|
56
|
+
@property
|
57
|
+
def is_root(self) -> bool:
|
58
|
+
return self.trace_id == self.parent_id
|
59
|
+
|
60
|
+
def __str__(self) -> str:
|
61
|
+
return self.unique_name
|
62
|
+
|
63
|
+
def __enter__(self) -> None:
|
64
|
+
assert not hasattr(self, "_token"), "Context reentrance is not allowed" # nosec: B101
|
65
|
+
self._token: Token[ScopeIdentifier] = ScopeIdentifier._context.set(self)
|
66
|
+
|
67
|
+
def __exit__(
|
68
|
+
self,
|
69
|
+
exc_type: type[BaseException] | None,
|
70
|
+
exc_val: BaseException | None,
|
71
|
+
exc_tb: TracebackType | None,
|
72
|
+
) -> None:
|
73
|
+
assert hasattr(self, "_token"), "Unbalanced context enter/exit" # nosec: B101
|
74
|
+
ScopeIdentifier._context.reset(self._token)
|
75
|
+
del self._token
|
@@ -0,0 +1,176 @@
|
|
1
|
+
from contextvars import ContextVar, Token
|
2
|
+
from logging import DEBUG, ERROR, INFO, WARNING, Logger, getLogger
|
3
|
+
from time import monotonic
|
4
|
+
from types import TracebackType
|
5
|
+
from typing import Any, Self, final
|
6
|
+
|
7
|
+
from haiway.context.identifier import ScopeIdentifier
|
8
|
+
|
9
|
+
__all__ = [
|
10
|
+
"LoggerContext",
|
11
|
+
]
|
12
|
+
|
13
|
+
|
14
|
+
@final
|
15
|
+
class LoggerContext:
|
16
|
+
_context = ContextVar[Self]("LoggerContext")
|
17
|
+
|
18
|
+
@classmethod
|
19
|
+
def scope(
|
20
|
+
cls,
|
21
|
+
scope: ScopeIdentifier,
|
22
|
+
/,
|
23
|
+
*,
|
24
|
+
logger: Logger | None,
|
25
|
+
) -> Self:
|
26
|
+
current: Self
|
27
|
+
try: # check for current scope
|
28
|
+
current = cls._context.get()
|
29
|
+
|
30
|
+
except LookupError:
|
31
|
+
# create root scope when missing
|
32
|
+
return cls(
|
33
|
+
scope=scope,
|
34
|
+
logger=logger,
|
35
|
+
)
|
36
|
+
|
37
|
+
# create nested scope otherwise
|
38
|
+
return cls(
|
39
|
+
scope=scope,
|
40
|
+
logger=logger or current._logger,
|
41
|
+
)
|
42
|
+
|
43
|
+
@classmethod
|
44
|
+
def log_error(
|
45
|
+
cls,
|
46
|
+
message: str,
|
47
|
+
/,
|
48
|
+
*args: Any,
|
49
|
+
exception: BaseException | None = None,
|
50
|
+
) -> None:
|
51
|
+
try:
|
52
|
+
cls._context.get().log(
|
53
|
+
ERROR,
|
54
|
+
message,
|
55
|
+
*args,
|
56
|
+
exception=exception,
|
57
|
+
)
|
58
|
+
|
59
|
+
except LookupError:
|
60
|
+
getLogger().log(
|
61
|
+
ERROR,
|
62
|
+
message,
|
63
|
+
*args,
|
64
|
+
exc_info=exception,
|
65
|
+
)
|
66
|
+
|
67
|
+
@classmethod
|
68
|
+
def log_warning(
|
69
|
+
cls,
|
70
|
+
message: str,
|
71
|
+
/,
|
72
|
+
*args: Any,
|
73
|
+
exception: Exception | None = None,
|
74
|
+
) -> None:
|
75
|
+
try:
|
76
|
+
cls._context.get().log(
|
77
|
+
WARNING,
|
78
|
+
message,
|
79
|
+
*args,
|
80
|
+
exception=exception,
|
81
|
+
)
|
82
|
+
|
83
|
+
except LookupError:
|
84
|
+
getLogger().log(
|
85
|
+
WARNING,
|
86
|
+
message,
|
87
|
+
*args,
|
88
|
+
exc_info=exception,
|
89
|
+
)
|
90
|
+
|
91
|
+
@classmethod
|
92
|
+
def log_info(
|
93
|
+
cls,
|
94
|
+
message: str,
|
95
|
+
/,
|
96
|
+
*args: Any,
|
97
|
+
) -> None:
|
98
|
+
try:
|
99
|
+
cls._context.get().log(
|
100
|
+
INFO,
|
101
|
+
message,
|
102
|
+
*args,
|
103
|
+
)
|
104
|
+
|
105
|
+
except LookupError:
|
106
|
+
getLogger().log(
|
107
|
+
INFO,
|
108
|
+
message,
|
109
|
+
*args,
|
110
|
+
)
|
111
|
+
|
112
|
+
@classmethod
|
113
|
+
def log_debug(
|
114
|
+
cls,
|
115
|
+
message: str,
|
116
|
+
/,
|
117
|
+
*args: Any,
|
118
|
+
exception: Exception | None = None,
|
119
|
+
) -> None:
|
120
|
+
try:
|
121
|
+
cls._context.get().log(
|
122
|
+
DEBUG,
|
123
|
+
message,
|
124
|
+
*args,
|
125
|
+
exception=exception,
|
126
|
+
)
|
127
|
+
|
128
|
+
except LookupError:
|
129
|
+
getLogger().log(
|
130
|
+
DEBUG,
|
131
|
+
message,
|
132
|
+
*args,
|
133
|
+
exc_info=exception,
|
134
|
+
)
|
135
|
+
|
136
|
+
def __init__(
|
137
|
+
self,
|
138
|
+
scope: ScopeIdentifier,
|
139
|
+
logger: Logger | None,
|
140
|
+
) -> None:
|
141
|
+
self._prefix: str = scope.unique_name
|
142
|
+
self._logger: Logger = logger or getLogger(name=scope.label)
|
143
|
+
|
144
|
+
def log(
|
145
|
+
self,
|
146
|
+
level: int,
|
147
|
+
message: str,
|
148
|
+
/,
|
149
|
+
*args: Any,
|
150
|
+
exception: BaseException | None = None,
|
151
|
+
) -> None:
|
152
|
+
self._logger.log(
|
153
|
+
level,
|
154
|
+
f"{self._prefix} {message}",
|
155
|
+
*args,
|
156
|
+
exc_info=exception,
|
157
|
+
)
|
158
|
+
|
159
|
+
def __enter__(self) -> None:
|
160
|
+
assert not hasattr(self, "_token"), "Context reentrance is not allowed" # nosec: B101
|
161
|
+
assert not hasattr(self, "_entered"), "Context reentrance is not allowed" # nosec: B101
|
162
|
+
self._entered: float = monotonic()
|
163
|
+
self._token: Token[LoggerContext] = LoggerContext._context.set(self)
|
164
|
+
self.log(DEBUG, "Entering context...")
|
165
|
+
|
166
|
+
def __exit__(
|
167
|
+
self,
|
168
|
+
exc_type: type[BaseException] | None,
|
169
|
+
exc_val: BaseException | None,
|
170
|
+
exc_tb: TracebackType | None,
|
171
|
+
) -> None:
|
172
|
+
assert hasattr(self, "_token"), "Unbalanced context enter/exit" # nosec: B101
|
173
|
+
LoggerContext._context.reset(self._token)
|
174
|
+
del self._token
|
175
|
+
self.log(DEBUG, f"...exiting context after {monotonic() - self._entered:.2f}s")
|
176
|
+
del self._entered
|