omlish 0.0.0.dev429__py3-none-any.whl → 0.0.0.dev431__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.
- omlish/__about__.py +2 -2
- omlish/asyncs/ioproxy/proxy.py +1 -1
- omlish/concurrent/__init__.py +17 -11
- omlish/funcs/match.py +1 -1
- omlish/lang/asyncs.py +2 -6
- omlish/lang/imports/proxyinit.py +4 -3
- omlish/lite/attrops.py +14 -9
- omlish/logs/all.py +29 -2
- omlish/logs/base.py +1 -32
- omlish/logs/contexts.py +64 -180
- omlish/logs/infos.py +347 -90
- omlish/logs/levels.py +46 -13
- omlish/logs/modules.py +1 -1
- omlish/logs/protocols.py +1 -0
- omlish/logs/std/{adapters.py → loggers.py} +12 -9
- omlish/logs/std/records.py +528 -210
- omlish/logs/typed/types.py +1 -0
- omlish/logs/typed/values.py +2 -2
- omlish/math/fixed.py +2 -2
- omlish/secrets/secrets.py +3 -0
- {omlish-0.0.0.dev429.dist-info → omlish-0.0.0.dev431.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev429.dist-info → omlish-0.0.0.dev431.dist-info}/RECORD +26 -28
- omlish/logs/callers.py +0 -75
- omlish/logs/times.py +0 -89
- {omlish-0.0.0.dev429.dist-info → omlish-0.0.0.dev431.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev429.dist-info → omlish-0.0.0.dev431.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev429.dist-info → omlish-0.0.0.dev431.dist-info}/licenses/LICENSE +0 -0
- {omlish-0.0.0.dev429.dist-info → omlish-0.0.0.dev431.dist-info}/top_level.txt +0 -0
omlish/logs/infos.py
CHANGED
@@ -1,120 +1,377 @@
|
|
1
|
-
# ruff: noqa: UP045
|
1
|
+
# ruff: noqa: UP006 UP007 UP045
|
2
2
|
# @omlish-lite
|
3
|
+
"""
|
4
|
+
TODO:
|
5
|
+
- remove redundant info fields only present for std adaptation (Level.name, ...)
|
6
|
+
"""
|
7
|
+
import collections.abc
|
8
|
+
import io
|
9
|
+
import logging
|
3
10
|
import os.path
|
4
11
|
import sys
|
5
12
|
import threading
|
13
|
+
import time
|
14
|
+
import traceback
|
15
|
+
import types
|
6
16
|
import typing as ta
|
7
17
|
|
18
|
+
from .levels import NamedLogLevel
|
19
|
+
from .warnings import LoggingSetupWarning
|
8
20
|
|
9
|
-
##
|
10
|
-
|
11
|
-
|
12
|
-
def logging_context_info(cls):
|
13
|
-
return cls
|
14
21
|
|
22
|
+
LoggingMsgFn = ta.Callable[[], ta.Union[str, tuple]] # ta.TypeAlias
|
15
23
|
|
16
|
-
|
24
|
+
LoggingExcInfoTuple = ta.Tuple[ta.Type[BaseException], BaseException, ta.Optional[types.TracebackType]] # ta.TypeAlias
|
25
|
+
LoggingExcInfo = ta.Union[BaseException, LoggingExcInfoTuple] # ta.TypeAlias
|
26
|
+
LoggingExcInfoArg = ta.Union[LoggingExcInfo, bool, None] # ta.TypeAlias
|
17
27
|
|
18
|
-
|
19
|
-
@logging_context_info
|
20
|
-
@ta.final
|
21
|
-
class LoggingSourceFileInfo(ta.NamedTuple):
|
22
|
-
file_name: str
|
23
|
-
module: str
|
24
|
-
|
25
|
-
@classmethod
|
26
|
-
def build(cls, file_path: ta.Optional[str]) -> ta.Optional['LoggingSourceFileInfo']:
|
27
|
-
if file_path is None:
|
28
|
-
return None
|
29
|
-
|
30
|
-
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L331-L336 # noqa
|
31
|
-
try:
|
32
|
-
file_name = os.path.basename(file_path)
|
33
|
-
module = os.path.splitext(file_name)[0]
|
34
|
-
except (TypeError, ValueError, AttributeError):
|
35
|
-
return None
|
36
|
-
|
37
|
-
return cls(
|
38
|
-
file_name,
|
39
|
-
module,
|
40
|
-
)
|
28
|
+
LoggingContextInfo = ta.Any # ta.TypeAlias
|
41
29
|
|
42
30
|
|
43
31
|
##
|
44
32
|
|
45
33
|
|
46
|
-
|
47
|
-
|
48
|
-
class LoggingThreadInfo(ta.NamedTuple):
|
49
|
-
ident: int
|
50
|
-
native_id: ta.Optional[int]
|
51
|
-
name: str
|
52
|
-
|
53
|
-
@classmethod
|
54
|
-
def build(cls) -> 'LoggingThreadInfo':
|
55
|
-
return cls(
|
56
|
-
threading.get_ident(),
|
57
|
-
threading.get_native_id() if hasattr(threading, 'get_native_id') else None,
|
58
|
-
threading.current_thread().name,
|
59
|
-
)
|
60
|
-
|
61
|
-
|
62
|
-
##
|
63
|
-
|
64
|
-
|
65
|
-
@logging_context_info
|
66
|
-
@ta.final
|
67
|
-
class LoggingProcessInfo(ta.NamedTuple):
|
68
|
-
pid: int
|
69
|
-
|
70
|
-
@classmethod
|
71
|
-
def build(cls) -> 'LoggingProcessInfo':
|
72
|
-
return cls(
|
73
|
-
os.getpid(),
|
74
|
-
)
|
75
|
-
|
76
|
-
|
77
|
-
##
|
34
|
+
def logging_context_info(cls):
|
35
|
+
return cls
|
78
36
|
|
79
37
|
|
80
|
-
@logging_context_info
|
81
38
|
@ta.final
|
82
|
-
class
|
83
|
-
|
39
|
+
class LoggingContextInfos:
|
40
|
+
def __new__(cls, *args, **kwargs): # noqa
|
41
|
+
raise TypeError
|
42
|
+
|
43
|
+
#
|
44
|
+
|
45
|
+
@logging_context_info
|
46
|
+
@ta.final
|
47
|
+
class Name(ta.NamedTuple):
|
48
|
+
name: str
|
49
|
+
|
50
|
+
@logging_context_info
|
51
|
+
@ta.final
|
52
|
+
class Level(ta.NamedTuple):
|
53
|
+
level: NamedLogLevel
|
54
|
+
name: str
|
55
|
+
|
56
|
+
@classmethod
|
57
|
+
def build(cls, level: int) -> 'LoggingContextInfos.Level':
|
58
|
+
nl: NamedLogLevel = level if level.__class__ is NamedLogLevel else NamedLogLevel(level) # type: ignore[assignment] # noqa
|
59
|
+
return cls(
|
60
|
+
level=nl,
|
61
|
+
name=logging.getLevelName(nl),
|
62
|
+
)
|
63
|
+
|
64
|
+
@logging_context_info
|
65
|
+
@ta.final
|
66
|
+
class Msg(ta.NamedTuple):
|
67
|
+
msg: str
|
68
|
+
args: ta.Union[tuple, ta.Mapping[ta.Any, ta.Any], None]
|
69
|
+
|
70
|
+
@classmethod
|
71
|
+
def build(
|
72
|
+
cls,
|
73
|
+
msg: ta.Union[str, tuple, LoggingMsgFn],
|
74
|
+
*args: ta.Any,
|
75
|
+
) -> 'LoggingContextInfos.Msg':
|
76
|
+
s: str
|
77
|
+
a: ta.Any
|
78
|
+
|
79
|
+
if callable(msg):
|
80
|
+
if args:
|
81
|
+
raise TypeError(f'Must not provide both a message function and args: {msg=} {args=}')
|
82
|
+
x = msg()
|
83
|
+
if isinstance(x, str):
|
84
|
+
s, a = x, ()
|
85
|
+
elif isinstance(x, tuple):
|
86
|
+
if x:
|
87
|
+
s, a = x[0], x[1:]
|
88
|
+
else:
|
89
|
+
s, a = '', ()
|
90
|
+
else:
|
91
|
+
raise TypeError(x)
|
92
|
+
|
93
|
+
elif isinstance(msg, tuple):
|
94
|
+
if args:
|
95
|
+
raise TypeError(f'Must not provide both a tuple message and args: {msg=} {args=}')
|
96
|
+
if msg:
|
97
|
+
s, a = msg[0], msg[1:]
|
98
|
+
else:
|
99
|
+
s, a = '', ()
|
100
|
+
|
101
|
+
elif isinstance(msg, str):
|
102
|
+
s, a = msg, args
|
103
|
+
|
104
|
+
else:
|
105
|
+
raise TypeError(msg)
|
106
|
+
|
107
|
+
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L307 # noqa
|
108
|
+
if a and len(a) == 1 and isinstance(a[0], collections.abc.Mapping) and a[0]:
|
109
|
+
a = a[0]
|
110
|
+
|
111
|
+
return cls(
|
112
|
+
msg=s,
|
113
|
+
args=a,
|
114
|
+
)
|
115
|
+
|
116
|
+
@logging_context_info
|
117
|
+
@ta.final
|
118
|
+
class Extra(ta.NamedTuple):
|
119
|
+
extra: ta.Mapping[ta.Any, ta.Any]
|
120
|
+
|
121
|
+
@logging_context_info
|
122
|
+
@ta.final
|
123
|
+
class Time(ta.NamedTuple):
|
124
|
+
ns: int
|
125
|
+
secs: float
|
126
|
+
msecs: float
|
127
|
+
relative_secs: float
|
128
|
+
|
129
|
+
@classmethod
|
130
|
+
def get_std_start_ns(cls) -> int:
|
131
|
+
x: ta.Any = logging._startTime # type: ignore[attr-defined] # noqa
|
132
|
+
|
133
|
+
# Before 3.13.0b1 this will be `time.time()`, a float of seconds. After that, it will be `time.time_ns()`,
|
134
|
+
# an int.
|
135
|
+
#
|
136
|
+
# See:
|
137
|
+
# - https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
|
138
|
+
#
|
139
|
+
if isinstance(x, float):
|
140
|
+
return int(x * 1e9)
|
141
|
+
else:
|
142
|
+
return x
|
143
|
+
|
144
|
+
@classmethod
|
145
|
+
def build(
|
146
|
+
cls,
|
147
|
+
ns: int,
|
148
|
+
*,
|
149
|
+
start_ns: ta.Optional[int] = None,
|
150
|
+
) -> 'LoggingContextInfos.Time':
|
151
|
+
# https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
|
152
|
+
secs = ns / 1e9 # ns to float seconds
|
153
|
+
|
154
|
+
# Get the number of whole milliseconds (0-999) in the fractional part of seconds.
|
155
|
+
# Eg: 1_677_903_920_999_998_503 ns --> 999_998_503 ns--> 999 ms
|
156
|
+
# Convert to float by adding 0.0 for historical reasons. See gh-89047
|
157
|
+
msecs = (ns % 1_000_000_000) // 1_000_000 + 0.0
|
158
|
+
|
159
|
+
# https://github.com/python/cpython/commit/1500a23f33f5a6d052ff1ef6383d9839928b8ff1
|
160
|
+
if msecs == 999.0 and int(secs) != ns // 1_000_000_000:
|
161
|
+
# ns -> sec conversion can round up, e.g:
|
162
|
+
# 1_677_903_920_999_999_900 ns --> 1_677_903_921.0 sec
|
163
|
+
msecs = 0.0
|
164
|
+
|
165
|
+
if start_ns is None:
|
166
|
+
start_ns = cls.get_std_start_ns()
|
167
|
+
relative_secs = (ns - start_ns) / 1e6
|
168
|
+
|
169
|
+
return cls(
|
170
|
+
ns=ns,
|
171
|
+
secs=secs,
|
172
|
+
msecs=msecs,
|
173
|
+
relative_secs=relative_secs,
|
174
|
+
)
|
175
|
+
|
176
|
+
@logging_context_info
|
177
|
+
@ta.final
|
178
|
+
class Exc(ta.NamedTuple):
|
179
|
+
info: LoggingExcInfo
|
180
|
+
info_tuple: LoggingExcInfoTuple
|
181
|
+
|
182
|
+
@classmethod
|
183
|
+
def build(
|
184
|
+
cls,
|
185
|
+
arg: LoggingExcInfoArg = False,
|
186
|
+
) -> ta.Optional['LoggingContextInfos.Exc']:
|
187
|
+
if arg is True:
|
188
|
+
sys_exc_info = sys.exc_info()
|
189
|
+
if sys_exc_info[0] is not None:
|
190
|
+
arg = sys_exc_info
|
191
|
+
else:
|
192
|
+
arg = None
|
193
|
+
elif arg is False:
|
194
|
+
arg = None
|
195
|
+
if arg is None:
|
196
|
+
return None
|
197
|
+
|
198
|
+
info: LoggingExcInfo = arg
|
199
|
+
if isinstance(info, BaseException):
|
200
|
+
info_tuple: LoggingExcInfoTuple = (type(info), info, info.__traceback__) # noqa
|
201
|
+
else:
|
202
|
+
info_tuple = info
|
203
|
+
|
204
|
+
return cls(
|
205
|
+
info=info,
|
206
|
+
info_tuple=info_tuple,
|
207
|
+
)
|
208
|
+
|
209
|
+
@logging_context_info
|
210
|
+
@ta.final
|
211
|
+
class Caller(ta.NamedTuple):
|
212
|
+
file_path: str
|
213
|
+
line_no: int
|
214
|
+
func_name: str
|
215
|
+
stack_info: ta.Optional[str]
|
216
|
+
|
217
|
+
@classmethod
|
218
|
+
def is_internal_frame(cls, frame: types.FrameType) -> bool:
|
219
|
+
file_path = os.path.normcase(frame.f_code.co_filename)
|
220
|
+
|
221
|
+
# Yes, really.
|
222
|
+
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L204 # noqa
|
223
|
+
# https://github.com/python/cpython/commit/5ca6d7469be53960843df39bb900e9c3359f127f
|
224
|
+
if 'importlib' in file_path and '_bootstrap' in file_path:
|
225
|
+
return True
|
226
|
+
|
227
|
+
return False
|
228
|
+
|
229
|
+
@classmethod
|
230
|
+
def find_frame(cls, stack_offset: int = 0) -> ta.Optional[types.FrameType]:
|
231
|
+
f: ta.Optional[types.FrameType] = sys._getframe(2 + stack_offset) # noqa
|
232
|
+
|
233
|
+
while f is not None:
|
234
|
+
# NOTE: We don't check __file__ like stdlib since we may be running amalgamated - we rely on careful,
|
235
|
+
# manual stack_offset management.
|
236
|
+
if hasattr(f, 'f_code'):
|
237
|
+
return f
|
238
|
+
|
239
|
+
f = f.f_back
|
84
240
|
|
85
|
-
@classmethod
|
86
|
-
def build(cls) -> ta.Optional['LoggingMultiprocessingInfo']:
|
87
|
-
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L355-L364 # noqa
|
88
|
-
if (mp := sys.modules.get('multiprocessing')) is None:
|
89
241
|
return None
|
90
242
|
|
91
|
-
|
92
|
-
|
93
|
-
|
243
|
+
@classmethod
|
244
|
+
def build(
|
245
|
+
cls,
|
246
|
+
stack_offset: int = 0,
|
247
|
+
*,
|
248
|
+
stack_info: bool = False,
|
249
|
+
) -> ta.Optional['LoggingContextInfos.Caller']:
|
250
|
+
if (f := cls.find_frame(stack_offset + 1)) is None:
|
251
|
+
return None
|
252
|
+
|
253
|
+
# https://github.com/python/cpython/blob/08e9794517063c8cd92c48714071b1d3c60b71bd/Lib/logging/__init__.py#L1616-L1623 # noqa
|
254
|
+
sinfo = None
|
255
|
+
if stack_info:
|
256
|
+
sio = io.StringIO()
|
257
|
+
traceback.print_stack(f, file=sio)
|
258
|
+
sinfo = sio.getvalue()
|
259
|
+
sio.close()
|
260
|
+
if sinfo[-1] == '\n':
|
261
|
+
sinfo = sinfo[:-1]
|
262
|
+
|
263
|
+
return cls(
|
264
|
+
file_path=f.f_code.co_filename,
|
265
|
+
line_no=f.f_lineno or 0,
|
266
|
+
func_name=f.f_code.co_name,
|
267
|
+
stack_info=sinfo,
|
268
|
+
)
|
269
|
+
|
270
|
+
@logging_context_info
|
271
|
+
@ta.final
|
272
|
+
class SourceFile(ta.NamedTuple):
|
273
|
+
file_name: str
|
274
|
+
module: str
|
275
|
+
|
276
|
+
@classmethod
|
277
|
+
def build(cls, caller_file_path: ta.Optional[str]) -> ta.Optional['LoggingContextInfos.SourceFile']:
|
278
|
+
if caller_file_path is None:
|
279
|
+
return None
|
280
|
+
|
281
|
+
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L331-L336 # noqa
|
282
|
+
try:
|
283
|
+
file_name = os.path.basename(caller_file_path)
|
284
|
+
module = os.path.splitext(file_name)[0]
|
285
|
+
except (TypeError, ValueError, AttributeError):
|
286
|
+
return None
|
287
|
+
|
288
|
+
return cls(
|
289
|
+
file_name=file_name,
|
290
|
+
module=module,
|
291
|
+
)
|
292
|
+
|
293
|
+
@logging_context_info
|
294
|
+
@ta.final
|
295
|
+
class Thread(ta.NamedTuple):
|
296
|
+
ident: int
|
297
|
+
native_id: ta.Optional[int]
|
298
|
+
name: str
|
299
|
+
|
300
|
+
@classmethod
|
301
|
+
def build(cls) -> 'LoggingContextInfos.Thread':
|
302
|
+
return cls(
|
303
|
+
ident=threading.get_ident(),
|
304
|
+
native_id=threading.get_native_id() if hasattr(threading, 'get_native_id') else None,
|
305
|
+
name=threading.current_thread().name,
|
306
|
+
)
|
307
|
+
|
308
|
+
@logging_context_info
|
309
|
+
@ta.final
|
310
|
+
class Process(ta.NamedTuple):
|
311
|
+
pid: int
|
312
|
+
|
313
|
+
@classmethod
|
314
|
+
def build(cls) -> 'LoggingContextInfos.Process':
|
315
|
+
return cls(
|
316
|
+
pid=os.getpid(),
|
317
|
+
)
|
318
|
+
|
319
|
+
@logging_context_info
|
320
|
+
@ta.final
|
321
|
+
class Multiprocessing(ta.NamedTuple):
|
322
|
+
process_name: str
|
323
|
+
|
324
|
+
@classmethod
|
325
|
+
def build(cls) -> ta.Optional['LoggingContextInfos.Multiprocessing']:
|
326
|
+
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L355-L364 # noqa
|
327
|
+
if (mp := sys.modules.get('multiprocessing')) is None:
|
328
|
+
return None
|
329
|
+
|
330
|
+
return cls(
|
331
|
+
process_name=mp.current_process().name,
|
332
|
+
)
|
333
|
+
|
334
|
+
@logging_context_info
|
335
|
+
@ta.final
|
336
|
+
class AsyncioTask(ta.NamedTuple):
|
337
|
+
name: str
|
338
|
+
|
339
|
+
@classmethod
|
340
|
+
def build(cls) -> ta.Optional['LoggingContextInfos.AsyncioTask']:
|
341
|
+
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L372-L377 # noqa
|
342
|
+
if (asyncio := sys.modules.get('asyncio')) is None:
|
343
|
+
return None
|
344
|
+
|
345
|
+
try:
|
346
|
+
task = asyncio.current_task()
|
347
|
+
except Exception: # noqa
|
348
|
+
return None
|
349
|
+
|
350
|
+
if task is None:
|
351
|
+
return None
|
352
|
+
|
353
|
+
return cls(
|
354
|
+
name=task.get_name(), # Always non-None
|
355
|
+
)
|
94
356
|
|
95
357
|
|
96
358
|
##
|
97
359
|
|
98
360
|
|
99
|
-
|
100
|
-
|
101
|
-
class LoggingAsyncioTaskInfo(ta.NamedTuple):
|
102
|
-
name: str
|
361
|
+
class UnexpectedLoggingStartTimeWarning(LoggingSetupWarning):
|
362
|
+
pass
|
103
363
|
|
104
|
-
@classmethod
|
105
|
-
def build(cls) -> ta.Optional['LoggingAsyncioTaskInfo']:
|
106
|
-
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L372-L377 # noqa
|
107
|
-
if (asyncio := sys.modules.get('asyncio')) is None:
|
108
|
-
return None
|
109
364
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
return None
|
365
|
+
def _check_logging_start_time() -> None:
|
366
|
+
if (x := LoggingContextInfos.Time.get_std_start_ns()) < (t := time.time()):
|
367
|
+
import warnings # noqa
|
114
368
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
369
|
+
warnings.warn(
|
370
|
+
f'Unexpected logging start time detected: '
|
371
|
+
f'get_std_start_ns={x}, '
|
372
|
+
f'time.time()={t}',
|
373
|
+
UnexpectedLoggingStartTimeWarning,
|
120
374
|
)
|
375
|
+
|
376
|
+
|
377
|
+
_check_logging_start_time()
|
omlish/logs/levels.py
CHANGED
@@ -22,36 +22,66 @@ class NamedLogLevel(int):
|
|
22
22
|
|
23
23
|
#
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
25
|
+
_CACHE: ta.ClassVar[ta.MutableMapping[int, 'NamedLogLevel']] = {}
|
26
|
+
|
27
|
+
@ta.overload
|
28
|
+
def __new__(cls, name: str, offset: int = 0, /) -> 'NamedLogLevel':
|
29
|
+
...
|
28
30
|
|
29
|
-
|
31
|
+
@ta.overload
|
32
|
+
def __new__(cls, i: int, /) -> 'NamedLogLevel':
|
33
|
+
...
|
34
|
+
|
35
|
+
def __new__(cls, x, offset=0, /):
|
36
|
+
if isinstance(x, str):
|
37
|
+
return cls(cls._INTS_BY_NAME[x.upper()] + offset)
|
38
|
+
elif not offset and (c := cls._CACHE.get(x)) is not None:
|
39
|
+
return c
|
40
|
+
else:
|
41
|
+
return super().__new__(cls, x + offset)
|
42
|
+
|
43
|
+
#
|
44
|
+
|
45
|
+
_name_and_offset: ta.Tuple[str, int]
|
30
46
|
|
31
47
|
@property
|
32
|
-
def
|
48
|
+
def name_and_offset(self) -> ta.Tuple[str, int]:
|
33
49
|
try:
|
34
|
-
return self.
|
50
|
+
return self._name_and_offset
|
35
51
|
except AttributeError:
|
36
52
|
pass
|
37
53
|
|
38
|
-
if (n := self.
|
54
|
+
if (n := self._NAMES_BY_INT.get(self)) is not None:
|
55
|
+
t = (n, 0)
|
56
|
+
else:
|
39
57
|
for n, i in self._NAME_INT_PAIRS: # noqa
|
40
58
|
if self >= i:
|
59
|
+
t = (n, (self - i))
|
41
60
|
break
|
42
61
|
else:
|
43
|
-
|
62
|
+
t = ('NOTSET', int(self))
|
63
|
+
|
64
|
+
self._name_and_offset = t
|
65
|
+
return t
|
66
|
+
|
67
|
+
@property
|
68
|
+
def exact_name(self) -> ta.Optional[str]:
|
69
|
+
n, o = self.name_and_offset
|
70
|
+
return n if not o else None
|
44
71
|
|
45
|
-
|
72
|
+
@property
|
73
|
+
def effective_name(self) -> str:
|
74
|
+
n, _ = self.name_and_offset
|
46
75
|
return n
|
47
76
|
|
48
77
|
#
|
49
78
|
|
50
|
-
def __repr__(self) -> str:
|
51
|
-
return f'{self.__class__.__name__}({int(self)})'
|
52
|
-
|
53
79
|
def __str__(self) -> str:
|
54
|
-
return self.exact_name or f'{self.effective_name
|
80
|
+
return self.exact_name or f'{self.effective_name}{int(self):+}'
|
81
|
+
|
82
|
+
def __repr__(self) -> str:
|
83
|
+
n, o = self.name_and_offset
|
84
|
+
return f'{self.__class__.__name__}({n!r}{f", {int(o)}" if o else ""})'
|
55
85
|
|
56
86
|
#
|
57
87
|
|
@@ -69,3 +99,6 @@ NamedLogLevel.WARNING = NamedLogLevel(logging.WARNING)
|
|
69
99
|
NamedLogLevel.INFO = NamedLogLevel(logging.INFO)
|
70
100
|
NamedLogLevel.DEBUG = NamedLogLevel(logging.DEBUG)
|
71
101
|
NamedLogLevel.NOTSET = NamedLogLevel(logging.NOTSET)
|
102
|
+
|
103
|
+
|
104
|
+
NamedLogLevel._CACHE.update({i: NamedLogLevel(i) for i in NamedLogLevel._NAMES_BY_INT}) # noqa
|
omlish/logs/modules.py
CHANGED
omlish/logs/protocols.py
CHANGED
@@ -6,6 +6,7 @@ import typing as ta
|
|
6
6
|
from ..base import Logger
|
7
7
|
from ..base import LoggingMsgFn
|
8
8
|
from ..contexts import CaptureLoggingContext
|
9
|
+
from ..infos import LoggingContextInfos
|
9
10
|
from ..levels import LogLevel
|
10
11
|
from .records import LoggingContextLogRecord
|
11
12
|
|
@@ -23,23 +24,25 @@ class StdLogger(Logger):
|
|
23
24
|
def std(self) -> logging.Logger:
|
24
25
|
return self._std
|
25
26
|
|
27
|
+
def is_enabled_for(self, level: LogLevel) -> bool:
|
28
|
+
return self._std.isEnabledFor(level)
|
29
|
+
|
26
30
|
def get_effective_level(self) -> LogLevel:
|
27
31
|
return self._std.getEffectiveLevel()
|
28
32
|
|
29
33
|
def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any) -> None:
|
30
|
-
if not self.is_enabled_for(ctx.level):
|
34
|
+
if not self.is_enabled_for(ctx.must_get_info(LoggingContextInfos.Level).level):
|
31
35
|
return
|
32
36
|
|
33
|
-
ctx.
|
34
|
-
|
35
|
-
ms, args = self._prepare_msg_args(msg, *args)
|
36
|
-
|
37
|
-
rec = LoggingContextLogRecord(
|
37
|
+
ctx.set_basic(
|
38
38
|
name=self._std.name,
|
39
|
-
msg=ms,
|
40
|
-
args=args,
|
41
39
|
|
42
|
-
|
40
|
+
msg=msg,
|
41
|
+
args=args,
|
43
42
|
)
|
44
43
|
|
44
|
+
ctx.capture()
|
45
|
+
|
46
|
+
rec = LoggingContextLogRecord(_logging_context=ctx)
|
47
|
+
|
45
48
|
self._std.handle(rec)
|