ominfra 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.
- ominfra/scripts/journald2aws.py +1007 -691
- ominfra/scripts/manage.py +995 -679
- ominfra/scripts/supervisor.py +993 -677
- {ominfra-0.0.0.dev430.dist-info → ominfra-0.0.0.dev432.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev430.dist-info → ominfra-0.0.0.dev432.dist-info}/RECORD +9 -9
- {ominfra-0.0.0.dev430.dist-info → ominfra-0.0.0.dev432.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev430.dist-info → ominfra-0.0.0.dev432.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev430.dist-info → ominfra-0.0.0.dev432.dist-info}/licenses/LICENSE +0 -0
- {ominfra-0.0.0.dev430.dist-info → ominfra-0.0.0.dev432.dist-info}/top_level.txt +0 -0
ominfra/scripts/manage.py
CHANGED
@@ -133,6 +133,13 @@ U = ta.TypeVar('U')
|
|
133
133
|
# ../../omlish/lite/timeouts.py
|
134
134
|
TimeoutLike = ta.Union['Timeout', ta.Type['Timeout.DEFAULT'], ta.Iterable['TimeoutLike'], float, None] # ta.TypeAlias
|
135
135
|
|
136
|
+
# ../../omlish/logs/infos.py
|
137
|
+
LoggingMsgFn = ta.Callable[[], ta.Union[str, tuple]] # ta.TypeAlias
|
138
|
+
LoggingExcInfoTuple = ta.Tuple[ta.Type[BaseException], BaseException, ta.Optional[types.TracebackType]] # ta.TypeAlias
|
139
|
+
LoggingExcInfo = ta.Union[BaseException, LoggingExcInfoTuple] # ta.TypeAlias
|
140
|
+
LoggingExcInfoArg = ta.Union[LoggingExcInfo, bool, None] # ta.TypeAlias
|
141
|
+
LoggingContextInfo = ta.Any # ta.TypeAlias
|
142
|
+
|
136
143
|
# ../../omlish/os/atomics.py
|
137
144
|
AtomicPathSwapKind = ta.Literal['dir', 'file']
|
138
145
|
AtomicPathSwapState = ta.Literal['open', 'committed', 'aborted'] # ta.TypeAlias
|
@@ -151,16 +158,11 @@ InjectorProviderFnMap = ta.Mapping['InjectorKey', 'InjectorProviderFn']
|
|
151
158
|
InjectorBindingOrBindings = ta.Union['InjectorBinding', 'InjectorBindings']
|
152
159
|
|
153
160
|
# ../../omlish/logs/contexts.py
|
154
|
-
|
155
|
-
LoggingExcInfo = ta.Union[BaseException, LoggingExcInfoTuple] # ta.TypeAlias
|
156
|
-
LoggingExcInfoArg = ta.Union[LoggingExcInfo, bool, None] # ta.TypeAlias
|
161
|
+
LoggingContextInfoT = ta.TypeVar('LoggingContextInfoT', bound=LoggingContextInfo)
|
157
162
|
|
158
163
|
# deploy/specs.py
|
159
164
|
KeyDeployTagT = ta.TypeVar('KeyDeployTagT', bound='KeyDeployTag')
|
160
165
|
|
161
|
-
# ../../omlish/logs/base.py
|
162
|
-
LoggingMsgFn = ta.Callable[[], ta.Union[str, tuple]] # ta.TypeAlias
|
163
|
-
|
164
166
|
# ../../omlish/subprocesses/base.py
|
165
167
|
SubprocessChannelOption = ta.Literal['pipe', 'stdout', 'devnull'] # ta.TypeAlias
|
166
168
|
|
@@ -3991,124 +3993,6 @@ def typing_annotations_attr() -> str:
|
|
3991
3993
|
return _TYPING_ANNOTATIONS_ATTR
|
3992
3994
|
|
3993
3995
|
|
3994
|
-
########################################
|
3995
|
-
# ../../../omlish/logs/infos.py
|
3996
|
-
|
3997
|
-
|
3998
|
-
##
|
3999
|
-
|
4000
|
-
|
4001
|
-
def logging_context_info(cls):
|
4002
|
-
return cls
|
4003
|
-
|
4004
|
-
|
4005
|
-
##
|
4006
|
-
|
4007
|
-
|
4008
|
-
@logging_context_info
|
4009
|
-
@ta.final
|
4010
|
-
class LoggingSourceFileInfo(ta.NamedTuple):
|
4011
|
-
file_name: str
|
4012
|
-
module: str
|
4013
|
-
|
4014
|
-
@classmethod
|
4015
|
-
def build(cls, file_path: ta.Optional[str]) -> ta.Optional['LoggingSourceFileInfo']:
|
4016
|
-
if file_path is None:
|
4017
|
-
return None
|
4018
|
-
|
4019
|
-
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L331-L336 # noqa
|
4020
|
-
try:
|
4021
|
-
file_name = os.path.basename(file_path)
|
4022
|
-
module = os.path.splitext(file_name)[0]
|
4023
|
-
except (TypeError, ValueError, AttributeError):
|
4024
|
-
return None
|
4025
|
-
|
4026
|
-
return cls(
|
4027
|
-
file_name=file_name,
|
4028
|
-
module=module,
|
4029
|
-
)
|
4030
|
-
|
4031
|
-
|
4032
|
-
##
|
4033
|
-
|
4034
|
-
|
4035
|
-
@logging_context_info
|
4036
|
-
@ta.final
|
4037
|
-
class LoggingThreadInfo(ta.NamedTuple):
|
4038
|
-
ident: int
|
4039
|
-
native_id: ta.Optional[int]
|
4040
|
-
name: str
|
4041
|
-
|
4042
|
-
@classmethod
|
4043
|
-
def build(cls) -> 'LoggingThreadInfo':
|
4044
|
-
return cls(
|
4045
|
-
ident=threading.get_ident(),
|
4046
|
-
native_id=threading.get_native_id() if hasattr(threading, 'get_native_id') else None,
|
4047
|
-
name=threading.current_thread().name,
|
4048
|
-
)
|
4049
|
-
|
4050
|
-
|
4051
|
-
##
|
4052
|
-
|
4053
|
-
|
4054
|
-
@logging_context_info
|
4055
|
-
@ta.final
|
4056
|
-
class LoggingProcessInfo(ta.NamedTuple):
|
4057
|
-
pid: int
|
4058
|
-
|
4059
|
-
@classmethod
|
4060
|
-
def build(cls) -> 'LoggingProcessInfo':
|
4061
|
-
return cls(
|
4062
|
-
pid=os.getpid(),
|
4063
|
-
)
|
4064
|
-
|
4065
|
-
|
4066
|
-
##
|
4067
|
-
|
4068
|
-
|
4069
|
-
@logging_context_info
|
4070
|
-
@ta.final
|
4071
|
-
class LoggingMultiprocessingInfo(ta.NamedTuple):
|
4072
|
-
process_name: str
|
4073
|
-
|
4074
|
-
@classmethod
|
4075
|
-
def build(cls) -> ta.Optional['LoggingMultiprocessingInfo']:
|
4076
|
-
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L355-L364 # noqa
|
4077
|
-
if (mp := sys.modules.get('multiprocessing')) is None:
|
4078
|
-
return None
|
4079
|
-
|
4080
|
-
return cls(
|
4081
|
-
process_name=mp.current_process().name,
|
4082
|
-
)
|
4083
|
-
|
4084
|
-
|
4085
|
-
##
|
4086
|
-
|
4087
|
-
|
4088
|
-
@logging_context_info
|
4089
|
-
@ta.final
|
4090
|
-
class LoggingAsyncioTaskInfo(ta.NamedTuple):
|
4091
|
-
name: str
|
4092
|
-
|
4093
|
-
@classmethod
|
4094
|
-
def build(cls) -> ta.Optional['LoggingAsyncioTaskInfo']:
|
4095
|
-
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L372-L377 # noqa
|
4096
|
-
if (asyncio := sys.modules.get('asyncio')) is None:
|
4097
|
-
return None
|
4098
|
-
|
4099
|
-
try:
|
4100
|
-
task = asyncio.current_task()
|
4101
|
-
except Exception: # noqa
|
4102
|
-
return None
|
4103
|
-
|
4104
|
-
if task is None:
|
4105
|
-
return None
|
4106
|
-
|
4107
|
-
return cls(
|
4108
|
-
name=task.get_name(), # Always non-None
|
4109
|
-
)
|
4110
|
-
|
4111
|
-
|
4112
3996
|
########################################
|
4113
3997
|
# ../../../omlish/logs/levels.py
|
4114
3998
|
|
@@ -7623,74 +7507,362 @@ class PredicateTimeout(Timeout):
|
|
7623
7507
|
|
7624
7508
|
|
7625
7509
|
########################################
|
7626
|
-
# ../../../omlish/logs/
|
7510
|
+
# ../../../omlish/logs/infos.py
|
7511
|
+
"""
|
7512
|
+
TODO:
|
7513
|
+
- remove redundant info fields only present for std adaptation (Level.name, ...)
|
7514
|
+
"""
|
7627
7515
|
|
7628
7516
|
|
7629
7517
|
##
|
7630
7518
|
|
7631
7519
|
|
7632
|
-
|
7520
|
+
def logging_context_info(cls):
|
7521
|
+
return cls
|
7522
|
+
|
7523
|
+
|
7633
7524
|
@ta.final
|
7634
|
-
class
|
7635
|
-
|
7636
|
-
|
7637
|
-
name: str
|
7638
|
-
stack_info: ta.Optional[str]
|
7525
|
+
class LoggingContextInfos:
|
7526
|
+
def __new__(cls, *args, **kwargs): # noqa
|
7527
|
+
raise TypeError
|
7639
7528
|
|
7640
|
-
|
7641
|
-
def is_internal_frame(cls, frame: types.FrameType) -> bool:
|
7642
|
-
file_path = os.path.normcase(frame.f_code.co_filename)
|
7529
|
+
#
|
7643
7530
|
|
7644
|
-
|
7645
|
-
|
7646
|
-
|
7647
|
-
|
7648
|
-
return True
|
7531
|
+
@logging_context_info
|
7532
|
+
@ta.final
|
7533
|
+
class Name(ta.NamedTuple):
|
7534
|
+
name: str
|
7649
7535
|
|
7650
|
-
|
7536
|
+
@logging_context_info
|
7537
|
+
@ta.final
|
7538
|
+
class Level(ta.NamedTuple):
|
7539
|
+
level: NamedLogLevel
|
7540
|
+
name: str
|
7651
7541
|
|
7652
|
-
|
7653
|
-
|
7654
|
-
|
7542
|
+
@classmethod
|
7543
|
+
def build(cls, level: int) -> 'LoggingContextInfos.Level':
|
7544
|
+
nl: NamedLogLevel = level if level.__class__ is NamedLogLevel else NamedLogLevel(level) # type: ignore[assignment] # noqa
|
7545
|
+
return cls(
|
7546
|
+
level=nl,
|
7547
|
+
name=logging.getLevelName(nl),
|
7548
|
+
)
|
7549
|
+
|
7550
|
+
@logging_context_info
|
7551
|
+
@ta.final
|
7552
|
+
class Msg(ta.NamedTuple):
|
7553
|
+
msg: str
|
7554
|
+
args: ta.Union[tuple, ta.Mapping[ta.Any, ta.Any], None]
|
7655
7555
|
|
7656
|
-
|
7657
|
-
|
7658
|
-
|
7659
|
-
|
7660
|
-
|
7556
|
+
@classmethod
|
7557
|
+
def build(
|
7558
|
+
cls,
|
7559
|
+
msg: ta.Union[str, tuple, LoggingMsgFn],
|
7560
|
+
*args: ta.Any,
|
7561
|
+
) -> 'LoggingContextInfos.Msg':
|
7562
|
+
s: str
|
7563
|
+
a: ta.Any
|
7564
|
+
|
7565
|
+
if callable(msg):
|
7566
|
+
if args:
|
7567
|
+
raise TypeError(f'Must not provide both a message function and args: {msg=} {args=}')
|
7568
|
+
x = msg()
|
7569
|
+
if isinstance(x, str):
|
7570
|
+
s, a = x, ()
|
7571
|
+
elif isinstance(x, tuple):
|
7572
|
+
if x:
|
7573
|
+
s, a = x[0], x[1:]
|
7574
|
+
else:
|
7575
|
+
s, a = '', ()
|
7576
|
+
else:
|
7577
|
+
raise TypeError(x)
|
7661
7578
|
|
7662
|
-
|
7579
|
+
elif isinstance(msg, tuple):
|
7580
|
+
if args:
|
7581
|
+
raise TypeError(f'Must not provide both a tuple message and args: {msg=} {args=}')
|
7582
|
+
if msg:
|
7583
|
+
s, a = msg[0], msg[1:]
|
7584
|
+
else:
|
7585
|
+
s, a = '', ()
|
7663
7586
|
|
7664
|
-
|
7587
|
+
elif isinstance(msg, str):
|
7588
|
+
s, a = msg, args
|
7589
|
+
|
7590
|
+
else:
|
7591
|
+
raise TypeError(msg)
|
7592
|
+
|
7593
|
+
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L307 # noqa
|
7594
|
+
if a and len(a) == 1 and isinstance(a[0], collections.abc.Mapping) and a[0]:
|
7595
|
+
a = a[0]
|
7596
|
+
|
7597
|
+
return cls(
|
7598
|
+
msg=s,
|
7599
|
+
args=a,
|
7600
|
+
)
|
7601
|
+
|
7602
|
+
@logging_context_info
|
7603
|
+
@ta.final
|
7604
|
+
class Extra(ta.NamedTuple):
|
7605
|
+
extra: ta.Mapping[ta.Any, ta.Any]
|
7606
|
+
|
7607
|
+
@logging_context_info
|
7608
|
+
@ta.final
|
7609
|
+
class Time(ta.NamedTuple):
|
7610
|
+
ns: int
|
7611
|
+
secs: float
|
7612
|
+
msecs: float
|
7613
|
+
relative_secs: float
|
7614
|
+
|
7615
|
+
@classmethod
|
7616
|
+
def get_std_start_ns(cls) -> int:
|
7617
|
+
x: ta.Any = logging._startTime # type: ignore[attr-defined] # noqa
|
7618
|
+
|
7619
|
+
# Before 3.13.0b1 this will be `time.time()`, a float of seconds. After that, it will be `time.time_ns()`,
|
7620
|
+
# an int.
|
7621
|
+
#
|
7622
|
+
# See:
|
7623
|
+
# - https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
|
7624
|
+
#
|
7625
|
+
if isinstance(x, float):
|
7626
|
+
return int(x * 1e9)
|
7627
|
+
else:
|
7628
|
+
return x
|
7629
|
+
|
7630
|
+
@classmethod
|
7631
|
+
def build(
|
7632
|
+
cls,
|
7633
|
+
ns: int,
|
7634
|
+
*,
|
7635
|
+
start_ns: ta.Optional[int] = None,
|
7636
|
+
) -> 'LoggingContextInfos.Time':
|
7637
|
+
# https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
|
7638
|
+
secs = ns / 1e9 # ns to float seconds
|
7639
|
+
|
7640
|
+
# Get the number of whole milliseconds (0-999) in the fractional part of seconds.
|
7641
|
+
# Eg: 1_677_903_920_999_998_503 ns --> 999_998_503 ns--> 999 ms
|
7642
|
+
# Convert to float by adding 0.0 for historical reasons. See gh-89047
|
7643
|
+
msecs = (ns % 1_000_000_000) // 1_000_000 + 0.0
|
7644
|
+
|
7645
|
+
# https://github.com/python/cpython/commit/1500a23f33f5a6d052ff1ef6383d9839928b8ff1
|
7646
|
+
if msecs == 999.0 and int(secs) != ns // 1_000_000_000:
|
7647
|
+
# ns -> sec conversion can round up, e.g:
|
7648
|
+
# 1_677_903_920_999_999_900 ns --> 1_677_903_921.0 sec
|
7649
|
+
msecs = 0.0
|
7650
|
+
|
7651
|
+
if start_ns is None:
|
7652
|
+
start_ns = cls.get_std_start_ns()
|
7653
|
+
relative_secs = (ns - start_ns) / 1e6
|
7654
|
+
|
7655
|
+
return cls(
|
7656
|
+
ns=ns,
|
7657
|
+
secs=secs,
|
7658
|
+
msecs=msecs,
|
7659
|
+
relative_secs=relative_secs,
|
7660
|
+
)
|
7661
|
+
|
7662
|
+
@logging_context_info
|
7663
|
+
@ta.final
|
7664
|
+
class Exc(ta.NamedTuple):
|
7665
|
+
info: LoggingExcInfo
|
7666
|
+
info_tuple: LoggingExcInfoTuple
|
7667
|
+
|
7668
|
+
@classmethod
|
7669
|
+
def build(
|
7670
|
+
cls,
|
7671
|
+
arg: LoggingExcInfoArg = False,
|
7672
|
+
) -> ta.Optional['LoggingContextInfos.Exc']:
|
7673
|
+
if arg is True:
|
7674
|
+
sys_exc_info = sys.exc_info()
|
7675
|
+
if sys_exc_info[0] is not None:
|
7676
|
+
arg = sys_exc_info
|
7677
|
+
else:
|
7678
|
+
arg = None
|
7679
|
+
elif arg is False:
|
7680
|
+
arg = None
|
7681
|
+
if arg is None:
|
7682
|
+
return None
|
7683
|
+
|
7684
|
+
info: LoggingExcInfo = arg
|
7685
|
+
if isinstance(info, BaseException):
|
7686
|
+
info_tuple: LoggingExcInfoTuple = (type(info), info, info.__traceback__) # noqa
|
7687
|
+
else:
|
7688
|
+
info_tuple = info
|
7689
|
+
|
7690
|
+
return cls(
|
7691
|
+
info=info,
|
7692
|
+
info_tuple=info_tuple,
|
7693
|
+
)
|
7694
|
+
|
7695
|
+
@logging_context_info
|
7696
|
+
@ta.final
|
7697
|
+
class Caller(ta.NamedTuple):
|
7698
|
+
file_path: str
|
7699
|
+
line_no: int
|
7700
|
+
func_name: str
|
7701
|
+
stack_info: ta.Optional[str]
|
7702
|
+
|
7703
|
+
@classmethod
|
7704
|
+
def is_internal_frame(cls, frame: types.FrameType) -> bool:
|
7705
|
+
file_path = os.path.normcase(frame.f_code.co_filename)
|
7706
|
+
|
7707
|
+
# Yes, really.
|
7708
|
+
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L204 # noqa
|
7709
|
+
# https://github.com/python/cpython/commit/5ca6d7469be53960843df39bb900e9c3359f127f
|
7710
|
+
if 'importlib' in file_path and '_bootstrap' in file_path:
|
7711
|
+
return True
|
7712
|
+
|
7713
|
+
return False
|
7714
|
+
|
7715
|
+
@classmethod
|
7716
|
+
def find_frame(cls, stack_offset: int = 0) -> ta.Optional[types.FrameType]:
|
7717
|
+
f: ta.Optional[types.FrameType] = sys._getframe(2 + stack_offset) # noqa
|
7718
|
+
|
7719
|
+
while f is not None:
|
7720
|
+
# NOTE: We don't check __file__ like stdlib since we may be running amalgamated - we rely on careful,
|
7721
|
+
# manual stack_offset management.
|
7722
|
+
if hasattr(f, 'f_code'):
|
7723
|
+
return f
|
7724
|
+
|
7725
|
+
f = f.f_back
|
7665
7726
|
|
7666
|
-
@classmethod
|
7667
|
-
def find(
|
7668
|
-
cls,
|
7669
|
-
ofs: int = 0,
|
7670
|
-
*,
|
7671
|
-
stack_info: bool = False,
|
7672
|
-
) -> ta.Optional['LoggingCaller']:
|
7673
|
-
if (f := cls.find_frame(ofs + 1)) is None:
|
7674
7727
|
return None
|
7675
7728
|
|
7676
|
-
|
7677
|
-
|
7678
|
-
|
7679
|
-
|
7680
|
-
|
7681
|
-
|
7682
|
-
|
7683
|
-
if
|
7684
|
-
|
7729
|
+
@classmethod
|
7730
|
+
def build(
|
7731
|
+
cls,
|
7732
|
+
stack_offset: int = 0,
|
7733
|
+
*,
|
7734
|
+
stack_info: bool = False,
|
7735
|
+
) -> ta.Optional['LoggingContextInfos.Caller']:
|
7736
|
+
if (f := cls.find_frame(stack_offset + 1)) is None:
|
7737
|
+
return None
|
7738
|
+
|
7739
|
+
# https://github.com/python/cpython/blob/08e9794517063c8cd92c48714071b1d3c60b71bd/Lib/logging/__init__.py#L1616-L1623 # noqa
|
7740
|
+
sinfo = None
|
7741
|
+
if stack_info:
|
7742
|
+
sio = io.StringIO()
|
7743
|
+
traceback.print_stack(f, file=sio)
|
7744
|
+
sinfo = sio.getvalue()
|
7745
|
+
sio.close()
|
7746
|
+
if sinfo[-1] == '\n':
|
7747
|
+
sinfo = sinfo[:-1]
|
7748
|
+
|
7749
|
+
return cls(
|
7750
|
+
file_path=f.f_code.co_filename,
|
7751
|
+
line_no=f.f_lineno or 0,
|
7752
|
+
func_name=f.f_code.co_name,
|
7753
|
+
stack_info=sinfo,
|
7754
|
+
)
|
7755
|
+
|
7756
|
+
@logging_context_info
|
7757
|
+
@ta.final
|
7758
|
+
class SourceFile(ta.NamedTuple):
|
7759
|
+
file_name: str
|
7760
|
+
module: str
|
7685
7761
|
|
7686
|
-
|
7687
|
-
|
7688
|
-
|
7689
|
-
|
7690
|
-
|
7762
|
+
@classmethod
|
7763
|
+
def build(cls, caller_file_path: ta.Optional[str]) -> ta.Optional['LoggingContextInfos.SourceFile']:
|
7764
|
+
if caller_file_path is None:
|
7765
|
+
return None
|
7766
|
+
|
7767
|
+
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L331-L336 # noqa
|
7768
|
+
try:
|
7769
|
+
file_name = os.path.basename(caller_file_path)
|
7770
|
+
module = os.path.splitext(file_name)[0]
|
7771
|
+
except (TypeError, ValueError, AttributeError):
|
7772
|
+
return None
|
7773
|
+
|
7774
|
+
return cls(
|
7775
|
+
file_name=file_name,
|
7776
|
+
module=module,
|
7777
|
+
)
|
7778
|
+
|
7779
|
+
@logging_context_info
|
7780
|
+
@ta.final
|
7781
|
+
class Thread(ta.NamedTuple):
|
7782
|
+
ident: int
|
7783
|
+
native_id: ta.Optional[int]
|
7784
|
+
name: str
|
7785
|
+
|
7786
|
+
@classmethod
|
7787
|
+
def build(cls) -> 'LoggingContextInfos.Thread':
|
7788
|
+
return cls(
|
7789
|
+
ident=threading.get_ident(),
|
7790
|
+
native_id=threading.get_native_id() if hasattr(threading, 'get_native_id') else None,
|
7791
|
+
name=threading.current_thread().name,
|
7792
|
+
)
|
7793
|
+
|
7794
|
+
@logging_context_info
|
7795
|
+
@ta.final
|
7796
|
+
class Process(ta.NamedTuple):
|
7797
|
+
pid: int
|
7798
|
+
|
7799
|
+
@classmethod
|
7800
|
+
def build(cls) -> 'LoggingContextInfos.Process':
|
7801
|
+
return cls(
|
7802
|
+
pid=os.getpid(),
|
7803
|
+
)
|
7804
|
+
|
7805
|
+
@logging_context_info
|
7806
|
+
@ta.final
|
7807
|
+
class Multiprocessing(ta.NamedTuple):
|
7808
|
+
process_name: str
|
7809
|
+
|
7810
|
+
@classmethod
|
7811
|
+
def build(cls) -> ta.Optional['LoggingContextInfos.Multiprocessing']:
|
7812
|
+
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L355-L364 # noqa
|
7813
|
+
if (mp := sys.modules.get('multiprocessing')) is None:
|
7814
|
+
return None
|
7815
|
+
|
7816
|
+
return cls(
|
7817
|
+
process_name=mp.current_process().name,
|
7818
|
+
)
|
7819
|
+
|
7820
|
+
@logging_context_info
|
7821
|
+
@ta.final
|
7822
|
+
class AsyncioTask(ta.NamedTuple):
|
7823
|
+
name: str
|
7824
|
+
|
7825
|
+
@classmethod
|
7826
|
+
def build(cls) -> ta.Optional['LoggingContextInfos.AsyncioTask']:
|
7827
|
+
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L372-L377 # noqa
|
7828
|
+
if (asyncio := sys.modules.get('asyncio')) is None:
|
7829
|
+
return None
|
7830
|
+
|
7831
|
+
try:
|
7832
|
+
task = asyncio.current_task()
|
7833
|
+
except Exception: # noqa
|
7834
|
+
return None
|
7835
|
+
|
7836
|
+
if task is None:
|
7837
|
+
return None
|
7838
|
+
|
7839
|
+
return cls(
|
7840
|
+
name=task.get_name(), # Always non-None
|
7841
|
+
)
|
7842
|
+
|
7843
|
+
|
7844
|
+
##
|
7845
|
+
|
7846
|
+
|
7847
|
+
class UnexpectedLoggingStartTimeWarning(LoggingSetupWarning):
|
7848
|
+
pass
|
7849
|
+
|
7850
|
+
|
7851
|
+
def _check_logging_start_time() -> None:
|
7852
|
+
if (x := LoggingContextInfos.Time.get_std_start_ns()) < (t := time.time()):
|
7853
|
+
import warnings # noqa
|
7854
|
+
|
7855
|
+
warnings.warn(
|
7856
|
+
f'Unexpected logging start time detected: '
|
7857
|
+
f'get_std_start_ns={x}, '
|
7858
|
+
f'time.time()={t}',
|
7859
|
+
UnexpectedLoggingStartTimeWarning,
|
7691
7860
|
)
|
7692
7861
|
|
7693
7862
|
|
7863
|
+
_check_logging_start_time()
|
7864
|
+
|
7865
|
+
|
7694
7866
|
########################################
|
7695
7867
|
# ../../../omlish/logs/protocols.py
|
7696
7868
|
|
@@ -7781,115 +7953,30 @@ class JsonLoggingFormatter(logging.Formatter):
|
|
7781
7953
|
|
7782
7954
|
|
7783
7955
|
########################################
|
7784
|
-
# ../../../omlish/
|
7956
|
+
# ../../../omlish/os/atomics.py
|
7785
7957
|
|
7786
7958
|
|
7787
7959
|
##
|
7788
7960
|
|
7789
7961
|
|
7790
|
-
|
7791
|
-
|
7792
|
-
|
7793
|
-
|
7962
|
+
class AtomicPathSwap(Abstract):
|
7963
|
+
def __init__(
|
7964
|
+
self,
|
7965
|
+
kind: AtomicPathSwapKind,
|
7966
|
+
dst_path: str,
|
7967
|
+
*,
|
7968
|
+
auto_commit: bool = False,
|
7969
|
+
) -> None:
|
7970
|
+
super().__init__()
|
7794
7971
|
|
7795
|
-
|
7796
|
-
|
7797
|
-
|
7972
|
+
self._kind = kind
|
7973
|
+
self._dst_path = dst_path
|
7974
|
+
self._auto_commit = auto_commit
|
7798
7975
|
|
7799
|
-
|
7800
|
-
def get_std_start_time_ns(cls) -> int:
|
7801
|
-
x: ta.Any = logging._startTime # type: ignore[attr-defined] # noqa
|
7976
|
+
self._state: AtomicPathSwapState = 'open'
|
7802
7977
|
|
7803
|
-
|
7804
|
-
|
7805
|
-
#
|
7806
|
-
# See:
|
7807
|
-
# - https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
|
7808
|
-
#
|
7809
|
-
if isinstance(x, float):
|
7810
|
-
return int(x * 1e9)
|
7811
|
-
else:
|
7812
|
-
return x
|
7813
|
-
|
7814
|
-
@classmethod
|
7815
|
-
def build(
|
7816
|
-
cls,
|
7817
|
-
time_ns: int,
|
7818
|
-
*,
|
7819
|
-
start_time_ns: ta.Optional[int] = None,
|
7820
|
-
) -> 'LoggingTimeFields':
|
7821
|
-
# https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
|
7822
|
-
created = time_ns / 1e9 # ns to float seconds
|
7823
|
-
|
7824
|
-
# Get the number of whole milliseconds (0-999) in the fractional part of seconds.
|
7825
|
-
# Eg: 1_677_903_920_999_998_503 ns --> 999_998_503 ns--> 999 ms
|
7826
|
-
# Convert to float by adding 0.0 for historical reasons. See gh-89047
|
7827
|
-
msecs = (time_ns % 1_000_000_000) // 1_000_000 + 0.0
|
7828
|
-
|
7829
|
-
# https://github.com/python/cpython/commit/1500a23f33f5a6d052ff1ef6383d9839928b8ff1
|
7830
|
-
if msecs == 999.0 and int(created) != time_ns // 1_000_000_000:
|
7831
|
-
# ns -> sec conversion can round up, e.g:
|
7832
|
-
# 1_677_903_920_999_999_900 ns --> 1_677_903_921.0 sec
|
7833
|
-
msecs = 0.0
|
7834
|
-
|
7835
|
-
if start_time_ns is None:
|
7836
|
-
start_time_ns = cls.get_std_start_time_ns()
|
7837
|
-
relative_created = (time_ns - start_time_ns) / 1e6
|
7838
|
-
|
7839
|
-
return cls(
|
7840
|
-
created=created,
|
7841
|
-
msecs=msecs,
|
7842
|
-
relative_created=relative_created,
|
7843
|
-
)
|
7844
|
-
|
7845
|
-
|
7846
|
-
##
|
7847
|
-
|
7848
|
-
|
7849
|
-
class UnexpectedLoggingStartTimeWarning(LoggingSetupWarning):
|
7850
|
-
pass
|
7851
|
-
|
7852
|
-
|
7853
|
-
def _check_logging_start_time() -> None:
|
7854
|
-
if (x := LoggingTimeFields.get_std_start_time_ns()) < (t := time.time()):
|
7855
|
-
import warnings # noqa
|
7856
|
-
|
7857
|
-
warnings.warn(
|
7858
|
-
f'Unexpected logging start time detected: '
|
7859
|
-
f'get_std_start_time_ns={x}, '
|
7860
|
-
f'time.time()={t}',
|
7861
|
-
UnexpectedLoggingStartTimeWarning,
|
7862
|
-
)
|
7863
|
-
|
7864
|
-
|
7865
|
-
_check_logging_start_time()
|
7866
|
-
|
7867
|
-
|
7868
|
-
########################################
|
7869
|
-
# ../../../omlish/os/atomics.py
|
7870
|
-
|
7871
|
-
|
7872
|
-
##
|
7873
|
-
|
7874
|
-
|
7875
|
-
class AtomicPathSwap(Abstract):
|
7876
|
-
def __init__(
|
7877
|
-
self,
|
7878
|
-
kind: AtomicPathSwapKind,
|
7879
|
-
dst_path: str,
|
7880
|
-
*,
|
7881
|
-
auto_commit: bool = False,
|
7882
|
-
) -> None:
|
7883
|
-
super().__init__()
|
7884
|
-
|
7885
|
-
self._kind = kind
|
7886
|
-
self._dst_path = dst_path
|
7887
|
-
self._auto_commit = auto_commit
|
7888
|
-
|
7889
|
-
self._state: AtomicPathSwapState = 'open'
|
7890
|
-
|
7891
|
-
def __repr__(self) -> str:
|
7892
|
-
return attr_repr(self, 'kind', 'dst_path', 'tmp_path')
|
7978
|
+
def __repr__(self) -> str:
|
7979
|
+
return attr_repr(self, 'kind', 'dst_path', 'tmp_path')
|
7893
7980
|
|
7894
7981
|
@property
|
7895
7982
|
def kind(self) -> AtomicPathSwapKind:
|
@@ -10004,68 +10091,46 @@ inj = InjectionApi()
|
|
10004
10091
|
|
10005
10092
|
|
10006
10093
|
class LoggingContext(Abstract):
|
10007
|
-
@property
|
10008
10094
|
@abc.abstractmethod
|
10009
|
-
def
|
10095
|
+
def get_info(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
|
10010
10096
|
raise NotImplementedError
|
10011
10097
|
|
10012
|
-
|
10013
|
-
|
10014
|
-
|
10015
|
-
@abc.abstractmethod
|
10016
|
-
def time_ns(self) -> int:
|
10017
|
-
raise NotImplementedError
|
10018
|
-
|
10019
|
-
@property
|
10020
|
-
@abc.abstractmethod
|
10021
|
-
def times(self) -> LoggingTimeFields:
|
10022
|
-
raise NotImplementedError
|
10098
|
+
@ta.final
|
10099
|
+
def __getitem__(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
|
10100
|
+
return self.get_info(ty)
|
10023
10101
|
|
10024
|
-
|
10102
|
+
@ta.final
|
10103
|
+
def must_get_info(self, ty: ta.Type[LoggingContextInfoT]) -> LoggingContextInfoT:
|
10104
|
+
if (info := self.get_info(ty)) is None:
|
10105
|
+
raise TypeError(f'LoggingContextInfo absent: {ty}')
|
10106
|
+
return info
|
10025
10107
|
|
10026
|
-
@property
|
10027
|
-
@abc.abstractmethod
|
10028
|
-
def exc_info(self) -> ta.Optional[LoggingExcInfo]:
|
10029
|
-
raise NotImplementedError
|
10030
10108
|
|
10031
|
-
|
10032
|
-
|
10033
|
-
def
|
10034
|
-
|
10109
|
+
@ta.final
|
10110
|
+
class SimpleLoggingContext(LoggingContext):
|
10111
|
+
def __init__(self, *infos: LoggingContextInfo) -> None:
|
10112
|
+
self._infos: ta.Dict[ta.Type[LoggingContextInfo], LoggingContextInfo] = {type(i): i for i in infos}
|
10035
10113
|
|
10036
|
-
|
10114
|
+
def get_info(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
|
10115
|
+
return self._infos.get(ty)
|
10037
10116
|
|
10038
|
-
@abc.abstractmethod
|
10039
|
-
def caller(self) -> ta.Optional[LoggingCaller]:
|
10040
|
-
raise NotImplementedError
|
10041
10117
|
|
10042
|
-
|
10043
|
-
def source_file(self) -> ta.Optional[LoggingSourceFileInfo]:
|
10044
|
-
raise NotImplementedError
|
10118
|
+
##
|
10045
10119
|
|
10046
|
-
#
|
10047
|
-
|
10048
|
-
@abc.abstractmethod
|
10049
|
-
def thread(self) -> ta.Optional[LoggingThreadInfo]:
|
10050
|
-
raise NotImplementedError
|
10051
|
-
|
10052
|
-
@abc.abstractmethod
|
10053
|
-
def process(self) -> ta.Optional[LoggingProcessInfo]:
|
10054
|
-
raise NotImplementedError
|
10055
10120
|
|
10121
|
+
class CaptureLoggingContext(LoggingContext, Abstract):
|
10056
10122
|
@abc.abstractmethod
|
10057
|
-
def
|
10058
|
-
|
10123
|
+
def set_basic(
|
10124
|
+
self,
|
10125
|
+
name: str,
|
10059
10126
|
|
10060
|
-
|
10061
|
-
|
10127
|
+
msg: ta.Union[str, tuple, LoggingMsgFn],
|
10128
|
+
args: tuple,
|
10129
|
+
) -> 'CaptureLoggingContext':
|
10062
10130
|
raise NotImplementedError
|
10063
10131
|
|
10132
|
+
#
|
10064
10133
|
|
10065
|
-
##
|
10066
|
-
|
10067
|
-
|
10068
|
-
class CaptureLoggingContext(LoggingContext, Abstract):
|
10069
10134
|
class AlreadyCapturedError(Exception):
|
10070
10135
|
pass
|
10071
10136
|
|
@@ -10096,80 +10161,50 @@ class CaptureLoggingContextImpl(CaptureLoggingContext):
|
|
10096
10161
|
|
10097
10162
|
exc_info: LoggingExcInfoArg = False,
|
10098
10163
|
|
10099
|
-
caller: ta.Union[
|
10164
|
+
caller: ta.Union[LoggingContextInfos.Caller, ta.Type[NOT_SET], None] = NOT_SET,
|
10100
10165
|
stack_offset: int = 0,
|
10101
10166
|
stack_info: bool = False,
|
10102
10167
|
) -> None:
|
10103
|
-
|
10104
|
-
|
10105
|
-
#
|
10168
|
+
# TODO: Name, Msg, Extra
|
10106
10169
|
|
10107
10170
|
if time_ns is None:
|
10108
10171
|
time_ns = time.time_ns()
|
10109
|
-
self._time_ns: int = time_ns
|
10110
|
-
|
10111
|
-
#
|
10112
|
-
|
10113
|
-
if exc_info is True:
|
10114
|
-
sys_exc_info = sys.exc_info()
|
10115
|
-
if sys_exc_info[0] is not None:
|
10116
|
-
exc_info = sys_exc_info
|
10117
|
-
else:
|
10118
|
-
exc_info = None
|
10119
|
-
elif exc_info is False:
|
10120
|
-
exc_info = None
|
10121
|
-
|
10122
|
-
if exc_info is not None:
|
10123
|
-
self._exc_info: ta.Optional[LoggingExcInfo] = exc_info
|
10124
|
-
if isinstance(exc_info, BaseException):
|
10125
|
-
self._exc_info_tuple: ta.Optional[LoggingExcInfoTuple] = (type(exc_info), exc_info, exc_info.__traceback__) # noqa
|
10126
|
-
else:
|
10127
|
-
self._exc_info_tuple = exc_info
|
10128
10172
|
|
10129
|
-
|
10173
|
+
self._infos: ta.Dict[ta.Type[LoggingContextInfo], LoggingContextInfo] = {}
|
10174
|
+
self._set_info(
|
10175
|
+
LoggingContextInfos.Level.build(level),
|
10176
|
+
LoggingContextInfos.Time.build(time_ns),
|
10177
|
+
LoggingContextInfos.Exc.build(exc_info),
|
10178
|
+
)
|
10130
10179
|
|
10131
10180
|
if caller is not CaptureLoggingContextImpl.NOT_SET:
|
10132
|
-
self.
|
10181
|
+
self._infos[LoggingContextInfos.Caller] = caller
|
10133
10182
|
else:
|
10134
10183
|
self._stack_offset = stack_offset
|
10135
10184
|
self._stack_info = stack_info
|
10136
10185
|
|
10137
|
-
|
10138
|
-
|
10139
|
-
|
10140
|
-
|
10141
|
-
return self
|
10142
|
-
|
10143
|
-
#
|
10144
|
-
|
10145
|
-
@property
|
10146
|
-
def time_ns(self) -> int:
|
10147
|
-
return self._time_ns
|
10148
|
-
|
10149
|
-
_times: LoggingTimeFields
|
10150
|
-
|
10151
|
-
@property
|
10152
|
-
def times(self) -> LoggingTimeFields:
|
10153
|
-
try:
|
10154
|
-
return self._times
|
10155
|
-
except AttributeError:
|
10156
|
-
pass
|
10157
|
-
|
10158
|
-
times = self._times = LoggingTimeFields.build(self.time_ns)
|
10159
|
-
return times
|
10186
|
+
def _set_info(self, *infos: ta.Optional[LoggingContextInfo]) -> 'CaptureLoggingContextImpl':
|
10187
|
+
for info in infos:
|
10188
|
+
if info is not None:
|
10189
|
+
self._infos[type(info)] = info
|
10190
|
+
return self
|
10160
10191
|
|
10161
|
-
|
10192
|
+
def get_info(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
|
10193
|
+
return self._infos.get(ty)
|
10162
10194
|
|
10163
|
-
|
10164
|
-
_exc_info_tuple: ta.Optional[LoggingExcInfoTuple] = None
|
10195
|
+
##
|
10165
10196
|
|
10166
|
-
|
10167
|
-
|
10168
|
-
|
10197
|
+
def set_basic(
|
10198
|
+
self,
|
10199
|
+
name: str,
|
10169
10200
|
|
10170
|
-
|
10171
|
-
|
10172
|
-
|
10201
|
+
msg: ta.Union[str, tuple, LoggingMsgFn],
|
10202
|
+
args: tuple,
|
10203
|
+
) -> 'CaptureLoggingContextImpl':
|
10204
|
+
return self._set_info(
|
10205
|
+
LoggingContextInfos.Name(name),
|
10206
|
+
LoggingContextInfos.Msg.build(msg, *args),
|
10207
|
+
)
|
10173
10208
|
|
10174
10209
|
##
|
10175
10210
|
|
@@ -10183,74 +10218,28 @@ class CaptureLoggingContextImpl(CaptureLoggingContext):
|
|
10183
10218
|
|
10184
10219
|
_has_captured: bool = False
|
10185
10220
|
|
10186
|
-
_caller: ta.Optional[LoggingCaller]
|
10187
|
-
_source_file: ta.Optional[LoggingSourceFileInfo]
|
10188
|
-
|
10189
|
-
_thread: ta.Optional[LoggingThreadInfo]
|
10190
|
-
_process: ta.Optional[LoggingProcessInfo]
|
10191
|
-
_multiprocessing: ta.Optional[LoggingMultiprocessingInfo]
|
10192
|
-
_asyncio_task: ta.Optional[LoggingAsyncioTaskInfo]
|
10193
|
-
|
10194
10221
|
def capture(self) -> None:
|
10195
10222
|
if self._has_captured:
|
10196
10223
|
raise CaptureLoggingContextImpl.AlreadyCapturedError
|
10197
10224
|
self._has_captured = True
|
10198
10225
|
|
10199
|
-
if not
|
10200
|
-
self.
|
10226
|
+
if LoggingContextInfos.Caller not in self._infos:
|
10227
|
+
self._set_info(LoggingContextInfos.Caller.build(
|
10201
10228
|
self._stack_offset + 1,
|
10202
10229
|
stack_info=self._stack_info,
|
10203
|
-
)
|
10204
|
-
|
10205
|
-
if (caller := self._caller) is not None:
|
10206
|
-
self._source_file = LoggingSourceFileInfo.build(caller.file_path)
|
10207
|
-
else:
|
10208
|
-
self._source_file = None
|
10209
|
-
|
10210
|
-
self._thread = LoggingThreadInfo.build()
|
10211
|
-
self._process = LoggingProcessInfo.build()
|
10212
|
-
self._multiprocessing = LoggingMultiprocessingInfo.build()
|
10213
|
-
self._asyncio_task = LoggingAsyncioTaskInfo.build()
|
10214
|
-
|
10215
|
-
#
|
10216
|
-
|
10217
|
-
def caller(self) -> ta.Optional[LoggingCaller]:
|
10218
|
-
try:
|
10219
|
-
return self._caller
|
10220
|
-
except AttributeError:
|
10221
|
-
raise CaptureLoggingContext.NotCapturedError from None
|
10222
|
-
|
10223
|
-
def source_file(self) -> ta.Optional[LoggingSourceFileInfo]:
|
10224
|
-
try:
|
10225
|
-
return self._source_file
|
10226
|
-
except AttributeError:
|
10227
|
-
raise CaptureLoggingContext.NotCapturedError from None
|
10228
|
-
|
10229
|
-
#
|
10230
|
-
|
10231
|
-
def thread(self) -> ta.Optional[LoggingThreadInfo]:
|
10232
|
-
try:
|
10233
|
-
return self._thread
|
10234
|
-
except AttributeError:
|
10235
|
-
raise CaptureLoggingContext.NotCapturedError from None
|
10236
|
-
|
10237
|
-
def process(self) -> ta.Optional[LoggingProcessInfo]:
|
10238
|
-
try:
|
10239
|
-
return self._process
|
10240
|
-
except AttributeError:
|
10241
|
-
raise CaptureLoggingContext.NotCapturedError from None
|
10230
|
+
))
|
10242
10231
|
|
10243
|
-
|
10244
|
-
|
10245
|
-
|
10246
|
-
|
10247
|
-
raise CaptureLoggingContext.NotCapturedError from None
|
10232
|
+
if (caller := self[LoggingContextInfos.Caller]) is not None:
|
10233
|
+
self._set_info(LoggingContextInfos.SourceFile.build(
|
10234
|
+
caller.file_path,
|
10235
|
+
))
|
10248
10236
|
|
10249
|
-
|
10250
|
-
|
10251
|
-
|
10252
|
-
|
10253
|
-
|
10237
|
+
self._set_info(
|
10238
|
+
LoggingContextInfos.Thread.build(),
|
10239
|
+
LoggingContextInfos.Process.build(),
|
10240
|
+
LoggingContextInfos.Multiprocessing.build(),
|
10241
|
+
LoggingContextInfos.AsyncioTask.build(),
|
10242
|
+
)
|
10254
10243
|
|
10255
10244
|
|
10256
10245
|
########################################
|
@@ -10327,10 +10316,14 @@ def _locking_logging_module_lock() -> ta.Iterator[None]:
|
|
10327
10316
|
def configure_standard_logging(
|
10328
10317
|
level: ta.Union[int, str] = logging.INFO,
|
10329
10318
|
*,
|
10330
|
-
json: bool = False,
|
10331
10319
|
target: ta.Optional[logging.Logger] = None,
|
10320
|
+
|
10332
10321
|
force: bool = False,
|
10322
|
+
|
10333
10323
|
handler_factory: ta.Optional[ta.Callable[[], logging.Handler]] = None,
|
10324
|
+
|
10325
|
+
formatter: ta.Optional[logging.Formatter] = None, # noqa
|
10326
|
+
json: bool = False,
|
10334
10327
|
) -> ta.Optional[StandardConfiguredLoggingHandler]:
|
10335
10328
|
with _locking_logging_module_lock():
|
10336
10329
|
if target is None:
|
@@ -10351,11 +10344,11 @@ def configure_standard_logging(
|
|
10351
10344
|
|
10352
10345
|
#
|
10353
10346
|
|
10354
|
-
formatter:
|
10355
|
-
|
10356
|
-
|
10357
|
-
|
10358
|
-
|
10347
|
+
if formatter is None:
|
10348
|
+
if json:
|
10349
|
+
formatter = JsonLoggingFormatter()
|
10350
|
+
else:
|
10351
|
+
formatter = StandardLoggingFormatter(StandardLoggingFormatter.build_log_format(STANDARD_LOG_FORMAT_PARTS)) # noqa
|
10359
10352
|
handler.setFormatter(formatter)
|
10360
10353
|
|
10361
10354
|
#
|
@@ -11154,36 +11147,6 @@ class AnyLogger(Abstract, ta.Generic[T]):
|
|
11154
11147
|
|
11155
11148
|
##
|
11156
11149
|
|
11157
|
-
@classmethod
|
11158
|
-
def _prepare_msg_args(cls, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any) -> ta.Tuple[str, tuple]:
|
11159
|
-
if callable(msg):
|
11160
|
-
if args:
|
11161
|
-
raise TypeError(f'Must not provide both a message function and args: {msg=} {args=}')
|
11162
|
-
x = msg()
|
11163
|
-
if isinstance(x, str):
|
11164
|
-
return x, ()
|
11165
|
-
elif isinstance(x, tuple):
|
11166
|
-
if x:
|
11167
|
-
return x[0], x[1:]
|
11168
|
-
else:
|
11169
|
-
return '', ()
|
11170
|
-
else:
|
11171
|
-
raise TypeError(x)
|
11172
|
-
|
11173
|
-
elif isinstance(msg, tuple):
|
11174
|
-
if args:
|
11175
|
-
raise TypeError(f'Must not provide both a tuple message and args: {msg=} {args=}')
|
11176
|
-
if msg:
|
11177
|
-
return msg[0], msg[1:]
|
11178
|
-
else:
|
11179
|
-
return '', ()
|
11180
|
-
|
11181
|
-
elif isinstance(msg, str):
|
11182
|
-
return msg, args
|
11183
|
-
|
11184
|
-
else:
|
11185
|
-
raise TypeError(msg)
|
11186
|
-
|
11187
11150
|
@abc.abstractmethod
|
11188
11151
|
def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any, **kwargs: ta.Any) -> T: # noqa
|
11189
11152
|
raise NotImplementedError
|
@@ -11224,144 +11187,560 @@ class AsyncNopLogger(AnyNopLogger[ta.Awaitable[None]], AsyncLogger):
|
|
11224
11187
|
|
11225
11188
|
########################################
|
11226
11189
|
# ../../../omlish/logs/std/records.py
|
11190
|
+
"""
|
11191
|
+
TODO:
|
11192
|
+
- TypedDict?
|
11193
|
+
"""
|
11227
11194
|
|
11228
11195
|
|
11229
11196
|
##
|
11230
11197
|
|
11231
11198
|
|
11232
|
-
|
11233
|
-
#
|
11234
|
-
#
|
11235
|
-
#
|
11236
|
-
#
|
11237
|
-
# - https://github.com/python/cpython/blob/
|
11238
|
-
#
|
11239
|
-
#
|
11240
|
-
# - name: str
|
11241
|
-
# - level: int
|
11242
|
-
# - pathname: str - Confusingly referred to as `fn` before the LogRecord ctor. May be empty or "(unknown file)".
|
11243
|
-
# - lineno: int - May be 0.
|
11244
|
-
# - msg: str
|
11245
|
-
# - args: tuple | dict | 1-tuple[dict]
|
11246
|
-
# - exc_info: LoggingExcInfoTuple | None
|
11247
|
-
# - func: str | None = None -> funcName
|
11248
|
-
# - sinfo: str | None = None -> stack_info
|
11249
|
-
#
|
11250
|
-
KNOWN_STD_LOGGING_RECORD_ATTRS: ta.Dict[str, ta.Any] = dict(
|
11251
|
-
# Name of the logger used to log the call. Unmodified by ctor.
|
11252
|
-
name=str,
|
11199
|
+
class LoggingContextInfoRecordAdapters:
|
11200
|
+
# Ref:
|
11201
|
+
# - https://docs.python.org/3/library/logging.html#logrecord-attributes
|
11202
|
+
#
|
11203
|
+
# LogRecord:
|
11204
|
+
# - https://github.com/python/cpython/blob/39b2f82717a69dde7212bc39b673b0f55c99e6a3/Lib/logging/__init__.py#L276 (3.8) # noqa
|
11205
|
+
# - https://github.com/python/cpython/blob/f070f54c5f4a42c7c61d1d5d3b8f3b7203b4a0fb/Lib/logging/__init__.py#L286 (~3.14) # noqa
|
11206
|
+
#
|
11253
11207
|
|
11254
|
-
|
11255
|
-
|
11256
|
-
msg=str,
|
11208
|
+
def __new__(cls, *args, **kwargs): # noqa
|
11209
|
+
raise TypeError
|
11257
11210
|
|
11258
|
-
|
11259
|
-
|
11260
|
-
|
11261
|
-
|
11211
|
+
class Adapter(Abstract, ta.Generic[T]):
|
11212
|
+
@property
|
11213
|
+
@abc.abstractmethod
|
11214
|
+
def info_cls(self) -> ta.Type[LoggingContextInfo]:
|
11215
|
+
raise NotImplementedError
|
11262
11216
|
|
11263
|
-
|
11217
|
+
#
|
11264
11218
|
|
11265
|
-
|
11266
|
-
|
11267
|
-
|
11219
|
+
@ta.final
|
11220
|
+
class NOT_SET: # noqa
|
11221
|
+
def __new__(cls, *args, **kwargs): # noqa
|
11222
|
+
raise TypeError
|
11268
11223
|
|
11269
|
-
|
11270
|
-
|
11224
|
+
class RecordAttr(ta.NamedTuple):
|
11225
|
+
name: str
|
11226
|
+
type: ta.Any
|
11227
|
+
default: ta.Any
|
11271
11228
|
|
11272
|
-
|
11229
|
+
# @abc.abstractmethod
|
11230
|
+
record_attrs: ta.ClassVar[ta.Mapping[str, RecordAttr]]
|
11273
11231
|
|
11274
|
-
|
11275
|
-
|
11276
|
-
|
11232
|
+
@property
|
11233
|
+
@abc.abstractmethod
|
11234
|
+
def _record_attrs(self) -> ta.Union[
|
11235
|
+
ta.Mapping[str, ta.Any],
|
11236
|
+
ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]],
|
11237
|
+
]:
|
11238
|
+
raise NotImplementedError
|
11277
11239
|
|
11278
|
-
|
11279
|
-
filename=str,
|
11240
|
+
#
|
11280
11241
|
|
11281
|
-
|
11282
|
-
|
11283
|
-
|
11242
|
+
@abc.abstractmethod
|
11243
|
+
def context_to_record(self, ctx: LoggingContext) -> ta.Mapping[str, ta.Any]:
|
11244
|
+
raise NotImplementedError
|
11284
11245
|
|
11285
|
-
|
11246
|
+
#
|
11286
11247
|
|
11287
|
-
|
11288
|
-
|
11248
|
+
@abc.abstractmethod
|
11249
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[T]:
|
11250
|
+
raise NotImplementedError
|
11289
11251
|
|
11290
|
-
|
11291
|
-
exc_text=ta.Optional[str],
|
11252
|
+
#
|
11292
11253
|
|
11293
|
-
|
11254
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
11255
|
+
super().__init_subclass__(**kwargs)
|
11294
11256
|
|
11295
|
-
|
11296
|
-
|
11297
|
-
# unmodified. Mostly set, if requested, by `Logger.findCaller`, to `traceback.print_stack(f)`, but prepended with
|
11298
|
-
# the literal "Stack (most recent call last):\n", and stripped of exactly one trailing `\n` if present.
|
11299
|
-
stack_info=ta.Optional[str],
|
11257
|
+
if Abstract in cls.__bases__:
|
11258
|
+
return
|
11300
11259
|
|
11301
|
-
|
11302
|
-
|
11303
|
-
|
11260
|
+
if 'record_attrs' in cls.__dict__:
|
11261
|
+
raise TypeError(cls)
|
11262
|
+
if not isinstance(ra := cls.__dict__['_record_attrs'], collections.abc.Mapping):
|
11263
|
+
raise TypeError(ra)
|
11264
|
+
|
11265
|
+
rd: ta.Dict[str, LoggingContextInfoRecordAdapters.Adapter.RecordAttr] = {}
|
11266
|
+
for n, v in ra.items():
|
11267
|
+
if not n or not isinstance(n, str) or n in rd:
|
11268
|
+
raise AttributeError(n)
|
11269
|
+
if isinstance(v, tuple):
|
11270
|
+
t, d = v
|
11271
|
+
else:
|
11272
|
+
t, d = v, cls.NOT_SET
|
11273
|
+
rd[n] = cls.RecordAttr(
|
11274
|
+
name=n,
|
11275
|
+
type=t,
|
11276
|
+
default=d,
|
11277
|
+
)
|
11278
|
+
cls.record_attrs = rd
|
11304
11279
|
|
11305
|
-
|
11306
|
-
|
11307
|
-
|
11280
|
+
class RequiredAdapter(Adapter[T], Abstract):
|
11281
|
+
@property
|
11282
|
+
@abc.abstractmethod
|
11283
|
+
def _record_attrs(self) -> ta.Mapping[str, ta.Any]:
|
11284
|
+
raise NotImplementedError
|
11308
11285
|
|
11309
|
-
|
11286
|
+
#
|
11310
11287
|
|
11311
|
-
|
11312
|
-
|
11313
|
-
|
11314
|
-
|
11315
|
-
|
11316
|
-
|
11317
|
-
created=float,
|
11288
|
+
@ta.final
|
11289
|
+
def context_to_record(self, ctx: LoggingContext) -> ta.Mapping[str, ta.Any]:
|
11290
|
+
if (info := ctx.get_info(self.info_cls)) is not None:
|
11291
|
+
return self._info_to_record(info)
|
11292
|
+
else:
|
11293
|
+
raise TypeError # FIXME: fallback?
|
11318
11294
|
|
11319
|
-
|
11320
|
-
|
11295
|
+
@abc.abstractmethod
|
11296
|
+
def _info_to_record(self, info: T) -> ta.Mapping[str, ta.Any]:
|
11297
|
+
raise NotImplementedError
|
11321
11298
|
|
11322
|
-
|
11323
|
-
relativeCreated=float,
|
11299
|
+
#
|
11324
11300
|
|
11325
|
-
|
11301
|
+
@abc.abstractmethod
|
11302
|
+
def record_to_info(self, rec: logging.LogRecord) -> T:
|
11303
|
+
raise NotImplementedError
|
11326
11304
|
|
11327
|
-
|
11328
|
-
thread=ta.Optional[int],
|
11305
|
+
#
|
11329
11306
|
|
11330
|
-
|
11331
|
-
|
11307
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
11308
|
+
super().__init_subclass__(**kwargs)
|
11332
11309
|
|
11333
|
-
|
11310
|
+
if any(a.default is not cls.NOT_SET for a in cls.record_attrs.values()):
|
11311
|
+
raise TypeError(cls.record_attrs)
|
11312
|
+
|
11313
|
+
class OptionalAdapter(Adapter[T], Abstract, ta.Generic[T]):
|
11314
|
+
@property
|
11315
|
+
@abc.abstractmethod
|
11316
|
+
def _record_attrs(self) -> ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]:
|
11317
|
+
raise NotImplementedError
|
11318
|
+
|
11319
|
+
record_defaults: ta.ClassVar[ta.Mapping[str, ta.Any]]
|
11320
|
+
|
11321
|
+
#
|
11322
|
+
|
11323
|
+
@ta.final
|
11324
|
+
def context_to_record(self, ctx: LoggingContext) -> ta.Mapping[str, ta.Any]:
|
11325
|
+
if (info := ctx.get_info(self.info_cls)) is not None:
|
11326
|
+
return self._info_to_record(info)
|
11327
|
+
else:
|
11328
|
+
return self.record_defaults
|
11329
|
+
|
11330
|
+
@abc.abstractmethod
|
11331
|
+
def _info_to_record(self, info: T) -> ta.Mapping[str, ta.Any]:
|
11332
|
+
raise NotImplementedError
|
11333
|
+
|
11334
|
+
#
|
11335
|
+
|
11336
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
11337
|
+
super().__init_subclass__(**kwargs)
|
11338
|
+
|
11339
|
+
dd: ta.Dict[str, ta.Any] = {a.name: a.default for a in cls.record_attrs.values()}
|
11340
|
+
if any(d is cls.NOT_SET for d in dd.values()):
|
11341
|
+
raise TypeError(cls.record_attrs)
|
11342
|
+
cls.record_defaults = dd
|
11334
11343
|
|
11335
|
-
# Process name if available. Set to None if `logging.logMultiprocessing` is not truthy. Otherwise, set to
|
11336
|
-
# 'MainProcess', then `sys.modules.get('multiprocessing').current_process().name` if that works, otherwise remains
|
11337
|
-
# as 'MainProcess'.
|
11338
11344
|
#
|
11339
|
-
|
11345
|
+
|
11346
|
+
class Name(RequiredAdapter[LoggingContextInfos.Name]):
|
11347
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Name]] = LoggingContextInfos.Name
|
11348
|
+
|
11349
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
|
11350
|
+
# Name of the logger used to log the call. Unmodified by ctor.
|
11351
|
+
name=str,
|
11352
|
+
)
|
11353
|
+
|
11354
|
+
def _info_to_record(self, info: LoggingContextInfos.Name) -> ta.Mapping[str, ta.Any]:
|
11355
|
+
return dict(
|
11356
|
+
name=info.name,
|
11357
|
+
)
|
11358
|
+
|
11359
|
+
def record_to_info(self, rec: logging.LogRecord) -> LoggingContextInfos.Name:
|
11360
|
+
return LoggingContextInfos.Name(
|
11361
|
+
name=rec.name,
|
11362
|
+
)
|
11363
|
+
|
11364
|
+
class Level(RequiredAdapter[LoggingContextInfos.Level]):
|
11365
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Level]] = LoggingContextInfos.Level
|
11366
|
+
|
11367
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
|
11368
|
+
# Text logging level for the message ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'). Set to
|
11369
|
+
# `getLevelName(level)`.
|
11370
|
+
levelname=str,
|
11371
|
+
|
11372
|
+
# Numeric logging level for the message (DEBUG, INFO, WARNING, ERROR, CRITICAL). Unmodified by ctor.
|
11373
|
+
levelno=int,
|
11374
|
+
)
|
11375
|
+
|
11376
|
+
def _info_to_record(self, info: LoggingContextInfos.Level) -> ta.Mapping[str, ta.Any]:
|
11377
|
+
return dict(
|
11378
|
+
levelname=info.name,
|
11379
|
+
levelno=int(info.level),
|
11380
|
+
)
|
11381
|
+
|
11382
|
+
def record_to_info(self, rec: logging.LogRecord) -> LoggingContextInfos.Level:
|
11383
|
+
return LoggingContextInfos.Level.build(rec.levelno)
|
11384
|
+
|
11385
|
+
class Msg(RequiredAdapter[LoggingContextInfos.Msg]):
|
11386
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Msg]] = LoggingContextInfos.Msg
|
11387
|
+
|
11388
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
|
11389
|
+
# The format string passed in the original logging call. Merged with args to produce message, or an
|
11390
|
+
# arbitrary object (see Using arbitrary objects as messages). Unmodified by ctor.
|
11391
|
+
msg=str,
|
11392
|
+
|
11393
|
+
# The tuple of arguments merged into msg to produce message, or a dict whose values are used for the merge
|
11394
|
+
# (when there is only one argument, and it is a dictionary). Ctor will transform a 1-tuple containing a
|
11395
|
+
# Mapping into just the mapping, but is otherwise unmodified.
|
11396
|
+
args=ta.Union[tuple, dict, None],
|
11397
|
+
)
|
11398
|
+
|
11399
|
+
def _info_to_record(self, info: LoggingContextInfos.Msg) -> ta.Mapping[str, ta.Any]:
|
11400
|
+
return dict(
|
11401
|
+
msg=info.msg,
|
11402
|
+
args=info.args,
|
11403
|
+
)
|
11404
|
+
|
11405
|
+
def record_to_info(self, rec: logging.LogRecord) -> LoggingContextInfos.Msg:
|
11406
|
+
return LoggingContextInfos.Msg(
|
11407
|
+
msg=rec.msg,
|
11408
|
+
args=rec.args,
|
11409
|
+
)
|
11410
|
+
|
11411
|
+
# FIXME: handled specially - all unknown attrs on LogRecord
|
11412
|
+
# class Extra(Adapter[LoggingContextInfos.Extra]):
|
11413
|
+
# _record_attrs: ta.ClassVar[ta.Mapping[str, ta.Union[ta.Any, ta.Tuple[ta.Any, ta.Any]]]] = dict()
|
11340
11414
|
#
|
11341
|
-
#
|
11342
|
-
#
|
11415
|
+
# def info_to_record(self, info: ta.Optional[LoggingContextInfos.Extra]) -> ta.Mapping[str, ta.Any]:
|
11416
|
+
# # FIXME:
|
11417
|
+
# # if extra is not None:
|
11418
|
+
# # for key in extra:
|
11419
|
+
# # if (key in ["message", "asctime"]) or (key in rv.__dict__):
|
11420
|
+
# # raise KeyError("Attempt to overwrite %r in LogRecord" % key)
|
11421
|
+
# # rv.__dict__[key] = extra[key]
|
11422
|
+
# return dict()
|
11343
11423
|
#
|
11344
|
-
|
11424
|
+
# def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Extra]:
|
11425
|
+
# return None
|
11345
11426
|
|
11346
|
-
|
11347
|
-
|
11348
|
-
process=ta.Optional[int],
|
11427
|
+
class Time(RequiredAdapter[LoggingContextInfos.Time]):
|
11428
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Time]] = LoggingContextInfos.Time
|
11349
11429
|
|
11350
|
-
|
11430
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
|
11431
|
+
# Time when the LogRecord was created. Set to `time.time_ns() / 1e9` for >=3.13.0b1, otherwise simply
|
11432
|
+
# `time.time()`.
|
11433
|
+
#
|
11434
|
+
# See:
|
11435
|
+
# - https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
|
11436
|
+
# - https://github.com/python/cpython/commit/1500a23f33f5a6d052ff1ef6383d9839928b8ff1
|
11437
|
+
#
|
11438
|
+
created=float,
|
11351
11439
|
|
11352
|
-
|
11353
|
-
|
11354
|
-
taskName=ta.Optional[str],
|
11355
|
-
)
|
11440
|
+
# Millisecond portion of the time when the LogRecord was created.
|
11441
|
+
msecs=float,
|
11356
11442
|
|
11357
|
-
|
11443
|
+
# Time in milliseconds when the LogRecord was created, relative to the time the logging module was loaded.
|
11444
|
+
relativeCreated=float,
|
11445
|
+
)
|
11446
|
+
|
11447
|
+
def _info_to_record(self, info: LoggingContextInfos.Time) -> ta.Mapping[str, ta.Any]:
|
11448
|
+
return dict(
|
11449
|
+
created=info.secs,
|
11450
|
+
msecs=info.msecs,
|
11451
|
+
relativeCreated=info.relative_secs,
|
11452
|
+
)
|
11453
|
+
|
11454
|
+
def record_to_info(self, rec: logging.LogRecord) -> LoggingContextInfos.Time:
|
11455
|
+
return LoggingContextInfos.Time.build(
|
11456
|
+
int(rec.created * 1e9),
|
11457
|
+
)
|
11458
|
+
|
11459
|
+
class Exc(OptionalAdapter[LoggingContextInfos.Exc]):
|
11460
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Exc]] = LoggingContextInfos.Exc
|
11461
|
+
|
11462
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
|
11463
|
+
# Exception tuple (à la sys.exc_info) or, if no exception has occurred, None. Unmodified by ctor.
|
11464
|
+
exc_info=(ta.Optional[LoggingExcInfoTuple], None),
|
11465
|
+
|
11466
|
+
# Used to cache the traceback text. Simply set to None by ctor, later set by Formatter.format.
|
11467
|
+
exc_text=(ta.Optional[str], None),
|
11468
|
+
)
|
11469
|
+
|
11470
|
+
def _info_to_record(self, info: LoggingContextInfos.Exc) -> ta.Mapping[str, ta.Any]:
|
11471
|
+
return dict(
|
11472
|
+
exc_info=info.info_tuple,
|
11473
|
+
exc_text=None,
|
11474
|
+
)
|
11475
|
+
|
11476
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Exc]:
|
11477
|
+
# FIXME:
|
11478
|
+
# error: Argument 1 to "build" of "Exc" has incompatible type
|
11479
|
+
# "tuple[type[BaseException], BaseException, TracebackType | None] | tuple[None, None, None] | None"; expected # noqa
|
11480
|
+
# "BaseException | tuple[type[BaseException], BaseException, TracebackType | None] | bool | None" [arg-type] # noqa
|
11481
|
+
return LoggingContextInfos.Exc.build(rec.exc_info) # type: ignore[arg-type]
|
11482
|
+
|
11483
|
+
class Caller(OptionalAdapter[LoggingContextInfos.Caller]):
|
11484
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Caller]] = LoggingContextInfos.Caller
|
11485
|
+
|
11486
|
+
_UNKNOWN_PATH_NAME: ta.ClassVar[str] = '(unknown file)'
|
11487
|
+
_UNKNOWN_FUNC_NAME: ta.ClassVar[str] = '(unknown function)'
|
11488
|
+
|
11489
|
+
_STACK_INFO_PREFIX: ta.ClassVar[str] = 'Stack (most recent call last):\n'
|
11490
|
+
|
11491
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
|
11492
|
+
# Full pathname of the source file where the logging call was issued (if available). Unmodified by ctor. May
|
11493
|
+
# default to "(unknown file)" by Logger.findCaller / Logger._log.
|
11494
|
+
pathname=(str, _UNKNOWN_PATH_NAME),
|
11495
|
+
|
11496
|
+
# Source line number where the logging call was issued (if available). Unmodified by ctor. May default to 0
|
11497
|
+
# y Logger.findCaller / Logger._log.
|
11498
|
+
lineno=(int, 0),
|
11499
|
+
|
11500
|
+
# Name of function containing the logging call. Set by ctor to `func` arg, unmodified. May default to
|
11501
|
+
# "(unknown function)" by Logger.findCaller / Logger._log.
|
11502
|
+
funcName=(str, _UNKNOWN_FUNC_NAME),
|
11503
|
+
|
11504
|
+
# Stack frame information (where available) from the bottom of the stack in the current thread, up to and
|
11505
|
+
# including the stack frame of the logging call which resulted in the creation of this record. Set by ctor
|
11506
|
+
# to `sinfo` arg, unmodified. Mostly set, if requested, by `Logger.findCaller`, to
|
11507
|
+
# `traceback.print_stack(f)`, but prepended with the literal "Stack (most recent call last):\n", and
|
11508
|
+
# stripped of exactly one trailing `\n` if present.
|
11509
|
+
stack_info=(ta.Optional[str], None),
|
11510
|
+
)
|
11511
|
+
|
11512
|
+
def _info_to_record(self, caller: LoggingContextInfos.Caller) -> ta.Mapping[str, ta.Any]:
|
11513
|
+
if (sinfo := caller.stack_info) is not None:
|
11514
|
+
stack_info: ta.Optional[str] = '\n'.join([
|
11515
|
+
self._STACK_INFO_PREFIX,
|
11516
|
+
sinfo[1:] if sinfo.endswith('\n') else sinfo,
|
11517
|
+
])
|
11518
|
+
else:
|
11519
|
+
stack_info = None
|
11520
|
+
|
11521
|
+
return dict(
|
11522
|
+
pathname=caller.file_path,
|
11523
|
+
|
11524
|
+
lineno=caller.line_no,
|
11525
|
+
funcName=caller.func_name,
|
11526
|
+
|
11527
|
+
stack_info=stack_info,
|
11528
|
+
)
|
11529
|
+
|
11530
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Caller]:
|
11531
|
+
# FIXME: piecemeal?
|
11532
|
+
if (
|
11533
|
+
rec.pathname != self._UNKNOWN_PATH_NAME and
|
11534
|
+
rec.lineno != 0 and
|
11535
|
+
rec.funcName != self._UNKNOWN_FUNC_NAME
|
11536
|
+
):
|
11537
|
+
if (sinfo := rec.stack_info) is not None and sinfo.startswith(self._STACK_INFO_PREFIX):
|
11538
|
+
sinfo = sinfo[len(self._STACK_INFO_PREFIX):]
|
11539
|
+
return LoggingContextInfos.Caller(
|
11540
|
+
file_path=rec.pathname,
|
11541
|
+
|
11542
|
+
line_no=rec.lineno,
|
11543
|
+
func_name=rec.funcName,
|
11544
|
+
|
11545
|
+
stack_info=sinfo,
|
11546
|
+
)
|
11547
|
+
|
11548
|
+
return None
|
11549
|
+
|
11550
|
+
class SourceFile(Adapter[LoggingContextInfos.SourceFile]):
|
11551
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.SourceFile]] = LoggingContextInfos.SourceFile
|
11552
|
+
|
11553
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
|
11554
|
+
# Filename portion of pathname. Set to `os.path.basename(pathname)` if successful, otherwise defaults to
|
11555
|
+
# pathname.
|
11556
|
+
filename=str,
|
11557
|
+
|
11558
|
+
# Module (name portion of filename). Set to `os.path.splitext(filename)[0]`, otherwise defaults to
|
11559
|
+
# "Unknown module".
|
11560
|
+
module=str,
|
11561
|
+
)
|
11562
|
+
|
11563
|
+
_UNKNOWN_MODULE: ta.ClassVar[str] = 'Unknown module'
|
11564
|
+
|
11565
|
+
def context_to_record(self, ctx: LoggingContext) -> ta.Mapping[str, ta.Any]:
|
11566
|
+
if (info := ctx.get_info(LoggingContextInfos.SourceFile)) is not None:
|
11567
|
+
return dict(
|
11568
|
+
filename=info.file_name,
|
11569
|
+
module=info.module,
|
11570
|
+
)
|
11571
|
+
|
11572
|
+
if (caller := ctx.get_info(LoggingContextInfos.Caller)) is not None:
|
11573
|
+
return dict(
|
11574
|
+
filename=caller.file_path,
|
11575
|
+
module=self._UNKNOWN_MODULE,
|
11576
|
+
)
|
11577
|
+
|
11578
|
+
return dict(
|
11579
|
+
filename=LoggingContextInfoRecordAdapters.Caller._UNKNOWN_PATH_NAME, # noqa
|
11580
|
+
module=self._UNKNOWN_MODULE,
|
11581
|
+
)
|
11582
|
+
|
11583
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.SourceFile]:
|
11584
|
+
if (
|
11585
|
+
rec.module is not None and
|
11586
|
+
rec.module != self._UNKNOWN_MODULE
|
11587
|
+
):
|
11588
|
+
return LoggingContextInfos.SourceFile(
|
11589
|
+
file_name=rec.filename,
|
11590
|
+
module=rec.module, # FIXME: piecemeal?
|
11591
|
+
)
|
11592
|
+
|
11593
|
+
return None
|
11594
|
+
|
11595
|
+
class Thread(OptionalAdapter[LoggingContextInfos.Thread]):
|
11596
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Thread]] = LoggingContextInfos.Thread
|
11597
|
+
|
11598
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
|
11599
|
+
# Thread ID if available, and `logging.logThreads` is truthy.
|
11600
|
+
thread=(ta.Optional[int], None),
|
11601
|
+
|
11602
|
+
# Thread name if available, and `logging.logThreads` is truthy.
|
11603
|
+
threadName=(ta.Optional[str], None),
|
11604
|
+
)
|
11605
|
+
|
11606
|
+
def _info_to_record(self, info: LoggingContextInfos.Thread) -> ta.Mapping[str, ta.Any]:
|
11607
|
+
if logging.logThreads:
|
11608
|
+
return dict(
|
11609
|
+
thread=info.ident,
|
11610
|
+
threadName=info.name,
|
11611
|
+
)
|
11612
|
+
|
11613
|
+
return self.record_defaults
|
11614
|
+
|
11615
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Thread]:
|
11616
|
+
if (
|
11617
|
+
(ident := rec.thread) is not None and
|
11618
|
+
(name := rec.threadName) is not None
|
11619
|
+
):
|
11620
|
+
return LoggingContextInfos.Thread(
|
11621
|
+
ident=ident,
|
11622
|
+
native_id=None,
|
11623
|
+
name=name,
|
11624
|
+
)
|
11625
|
+
|
11626
|
+
return None
|
11627
|
+
|
11628
|
+
class Process(OptionalAdapter[LoggingContextInfos.Process]):
|
11629
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Process]] = LoggingContextInfos.Process
|
11630
|
+
|
11631
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
|
11632
|
+
# Process ID if available - that is, if `hasattr(os, 'getpid')` - and `logging.logProcesses` is truthy,
|
11633
|
+
# otherwise None.
|
11634
|
+
process=(ta.Optional[int], None),
|
11635
|
+
)
|
11636
|
+
|
11637
|
+
def _info_to_record(self, info: LoggingContextInfos.Process) -> ta.Mapping[str, ta.Any]:
|
11638
|
+
if logging.logProcesses:
|
11639
|
+
return dict(
|
11640
|
+
process=info.pid,
|
11641
|
+
)
|
11642
|
+
|
11643
|
+
return self.record_defaults
|
11644
|
+
|
11645
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Process]:
|
11646
|
+
if (
|
11647
|
+
(pid := rec.process) is not None
|
11648
|
+
):
|
11649
|
+
return LoggingContextInfos.Process(
|
11650
|
+
pid=pid,
|
11651
|
+
)
|
11652
|
+
|
11653
|
+
return None
|
11654
|
+
|
11655
|
+
class Multiprocessing(OptionalAdapter[LoggingContextInfos.Multiprocessing]):
|
11656
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Multiprocessing]] = LoggingContextInfos.Multiprocessing
|
11657
|
+
|
11658
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
|
11659
|
+
# Process name if available. Set to None if `logging.logMultiprocessing` is not truthy. Otherwise, set to
|
11660
|
+
# 'MainProcess', then `sys.modules.get('multiprocessing').current_process().name` if that works, otherwise
|
11661
|
+
# remains as 'MainProcess'.
|
11662
|
+
#
|
11663
|
+
# As noted by stdlib:
|
11664
|
+
#
|
11665
|
+
# Errors may occur if multiprocessing has not finished loading yet - e.g. if a custom import hook causes
|
11666
|
+
# third-party code to run when multiprocessing calls import. See issue 8200 for an example
|
11667
|
+
#
|
11668
|
+
processName=(ta.Optional[str], None),
|
11669
|
+
)
|
11670
|
+
|
11671
|
+
def _info_to_record(self, info: LoggingContextInfos.Multiprocessing) -> ta.Mapping[str, ta.Any]:
|
11672
|
+
if logging.logMultiprocessing:
|
11673
|
+
return dict(
|
11674
|
+
processName=info.process_name,
|
11675
|
+
)
|
11676
|
+
|
11677
|
+
return self.record_defaults
|
11678
|
+
|
11679
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Multiprocessing]:
|
11680
|
+
if (
|
11681
|
+
(process_name := rec.processName) is not None
|
11682
|
+
):
|
11683
|
+
return LoggingContextInfos.Multiprocessing(
|
11684
|
+
process_name=process_name,
|
11685
|
+
)
|
11686
|
+
|
11687
|
+
return None
|
11688
|
+
|
11689
|
+
class AsyncioTask(OptionalAdapter[LoggingContextInfos.AsyncioTask]):
|
11690
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.AsyncioTask]] = LoggingContextInfos.AsyncioTask
|
11691
|
+
|
11692
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Union[ta.Any, ta.Tuple[ta.Any, ta.Any]]]] = dict(
|
11693
|
+
# Absent <3.12, otherwise asyncio.Task name if available, and `logging.logAsyncioTasks` is truthy. Set to
|
11694
|
+
# `sys.modules.get('asyncio').current_task().get_name()`, otherwise None.
|
11695
|
+
taskName=(ta.Optional[str], None),
|
11696
|
+
)
|
11697
|
+
|
11698
|
+
def _info_to_record(self, info: LoggingContextInfos.AsyncioTask) -> ta.Mapping[str, ta.Any]:
|
11699
|
+
if getattr(logging, 'logAsyncioTasks', None): # Absent <3.12
|
11700
|
+
return dict(
|
11701
|
+
taskName=info.name,
|
11702
|
+
)
|
11703
|
+
|
11704
|
+
return self.record_defaults
|
11705
|
+
|
11706
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.AsyncioTask]:
|
11707
|
+
if (
|
11708
|
+
(name := getattr(rec, 'taskName', None)) is not None
|
11709
|
+
):
|
11710
|
+
return LoggingContextInfos.AsyncioTask(
|
11711
|
+
name=name,
|
11712
|
+
)
|
11713
|
+
|
11714
|
+
return None
|
11715
|
+
|
11716
|
+
|
11717
|
+
_LOGGING_CONTEXT_INFO_RECORD_ADAPTERS_: ta.Sequence[LoggingContextInfoRecordAdapters.Adapter] = [ # noqa
|
11718
|
+
LoggingContextInfoRecordAdapters.Name(),
|
11719
|
+
LoggingContextInfoRecordAdapters.Level(),
|
11720
|
+
LoggingContextInfoRecordAdapters.Msg(),
|
11721
|
+
LoggingContextInfoRecordAdapters.Time(),
|
11722
|
+
LoggingContextInfoRecordAdapters.Exc(),
|
11723
|
+
LoggingContextInfoRecordAdapters.Caller(),
|
11724
|
+
LoggingContextInfoRecordAdapters.SourceFile(),
|
11725
|
+
LoggingContextInfoRecordAdapters.Thread(),
|
11726
|
+
LoggingContextInfoRecordAdapters.Process(),
|
11727
|
+
LoggingContextInfoRecordAdapters.Multiprocessing(),
|
11728
|
+
LoggingContextInfoRecordAdapters.AsyncioTask(),
|
11729
|
+
]
|
11730
|
+
|
11731
|
+
_LOGGING_CONTEXT_INFO_RECORD_ADAPTERS: ta.Mapping[ta.Type[LoggingContextInfo], LoggingContextInfoRecordAdapters.Adapter] = { # noqa
|
11732
|
+
ad.info_cls: ad for ad in _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS_
|
11733
|
+
}
|
11734
|
+
|
11735
|
+
|
11736
|
+
##
|
11358
11737
|
|
11359
11738
|
|
11360
11739
|
# Formatter:
|
11361
11740
|
# - https://github.com/python/cpython/blob/39b2f82717a69dde7212bc39b673b0f55c99e6a3/Lib/logging/__init__.py#L514 (3.8)
|
11362
11741
|
# - https://github.com/python/cpython/blob/f070f54c5f4a42c7c61d1d5d3b8f3b7203b4a0fb/Lib/logging/__init__.py#L554 (~3.14) # noqa
|
11363
11742
|
#
|
11364
|
-
|
11743
|
+
_KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTRS: ta.Dict[str, ta.Any] = dict(
|
11365
11744
|
# The logged message, computed as msg % args. Set to `record.getMessage()`.
|
11366
11745
|
message=str,
|
11367
11746
|
|
@@ -11375,20 +11754,31 @@ KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTRS: ta.Dict[str, ta.Any] = dict(
|
|
11375
11754
|
exc_text=ta.Optional[str],
|
11376
11755
|
)
|
11377
11756
|
|
11378
|
-
KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTR_SET: ta.FrozenSet[str] = frozenset(KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTRS)
|
11379
|
-
|
11380
11757
|
|
11381
11758
|
##
|
11382
11759
|
|
11383
11760
|
|
11761
|
+
_KNOWN_STD_LOGGING_RECORD_ATTR_SET: ta.FrozenSet[str] = frozenset(
|
11762
|
+
a for ad in _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS.values() for a in ad.record_attrs
|
11763
|
+
)
|
11764
|
+
|
11765
|
+
_KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTR_SET: ta.FrozenSet[str] = frozenset(_KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTRS)
|
11766
|
+
|
11767
|
+
|
11384
11768
|
class UnknownStdLoggingRecordAttrsWarning(LoggingSetupWarning):
|
11385
11769
|
pass
|
11386
11770
|
|
11387
11771
|
|
11388
11772
|
def _check_std_logging_record_attrs() -> None:
|
11773
|
+
if (
|
11774
|
+
len([a for ad in _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS.values() for a in ad.record_attrs]) !=
|
11775
|
+
len(_KNOWN_STD_LOGGING_RECORD_ATTR_SET)
|
11776
|
+
):
|
11777
|
+
raise RuntimeError('Duplicate LoggingContextInfoRecordAdapter record attrs')
|
11778
|
+
|
11389
11779
|
rec_dct = dict(logging.makeLogRecord({}).__dict__)
|
11390
11780
|
|
11391
|
-
if (unk_rec_fields := frozenset(rec_dct) -
|
11781
|
+
if (unk_rec_fields := frozenset(rec_dct) - _KNOWN_STD_LOGGING_RECORD_ATTR_SET):
|
11392
11782
|
import warnings # noqa
|
11393
11783
|
|
11394
11784
|
warnings.warn(
|
@@ -11404,116 +11794,43 @@ _check_std_logging_record_attrs()
|
|
11404
11794
|
|
11405
11795
|
|
11406
11796
|
class LoggingContextLogRecord(logging.LogRecord):
|
11407
|
-
|
11408
|
-
|
11409
|
-
|
11410
|
-
|
11411
|
-
|
11412
|
-
|
11413
|
-
|
11797
|
+
# LogRecord.__init__ args:
|
11798
|
+
# - name: str
|
11799
|
+
# - level: int
|
11800
|
+
# - pathname: str - Confusingly referred to as `fn` before the LogRecord ctor. May be empty or "(unknown file)".
|
11801
|
+
# - lineno: int - May be 0.
|
11802
|
+
# - msg: str
|
11803
|
+
# - args: tuple | dict | 1-tuple[dict]
|
11804
|
+
# - exc_info: LoggingExcInfoTuple | None
|
11805
|
+
# - func: str | None = None -> funcName
|
11806
|
+
# - sinfo: str | None = None -> stack_info
|
11414
11807
|
|
11415
|
-
def __init__( # noqa
|
11416
|
-
|
11417
|
-
# name,
|
11418
|
-
# level,
|
11419
|
-
# pathname,
|
11420
|
-
# lineno,
|
11421
|
-
# msg,
|
11422
|
-
# args,
|
11423
|
-
# exc_info,
|
11424
|
-
# func=None,
|
11425
|
-
# sinfo=None,
|
11426
|
-
# **kwargs,
|
11427
|
-
*,
|
11428
|
-
name: str,
|
11429
|
-
msg: str,
|
11430
|
-
args: ta.Union[tuple, dict],
|
11431
|
-
|
11432
|
-
_logging_context: LoggingContext,
|
11433
|
-
) -> None:
|
11434
|
-
ctx = _logging_context
|
11435
|
-
|
11436
|
-
self.name: str = name
|
11437
|
-
|
11438
|
-
self.msg: str = msg
|
11808
|
+
def __init__(self, *, _logging_context: LoggingContext) -> None: # noqa
|
11809
|
+
self._logging_context = _logging_context
|
11439
11810
|
|
11440
|
-
|
11441
|
-
|
11442
|
-
args = args[0] # type: ignore[assignment]
|
11443
|
-
self.args: ta.Union[tuple, dict] = args
|
11811
|
+
for ad in _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS_:
|
11812
|
+
self.__dict__.update(ad.context_to_record(_logging_context))
|
11444
11813
|
|
11445
|
-
self.levelname: str = logging.getLevelName(ctx.level)
|
11446
|
-
self.levelno: int = ctx.level
|
11447
11814
|
|
11448
|
-
|
11449
|
-
self.pathname: str = caller.file_path
|
11450
|
-
else:
|
11451
|
-
self.pathname = self._UNKNOWN_PATH_NAME
|
11452
|
-
|
11453
|
-
if (src_file := ctx.source_file()) is not None:
|
11454
|
-
self.filename: str = src_file.file_name
|
11455
|
-
self.module: str = src_file.module
|
11456
|
-
else:
|
11457
|
-
self.filename = self.pathname
|
11458
|
-
self.module = self._UNKNOWN_MODULE
|
11459
|
-
|
11460
|
-
self.exc_info: ta.Optional[LoggingExcInfoTuple] = ctx.exc_info_tuple
|
11461
|
-
self.exc_text: ta.Optional[str] = None
|
11462
|
-
|
11463
|
-
# If ctx.build_caller() was never called, we simply don't have a stack trace.
|
11464
|
-
if caller is not None:
|
11465
|
-
if (sinfo := caller.stack_info) is not None:
|
11466
|
-
self.stack_info: ta.Optional[str] = '\n'.join([
|
11467
|
-
self._STACK_INFO_PREFIX,
|
11468
|
-
sinfo[1:] if sinfo.endswith('\n') else sinfo,
|
11469
|
-
])
|
11470
|
-
else:
|
11471
|
-
self.stack_info = None
|
11472
|
-
|
11473
|
-
self.lineno: int = caller.line_no
|
11474
|
-
self.funcName: str = caller.name
|
11475
|
-
|
11476
|
-
else:
|
11477
|
-
self.stack_info = None
|
11478
|
-
|
11479
|
-
self.lineno = 0
|
11480
|
-
self.funcName = self._UNKNOWN_FUNC_NAME
|
11815
|
+
##
|
11481
11816
|
|
11482
|
-
times = ctx.times
|
11483
|
-
self.created: float = times.created
|
11484
|
-
self.msecs: float = times.msecs
|
11485
|
-
self.relativeCreated: float = times.relative_created
|
11486
11817
|
|
11487
|
-
|
11488
|
-
|
11489
|
-
|
11490
|
-
|
11491
|
-
|
11492
|
-
self.thread = None
|
11493
|
-
self.threadName = None
|
11818
|
+
@ta.final
|
11819
|
+
class LogRecordLoggingContext(LoggingContext):
|
11820
|
+
def __init__(self, rec: logging.LogRecord) -> None:
|
11821
|
+
if isinstance(rec, LoggingContextLogRecord):
|
11822
|
+
raise TypeError(rec)
|
11494
11823
|
|
11495
|
-
|
11496
|
-
process = check.not_none(ctx.process())
|
11497
|
-
self.process: ta.Optional[int] = process.pid
|
11498
|
-
else:
|
11499
|
-
self.process = None
|
11824
|
+
self._rec = rec
|
11500
11825
|
|
11501
|
-
|
11502
|
-
|
11503
|
-
|
11504
|
-
|
11505
|
-
|
11506
|
-
else:
|
11507
|
-
self.processName = None
|
11826
|
+
self._infos: ta.Dict[ta.Type[LoggingContextInfo], LoggingContextInfo] = {
|
11827
|
+
type(info): info
|
11828
|
+
for ad in _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS_
|
11829
|
+
if (info := ad.record_to_info(rec)) is not None
|
11830
|
+
}
|
11508
11831
|
|
11509
|
-
|
11510
|
-
|
11511
|
-
if (at := ctx.asyncio_task()) is not None:
|
11512
|
-
self.taskName: ta.Optional[str] = at.name
|
11513
|
-
else:
|
11514
|
-
self.taskName = None
|
11515
|
-
else:
|
11516
|
-
self.taskName = None
|
11832
|
+
def get_info(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
|
11833
|
+
return self._infos.get(ty)
|
11517
11834
|
|
11518
11835
|
|
11519
11836
|
########################################
|
@@ -12152,21 +12469,20 @@ class StdLogger(Logger):
|
|
12152
12469
|
return self._std.getEffectiveLevel()
|
12153
12470
|
|
12154
12471
|
def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any) -> None:
|
12155
|
-
if not self.is_enabled_for(ctx.level):
|
12472
|
+
if not self.is_enabled_for(ctx.must_get_info(LoggingContextInfos.Level).level):
|
12156
12473
|
return
|
12157
12474
|
|
12158
|
-
ctx.
|
12159
|
-
|
12160
|
-
ms, args = self._prepare_msg_args(msg, *args)
|
12161
|
-
|
12162
|
-
rec = LoggingContextLogRecord(
|
12475
|
+
ctx.set_basic(
|
12163
12476
|
name=self._std.name,
|
12164
|
-
msg=ms,
|
12165
|
-
args=args,
|
12166
12477
|
|
12167
|
-
|
12478
|
+
msg=msg,
|
12479
|
+
args=args,
|
12168
12480
|
)
|
12169
12481
|
|
12482
|
+
ctx.capture()
|
12483
|
+
|
12484
|
+
rec = LoggingContextLogRecord(_logging_context=ctx)
|
12485
|
+
|
12170
12486
|
self._std.handle(rec)
|
12171
12487
|
|
12172
12488
|
|