omdev 0.0.0.dev430__py3-none-any.whl → 0.0.0.dev432__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.
- omdev/scripts/ci.py +991 -675
- omdev/scripts/interp.py +10 -6
- omdev/scripts/pyproject.py +979 -663
- {omdev-0.0.0.dev430.dist-info → omdev-0.0.0.dev432.dist-info}/METADATA +2 -2
- {omdev-0.0.0.dev430.dist-info → omdev-0.0.0.dev432.dist-info}/RECORD +9 -9
- {omdev-0.0.0.dev430.dist-info → omdev-0.0.0.dev432.dist-info}/WHEEL +0 -0
- {omdev-0.0.0.dev430.dist-info → omdev-0.0.0.dev432.dist-info}/entry_points.txt +0 -0
- {omdev-0.0.0.dev430.dist-info → omdev-0.0.0.dev432.dist-info}/licenses/LICENSE +0 -0
- {omdev-0.0.0.dev430.dist-info → omdev-0.0.0.dev432.dist-info}/top_level.txt +0 -0
omdev/scripts/pyproject.py
CHANGED
@@ -130,6 +130,13 @@ U = ta.TypeVar('U')
|
|
130
130
|
# ../../omlish/lite/timeouts.py
|
131
131
|
TimeoutLike = ta.Union['Timeout', ta.Type['Timeout.DEFAULT'], ta.Iterable['TimeoutLike'], float, None] # ta.TypeAlias
|
132
132
|
|
133
|
+
# ../../omlish/logs/infos.py
|
134
|
+
LoggingMsgFn = ta.Callable[[], ta.Union[str, tuple]] # ta.TypeAlias
|
135
|
+
LoggingExcInfoTuple = ta.Tuple[ta.Type[BaseException], BaseException, ta.Optional[types.TracebackType]] # ta.TypeAlias
|
136
|
+
LoggingExcInfo = ta.Union[BaseException, LoggingExcInfoTuple] # ta.TypeAlias
|
137
|
+
LoggingExcInfoArg = ta.Union[LoggingExcInfo, bool, None] # ta.TypeAlias
|
138
|
+
LoggingContextInfo = ta.Any # ta.TypeAlias
|
139
|
+
|
133
140
|
# ../../omlish/asyncs/asyncio/timeouts.py
|
134
141
|
AwaitableT = ta.TypeVar('AwaitableT', bound=ta.Awaitable)
|
135
142
|
|
@@ -140,12 +147,7 @@ InjectorProviderFnMap = ta.Mapping['InjectorKey', 'InjectorProviderFn']
|
|
140
147
|
InjectorBindingOrBindings = ta.Union['InjectorBinding', 'InjectorBindings']
|
141
148
|
|
142
149
|
# ../../omlish/logs/contexts.py
|
143
|
-
|
144
|
-
LoggingExcInfo = ta.Union[BaseException, LoggingExcInfoTuple] # ta.TypeAlias
|
145
|
-
LoggingExcInfoArg = ta.Union[LoggingExcInfo, bool, None] # ta.TypeAlias
|
146
|
-
|
147
|
-
# ../../omlish/logs/base.py
|
148
|
-
LoggingMsgFn = ta.Callable[[], ta.Union[str, tuple]] # ta.TypeAlias
|
150
|
+
LoggingContextInfoT = ta.TypeVar('LoggingContextInfoT', bound=LoggingContextInfo)
|
149
151
|
|
150
152
|
# ../../omlish/subprocesses/base.py
|
151
153
|
SubprocessChannelOption = ta.Literal['pipe', 'stdout', 'devnull'] # ta.TypeAlias
|
@@ -2959,124 +2961,6 @@ def typing_annotations_attr() -> str:
|
|
2959
2961
|
return _TYPING_ANNOTATIONS_ATTR
|
2960
2962
|
|
2961
2963
|
|
2962
|
-
########################################
|
2963
|
-
# ../../../omlish/logs/infos.py
|
2964
|
-
|
2965
|
-
|
2966
|
-
##
|
2967
|
-
|
2968
|
-
|
2969
|
-
def logging_context_info(cls):
|
2970
|
-
return cls
|
2971
|
-
|
2972
|
-
|
2973
|
-
##
|
2974
|
-
|
2975
|
-
|
2976
|
-
@logging_context_info
|
2977
|
-
@ta.final
|
2978
|
-
class LoggingSourceFileInfo(ta.NamedTuple):
|
2979
|
-
file_name: str
|
2980
|
-
module: str
|
2981
|
-
|
2982
|
-
@classmethod
|
2983
|
-
def build(cls, file_path: ta.Optional[str]) -> ta.Optional['LoggingSourceFileInfo']:
|
2984
|
-
if file_path is None:
|
2985
|
-
return None
|
2986
|
-
|
2987
|
-
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L331-L336 # noqa
|
2988
|
-
try:
|
2989
|
-
file_name = os.path.basename(file_path)
|
2990
|
-
module = os.path.splitext(file_name)[0]
|
2991
|
-
except (TypeError, ValueError, AttributeError):
|
2992
|
-
return None
|
2993
|
-
|
2994
|
-
return cls(
|
2995
|
-
file_name=file_name,
|
2996
|
-
module=module,
|
2997
|
-
)
|
2998
|
-
|
2999
|
-
|
3000
|
-
##
|
3001
|
-
|
3002
|
-
|
3003
|
-
@logging_context_info
|
3004
|
-
@ta.final
|
3005
|
-
class LoggingThreadInfo(ta.NamedTuple):
|
3006
|
-
ident: int
|
3007
|
-
native_id: ta.Optional[int]
|
3008
|
-
name: str
|
3009
|
-
|
3010
|
-
@classmethod
|
3011
|
-
def build(cls) -> 'LoggingThreadInfo':
|
3012
|
-
return cls(
|
3013
|
-
ident=threading.get_ident(),
|
3014
|
-
native_id=threading.get_native_id() if hasattr(threading, 'get_native_id') else None,
|
3015
|
-
name=threading.current_thread().name,
|
3016
|
-
)
|
3017
|
-
|
3018
|
-
|
3019
|
-
##
|
3020
|
-
|
3021
|
-
|
3022
|
-
@logging_context_info
|
3023
|
-
@ta.final
|
3024
|
-
class LoggingProcessInfo(ta.NamedTuple):
|
3025
|
-
pid: int
|
3026
|
-
|
3027
|
-
@classmethod
|
3028
|
-
def build(cls) -> 'LoggingProcessInfo':
|
3029
|
-
return cls(
|
3030
|
-
pid=os.getpid(),
|
3031
|
-
)
|
3032
|
-
|
3033
|
-
|
3034
|
-
##
|
3035
|
-
|
3036
|
-
|
3037
|
-
@logging_context_info
|
3038
|
-
@ta.final
|
3039
|
-
class LoggingMultiprocessingInfo(ta.NamedTuple):
|
3040
|
-
process_name: str
|
3041
|
-
|
3042
|
-
@classmethod
|
3043
|
-
def build(cls) -> ta.Optional['LoggingMultiprocessingInfo']:
|
3044
|
-
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L355-L364 # noqa
|
3045
|
-
if (mp := sys.modules.get('multiprocessing')) is None:
|
3046
|
-
return None
|
3047
|
-
|
3048
|
-
return cls(
|
3049
|
-
process_name=mp.current_process().name,
|
3050
|
-
)
|
3051
|
-
|
3052
|
-
|
3053
|
-
##
|
3054
|
-
|
3055
|
-
|
3056
|
-
@logging_context_info
|
3057
|
-
@ta.final
|
3058
|
-
class LoggingAsyncioTaskInfo(ta.NamedTuple):
|
3059
|
-
name: str
|
3060
|
-
|
3061
|
-
@classmethod
|
3062
|
-
def build(cls) -> ta.Optional['LoggingAsyncioTaskInfo']:
|
3063
|
-
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L372-L377 # noqa
|
3064
|
-
if (asyncio := sys.modules.get('asyncio')) is None:
|
3065
|
-
return None
|
3066
|
-
|
3067
|
-
try:
|
3068
|
-
task = asyncio.current_task()
|
3069
|
-
except Exception: # noqa
|
3070
|
-
return None
|
3071
|
-
|
3072
|
-
if task is None:
|
3073
|
-
return None
|
3074
|
-
|
3075
|
-
return cls(
|
3076
|
-
name=task.get_name(), # Always non-None
|
3077
|
-
)
|
3078
|
-
|
3079
|
-
|
3080
2964
|
########################################
|
3081
2965
|
# ../../../omlish/logs/levels.py
|
3082
2966
|
|
@@ -5560,74 +5444,362 @@ class PredicateTimeout(Timeout):
|
|
5560
5444
|
|
5561
5445
|
|
5562
5446
|
########################################
|
5563
|
-
# ../../../omlish/logs/
|
5447
|
+
# ../../../omlish/logs/infos.py
|
5448
|
+
"""
|
5449
|
+
TODO:
|
5450
|
+
- remove redundant info fields only present for std adaptation (Level.name, ...)
|
5451
|
+
"""
|
5564
5452
|
|
5565
5453
|
|
5566
5454
|
##
|
5567
5455
|
|
5568
5456
|
|
5569
|
-
|
5457
|
+
def logging_context_info(cls):
|
5458
|
+
return cls
|
5459
|
+
|
5460
|
+
|
5570
5461
|
@ta.final
|
5571
|
-
class
|
5572
|
-
|
5573
|
-
|
5574
|
-
name: str
|
5575
|
-
stack_info: ta.Optional[str]
|
5462
|
+
class LoggingContextInfos:
|
5463
|
+
def __new__(cls, *args, **kwargs): # noqa
|
5464
|
+
raise TypeError
|
5576
5465
|
|
5577
|
-
|
5578
|
-
def is_internal_frame(cls, frame: types.FrameType) -> bool:
|
5579
|
-
file_path = os.path.normcase(frame.f_code.co_filename)
|
5466
|
+
#
|
5580
5467
|
|
5581
|
-
|
5582
|
-
|
5583
|
-
|
5584
|
-
|
5585
|
-
return True
|
5468
|
+
@logging_context_info
|
5469
|
+
@ta.final
|
5470
|
+
class Name(ta.NamedTuple):
|
5471
|
+
name: str
|
5586
5472
|
|
5587
|
-
|
5473
|
+
@logging_context_info
|
5474
|
+
@ta.final
|
5475
|
+
class Level(ta.NamedTuple):
|
5476
|
+
level: NamedLogLevel
|
5477
|
+
name: str
|
5588
5478
|
|
5589
|
-
|
5590
|
-
|
5591
|
-
|
5479
|
+
@classmethod
|
5480
|
+
def build(cls, level: int) -> 'LoggingContextInfos.Level':
|
5481
|
+
nl: NamedLogLevel = level if level.__class__ is NamedLogLevel else NamedLogLevel(level) # type: ignore[assignment] # noqa
|
5482
|
+
return cls(
|
5483
|
+
level=nl,
|
5484
|
+
name=logging.getLevelName(nl),
|
5485
|
+
)
|
5486
|
+
|
5487
|
+
@logging_context_info
|
5488
|
+
@ta.final
|
5489
|
+
class Msg(ta.NamedTuple):
|
5490
|
+
msg: str
|
5491
|
+
args: ta.Union[tuple, ta.Mapping[ta.Any, ta.Any], None]
|
5492
|
+
|
5493
|
+
@classmethod
|
5494
|
+
def build(
|
5495
|
+
cls,
|
5496
|
+
msg: ta.Union[str, tuple, LoggingMsgFn],
|
5497
|
+
*args: ta.Any,
|
5498
|
+
) -> 'LoggingContextInfos.Msg':
|
5499
|
+
s: str
|
5500
|
+
a: ta.Any
|
5501
|
+
|
5502
|
+
if callable(msg):
|
5503
|
+
if args:
|
5504
|
+
raise TypeError(f'Must not provide both a message function and args: {msg=} {args=}')
|
5505
|
+
x = msg()
|
5506
|
+
if isinstance(x, str):
|
5507
|
+
s, a = x, ()
|
5508
|
+
elif isinstance(x, tuple):
|
5509
|
+
if x:
|
5510
|
+
s, a = x[0], x[1:]
|
5511
|
+
else:
|
5512
|
+
s, a = '', ()
|
5513
|
+
else:
|
5514
|
+
raise TypeError(x)
|
5592
5515
|
|
5593
|
-
|
5594
|
-
|
5595
|
-
|
5596
|
-
|
5597
|
-
|
5516
|
+
elif isinstance(msg, tuple):
|
5517
|
+
if args:
|
5518
|
+
raise TypeError(f'Must not provide both a tuple message and args: {msg=} {args=}')
|
5519
|
+
if msg:
|
5520
|
+
s, a = msg[0], msg[1:]
|
5521
|
+
else:
|
5522
|
+
s, a = '', ()
|
5598
5523
|
|
5599
|
-
|
5524
|
+
elif isinstance(msg, str):
|
5525
|
+
s, a = msg, args
|
5600
5526
|
|
5601
|
-
|
5527
|
+
else:
|
5528
|
+
raise TypeError(msg)
|
5529
|
+
|
5530
|
+
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L307 # noqa
|
5531
|
+
if a and len(a) == 1 and isinstance(a[0], collections.abc.Mapping) and a[0]:
|
5532
|
+
a = a[0]
|
5533
|
+
|
5534
|
+
return cls(
|
5535
|
+
msg=s,
|
5536
|
+
args=a,
|
5537
|
+
)
|
5538
|
+
|
5539
|
+
@logging_context_info
|
5540
|
+
@ta.final
|
5541
|
+
class Extra(ta.NamedTuple):
|
5542
|
+
extra: ta.Mapping[ta.Any, ta.Any]
|
5543
|
+
|
5544
|
+
@logging_context_info
|
5545
|
+
@ta.final
|
5546
|
+
class Time(ta.NamedTuple):
|
5547
|
+
ns: int
|
5548
|
+
secs: float
|
5549
|
+
msecs: float
|
5550
|
+
relative_secs: float
|
5551
|
+
|
5552
|
+
@classmethod
|
5553
|
+
def get_std_start_ns(cls) -> int:
|
5554
|
+
x: ta.Any = logging._startTime # type: ignore[attr-defined] # noqa
|
5555
|
+
|
5556
|
+
# Before 3.13.0b1 this will be `time.time()`, a float of seconds. After that, it will be `time.time_ns()`,
|
5557
|
+
# an int.
|
5558
|
+
#
|
5559
|
+
# See:
|
5560
|
+
# - https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
|
5561
|
+
#
|
5562
|
+
if isinstance(x, float):
|
5563
|
+
return int(x * 1e9)
|
5564
|
+
else:
|
5565
|
+
return x
|
5566
|
+
|
5567
|
+
@classmethod
|
5568
|
+
def build(
|
5569
|
+
cls,
|
5570
|
+
ns: int,
|
5571
|
+
*,
|
5572
|
+
start_ns: ta.Optional[int] = None,
|
5573
|
+
) -> 'LoggingContextInfos.Time':
|
5574
|
+
# https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
|
5575
|
+
secs = ns / 1e9 # ns to float seconds
|
5576
|
+
|
5577
|
+
# Get the number of whole milliseconds (0-999) in the fractional part of seconds.
|
5578
|
+
# Eg: 1_677_903_920_999_998_503 ns --> 999_998_503 ns--> 999 ms
|
5579
|
+
# Convert to float by adding 0.0 for historical reasons. See gh-89047
|
5580
|
+
msecs = (ns % 1_000_000_000) // 1_000_000 + 0.0
|
5581
|
+
|
5582
|
+
# https://github.com/python/cpython/commit/1500a23f33f5a6d052ff1ef6383d9839928b8ff1
|
5583
|
+
if msecs == 999.0 and int(secs) != ns // 1_000_000_000:
|
5584
|
+
# ns -> sec conversion can round up, e.g:
|
5585
|
+
# 1_677_903_920_999_999_900 ns --> 1_677_903_921.0 sec
|
5586
|
+
msecs = 0.0
|
5587
|
+
|
5588
|
+
if start_ns is None:
|
5589
|
+
start_ns = cls.get_std_start_ns()
|
5590
|
+
relative_secs = (ns - start_ns) / 1e6
|
5591
|
+
|
5592
|
+
return cls(
|
5593
|
+
ns=ns,
|
5594
|
+
secs=secs,
|
5595
|
+
msecs=msecs,
|
5596
|
+
relative_secs=relative_secs,
|
5597
|
+
)
|
5598
|
+
|
5599
|
+
@logging_context_info
|
5600
|
+
@ta.final
|
5601
|
+
class Exc(ta.NamedTuple):
|
5602
|
+
info: LoggingExcInfo
|
5603
|
+
info_tuple: LoggingExcInfoTuple
|
5604
|
+
|
5605
|
+
@classmethod
|
5606
|
+
def build(
|
5607
|
+
cls,
|
5608
|
+
arg: LoggingExcInfoArg = False,
|
5609
|
+
) -> ta.Optional['LoggingContextInfos.Exc']:
|
5610
|
+
if arg is True:
|
5611
|
+
sys_exc_info = sys.exc_info()
|
5612
|
+
if sys_exc_info[0] is not None:
|
5613
|
+
arg = sys_exc_info
|
5614
|
+
else:
|
5615
|
+
arg = None
|
5616
|
+
elif arg is False:
|
5617
|
+
arg = None
|
5618
|
+
if arg is None:
|
5619
|
+
return None
|
5620
|
+
|
5621
|
+
info: LoggingExcInfo = arg
|
5622
|
+
if isinstance(info, BaseException):
|
5623
|
+
info_tuple: LoggingExcInfoTuple = (type(info), info, info.__traceback__) # noqa
|
5624
|
+
else:
|
5625
|
+
info_tuple = info
|
5626
|
+
|
5627
|
+
return cls(
|
5628
|
+
info=info,
|
5629
|
+
info_tuple=info_tuple,
|
5630
|
+
)
|
5631
|
+
|
5632
|
+
@logging_context_info
|
5633
|
+
@ta.final
|
5634
|
+
class Caller(ta.NamedTuple):
|
5635
|
+
file_path: str
|
5636
|
+
line_no: int
|
5637
|
+
func_name: str
|
5638
|
+
stack_info: ta.Optional[str]
|
5639
|
+
|
5640
|
+
@classmethod
|
5641
|
+
def is_internal_frame(cls, frame: types.FrameType) -> bool:
|
5642
|
+
file_path = os.path.normcase(frame.f_code.co_filename)
|
5643
|
+
|
5644
|
+
# Yes, really.
|
5645
|
+
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L204 # noqa
|
5646
|
+
# https://github.com/python/cpython/commit/5ca6d7469be53960843df39bb900e9c3359f127f
|
5647
|
+
if 'importlib' in file_path and '_bootstrap' in file_path:
|
5648
|
+
return True
|
5649
|
+
|
5650
|
+
return False
|
5651
|
+
|
5652
|
+
@classmethod
|
5653
|
+
def find_frame(cls, stack_offset: int = 0) -> ta.Optional[types.FrameType]:
|
5654
|
+
f: ta.Optional[types.FrameType] = sys._getframe(2 + stack_offset) # noqa
|
5655
|
+
|
5656
|
+
while f is not None:
|
5657
|
+
# NOTE: We don't check __file__ like stdlib since we may be running amalgamated - we rely on careful,
|
5658
|
+
# manual stack_offset management.
|
5659
|
+
if hasattr(f, 'f_code'):
|
5660
|
+
return f
|
5661
|
+
|
5662
|
+
f = f.f_back
|
5602
5663
|
|
5603
|
-
@classmethod
|
5604
|
-
def find(
|
5605
|
-
cls,
|
5606
|
-
ofs: int = 0,
|
5607
|
-
*,
|
5608
|
-
stack_info: bool = False,
|
5609
|
-
) -> ta.Optional['LoggingCaller']:
|
5610
|
-
if (f := cls.find_frame(ofs + 1)) is None:
|
5611
5664
|
return None
|
5612
5665
|
|
5613
|
-
|
5614
|
-
|
5615
|
-
|
5616
|
-
|
5617
|
-
|
5618
|
-
|
5619
|
-
|
5620
|
-
if
|
5621
|
-
|
5666
|
+
@classmethod
|
5667
|
+
def build(
|
5668
|
+
cls,
|
5669
|
+
stack_offset: int = 0,
|
5670
|
+
*,
|
5671
|
+
stack_info: bool = False,
|
5672
|
+
) -> ta.Optional['LoggingContextInfos.Caller']:
|
5673
|
+
if (f := cls.find_frame(stack_offset + 1)) is None:
|
5674
|
+
return None
|
5622
5675
|
|
5623
|
-
|
5624
|
-
|
5625
|
-
|
5626
|
-
|
5627
|
-
|
5676
|
+
# https://github.com/python/cpython/blob/08e9794517063c8cd92c48714071b1d3c60b71bd/Lib/logging/__init__.py#L1616-L1623 # noqa
|
5677
|
+
sinfo = None
|
5678
|
+
if stack_info:
|
5679
|
+
sio = io.StringIO()
|
5680
|
+
traceback.print_stack(f, file=sio)
|
5681
|
+
sinfo = sio.getvalue()
|
5682
|
+
sio.close()
|
5683
|
+
if sinfo[-1] == '\n':
|
5684
|
+
sinfo = sinfo[:-1]
|
5685
|
+
|
5686
|
+
return cls(
|
5687
|
+
file_path=f.f_code.co_filename,
|
5688
|
+
line_no=f.f_lineno or 0,
|
5689
|
+
func_name=f.f_code.co_name,
|
5690
|
+
stack_info=sinfo,
|
5691
|
+
)
|
5692
|
+
|
5693
|
+
@logging_context_info
|
5694
|
+
@ta.final
|
5695
|
+
class SourceFile(ta.NamedTuple):
|
5696
|
+
file_name: str
|
5697
|
+
module: str
|
5698
|
+
|
5699
|
+
@classmethod
|
5700
|
+
def build(cls, caller_file_path: ta.Optional[str]) -> ta.Optional['LoggingContextInfos.SourceFile']:
|
5701
|
+
if caller_file_path is None:
|
5702
|
+
return None
|
5703
|
+
|
5704
|
+
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L331-L336 # noqa
|
5705
|
+
try:
|
5706
|
+
file_name = os.path.basename(caller_file_path)
|
5707
|
+
module = os.path.splitext(file_name)[0]
|
5708
|
+
except (TypeError, ValueError, AttributeError):
|
5709
|
+
return None
|
5710
|
+
|
5711
|
+
return cls(
|
5712
|
+
file_name=file_name,
|
5713
|
+
module=module,
|
5714
|
+
)
|
5715
|
+
|
5716
|
+
@logging_context_info
|
5717
|
+
@ta.final
|
5718
|
+
class Thread(ta.NamedTuple):
|
5719
|
+
ident: int
|
5720
|
+
native_id: ta.Optional[int]
|
5721
|
+
name: str
|
5722
|
+
|
5723
|
+
@classmethod
|
5724
|
+
def build(cls) -> 'LoggingContextInfos.Thread':
|
5725
|
+
return cls(
|
5726
|
+
ident=threading.get_ident(),
|
5727
|
+
native_id=threading.get_native_id() if hasattr(threading, 'get_native_id') else None,
|
5728
|
+
name=threading.current_thread().name,
|
5729
|
+
)
|
5730
|
+
|
5731
|
+
@logging_context_info
|
5732
|
+
@ta.final
|
5733
|
+
class Process(ta.NamedTuple):
|
5734
|
+
pid: int
|
5735
|
+
|
5736
|
+
@classmethod
|
5737
|
+
def build(cls) -> 'LoggingContextInfos.Process':
|
5738
|
+
return cls(
|
5739
|
+
pid=os.getpid(),
|
5740
|
+
)
|
5741
|
+
|
5742
|
+
@logging_context_info
|
5743
|
+
@ta.final
|
5744
|
+
class Multiprocessing(ta.NamedTuple):
|
5745
|
+
process_name: str
|
5746
|
+
|
5747
|
+
@classmethod
|
5748
|
+
def build(cls) -> ta.Optional['LoggingContextInfos.Multiprocessing']:
|
5749
|
+
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L355-L364 # noqa
|
5750
|
+
if (mp := sys.modules.get('multiprocessing')) is None:
|
5751
|
+
return None
|
5752
|
+
|
5753
|
+
return cls(
|
5754
|
+
process_name=mp.current_process().name,
|
5755
|
+
)
|
5756
|
+
|
5757
|
+
@logging_context_info
|
5758
|
+
@ta.final
|
5759
|
+
class AsyncioTask(ta.NamedTuple):
|
5760
|
+
name: str
|
5761
|
+
|
5762
|
+
@classmethod
|
5763
|
+
def build(cls) -> ta.Optional['LoggingContextInfos.AsyncioTask']:
|
5764
|
+
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L372-L377 # noqa
|
5765
|
+
if (asyncio := sys.modules.get('asyncio')) is None:
|
5766
|
+
return None
|
5767
|
+
|
5768
|
+
try:
|
5769
|
+
task = asyncio.current_task()
|
5770
|
+
except Exception: # noqa
|
5771
|
+
return None
|
5772
|
+
|
5773
|
+
if task is None:
|
5774
|
+
return None
|
5775
|
+
|
5776
|
+
return cls(
|
5777
|
+
name=task.get_name(), # Always non-None
|
5778
|
+
)
|
5779
|
+
|
5780
|
+
|
5781
|
+
##
|
5782
|
+
|
5783
|
+
|
5784
|
+
class UnexpectedLoggingStartTimeWarning(LoggingSetupWarning):
|
5785
|
+
pass
|
5786
|
+
|
5787
|
+
|
5788
|
+
def _check_logging_start_time() -> None:
|
5789
|
+
if (x := LoggingContextInfos.Time.get_std_start_ns()) < (t := time.time()):
|
5790
|
+
import warnings # noqa
|
5791
|
+
|
5792
|
+
warnings.warn(
|
5793
|
+
f'Unexpected logging start time detected: '
|
5794
|
+
f'get_std_start_ns={x}, '
|
5795
|
+
f'time.time()={t}',
|
5796
|
+
UnexpectedLoggingStartTimeWarning,
|
5628
5797
|
)
|
5629
5798
|
|
5630
5799
|
|
5800
|
+
_check_logging_start_time()
|
5801
|
+
|
5802
|
+
|
5631
5803
|
########################################
|
5632
5804
|
# ../../../omlish/logs/protocols.py
|
5633
5805
|
|
@@ -5717,91 +5889,6 @@ class JsonLoggingFormatter(logging.Formatter):
|
|
5717
5889
|
return self._json_dumps(dct)
|
5718
5890
|
|
5719
5891
|
|
5720
|
-
########################################
|
5721
|
-
# ../../../omlish/logs/times.py
|
5722
|
-
|
5723
|
-
|
5724
|
-
##
|
5725
|
-
|
5726
|
-
|
5727
|
-
@logging_context_info
|
5728
|
-
@ta.final
|
5729
|
-
class LoggingTimeFields(ta.NamedTuple):
|
5730
|
-
"""Maps directly to stdlib `logging.LogRecord` fields, and must be kept in sync with it."""
|
5731
|
-
|
5732
|
-
created: float
|
5733
|
-
msecs: float
|
5734
|
-
relative_created: float
|
5735
|
-
|
5736
|
-
@classmethod
|
5737
|
-
def get_std_start_time_ns(cls) -> int:
|
5738
|
-
x: ta.Any = logging._startTime # type: ignore[attr-defined] # noqa
|
5739
|
-
|
5740
|
-
# Before 3.13.0b1 this will be `time.time()`, a float of seconds. After that, it will be `time.time_ns()`, an
|
5741
|
-
# int.
|
5742
|
-
#
|
5743
|
-
# See:
|
5744
|
-
# - https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
|
5745
|
-
#
|
5746
|
-
if isinstance(x, float):
|
5747
|
-
return int(x * 1e9)
|
5748
|
-
else:
|
5749
|
-
return x
|
5750
|
-
|
5751
|
-
@classmethod
|
5752
|
-
def build(
|
5753
|
-
cls,
|
5754
|
-
time_ns: int,
|
5755
|
-
*,
|
5756
|
-
start_time_ns: ta.Optional[int] = None,
|
5757
|
-
) -> 'LoggingTimeFields':
|
5758
|
-
# https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
|
5759
|
-
created = time_ns / 1e9 # ns to float seconds
|
5760
|
-
|
5761
|
-
# Get the number of whole milliseconds (0-999) in the fractional part of seconds.
|
5762
|
-
# Eg: 1_677_903_920_999_998_503 ns --> 999_998_503 ns--> 999 ms
|
5763
|
-
# Convert to float by adding 0.0 for historical reasons. See gh-89047
|
5764
|
-
msecs = (time_ns % 1_000_000_000) // 1_000_000 + 0.0
|
5765
|
-
|
5766
|
-
# https://github.com/python/cpython/commit/1500a23f33f5a6d052ff1ef6383d9839928b8ff1
|
5767
|
-
if msecs == 999.0 and int(created) != time_ns // 1_000_000_000:
|
5768
|
-
# ns -> sec conversion can round up, e.g:
|
5769
|
-
# 1_677_903_920_999_999_900 ns --> 1_677_903_921.0 sec
|
5770
|
-
msecs = 0.0
|
5771
|
-
|
5772
|
-
if start_time_ns is None:
|
5773
|
-
start_time_ns = cls.get_std_start_time_ns()
|
5774
|
-
relative_created = (time_ns - start_time_ns) / 1e6
|
5775
|
-
|
5776
|
-
return cls(
|
5777
|
-
created=created,
|
5778
|
-
msecs=msecs,
|
5779
|
-
relative_created=relative_created,
|
5780
|
-
)
|
5781
|
-
|
5782
|
-
|
5783
|
-
##
|
5784
|
-
|
5785
|
-
|
5786
|
-
class UnexpectedLoggingStartTimeWarning(LoggingSetupWarning):
|
5787
|
-
pass
|
5788
|
-
|
5789
|
-
|
5790
|
-
def _check_logging_start_time() -> None:
|
5791
|
-
if (x := LoggingTimeFields.get_std_start_time_ns()) < (t := time.time()):
|
5792
|
-
import warnings # noqa
|
5793
|
-
|
5794
|
-
warnings.warn(
|
5795
|
-
f'Unexpected logging start time detected: '
|
5796
|
-
f'get_std_start_time_ns={x}, '
|
5797
|
-
f'time.time()={t}',
|
5798
|
-
UnexpectedLoggingStartTimeWarning,
|
5799
|
-
)
|
5800
|
-
|
5801
|
-
|
5802
|
-
_check_logging_start_time()
|
5803
|
-
|
5804
|
-
|
5805
5892
|
########################################
|
5806
5893
|
# ../../interp/types.py
|
5807
5894
|
|
@@ -7015,68 +7102,46 @@ inj = InjectionApi()
|
|
7015
7102
|
|
7016
7103
|
|
7017
7104
|
class LoggingContext(Abstract):
|
7018
|
-
@property
|
7019
7105
|
@abc.abstractmethod
|
7020
|
-
def
|
7106
|
+
def get_info(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
|
7021
7107
|
raise NotImplementedError
|
7022
7108
|
|
7023
|
-
|
7109
|
+
@ta.final
|
7110
|
+
def __getitem__(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
|
7111
|
+
return self.get_info(ty)
|
7024
7112
|
|
7025
|
-
@
|
7026
|
-
|
7027
|
-
|
7028
|
-
|
7113
|
+
@ta.final
|
7114
|
+
def must_get_info(self, ty: ta.Type[LoggingContextInfoT]) -> LoggingContextInfoT:
|
7115
|
+
if (info := self.get_info(ty)) is None:
|
7116
|
+
raise TypeError(f'LoggingContextInfo absent: {ty}')
|
7117
|
+
return info
|
7029
7118
|
|
7030
|
-
@property
|
7031
|
-
@abc.abstractmethod
|
7032
|
-
def times(self) -> LoggingTimeFields:
|
7033
|
-
raise NotImplementedError
|
7034
7119
|
|
7035
|
-
|
7036
|
-
|
7037
|
-
|
7038
|
-
|
7039
|
-
def exc_info(self) -> ta.Optional[LoggingExcInfo]:
|
7040
|
-
raise NotImplementedError
|
7041
|
-
|
7042
|
-
@property
|
7043
|
-
@abc.abstractmethod
|
7044
|
-
def exc_info_tuple(self) -> ta.Optional[LoggingExcInfoTuple]:
|
7045
|
-
raise NotImplementedError
|
7046
|
-
|
7047
|
-
#
|
7048
|
-
|
7049
|
-
@abc.abstractmethod
|
7050
|
-
def caller(self) -> ta.Optional[LoggingCaller]:
|
7051
|
-
raise NotImplementedError
|
7120
|
+
@ta.final
|
7121
|
+
class SimpleLoggingContext(LoggingContext):
|
7122
|
+
def __init__(self, *infos: LoggingContextInfo) -> None:
|
7123
|
+
self._infos: ta.Dict[ta.Type[LoggingContextInfo], LoggingContextInfo] = {type(i): i for i in infos}
|
7052
7124
|
|
7053
|
-
|
7054
|
-
|
7055
|
-
raise NotImplementedError
|
7125
|
+
def get_info(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
|
7126
|
+
return self._infos.get(ty)
|
7056
7127
|
|
7057
|
-
#
|
7058
7128
|
|
7059
|
-
|
7060
|
-
def thread(self) -> ta.Optional[LoggingThreadInfo]:
|
7061
|
-
raise NotImplementedError
|
7129
|
+
##
|
7062
7130
|
|
7063
|
-
@abc.abstractmethod
|
7064
|
-
def process(self) -> ta.Optional[LoggingProcessInfo]:
|
7065
|
-
raise NotImplementedError
|
7066
7131
|
|
7132
|
+
class CaptureLoggingContext(LoggingContext, Abstract):
|
7067
7133
|
@abc.abstractmethod
|
7068
|
-
def
|
7069
|
-
|
7134
|
+
def set_basic(
|
7135
|
+
self,
|
7136
|
+
name: str,
|
7070
7137
|
|
7071
|
-
|
7072
|
-
|
7138
|
+
msg: ta.Union[str, tuple, LoggingMsgFn],
|
7139
|
+
args: tuple,
|
7140
|
+
) -> 'CaptureLoggingContext':
|
7073
7141
|
raise NotImplementedError
|
7074
7142
|
|
7143
|
+
#
|
7075
7144
|
|
7076
|
-
##
|
7077
|
-
|
7078
|
-
|
7079
|
-
class CaptureLoggingContext(LoggingContext, Abstract):
|
7080
7145
|
class AlreadyCapturedError(Exception):
|
7081
7146
|
pass
|
7082
7147
|
|
@@ -7107,80 +7172,50 @@ class CaptureLoggingContextImpl(CaptureLoggingContext):
|
|
7107
7172
|
|
7108
7173
|
exc_info: LoggingExcInfoArg = False,
|
7109
7174
|
|
7110
|
-
caller: ta.Union[
|
7175
|
+
caller: ta.Union[LoggingContextInfos.Caller, ta.Type[NOT_SET], None] = NOT_SET,
|
7111
7176
|
stack_offset: int = 0,
|
7112
7177
|
stack_info: bool = False,
|
7113
7178
|
) -> None:
|
7114
|
-
|
7115
|
-
|
7116
|
-
#
|
7179
|
+
# TODO: Name, Msg, Extra
|
7117
7180
|
|
7118
7181
|
if time_ns is None:
|
7119
7182
|
time_ns = time.time_ns()
|
7120
|
-
self._time_ns: int = time_ns
|
7121
|
-
|
7122
|
-
#
|
7123
7183
|
|
7124
|
-
|
7125
|
-
|
7126
|
-
|
7127
|
-
|
7128
|
-
|
7129
|
-
|
7130
|
-
elif exc_info is False:
|
7131
|
-
exc_info = None
|
7132
|
-
|
7133
|
-
if exc_info is not None:
|
7134
|
-
self._exc_info: ta.Optional[LoggingExcInfo] = exc_info
|
7135
|
-
if isinstance(exc_info, BaseException):
|
7136
|
-
self._exc_info_tuple: ta.Optional[LoggingExcInfoTuple] = (type(exc_info), exc_info, exc_info.__traceback__) # noqa
|
7137
|
-
else:
|
7138
|
-
self._exc_info_tuple = exc_info
|
7139
|
-
|
7140
|
-
#
|
7184
|
+
self._infos: ta.Dict[ta.Type[LoggingContextInfo], LoggingContextInfo] = {}
|
7185
|
+
self._set_info(
|
7186
|
+
LoggingContextInfos.Level.build(level),
|
7187
|
+
LoggingContextInfos.Time.build(time_ns),
|
7188
|
+
LoggingContextInfos.Exc.build(exc_info),
|
7189
|
+
)
|
7141
7190
|
|
7142
7191
|
if caller is not CaptureLoggingContextImpl.NOT_SET:
|
7143
|
-
self.
|
7192
|
+
self._infos[LoggingContextInfos.Caller] = caller
|
7144
7193
|
else:
|
7145
7194
|
self._stack_offset = stack_offset
|
7146
7195
|
self._stack_info = stack_info
|
7147
7196
|
|
7148
|
-
|
7149
|
-
|
7150
|
-
|
7151
|
-
|
7152
|
-
return self
|
7153
|
-
|
7154
|
-
#
|
7155
|
-
|
7156
|
-
@property
|
7157
|
-
def time_ns(self) -> int:
|
7158
|
-
return self._time_ns
|
7159
|
-
|
7160
|
-
_times: LoggingTimeFields
|
7161
|
-
|
7162
|
-
@property
|
7163
|
-
def times(self) -> LoggingTimeFields:
|
7164
|
-
try:
|
7165
|
-
return self._times
|
7166
|
-
except AttributeError:
|
7167
|
-
pass
|
7168
|
-
|
7169
|
-
times = self._times = LoggingTimeFields.build(self.time_ns)
|
7170
|
-
return times
|
7197
|
+
def _set_info(self, *infos: ta.Optional[LoggingContextInfo]) -> 'CaptureLoggingContextImpl':
|
7198
|
+
for info in infos:
|
7199
|
+
if info is not None:
|
7200
|
+
self._infos[type(info)] = info
|
7201
|
+
return self
|
7171
7202
|
|
7172
|
-
|
7203
|
+
def get_info(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
|
7204
|
+
return self._infos.get(ty)
|
7173
7205
|
|
7174
|
-
|
7175
|
-
_exc_info_tuple: ta.Optional[LoggingExcInfoTuple] = None
|
7206
|
+
##
|
7176
7207
|
|
7177
|
-
|
7178
|
-
|
7179
|
-
|
7208
|
+
def set_basic(
|
7209
|
+
self,
|
7210
|
+
name: str,
|
7180
7211
|
|
7181
|
-
|
7182
|
-
|
7183
|
-
|
7212
|
+
msg: ta.Union[str, tuple, LoggingMsgFn],
|
7213
|
+
args: tuple,
|
7214
|
+
) -> 'CaptureLoggingContextImpl':
|
7215
|
+
return self._set_info(
|
7216
|
+
LoggingContextInfos.Name(name),
|
7217
|
+
LoggingContextInfos.Msg.build(msg, *args),
|
7218
|
+
)
|
7184
7219
|
|
7185
7220
|
##
|
7186
7221
|
|
@@ -7194,74 +7229,28 @@ class CaptureLoggingContextImpl(CaptureLoggingContext):
|
|
7194
7229
|
|
7195
7230
|
_has_captured: bool = False
|
7196
7231
|
|
7197
|
-
_caller: ta.Optional[LoggingCaller]
|
7198
|
-
_source_file: ta.Optional[LoggingSourceFileInfo]
|
7199
|
-
|
7200
|
-
_thread: ta.Optional[LoggingThreadInfo]
|
7201
|
-
_process: ta.Optional[LoggingProcessInfo]
|
7202
|
-
_multiprocessing: ta.Optional[LoggingMultiprocessingInfo]
|
7203
|
-
_asyncio_task: ta.Optional[LoggingAsyncioTaskInfo]
|
7204
|
-
|
7205
7232
|
def capture(self) -> None:
|
7206
7233
|
if self._has_captured:
|
7207
7234
|
raise CaptureLoggingContextImpl.AlreadyCapturedError
|
7208
7235
|
self._has_captured = True
|
7209
7236
|
|
7210
|
-
if not
|
7211
|
-
self.
|
7237
|
+
if LoggingContextInfos.Caller not in self._infos:
|
7238
|
+
self._set_info(LoggingContextInfos.Caller.build(
|
7212
7239
|
self._stack_offset + 1,
|
7213
7240
|
stack_info=self._stack_info,
|
7214
|
-
)
|
7215
|
-
|
7216
|
-
if (caller := self._caller) is not None:
|
7217
|
-
self._source_file = LoggingSourceFileInfo.build(caller.file_path)
|
7218
|
-
else:
|
7219
|
-
self._source_file = None
|
7220
|
-
|
7221
|
-
self._thread = LoggingThreadInfo.build()
|
7222
|
-
self._process = LoggingProcessInfo.build()
|
7223
|
-
self._multiprocessing = LoggingMultiprocessingInfo.build()
|
7224
|
-
self._asyncio_task = LoggingAsyncioTaskInfo.build()
|
7225
|
-
|
7226
|
-
#
|
7227
|
-
|
7228
|
-
def caller(self) -> ta.Optional[LoggingCaller]:
|
7229
|
-
try:
|
7230
|
-
return self._caller
|
7231
|
-
except AttributeError:
|
7232
|
-
raise CaptureLoggingContext.NotCapturedError from None
|
7233
|
-
|
7234
|
-
def source_file(self) -> ta.Optional[LoggingSourceFileInfo]:
|
7235
|
-
try:
|
7236
|
-
return self._source_file
|
7237
|
-
except AttributeError:
|
7238
|
-
raise CaptureLoggingContext.NotCapturedError from None
|
7239
|
-
|
7240
|
-
#
|
7241
|
-
|
7242
|
-
def thread(self) -> ta.Optional[LoggingThreadInfo]:
|
7243
|
-
try:
|
7244
|
-
return self._thread
|
7245
|
-
except AttributeError:
|
7246
|
-
raise CaptureLoggingContext.NotCapturedError from None
|
7247
|
-
|
7248
|
-
def process(self) -> ta.Optional[LoggingProcessInfo]:
|
7249
|
-
try:
|
7250
|
-
return self._process
|
7251
|
-
except AttributeError:
|
7252
|
-
raise CaptureLoggingContext.NotCapturedError from None
|
7241
|
+
))
|
7253
7242
|
|
7254
|
-
|
7255
|
-
|
7256
|
-
|
7257
|
-
|
7258
|
-
raise CaptureLoggingContext.NotCapturedError from None
|
7243
|
+
if (caller := self[LoggingContextInfos.Caller]) is not None:
|
7244
|
+
self._set_info(LoggingContextInfos.SourceFile.build(
|
7245
|
+
caller.file_path,
|
7246
|
+
))
|
7259
7247
|
|
7260
|
-
|
7261
|
-
|
7262
|
-
|
7263
|
-
|
7264
|
-
|
7248
|
+
self._set_info(
|
7249
|
+
LoggingContextInfos.Thread.build(),
|
7250
|
+
LoggingContextInfos.Process.build(),
|
7251
|
+
LoggingContextInfos.Multiprocessing.build(),
|
7252
|
+
LoggingContextInfos.AsyncioTask.build(),
|
7253
|
+
)
|
7265
7254
|
|
7266
7255
|
|
7267
7256
|
########################################
|
@@ -7338,10 +7327,14 @@ def _locking_logging_module_lock() -> ta.Iterator[None]:
|
|
7338
7327
|
def configure_standard_logging(
|
7339
7328
|
level: ta.Union[int, str] = logging.INFO,
|
7340
7329
|
*,
|
7341
|
-
json: bool = False,
|
7342
7330
|
target: ta.Optional[logging.Logger] = None,
|
7331
|
+
|
7343
7332
|
force: bool = False,
|
7333
|
+
|
7344
7334
|
handler_factory: ta.Optional[ta.Callable[[], logging.Handler]] = None,
|
7335
|
+
|
7336
|
+
formatter: ta.Optional[logging.Formatter] = None, # noqa
|
7337
|
+
json: bool = False,
|
7345
7338
|
) -> ta.Optional[StandardConfiguredLoggingHandler]:
|
7346
7339
|
with _locking_logging_module_lock():
|
7347
7340
|
if target is None:
|
@@ -7362,11 +7355,11 @@ def configure_standard_logging(
|
|
7362
7355
|
|
7363
7356
|
#
|
7364
7357
|
|
7365
|
-
formatter:
|
7366
|
-
|
7367
|
-
|
7368
|
-
|
7369
|
-
|
7358
|
+
if formatter is None:
|
7359
|
+
if json:
|
7360
|
+
formatter = JsonLoggingFormatter()
|
7361
|
+
else:
|
7362
|
+
formatter = StandardLoggingFormatter(StandardLoggingFormatter.build_log_format(STANDARD_LOG_FORMAT_PARTS)) # noqa
|
7370
7363
|
handler.setFormatter(formatter)
|
7371
7364
|
|
7372
7365
|
#
|
@@ -7760,36 +7753,6 @@ class AnyLogger(Abstract, ta.Generic[T]):
|
|
7760
7753
|
|
7761
7754
|
##
|
7762
7755
|
|
7763
|
-
@classmethod
|
7764
|
-
def _prepare_msg_args(cls, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any) -> ta.Tuple[str, tuple]:
|
7765
|
-
if callable(msg):
|
7766
|
-
if args:
|
7767
|
-
raise TypeError(f'Must not provide both a message function and args: {msg=} {args=}')
|
7768
|
-
x = msg()
|
7769
|
-
if isinstance(x, str):
|
7770
|
-
return x, ()
|
7771
|
-
elif isinstance(x, tuple):
|
7772
|
-
if x:
|
7773
|
-
return x[0], x[1:]
|
7774
|
-
else:
|
7775
|
-
return '', ()
|
7776
|
-
else:
|
7777
|
-
raise TypeError(x)
|
7778
|
-
|
7779
|
-
elif isinstance(msg, tuple):
|
7780
|
-
if args:
|
7781
|
-
raise TypeError(f'Must not provide both a tuple message and args: {msg=} {args=}')
|
7782
|
-
if msg:
|
7783
|
-
return msg[0], msg[1:]
|
7784
|
-
else:
|
7785
|
-
return '', ()
|
7786
|
-
|
7787
|
-
elif isinstance(msg, str):
|
7788
|
-
return msg, args
|
7789
|
-
|
7790
|
-
else:
|
7791
|
-
raise TypeError(msg)
|
7792
|
-
|
7793
7756
|
@abc.abstractmethod
|
7794
7757
|
def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any, **kwargs: ta.Any) -> T: # noqa
|
7795
7758
|
raise NotImplementedError
|
@@ -7830,144 +7793,560 @@ class AsyncNopLogger(AnyNopLogger[ta.Awaitable[None]], AsyncLogger):
|
|
7830
7793
|
|
7831
7794
|
########################################
|
7832
7795
|
# ../../../omlish/logs/std/records.py
|
7796
|
+
"""
|
7797
|
+
TODO:
|
7798
|
+
- TypedDict?
|
7799
|
+
"""
|
7833
7800
|
|
7834
7801
|
|
7835
7802
|
##
|
7836
7803
|
|
7837
7804
|
|
7838
|
-
|
7839
|
-
#
|
7840
|
-
#
|
7841
|
-
#
|
7842
|
-
#
|
7843
|
-
# - https://github.com/python/cpython/blob/
|
7844
|
-
#
|
7845
|
-
#
|
7846
|
-
# - name: str
|
7847
|
-
# - level: int
|
7848
|
-
# - pathname: str - Confusingly referred to as `fn` before the LogRecord ctor. May be empty or "(unknown file)".
|
7849
|
-
# - lineno: int - May be 0.
|
7850
|
-
# - msg: str
|
7851
|
-
# - args: tuple | dict | 1-tuple[dict]
|
7852
|
-
# - exc_info: LoggingExcInfoTuple | None
|
7853
|
-
# - func: str | None = None -> funcName
|
7854
|
-
# - sinfo: str | None = None -> stack_info
|
7855
|
-
#
|
7856
|
-
KNOWN_STD_LOGGING_RECORD_ATTRS: ta.Dict[str, ta.Any] = dict(
|
7857
|
-
# Name of the logger used to log the call. Unmodified by ctor.
|
7858
|
-
name=str,
|
7805
|
+
class LoggingContextInfoRecordAdapters:
|
7806
|
+
# Ref:
|
7807
|
+
# - https://docs.python.org/3/library/logging.html#logrecord-attributes
|
7808
|
+
#
|
7809
|
+
# LogRecord:
|
7810
|
+
# - https://github.com/python/cpython/blob/39b2f82717a69dde7212bc39b673b0f55c99e6a3/Lib/logging/__init__.py#L276 (3.8) # noqa
|
7811
|
+
# - https://github.com/python/cpython/blob/f070f54c5f4a42c7c61d1d5d3b8f3b7203b4a0fb/Lib/logging/__init__.py#L286 (~3.14) # noqa
|
7812
|
+
#
|
7859
7813
|
|
7860
|
-
|
7861
|
-
|
7862
|
-
msg=str,
|
7814
|
+
def __new__(cls, *args, **kwargs): # noqa
|
7815
|
+
raise TypeError
|
7863
7816
|
|
7864
|
-
|
7865
|
-
|
7866
|
-
|
7867
|
-
|
7817
|
+
class Adapter(Abstract, ta.Generic[T]):
|
7818
|
+
@property
|
7819
|
+
@abc.abstractmethod
|
7820
|
+
def info_cls(self) -> ta.Type[LoggingContextInfo]:
|
7821
|
+
raise NotImplementedError
|
7868
7822
|
|
7869
|
-
|
7823
|
+
#
|
7870
7824
|
|
7871
|
-
|
7872
|
-
|
7873
|
-
|
7825
|
+
@ta.final
|
7826
|
+
class NOT_SET: # noqa
|
7827
|
+
def __new__(cls, *args, **kwargs): # noqa
|
7828
|
+
raise TypeError
|
7874
7829
|
|
7875
|
-
|
7876
|
-
|
7830
|
+
class RecordAttr(ta.NamedTuple):
|
7831
|
+
name: str
|
7832
|
+
type: ta.Any
|
7833
|
+
default: ta.Any
|
7877
7834
|
|
7878
|
-
|
7835
|
+
# @abc.abstractmethod
|
7836
|
+
record_attrs: ta.ClassVar[ta.Mapping[str, RecordAttr]]
|
7879
7837
|
|
7880
|
-
|
7881
|
-
|
7882
|
-
|
7838
|
+
@property
|
7839
|
+
@abc.abstractmethod
|
7840
|
+
def _record_attrs(self) -> ta.Union[
|
7841
|
+
ta.Mapping[str, ta.Any],
|
7842
|
+
ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]],
|
7843
|
+
]:
|
7844
|
+
raise NotImplementedError
|
7883
7845
|
|
7884
|
-
|
7885
|
-
filename=str,
|
7846
|
+
#
|
7886
7847
|
|
7887
|
-
|
7888
|
-
|
7889
|
-
|
7848
|
+
@abc.abstractmethod
|
7849
|
+
def context_to_record(self, ctx: LoggingContext) -> ta.Mapping[str, ta.Any]:
|
7850
|
+
raise NotImplementedError
|
7890
7851
|
|
7891
|
-
|
7852
|
+
#
|
7892
7853
|
|
7893
|
-
|
7894
|
-
|
7854
|
+
@abc.abstractmethod
|
7855
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[T]:
|
7856
|
+
raise NotImplementedError
|
7895
7857
|
|
7896
|
-
|
7897
|
-
exc_text=ta.Optional[str],
|
7858
|
+
#
|
7898
7859
|
|
7899
|
-
|
7860
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
7861
|
+
super().__init_subclass__(**kwargs)
|
7900
7862
|
|
7901
|
-
|
7902
|
-
|
7903
|
-
# unmodified. Mostly set, if requested, by `Logger.findCaller`, to `traceback.print_stack(f)`, but prepended with
|
7904
|
-
# the literal "Stack (most recent call last):\n", and stripped of exactly one trailing `\n` if present.
|
7905
|
-
stack_info=ta.Optional[str],
|
7863
|
+
if Abstract in cls.__bases__:
|
7864
|
+
return
|
7906
7865
|
|
7907
|
-
|
7908
|
-
|
7909
|
-
|
7866
|
+
if 'record_attrs' in cls.__dict__:
|
7867
|
+
raise TypeError(cls)
|
7868
|
+
if not isinstance(ra := cls.__dict__['_record_attrs'], collections.abc.Mapping):
|
7869
|
+
raise TypeError(ra)
|
7870
|
+
|
7871
|
+
rd: ta.Dict[str, LoggingContextInfoRecordAdapters.Adapter.RecordAttr] = {}
|
7872
|
+
for n, v in ra.items():
|
7873
|
+
if not n or not isinstance(n, str) or n in rd:
|
7874
|
+
raise AttributeError(n)
|
7875
|
+
if isinstance(v, tuple):
|
7876
|
+
t, d = v
|
7877
|
+
else:
|
7878
|
+
t, d = v, cls.NOT_SET
|
7879
|
+
rd[n] = cls.RecordAttr(
|
7880
|
+
name=n,
|
7881
|
+
type=t,
|
7882
|
+
default=d,
|
7883
|
+
)
|
7884
|
+
cls.record_attrs = rd
|
7910
7885
|
|
7911
|
-
|
7912
|
-
|
7913
|
-
|
7886
|
+
class RequiredAdapter(Adapter[T], Abstract):
|
7887
|
+
@property
|
7888
|
+
@abc.abstractmethod
|
7889
|
+
def _record_attrs(self) -> ta.Mapping[str, ta.Any]:
|
7890
|
+
raise NotImplementedError
|
7914
7891
|
|
7915
|
-
|
7892
|
+
#
|
7916
7893
|
|
7917
|
-
|
7918
|
-
|
7919
|
-
|
7920
|
-
|
7921
|
-
|
7922
|
-
|
7923
|
-
created=float,
|
7894
|
+
@ta.final
|
7895
|
+
def context_to_record(self, ctx: LoggingContext) -> ta.Mapping[str, ta.Any]:
|
7896
|
+
if (info := ctx.get_info(self.info_cls)) is not None:
|
7897
|
+
return self._info_to_record(info)
|
7898
|
+
else:
|
7899
|
+
raise TypeError # FIXME: fallback?
|
7924
7900
|
|
7925
|
-
|
7926
|
-
|
7901
|
+
@abc.abstractmethod
|
7902
|
+
def _info_to_record(self, info: T) -> ta.Mapping[str, ta.Any]:
|
7903
|
+
raise NotImplementedError
|
7927
7904
|
|
7928
|
-
|
7929
|
-
relativeCreated=float,
|
7905
|
+
#
|
7930
7906
|
|
7931
|
-
|
7907
|
+
@abc.abstractmethod
|
7908
|
+
def record_to_info(self, rec: logging.LogRecord) -> T:
|
7909
|
+
raise NotImplementedError
|
7910
|
+
|
7911
|
+
#
|
7932
7912
|
|
7933
|
-
|
7934
|
-
|
7913
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
7914
|
+
super().__init_subclass__(**kwargs)
|
7935
7915
|
|
7936
|
-
|
7937
|
-
|
7916
|
+
if any(a.default is not cls.NOT_SET for a in cls.record_attrs.values()):
|
7917
|
+
raise TypeError(cls.record_attrs)
|
7938
7918
|
|
7939
|
-
|
7919
|
+
class OptionalAdapter(Adapter[T], Abstract, ta.Generic[T]):
|
7920
|
+
@property
|
7921
|
+
@abc.abstractmethod
|
7922
|
+
def _record_attrs(self) -> ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]:
|
7923
|
+
raise NotImplementedError
|
7924
|
+
|
7925
|
+
record_defaults: ta.ClassVar[ta.Mapping[str, ta.Any]]
|
7926
|
+
|
7927
|
+
#
|
7928
|
+
|
7929
|
+
@ta.final
|
7930
|
+
def context_to_record(self, ctx: LoggingContext) -> ta.Mapping[str, ta.Any]:
|
7931
|
+
if (info := ctx.get_info(self.info_cls)) is not None:
|
7932
|
+
return self._info_to_record(info)
|
7933
|
+
else:
|
7934
|
+
return self.record_defaults
|
7935
|
+
|
7936
|
+
@abc.abstractmethod
|
7937
|
+
def _info_to_record(self, info: T) -> ta.Mapping[str, ta.Any]:
|
7938
|
+
raise NotImplementedError
|
7939
|
+
|
7940
|
+
#
|
7941
|
+
|
7942
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
7943
|
+
super().__init_subclass__(**kwargs)
|
7944
|
+
|
7945
|
+
dd: ta.Dict[str, ta.Any] = {a.name: a.default for a in cls.record_attrs.values()}
|
7946
|
+
if any(d is cls.NOT_SET for d in dd.values()):
|
7947
|
+
raise TypeError(cls.record_attrs)
|
7948
|
+
cls.record_defaults = dd
|
7940
7949
|
|
7941
|
-
# Process name if available. Set to None if `logging.logMultiprocessing` is not truthy. Otherwise, set to
|
7942
|
-
# 'MainProcess', then `sys.modules.get('multiprocessing').current_process().name` if that works, otherwise remains
|
7943
|
-
# as 'MainProcess'.
|
7944
7950
|
#
|
7945
|
-
|
7951
|
+
|
7952
|
+
class Name(RequiredAdapter[LoggingContextInfos.Name]):
|
7953
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Name]] = LoggingContextInfos.Name
|
7954
|
+
|
7955
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
|
7956
|
+
# Name of the logger used to log the call. Unmodified by ctor.
|
7957
|
+
name=str,
|
7958
|
+
)
|
7959
|
+
|
7960
|
+
def _info_to_record(self, info: LoggingContextInfos.Name) -> ta.Mapping[str, ta.Any]:
|
7961
|
+
return dict(
|
7962
|
+
name=info.name,
|
7963
|
+
)
|
7964
|
+
|
7965
|
+
def record_to_info(self, rec: logging.LogRecord) -> LoggingContextInfos.Name:
|
7966
|
+
return LoggingContextInfos.Name(
|
7967
|
+
name=rec.name,
|
7968
|
+
)
|
7969
|
+
|
7970
|
+
class Level(RequiredAdapter[LoggingContextInfos.Level]):
|
7971
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Level]] = LoggingContextInfos.Level
|
7972
|
+
|
7973
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
|
7974
|
+
# Text logging level for the message ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'). Set to
|
7975
|
+
# `getLevelName(level)`.
|
7976
|
+
levelname=str,
|
7977
|
+
|
7978
|
+
# Numeric logging level for the message (DEBUG, INFO, WARNING, ERROR, CRITICAL). Unmodified by ctor.
|
7979
|
+
levelno=int,
|
7980
|
+
)
|
7981
|
+
|
7982
|
+
def _info_to_record(self, info: LoggingContextInfos.Level) -> ta.Mapping[str, ta.Any]:
|
7983
|
+
return dict(
|
7984
|
+
levelname=info.name,
|
7985
|
+
levelno=int(info.level),
|
7986
|
+
)
|
7987
|
+
|
7988
|
+
def record_to_info(self, rec: logging.LogRecord) -> LoggingContextInfos.Level:
|
7989
|
+
return LoggingContextInfos.Level.build(rec.levelno)
|
7990
|
+
|
7991
|
+
class Msg(RequiredAdapter[LoggingContextInfos.Msg]):
|
7992
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Msg]] = LoggingContextInfos.Msg
|
7993
|
+
|
7994
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
|
7995
|
+
# The format string passed in the original logging call. Merged with args to produce message, or an
|
7996
|
+
# arbitrary object (see Using arbitrary objects as messages). Unmodified by ctor.
|
7997
|
+
msg=str,
|
7998
|
+
|
7999
|
+
# The tuple of arguments merged into msg to produce message, or a dict whose values are used for the merge
|
8000
|
+
# (when there is only one argument, and it is a dictionary). Ctor will transform a 1-tuple containing a
|
8001
|
+
# Mapping into just the mapping, but is otherwise unmodified.
|
8002
|
+
args=ta.Union[tuple, dict, None],
|
8003
|
+
)
|
8004
|
+
|
8005
|
+
def _info_to_record(self, info: LoggingContextInfos.Msg) -> ta.Mapping[str, ta.Any]:
|
8006
|
+
return dict(
|
8007
|
+
msg=info.msg,
|
8008
|
+
args=info.args,
|
8009
|
+
)
|
8010
|
+
|
8011
|
+
def record_to_info(self, rec: logging.LogRecord) -> LoggingContextInfos.Msg:
|
8012
|
+
return LoggingContextInfos.Msg(
|
8013
|
+
msg=rec.msg,
|
8014
|
+
args=rec.args,
|
8015
|
+
)
|
8016
|
+
|
8017
|
+
# FIXME: handled specially - all unknown attrs on LogRecord
|
8018
|
+
# class Extra(Adapter[LoggingContextInfos.Extra]):
|
8019
|
+
# _record_attrs: ta.ClassVar[ta.Mapping[str, ta.Union[ta.Any, ta.Tuple[ta.Any, ta.Any]]]] = dict()
|
7946
8020
|
#
|
7947
|
-
#
|
7948
|
-
#
|
8021
|
+
# def info_to_record(self, info: ta.Optional[LoggingContextInfos.Extra]) -> ta.Mapping[str, ta.Any]:
|
8022
|
+
# # FIXME:
|
8023
|
+
# # if extra is not None:
|
8024
|
+
# # for key in extra:
|
8025
|
+
# # if (key in ["message", "asctime"]) or (key in rv.__dict__):
|
8026
|
+
# # raise KeyError("Attempt to overwrite %r in LogRecord" % key)
|
8027
|
+
# # rv.__dict__[key] = extra[key]
|
8028
|
+
# return dict()
|
7949
8029
|
#
|
7950
|
-
|
8030
|
+
# def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Extra]:
|
8031
|
+
# return None
|
8032
|
+
|
8033
|
+
class Time(RequiredAdapter[LoggingContextInfos.Time]):
|
8034
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Time]] = LoggingContextInfos.Time
|
8035
|
+
|
8036
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
|
8037
|
+
# Time when the LogRecord was created. Set to `time.time_ns() / 1e9` for >=3.13.0b1, otherwise simply
|
8038
|
+
# `time.time()`.
|
8039
|
+
#
|
8040
|
+
# See:
|
8041
|
+
# - https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
|
8042
|
+
# - https://github.com/python/cpython/commit/1500a23f33f5a6d052ff1ef6383d9839928b8ff1
|
8043
|
+
#
|
8044
|
+
created=float,
|
8045
|
+
|
8046
|
+
# Millisecond portion of the time when the LogRecord was created.
|
8047
|
+
msecs=float,
|
8048
|
+
|
8049
|
+
# Time in milliseconds when the LogRecord was created, relative to the time the logging module was loaded.
|
8050
|
+
relativeCreated=float,
|
8051
|
+
)
|
7951
8052
|
|
7952
|
-
|
7953
|
-
|
7954
|
-
|
8053
|
+
def _info_to_record(self, info: LoggingContextInfos.Time) -> ta.Mapping[str, ta.Any]:
|
8054
|
+
return dict(
|
8055
|
+
created=info.secs,
|
8056
|
+
msecs=info.msecs,
|
8057
|
+
relativeCreated=info.relative_secs,
|
8058
|
+
)
|
7955
8059
|
|
7956
|
-
|
8060
|
+
def record_to_info(self, rec: logging.LogRecord) -> LoggingContextInfos.Time:
|
8061
|
+
return LoggingContextInfos.Time.build(
|
8062
|
+
int(rec.created * 1e9),
|
8063
|
+
)
|
7957
8064
|
|
7958
|
-
|
7959
|
-
|
7960
|
-
|
7961
|
-
|
8065
|
+
class Exc(OptionalAdapter[LoggingContextInfos.Exc]):
|
8066
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Exc]] = LoggingContextInfos.Exc
|
8067
|
+
|
8068
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
|
8069
|
+
# Exception tuple (à la sys.exc_info) or, if no exception has occurred, None. Unmodified by ctor.
|
8070
|
+
exc_info=(ta.Optional[LoggingExcInfoTuple], None),
|
8071
|
+
|
8072
|
+
# Used to cache the traceback text. Simply set to None by ctor, later set by Formatter.format.
|
8073
|
+
exc_text=(ta.Optional[str], None),
|
8074
|
+
)
|
8075
|
+
|
8076
|
+
def _info_to_record(self, info: LoggingContextInfos.Exc) -> ta.Mapping[str, ta.Any]:
|
8077
|
+
return dict(
|
8078
|
+
exc_info=info.info_tuple,
|
8079
|
+
exc_text=None,
|
8080
|
+
)
|
8081
|
+
|
8082
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Exc]:
|
8083
|
+
# FIXME:
|
8084
|
+
# error: Argument 1 to "build" of "Exc" has incompatible type
|
8085
|
+
# "tuple[type[BaseException], BaseException, TracebackType | None] | tuple[None, None, None] | None"; expected # noqa
|
8086
|
+
# "BaseException | tuple[type[BaseException], BaseException, TracebackType | None] | bool | None" [arg-type] # noqa
|
8087
|
+
return LoggingContextInfos.Exc.build(rec.exc_info) # type: ignore[arg-type]
|
7962
8088
|
|
7963
|
-
|
8089
|
+
class Caller(OptionalAdapter[LoggingContextInfos.Caller]):
|
8090
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Caller]] = LoggingContextInfos.Caller
|
8091
|
+
|
8092
|
+
_UNKNOWN_PATH_NAME: ta.ClassVar[str] = '(unknown file)'
|
8093
|
+
_UNKNOWN_FUNC_NAME: ta.ClassVar[str] = '(unknown function)'
|
8094
|
+
|
8095
|
+
_STACK_INFO_PREFIX: ta.ClassVar[str] = 'Stack (most recent call last):\n'
|
8096
|
+
|
8097
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
|
8098
|
+
# Full pathname of the source file where the logging call was issued (if available). Unmodified by ctor. May
|
8099
|
+
# default to "(unknown file)" by Logger.findCaller / Logger._log.
|
8100
|
+
pathname=(str, _UNKNOWN_PATH_NAME),
|
8101
|
+
|
8102
|
+
# Source line number where the logging call was issued (if available). Unmodified by ctor. May default to 0
|
8103
|
+
# y Logger.findCaller / Logger._log.
|
8104
|
+
lineno=(int, 0),
|
8105
|
+
|
8106
|
+
# Name of function containing the logging call. Set by ctor to `func` arg, unmodified. May default to
|
8107
|
+
# "(unknown function)" by Logger.findCaller / Logger._log.
|
8108
|
+
funcName=(str, _UNKNOWN_FUNC_NAME),
|
8109
|
+
|
8110
|
+
# Stack frame information (where available) from the bottom of the stack in the current thread, up to and
|
8111
|
+
# including the stack frame of the logging call which resulted in the creation of this record. Set by ctor
|
8112
|
+
# to `sinfo` arg, unmodified. Mostly set, if requested, by `Logger.findCaller`, to
|
8113
|
+
# `traceback.print_stack(f)`, but prepended with the literal "Stack (most recent call last):\n", and
|
8114
|
+
# stripped of exactly one trailing `\n` if present.
|
8115
|
+
stack_info=(ta.Optional[str], None),
|
8116
|
+
)
|
8117
|
+
|
8118
|
+
def _info_to_record(self, caller: LoggingContextInfos.Caller) -> ta.Mapping[str, ta.Any]:
|
8119
|
+
if (sinfo := caller.stack_info) is not None:
|
8120
|
+
stack_info: ta.Optional[str] = '\n'.join([
|
8121
|
+
self._STACK_INFO_PREFIX,
|
8122
|
+
sinfo[1:] if sinfo.endswith('\n') else sinfo,
|
8123
|
+
])
|
8124
|
+
else:
|
8125
|
+
stack_info = None
|
8126
|
+
|
8127
|
+
return dict(
|
8128
|
+
pathname=caller.file_path,
|
8129
|
+
|
8130
|
+
lineno=caller.line_no,
|
8131
|
+
funcName=caller.func_name,
|
8132
|
+
|
8133
|
+
stack_info=stack_info,
|
8134
|
+
)
|
8135
|
+
|
8136
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Caller]:
|
8137
|
+
# FIXME: piecemeal?
|
8138
|
+
if (
|
8139
|
+
rec.pathname != self._UNKNOWN_PATH_NAME and
|
8140
|
+
rec.lineno != 0 and
|
8141
|
+
rec.funcName != self._UNKNOWN_FUNC_NAME
|
8142
|
+
):
|
8143
|
+
if (sinfo := rec.stack_info) is not None and sinfo.startswith(self._STACK_INFO_PREFIX):
|
8144
|
+
sinfo = sinfo[len(self._STACK_INFO_PREFIX):]
|
8145
|
+
return LoggingContextInfos.Caller(
|
8146
|
+
file_path=rec.pathname,
|
8147
|
+
|
8148
|
+
line_no=rec.lineno,
|
8149
|
+
func_name=rec.funcName,
|
8150
|
+
|
8151
|
+
stack_info=sinfo,
|
8152
|
+
)
|
8153
|
+
|
8154
|
+
return None
|
8155
|
+
|
8156
|
+
class SourceFile(Adapter[LoggingContextInfos.SourceFile]):
|
8157
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.SourceFile]] = LoggingContextInfos.SourceFile
|
8158
|
+
|
8159
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
|
8160
|
+
# Filename portion of pathname. Set to `os.path.basename(pathname)` if successful, otherwise defaults to
|
8161
|
+
# pathname.
|
8162
|
+
filename=str,
|
8163
|
+
|
8164
|
+
# Module (name portion of filename). Set to `os.path.splitext(filename)[0]`, otherwise defaults to
|
8165
|
+
# "Unknown module".
|
8166
|
+
module=str,
|
8167
|
+
)
|
8168
|
+
|
8169
|
+
_UNKNOWN_MODULE: ta.ClassVar[str] = 'Unknown module'
|
8170
|
+
|
8171
|
+
def context_to_record(self, ctx: LoggingContext) -> ta.Mapping[str, ta.Any]:
|
8172
|
+
if (info := ctx.get_info(LoggingContextInfos.SourceFile)) is not None:
|
8173
|
+
return dict(
|
8174
|
+
filename=info.file_name,
|
8175
|
+
module=info.module,
|
8176
|
+
)
|
8177
|
+
|
8178
|
+
if (caller := ctx.get_info(LoggingContextInfos.Caller)) is not None:
|
8179
|
+
return dict(
|
8180
|
+
filename=caller.file_path,
|
8181
|
+
module=self._UNKNOWN_MODULE,
|
8182
|
+
)
|
8183
|
+
|
8184
|
+
return dict(
|
8185
|
+
filename=LoggingContextInfoRecordAdapters.Caller._UNKNOWN_PATH_NAME, # noqa
|
8186
|
+
module=self._UNKNOWN_MODULE,
|
8187
|
+
)
|
8188
|
+
|
8189
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.SourceFile]:
|
8190
|
+
if (
|
8191
|
+
rec.module is not None and
|
8192
|
+
rec.module != self._UNKNOWN_MODULE
|
8193
|
+
):
|
8194
|
+
return LoggingContextInfos.SourceFile(
|
8195
|
+
file_name=rec.filename,
|
8196
|
+
module=rec.module, # FIXME: piecemeal?
|
8197
|
+
)
|
8198
|
+
|
8199
|
+
return None
|
8200
|
+
|
8201
|
+
class Thread(OptionalAdapter[LoggingContextInfos.Thread]):
|
8202
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Thread]] = LoggingContextInfos.Thread
|
8203
|
+
|
8204
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
|
8205
|
+
# Thread ID if available, and `logging.logThreads` is truthy.
|
8206
|
+
thread=(ta.Optional[int], None),
|
8207
|
+
|
8208
|
+
# Thread name if available, and `logging.logThreads` is truthy.
|
8209
|
+
threadName=(ta.Optional[str], None),
|
8210
|
+
)
|
8211
|
+
|
8212
|
+
def _info_to_record(self, info: LoggingContextInfos.Thread) -> ta.Mapping[str, ta.Any]:
|
8213
|
+
if logging.logThreads:
|
8214
|
+
return dict(
|
8215
|
+
thread=info.ident,
|
8216
|
+
threadName=info.name,
|
8217
|
+
)
|
8218
|
+
|
8219
|
+
return self.record_defaults
|
8220
|
+
|
8221
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Thread]:
|
8222
|
+
if (
|
8223
|
+
(ident := rec.thread) is not None and
|
8224
|
+
(name := rec.threadName) is not None
|
8225
|
+
):
|
8226
|
+
return LoggingContextInfos.Thread(
|
8227
|
+
ident=ident,
|
8228
|
+
native_id=None,
|
8229
|
+
name=name,
|
8230
|
+
)
|
8231
|
+
|
8232
|
+
return None
|
8233
|
+
|
8234
|
+
class Process(OptionalAdapter[LoggingContextInfos.Process]):
|
8235
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Process]] = LoggingContextInfos.Process
|
8236
|
+
|
8237
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
|
8238
|
+
# Process ID if available - that is, if `hasattr(os, 'getpid')` - and `logging.logProcesses` is truthy,
|
8239
|
+
# otherwise None.
|
8240
|
+
process=(ta.Optional[int], None),
|
8241
|
+
)
|
8242
|
+
|
8243
|
+
def _info_to_record(self, info: LoggingContextInfos.Process) -> ta.Mapping[str, ta.Any]:
|
8244
|
+
if logging.logProcesses:
|
8245
|
+
return dict(
|
8246
|
+
process=info.pid,
|
8247
|
+
)
|
8248
|
+
|
8249
|
+
return self.record_defaults
|
8250
|
+
|
8251
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Process]:
|
8252
|
+
if (
|
8253
|
+
(pid := rec.process) is not None
|
8254
|
+
):
|
8255
|
+
return LoggingContextInfos.Process(
|
8256
|
+
pid=pid,
|
8257
|
+
)
|
8258
|
+
|
8259
|
+
return None
|
8260
|
+
|
8261
|
+
class Multiprocessing(OptionalAdapter[LoggingContextInfos.Multiprocessing]):
|
8262
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Multiprocessing]] = LoggingContextInfos.Multiprocessing
|
8263
|
+
|
8264
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
|
8265
|
+
# Process name if available. Set to None if `logging.logMultiprocessing` is not truthy. Otherwise, set to
|
8266
|
+
# 'MainProcess', then `sys.modules.get('multiprocessing').current_process().name` if that works, otherwise
|
8267
|
+
# remains as 'MainProcess'.
|
8268
|
+
#
|
8269
|
+
# As noted by stdlib:
|
8270
|
+
#
|
8271
|
+
# Errors may occur if multiprocessing has not finished loading yet - e.g. if a custom import hook causes
|
8272
|
+
# third-party code to run when multiprocessing calls import. See issue 8200 for an example
|
8273
|
+
#
|
8274
|
+
processName=(ta.Optional[str], None),
|
8275
|
+
)
|
8276
|
+
|
8277
|
+
def _info_to_record(self, info: LoggingContextInfos.Multiprocessing) -> ta.Mapping[str, ta.Any]:
|
8278
|
+
if logging.logMultiprocessing:
|
8279
|
+
return dict(
|
8280
|
+
processName=info.process_name,
|
8281
|
+
)
|
8282
|
+
|
8283
|
+
return self.record_defaults
|
8284
|
+
|
8285
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Multiprocessing]:
|
8286
|
+
if (
|
8287
|
+
(process_name := rec.processName) is not None
|
8288
|
+
):
|
8289
|
+
return LoggingContextInfos.Multiprocessing(
|
8290
|
+
process_name=process_name,
|
8291
|
+
)
|
8292
|
+
|
8293
|
+
return None
|
8294
|
+
|
8295
|
+
class AsyncioTask(OptionalAdapter[LoggingContextInfos.AsyncioTask]):
|
8296
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.AsyncioTask]] = LoggingContextInfos.AsyncioTask
|
8297
|
+
|
8298
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Union[ta.Any, ta.Tuple[ta.Any, ta.Any]]]] = dict(
|
8299
|
+
# Absent <3.12, otherwise asyncio.Task name if available, and `logging.logAsyncioTasks` is truthy. Set to
|
8300
|
+
# `sys.modules.get('asyncio').current_task().get_name()`, otherwise None.
|
8301
|
+
taskName=(ta.Optional[str], None),
|
8302
|
+
)
|
8303
|
+
|
8304
|
+
def _info_to_record(self, info: LoggingContextInfos.AsyncioTask) -> ta.Mapping[str, ta.Any]:
|
8305
|
+
if getattr(logging, 'logAsyncioTasks', None): # Absent <3.12
|
8306
|
+
return dict(
|
8307
|
+
taskName=info.name,
|
8308
|
+
)
|
8309
|
+
|
8310
|
+
return self.record_defaults
|
8311
|
+
|
8312
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.AsyncioTask]:
|
8313
|
+
if (
|
8314
|
+
(name := getattr(rec, 'taskName', None)) is not None
|
8315
|
+
):
|
8316
|
+
return LoggingContextInfos.AsyncioTask(
|
8317
|
+
name=name,
|
8318
|
+
)
|
8319
|
+
|
8320
|
+
return None
|
8321
|
+
|
8322
|
+
|
8323
|
+
_LOGGING_CONTEXT_INFO_RECORD_ADAPTERS_: ta.Sequence[LoggingContextInfoRecordAdapters.Adapter] = [ # noqa
|
8324
|
+
LoggingContextInfoRecordAdapters.Name(),
|
8325
|
+
LoggingContextInfoRecordAdapters.Level(),
|
8326
|
+
LoggingContextInfoRecordAdapters.Msg(),
|
8327
|
+
LoggingContextInfoRecordAdapters.Time(),
|
8328
|
+
LoggingContextInfoRecordAdapters.Exc(),
|
8329
|
+
LoggingContextInfoRecordAdapters.Caller(),
|
8330
|
+
LoggingContextInfoRecordAdapters.SourceFile(),
|
8331
|
+
LoggingContextInfoRecordAdapters.Thread(),
|
8332
|
+
LoggingContextInfoRecordAdapters.Process(),
|
8333
|
+
LoggingContextInfoRecordAdapters.Multiprocessing(),
|
8334
|
+
LoggingContextInfoRecordAdapters.AsyncioTask(),
|
8335
|
+
]
|
8336
|
+
|
8337
|
+
_LOGGING_CONTEXT_INFO_RECORD_ADAPTERS: ta.Mapping[ta.Type[LoggingContextInfo], LoggingContextInfoRecordAdapters.Adapter] = { # noqa
|
8338
|
+
ad.info_cls: ad for ad in _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS_
|
8339
|
+
}
|
8340
|
+
|
8341
|
+
|
8342
|
+
##
|
7964
8343
|
|
7965
8344
|
|
7966
8345
|
# Formatter:
|
7967
8346
|
# - https://github.com/python/cpython/blob/39b2f82717a69dde7212bc39b673b0f55c99e6a3/Lib/logging/__init__.py#L514 (3.8)
|
7968
8347
|
# - https://github.com/python/cpython/blob/f070f54c5f4a42c7c61d1d5d3b8f3b7203b4a0fb/Lib/logging/__init__.py#L554 (~3.14) # noqa
|
7969
8348
|
#
|
7970
|
-
|
8349
|
+
_KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTRS: ta.Dict[str, ta.Any] = dict(
|
7971
8350
|
# The logged message, computed as msg % args. Set to `record.getMessage()`.
|
7972
8351
|
message=str,
|
7973
8352
|
|
@@ -7981,20 +8360,31 @@ KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTRS: ta.Dict[str, ta.Any] = dict(
|
|
7981
8360
|
exc_text=ta.Optional[str],
|
7982
8361
|
)
|
7983
8362
|
|
7984
|
-
KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTR_SET: ta.FrozenSet[str] = frozenset(KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTRS)
|
7985
|
-
|
7986
8363
|
|
7987
8364
|
##
|
7988
8365
|
|
7989
8366
|
|
8367
|
+
_KNOWN_STD_LOGGING_RECORD_ATTR_SET: ta.FrozenSet[str] = frozenset(
|
8368
|
+
a for ad in _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS.values() for a in ad.record_attrs
|
8369
|
+
)
|
8370
|
+
|
8371
|
+
_KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTR_SET: ta.FrozenSet[str] = frozenset(_KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTRS)
|
8372
|
+
|
8373
|
+
|
7990
8374
|
class UnknownStdLoggingRecordAttrsWarning(LoggingSetupWarning):
|
7991
8375
|
pass
|
7992
8376
|
|
7993
8377
|
|
7994
8378
|
def _check_std_logging_record_attrs() -> None:
|
8379
|
+
if (
|
8380
|
+
len([a for ad in _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS.values() for a in ad.record_attrs]) !=
|
8381
|
+
len(_KNOWN_STD_LOGGING_RECORD_ATTR_SET)
|
8382
|
+
):
|
8383
|
+
raise RuntimeError('Duplicate LoggingContextInfoRecordAdapter record attrs')
|
8384
|
+
|
7995
8385
|
rec_dct = dict(logging.makeLogRecord({}).__dict__)
|
7996
8386
|
|
7997
|
-
if (unk_rec_fields := frozenset(rec_dct) -
|
8387
|
+
if (unk_rec_fields := frozenset(rec_dct) - _KNOWN_STD_LOGGING_RECORD_ATTR_SET):
|
7998
8388
|
import warnings # noqa
|
7999
8389
|
|
8000
8390
|
warnings.warn(
|
@@ -8010,116 +8400,43 @@ _check_std_logging_record_attrs()
|
|
8010
8400
|
|
8011
8401
|
|
8012
8402
|
class LoggingContextLogRecord(logging.LogRecord):
|
8013
|
-
|
8014
|
-
|
8015
|
-
|
8016
|
-
|
8017
|
-
|
8018
|
-
|
8019
|
-
|
8020
|
-
|
8021
|
-
|
8022
|
-
|
8023
|
-
# name,
|
8024
|
-
# level,
|
8025
|
-
# pathname,
|
8026
|
-
# lineno,
|
8027
|
-
# msg,
|
8028
|
-
# args,
|
8029
|
-
# exc_info,
|
8030
|
-
# func=None,
|
8031
|
-
# sinfo=None,
|
8032
|
-
# **kwargs,
|
8033
|
-
*,
|
8034
|
-
name: str,
|
8035
|
-
msg: str,
|
8036
|
-
args: ta.Union[tuple, dict],
|
8037
|
-
|
8038
|
-
_logging_context: LoggingContext,
|
8039
|
-
) -> None:
|
8040
|
-
ctx = _logging_context
|
8041
|
-
|
8042
|
-
self.name: str = name
|
8043
|
-
|
8044
|
-
self.msg: str = msg
|
8403
|
+
# LogRecord.__init__ args:
|
8404
|
+
# - name: str
|
8405
|
+
# - level: int
|
8406
|
+
# - pathname: str - Confusingly referred to as `fn` before the LogRecord ctor. May be empty or "(unknown file)".
|
8407
|
+
# - lineno: int - May be 0.
|
8408
|
+
# - msg: str
|
8409
|
+
# - args: tuple | dict | 1-tuple[dict]
|
8410
|
+
# - exc_info: LoggingExcInfoTuple | None
|
8411
|
+
# - func: str | None = None -> funcName
|
8412
|
+
# - sinfo: str | None = None -> stack_info
|
8045
8413
|
|
8046
|
-
|
8047
|
-
|
8048
|
-
args = args[0] # type: ignore[assignment]
|
8049
|
-
self.args: ta.Union[tuple, dict] = args
|
8414
|
+
def __init__(self, *, _logging_context: LoggingContext) -> None: # noqa
|
8415
|
+
self._logging_context = _logging_context
|
8050
8416
|
|
8051
|
-
|
8052
|
-
|
8417
|
+
for ad in _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS_:
|
8418
|
+
self.__dict__.update(ad.context_to_record(_logging_context))
|
8053
8419
|
|
8054
|
-
if (caller := ctx.caller()) is not None:
|
8055
|
-
self.pathname: str = caller.file_path
|
8056
|
-
else:
|
8057
|
-
self.pathname = self._UNKNOWN_PATH_NAME
|
8058
|
-
|
8059
|
-
if (src_file := ctx.source_file()) is not None:
|
8060
|
-
self.filename: str = src_file.file_name
|
8061
|
-
self.module: str = src_file.module
|
8062
|
-
else:
|
8063
|
-
self.filename = self.pathname
|
8064
|
-
self.module = self._UNKNOWN_MODULE
|
8065
|
-
|
8066
|
-
self.exc_info: ta.Optional[LoggingExcInfoTuple] = ctx.exc_info_tuple
|
8067
|
-
self.exc_text: ta.Optional[str] = None
|
8068
|
-
|
8069
|
-
# If ctx.build_caller() was never called, we simply don't have a stack trace.
|
8070
|
-
if caller is not None:
|
8071
|
-
if (sinfo := caller.stack_info) is not None:
|
8072
|
-
self.stack_info: ta.Optional[str] = '\n'.join([
|
8073
|
-
self._STACK_INFO_PREFIX,
|
8074
|
-
sinfo[1:] if sinfo.endswith('\n') else sinfo,
|
8075
|
-
])
|
8076
|
-
else:
|
8077
|
-
self.stack_info = None
|
8078
8420
|
|
8079
|
-
|
8080
|
-
self.funcName: str = caller.name
|
8081
|
-
|
8082
|
-
else:
|
8083
|
-
self.stack_info = None
|
8084
|
-
|
8085
|
-
self.lineno = 0
|
8086
|
-
self.funcName = self._UNKNOWN_FUNC_NAME
|
8421
|
+
##
|
8087
8422
|
|
8088
|
-
times = ctx.times
|
8089
|
-
self.created: float = times.created
|
8090
|
-
self.msecs: float = times.msecs
|
8091
|
-
self.relativeCreated: float = times.relative_created
|
8092
8423
|
|
8093
|
-
|
8094
|
-
|
8095
|
-
|
8096
|
-
|
8097
|
-
|
8098
|
-
self.thread = None
|
8099
|
-
self.threadName = None
|
8424
|
+
@ta.final
|
8425
|
+
class LogRecordLoggingContext(LoggingContext):
|
8426
|
+
def __init__(self, rec: logging.LogRecord) -> None:
|
8427
|
+
if isinstance(rec, LoggingContextLogRecord):
|
8428
|
+
raise TypeError(rec)
|
8100
8429
|
|
8101
|
-
|
8102
|
-
process = check.not_none(ctx.process())
|
8103
|
-
self.process: ta.Optional[int] = process.pid
|
8104
|
-
else:
|
8105
|
-
self.process = None
|
8430
|
+
self._rec = rec
|
8106
8431
|
|
8107
|
-
|
8108
|
-
|
8109
|
-
|
8110
|
-
|
8111
|
-
|
8112
|
-
else:
|
8113
|
-
self.processName = None
|
8432
|
+
self._infos: ta.Dict[ta.Type[LoggingContextInfo], LoggingContextInfo] = {
|
8433
|
+
type(info): info
|
8434
|
+
for ad in _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS_
|
8435
|
+
if (info := ad.record_to_info(rec)) is not None
|
8436
|
+
}
|
8114
8437
|
|
8115
|
-
|
8116
|
-
|
8117
|
-
if (at := ctx.asyncio_task()) is not None:
|
8118
|
-
self.taskName: ta.Optional[str] = at.name
|
8119
|
-
else:
|
8120
|
-
self.taskName = None
|
8121
|
-
else:
|
8122
|
-
self.taskName = None
|
8438
|
+
def get_info(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
|
8439
|
+
return self._infos.get(ty)
|
8123
8440
|
|
8124
8441
|
|
8125
8442
|
########################################
|
@@ -8446,21 +8763,20 @@ class StdLogger(Logger):
|
|
8446
8763
|
return self._std.getEffectiveLevel()
|
8447
8764
|
|
8448
8765
|
def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any) -> None:
|
8449
|
-
if not self.is_enabled_for(ctx.level):
|
8766
|
+
if not self.is_enabled_for(ctx.must_get_info(LoggingContextInfos.Level).level):
|
8450
8767
|
return
|
8451
8768
|
|
8452
|
-
ctx.
|
8453
|
-
|
8454
|
-
ms, args = self._prepare_msg_args(msg, *args)
|
8455
|
-
|
8456
|
-
rec = LoggingContextLogRecord(
|
8769
|
+
ctx.set_basic(
|
8457
8770
|
name=self._std.name,
|
8458
|
-
msg=ms,
|
8459
|
-
args=args,
|
8460
8771
|
|
8461
|
-
|
8772
|
+
msg=msg,
|
8773
|
+
args=args,
|
8462
8774
|
)
|
8463
8775
|
|
8776
|
+
ctx.capture()
|
8777
|
+
|
8778
|
+
rec = LoggingContextLogRecord(_logging_context=ctx)
|
8779
|
+
|
8464
8780
|
self._std.handle(rec)
|
8465
8781
|
|
8466
8782
|
|