ominfra 0.0.0.dev430__py3-none-any.whl → 0.0.0.dev431__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ominfra/scripts/journald2aws.py +957 -696
- ominfra/scripts/manage.py +945 -684
- ominfra/scripts/supervisor.py +943 -682
- {ominfra-0.0.0.dev430.dist-info → ominfra-0.0.0.dev431.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev430.dist-info → ominfra-0.0.0.dev431.dist-info}/RECORD +9 -9
- {ominfra-0.0.0.dev430.dist-info → ominfra-0.0.0.dev431.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev430.dist-info → ominfra-0.0.0.dev431.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev430.dist-info → ominfra-0.0.0.dev431.dist-info}/licenses/LICENSE +0 -0
- {ominfra-0.0.0.dev430.dist-info → ominfra-0.0.0.dev431.dist-info}/top_level.txt +0 -0
ominfra/scripts/journald2aws.py
CHANGED
@@ -92,13 +92,15 @@ LogLevel = int # ta.TypeAlias
|
|
92
92
|
# ../../../../omlish/configs/formats.py
|
93
93
|
ConfigDataT = ta.TypeVar('ConfigDataT', bound='ConfigData')
|
94
94
|
|
95
|
-
# ../../../../omlish/logs/
|
95
|
+
# ../../../../omlish/logs/infos.py
|
96
|
+
LoggingMsgFn = ta.Callable[[], ta.Union[str, tuple]] # ta.TypeAlias
|
96
97
|
LoggingExcInfoTuple = ta.Tuple[ta.Type[BaseException], BaseException, ta.Optional[types.TracebackType]] # ta.TypeAlias
|
97
98
|
LoggingExcInfo = ta.Union[BaseException, LoggingExcInfoTuple] # ta.TypeAlias
|
98
99
|
LoggingExcInfoArg = ta.Union[LoggingExcInfo, bool, None] # ta.TypeAlias
|
100
|
+
LoggingContextInfo = ta.Any # ta.TypeAlias
|
99
101
|
|
100
|
-
# ../../../../omlish/logs/
|
101
|
-
|
102
|
+
# ../../../../omlish/logs/contexts.py
|
103
|
+
LoggingContextInfoT = ta.TypeVar('LoggingContextInfoT', bound=LoggingContextInfo)
|
102
104
|
|
103
105
|
# ../../../threadworkers.py
|
104
106
|
ThreadWorkerT = ta.TypeVar('ThreadWorkerT', bound='ThreadWorker')
|
@@ -2689,124 +2691,6 @@ def format_num_bytes(num_bytes: int) -> str:
|
|
2689
2691
|
return f'{num_bytes / 1024 ** (len(FORMAT_NUM_BYTES_SUFFIXES) - 1):.2f}{FORMAT_NUM_BYTES_SUFFIXES[-1]}'
|
2690
2692
|
|
2691
2693
|
|
2692
|
-
########################################
|
2693
|
-
# ../../../../../omlish/logs/infos.py
|
2694
|
-
|
2695
|
-
|
2696
|
-
##
|
2697
|
-
|
2698
|
-
|
2699
|
-
def logging_context_info(cls):
|
2700
|
-
return cls
|
2701
|
-
|
2702
|
-
|
2703
|
-
##
|
2704
|
-
|
2705
|
-
|
2706
|
-
@logging_context_info
|
2707
|
-
@ta.final
|
2708
|
-
class LoggingSourceFileInfo(ta.NamedTuple):
|
2709
|
-
file_name: str
|
2710
|
-
module: str
|
2711
|
-
|
2712
|
-
@classmethod
|
2713
|
-
def build(cls, file_path: ta.Optional[str]) -> ta.Optional['LoggingSourceFileInfo']:
|
2714
|
-
if file_path is None:
|
2715
|
-
return None
|
2716
|
-
|
2717
|
-
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L331-L336 # noqa
|
2718
|
-
try:
|
2719
|
-
file_name = os.path.basename(file_path)
|
2720
|
-
module = os.path.splitext(file_name)[0]
|
2721
|
-
except (TypeError, ValueError, AttributeError):
|
2722
|
-
return None
|
2723
|
-
|
2724
|
-
return cls(
|
2725
|
-
file_name=file_name,
|
2726
|
-
module=module,
|
2727
|
-
)
|
2728
|
-
|
2729
|
-
|
2730
|
-
##
|
2731
|
-
|
2732
|
-
|
2733
|
-
@logging_context_info
|
2734
|
-
@ta.final
|
2735
|
-
class LoggingThreadInfo(ta.NamedTuple):
|
2736
|
-
ident: int
|
2737
|
-
native_id: ta.Optional[int]
|
2738
|
-
name: str
|
2739
|
-
|
2740
|
-
@classmethod
|
2741
|
-
def build(cls) -> 'LoggingThreadInfo':
|
2742
|
-
return cls(
|
2743
|
-
ident=threading.get_ident(),
|
2744
|
-
native_id=threading.get_native_id() if hasattr(threading, 'get_native_id') else None,
|
2745
|
-
name=threading.current_thread().name,
|
2746
|
-
)
|
2747
|
-
|
2748
|
-
|
2749
|
-
##
|
2750
|
-
|
2751
|
-
|
2752
|
-
@logging_context_info
|
2753
|
-
@ta.final
|
2754
|
-
class LoggingProcessInfo(ta.NamedTuple):
|
2755
|
-
pid: int
|
2756
|
-
|
2757
|
-
@classmethod
|
2758
|
-
def build(cls) -> 'LoggingProcessInfo':
|
2759
|
-
return cls(
|
2760
|
-
pid=os.getpid(),
|
2761
|
-
)
|
2762
|
-
|
2763
|
-
|
2764
|
-
##
|
2765
|
-
|
2766
|
-
|
2767
|
-
@logging_context_info
|
2768
|
-
@ta.final
|
2769
|
-
class LoggingMultiprocessingInfo(ta.NamedTuple):
|
2770
|
-
process_name: str
|
2771
|
-
|
2772
|
-
@classmethod
|
2773
|
-
def build(cls) -> ta.Optional['LoggingMultiprocessingInfo']:
|
2774
|
-
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L355-L364 # noqa
|
2775
|
-
if (mp := sys.modules.get('multiprocessing')) is None:
|
2776
|
-
return None
|
2777
|
-
|
2778
|
-
return cls(
|
2779
|
-
process_name=mp.current_process().name,
|
2780
|
-
)
|
2781
|
-
|
2782
|
-
|
2783
|
-
##
|
2784
|
-
|
2785
|
-
|
2786
|
-
@logging_context_info
|
2787
|
-
@ta.final
|
2788
|
-
class LoggingAsyncioTaskInfo(ta.NamedTuple):
|
2789
|
-
name: str
|
2790
|
-
|
2791
|
-
@classmethod
|
2792
|
-
def build(cls) -> ta.Optional['LoggingAsyncioTaskInfo']:
|
2793
|
-
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L372-L377 # noqa
|
2794
|
-
if (asyncio := sys.modules.get('asyncio')) is None:
|
2795
|
-
return None
|
2796
|
-
|
2797
|
-
try:
|
2798
|
-
task = asyncio.current_task()
|
2799
|
-
except Exception: # noqa
|
2800
|
-
return None
|
2801
|
-
|
2802
|
-
if task is None:
|
2803
|
-
return None
|
2804
|
-
|
2805
|
-
return cls(
|
2806
|
-
name=task.get_name(), # Always non-None
|
2807
|
-
)
|
2808
|
-
|
2809
|
-
|
2810
2694
|
########################################
|
2811
2695
|
# ../../../../../omlish/logs/levels.py
|
2812
2696
|
|
@@ -4945,74 +4829,362 @@ def check_lite_runtime_version() -> None:
|
|
4945
4829
|
|
4946
4830
|
|
4947
4831
|
########################################
|
4948
|
-
# ../../../../../omlish/logs/
|
4832
|
+
# ../../../../../omlish/logs/infos.py
|
4833
|
+
"""
|
4834
|
+
TODO:
|
4835
|
+
- remove redundant info fields only present for std adaptation (Level.name, ...)
|
4836
|
+
"""
|
4949
4837
|
|
4950
4838
|
|
4951
4839
|
##
|
4952
4840
|
|
4953
4841
|
|
4954
|
-
|
4842
|
+
def logging_context_info(cls):
|
4843
|
+
return cls
|
4844
|
+
|
4845
|
+
|
4955
4846
|
@ta.final
|
4956
|
-
class
|
4957
|
-
|
4958
|
-
|
4959
|
-
name: str
|
4960
|
-
stack_info: ta.Optional[str]
|
4847
|
+
class LoggingContextInfos:
|
4848
|
+
def __new__(cls, *args, **kwargs): # noqa
|
4849
|
+
raise TypeError
|
4961
4850
|
|
4962
|
-
|
4963
|
-
def is_internal_frame(cls, frame: types.FrameType) -> bool:
|
4964
|
-
file_path = os.path.normcase(frame.f_code.co_filename)
|
4851
|
+
#
|
4965
4852
|
|
4966
|
-
|
4967
|
-
|
4968
|
-
|
4969
|
-
|
4970
|
-
return True
|
4853
|
+
@logging_context_info
|
4854
|
+
@ta.final
|
4855
|
+
class Name(ta.NamedTuple):
|
4856
|
+
name: str
|
4971
4857
|
|
4972
|
-
|
4858
|
+
@logging_context_info
|
4859
|
+
@ta.final
|
4860
|
+
class Level(ta.NamedTuple):
|
4861
|
+
level: NamedLogLevel
|
4862
|
+
name: str
|
4973
4863
|
|
4974
|
-
|
4975
|
-
|
4976
|
-
|
4864
|
+
@classmethod
|
4865
|
+
def build(cls, level: int) -> 'LoggingContextInfos.Level':
|
4866
|
+
nl: NamedLogLevel = level if level.__class__ is NamedLogLevel else NamedLogLevel(level) # type: ignore[assignment] # noqa
|
4867
|
+
return cls(
|
4868
|
+
level=nl,
|
4869
|
+
name=logging.getLevelName(nl),
|
4870
|
+
)
|
4871
|
+
|
4872
|
+
@logging_context_info
|
4873
|
+
@ta.final
|
4874
|
+
class Msg(ta.NamedTuple):
|
4875
|
+
msg: str
|
4876
|
+
args: ta.Union[tuple, ta.Mapping[ta.Any, ta.Any], None]
|
4977
4877
|
|
4978
|
-
|
4979
|
-
|
4980
|
-
|
4981
|
-
|
4982
|
-
|
4878
|
+
@classmethod
|
4879
|
+
def build(
|
4880
|
+
cls,
|
4881
|
+
msg: ta.Union[str, tuple, LoggingMsgFn],
|
4882
|
+
*args: ta.Any,
|
4883
|
+
) -> 'LoggingContextInfos.Msg':
|
4884
|
+
s: str
|
4885
|
+
a: ta.Any
|
4886
|
+
|
4887
|
+
if callable(msg):
|
4888
|
+
if args:
|
4889
|
+
raise TypeError(f'Must not provide both a message function and args: {msg=} {args=}')
|
4890
|
+
x = msg()
|
4891
|
+
if isinstance(x, str):
|
4892
|
+
s, a = x, ()
|
4893
|
+
elif isinstance(x, tuple):
|
4894
|
+
if x:
|
4895
|
+
s, a = x[0], x[1:]
|
4896
|
+
else:
|
4897
|
+
s, a = '', ()
|
4898
|
+
else:
|
4899
|
+
raise TypeError(x)
|
4983
4900
|
|
4984
|
-
|
4901
|
+
elif isinstance(msg, tuple):
|
4902
|
+
if args:
|
4903
|
+
raise TypeError(f'Must not provide both a tuple message and args: {msg=} {args=}')
|
4904
|
+
if msg:
|
4905
|
+
s, a = msg[0], msg[1:]
|
4906
|
+
else:
|
4907
|
+
s, a = '', ()
|
4985
4908
|
|
4986
|
-
|
4909
|
+
elif isinstance(msg, str):
|
4910
|
+
s, a = msg, args
|
4911
|
+
|
4912
|
+
else:
|
4913
|
+
raise TypeError(msg)
|
4914
|
+
|
4915
|
+
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L307 # noqa
|
4916
|
+
if a and len(a) == 1 and isinstance(a[0], collections.abc.Mapping) and a[0]:
|
4917
|
+
a = a[0]
|
4918
|
+
|
4919
|
+
return cls(
|
4920
|
+
msg=s,
|
4921
|
+
args=a,
|
4922
|
+
)
|
4923
|
+
|
4924
|
+
@logging_context_info
|
4925
|
+
@ta.final
|
4926
|
+
class Extra(ta.NamedTuple):
|
4927
|
+
extra: ta.Mapping[ta.Any, ta.Any]
|
4928
|
+
|
4929
|
+
@logging_context_info
|
4930
|
+
@ta.final
|
4931
|
+
class Time(ta.NamedTuple):
|
4932
|
+
ns: int
|
4933
|
+
secs: float
|
4934
|
+
msecs: float
|
4935
|
+
relative_secs: float
|
4936
|
+
|
4937
|
+
@classmethod
|
4938
|
+
def get_std_start_ns(cls) -> int:
|
4939
|
+
x: ta.Any = logging._startTime # type: ignore[attr-defined] # noqa
|
4940
|
+
|
4941
|
+
# Before 3.13.0b1 this will be `time.time()`, a float of seconds. After that, it will be `time.time_ns()`,
|
4942
|
+
# an int.
|
4943
|
+
#
|
4944
|
+
# See:
|
4945
|
+
# - https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
|
4946
|
+
#
|
4947
|
+
if isinstance(x, float):
|
4948
|
+
return int(x * 1e9)
|
4949
|
+
else:
|
4950
|
+
return x
|
4951
|
+
|
4952
|
+
@classmethod
|
4953
|
+
def build(
|
4954
|
+
cls,
|
4955
|
+
ns: int,
|
4956
|
+
*,
|
4957
|
+
start_ns: ta.Optional[int] = None,
|
4958
|
+
) -> 'LoggingContextInfos.Time':
|
4959
|
+
# https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
|
4960
|
+
secs = ns / 1e9 # ns to float seconds
|
4961
|
+
|
4962
|
+
# Get the number of whole milliseconds (0-999) in the fractional part of seconds.
|
4963
|
+
# Eg: 1_677_903_920_999_998_503 ns --> 999_998_503 ns--> 999 ms
|
4964
|
+
# Convert to float by adding 0.0 for historical reasons. See gh-89047
|
4965
|
+
msecs = (ns % 1_000_000_000) // 1_000_000 + 0.0
|
4966
|
+
|
4967
|
+
# https://github.com/python/cpython/commit/1500a23f33f5a6d052ff1ef6383d9839928b8ff1
|
4968
|
+
if msecs == 999.0 and int(secs) != ns // 1_000_000_000:
|
4969
|
+
# ns -> sec conversion can round up, e.g:
|
4970
|
+
# 1_677_903_920_999_999_900 ns --> 1_677_903_921.0 sec
|
4971
|
+
msecs = 0.0
|
4972
|
+
|
4973
|
+
if start_ns is None:
|
4974
|
+
start_ns = cls.get_std_start_ns()
|
4975
|
+
relative_secs = (ns - start_ns) / 1e6
|
4976
|
+
|
4977
|
+
return cls(
|
4978
|
+
ns=ns,
|
4979
|
+
secs=secs,
|
4980
|
+
msecs=msecs,
|
4981
|
+
relative_secs=relative_secs,
|
4982
|
+
)
|
4983
|
+
|
4984
|
+
@logging_context_info
|
4985
|
+
@ta.final
|
4986
|
+
class Exc(ta.NamedTuple):
|
4987
|
+
info: LoggingExcInfo
|
4988
|
+
info_tuple: LoggingExcInfoTuple
|
4989
|
+
|
4990
|
+
@classmethod
|
4991
|
+
def build(
|
4992
|
+
cls,
|
4993
|
+
arg: LoggingExcInfoArg = False,
|
4994
|
+
) -> ta.Optional['LoggingContextInfos.Exc']:
|
4995
|
+
if arg is True:
|
4996
|
+
sys_exc_info = sys.exc_info()
|
4997
|
+
if sys_exc_info[0] is not None:
|
4998
|
+
arg = sys_exc_info
|
4999
|
+
else:
|
5000
|
+
arg = None
|
5001
|
+
elif arg is False:
|
5002
|
+
arg = None
|
5003
|
+
if arg is None:
|
5004
|
+
return None
|
5005
|
+
|
5006
|
+
info: LoggingExcInfo = arg
|
5007
|
+
if isinstance(info, BaseException):
|
5008
|
+
info_tuple: LoggingExcInfoTuple = (type(info), info, info.__traceback__) # noqa
|
5009
|
+
else:
|
5010
|
+
info_tuple = info
|
5011
|
+
|
5012
|
+
return cls(
|
5013
|
+
info=info,
|
5014
|
+
info_tuple=info_tuple,
|
5015
|
+
)
|
5016
|
+
|
5017
|
+
@logging_context_info
|
5018
|
+
@ta.final
|
5019
|
+
class Caller(ta.NamedTuple):
|
5020
|
+
file_path: str
|
5021
|
+
line_no: int
|
5022
|
+
func_name: str
|
5023
|
+
stack_info: ta.Optional[str]
|
5024
|
+
|
5025
|
+
@classmethod
|
5026
|
+
def is_internal_frame(cls, frame: types.FrameType) -> bool:
|
5027
|
+
file_path = os.path.normcase(frame.f_code.co_filename)
|
5028
|
+
|
5029
|
+
# Yes, really.
|
5030
|
+
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L204 # noqa
|
5031
|
+
# https://github.com/python/cpython/commit/5ca6d7469be53960843df39bb900e9c3359f127f
|
5032
|
+
if 'importlib' in file_path and '_bootstrap' in file_path:
|
5033
|
+
return True
|
5034
|
+
|
5035
|
+
return False
|
5036
|
+
|
5037
|
+
@classmethod
|
5038
|
+
def find_frame(cls, stack_offset: int = 0) -> ta.Optional[types.FrameType]:
|
5039
|
+
f: ta.Optional[types.FrameType] = sys._getframe(2 + stack_offset) # noqa
|
5040
|
+
|
5041
|
+
while f is not None:
|
5042
|
+
# NOTE: We don't check __file__ like stdlib since we may be running amalgamated - we rely on careful,
|
5043
|
+
# manual stack_offset management.
|
5044
|
+
if hasattr(f, 'f_code'):
|
5045
|
+
return f
|
5046
|
+
|
5047
|
+
f = f.f_back
|
4987
5048
|
|
4988
|
-
@classmethod
|
4989
|
-
def find(
|
4990
|
-
cls,
|
4991
|
-
ofs: int = 0,
|
4992
|
-
*,
|
4993
|
-
stack_info: bool = False,
|
4994
|
-
) -> ta.Optional['LoggingCaller']:
|
4995
|
-
if (f := cls.find_frame(ofs + 1)) is None:
|
4996
5049
|
return None
|
4997
5050
|
|
4998
|
-
|
4999
|
-
|
5000
|
-
|
5001
|
-
|
5002
|
-
|
5003
|
-
|
5004
|
-
|
5005
|
-
if
|
5006
|
-
|
5051
|
+
@classmethod
|
5052
|
+
def build(
|
5053
|
+
cls,
|
5054
|
+
stack_offset: int = 0,
|
5055
|
+
*,
|
5056
|
+
stack_info: bool = False,
|
5057
|
+
) -> ta.Optional['LoggingContextInfos.Caller']:
|
5058
|
+
if (f := cls.find_frame(stack_offset + 1)) is None:
|
5059
|
+
return None
|
5060
|
+
|
5061
|
+
# https://github.com/python/cpython/blob/08e9794517063c8cd92c48714071b1d3c60b71bd/Lib/logging/__init__.py#L1616-L1623 # noqa
|
5062
|
+
sinfo = None
|
5063
|
+
if stack_info:
|
5064
|
+
sio = io.StringIO()
|
5065
|
+
traceback.print_stack(f, file=sio)
|
5066
|
+
sinfo = sio.getvalue()
|
5067
|
+
sio.close()
|
5068
|
+
if sinfo[-1] == '\n':
|
5069
|
+
sinfo = sinfo[:-1]
|
5070
|
+
|
5071
|
+
return cls(
|
5072
|
+
file_path=f.f_code.co_filename,
|
5073
|
+
line_no=f.f_lineno or 0,
|
5074
|
+
func_name=f.f_code.co_name,
|
5075
|
+
stack_info=sinfo,
|
5076
|
+
)
|
5007
5077
|
|
5008
|
-
|
5009
|
-
|
5010
|
-
|
5011
|
-
|
5012
|
-
|
5078
|
+
@logging_context_info
|
5079
|
+
@ta.final
|
5080
|
+
class SourceFile(ta.NamedTuple):
|
5081
|
+
file_name: str
|
5082
|
+
module: str
|
5083
|
+
|
5084
|
+
@classmethod
|
5085
|
+
def build(cls, caller_file_path: ta.Optional[str]) -> ta.Optional['LoggingContextInfos.SourceFile']:
|
5086
|
+
if caller_file_path is None:
|
5087
|
+
return None
|
5088
|
+
|
5089
|
+
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L331-L336 # noqa
|
5090
|
+
try:
|
5091
|
+
file_name = os.path.basename(caller_file_path)
|
5092
|
+
module = os.path.splitext(file_name)[0]
|
5093
|
+
except (TypeError, ValueError, AttributeError):
|
5094
|
+
return None
|
5095
|
+
|
5096
|
+
return cls(
|
5097
|
+
file_name=file_name,
|
5098
|
+
module=module,
|
5099
|
+
)
|
5100
|
+
|
5101
|
+
@logging_context_info
|
5102
|
+
@ta.final
|
5103
|
+
class Thread(ta.NamedTuple):
|
5104
|
+
ident: int
|
5105
|
+
native_id: ta.Optional[int]
|
5106
|
+
name: str
|
5107
|
+
|
5108
|
+
@classmethod
|
5109
|
+
def build(cls) -> 'LoggingContextInfos.Thread':
|
5110
|
+
return cls(
|
5111
|
+
ident=threading.get_ident(),
|
5112
|
+
native_id=threading.get_native_id() if hasattr(threading, 'get_native_id') else None,
|
5113
|
+
name=threading.current_thread().name,
|
5114
|
+
)
|
5115
|
+
|
5116
|
+
@logging_context_info
|
5117
|
+
@ta.final
|
5118
|
+
class Process(ta.NamedTuple):
|
5119
|
+
pid: int
|
5120
|
+
|
5121
|
+
@classmethod
|
5122
|
+
def build(cls) -> 'LoggingContextInfos.Process':
|
5123
|
+
return cls(
|
5124
|
+
pid=os.getpid(),
|
5125
|
+
)
|
5126
|
+
|
5127
|
+
@logging_context_info
|
5128
|
+
@ta.final
|
5129
|
+
class Multiprocessing(ta.NamedTuple):
|
5130
|
+
process_name: str
|
5131
|
+
|
5132
|
+
@classmethod
|
5133
|
+
def build(cls) -> ta.Optional['LoggingContextInfos.Multiprocessing']:
|
5134
|
+
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L355-L364 # noqa
|
5135
|
+
if (mp := sys.modules.get('multiprocessing')) is None:
|
5136
|
+
return None
|
5137
|
+
|
5138
|
+
return cls(
|
5139
|
+
process_name=mp.current_process().name,
|
5140
|
+
)
|
5141
|
+
|
5142
|
+
@logging_context_info
|
5143
|
+
@ta.final
|
5144
|
+
class AsyncioTask(ta.NamedTuple):
|
5145
|
+
name: str
|
5146
|
+
|
5147
|
+
@classmethod
|
5148
|
+
def build(cls) -> ta.Optional['LoggingContextInfos.AsyncioTask']:
|
5149
|
+
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L372-L377 # noqa
|
5150
|
+
if (asyncio := sys.modules.get('asyncio')) is None:
|
5151
|
+
return None
|
5152
|
+
|
5153
|
+
try:
|
5154
|
+
task = asyncio.current_task()
|
5155
|
+
except Exception: # noqa
|
5156
|
+
return None
|
5157
|
+
|
5158
|
+
if task is None:
|
5159
|
+
return None
|
5160
|
+
|
5161
|
+
return cls(
|
5162
|
+
name=task.get_name(), # Always non-None
|
5163
|
+
)
|
5164
|
+
|
5165
|
+
|
5166
|
+
##
|
5167
|
+
|
5168
|
+
|
5169
|
+
class UnexpectedLoggingStartTimeWarning(LoggingSetupWarning):
|
5170
|
+
pass
|
5171
|
+
|
5172
|
+
|
5173
|
+
def _check_logging_start_time() -> None:
|
5174
|
+
if (x := LoggingContextInfos.Time.get_std_start_ns()) < (t := time.time()):
|
5175
|
+
import warnings # noqa
|
5176
|
+
|
5177
|
+
warnings.warn(
|
5178
|
+
f'Unexpected logging start time detected: '
|
5179
|
+
f'get_std_start_ns={x}, '
|
5180
|
+
f'time.time()={t}',
|
5181
|
+
UnexpectedLoggingStartTimeWarning,
|
5013
5182
|
)
|
5014
5183
|
|
5015
5184
|
|
5185
|
+
_check_logging_start_time()
|
5186
|
+
|
5187
|
+
|
5016
5188
|
########################################
|
5017
5189
|
# ../../../../../omlish/logs/std/json.py
|
5018
5190
|
"""
|
@@ -5071,131 +5243,46 @@ class JsonLoggingFormatter(logging.Formatter):
|
|
5071
5243
|
|
5072
5244
|
|
5073
5245
|
########################################
|
5074
|
-
#
|
5246
|
+
# ../../logs.py
|
5247
|
+
"""
|
5248
|
+
https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_PutLogEvents.html :
|
5249
|
+
- The maximum batch size is 1,048,576 bytes. This size is calculated as the sum of all event messages in UTF-8, plus 26
|
5250
|
+
bytes for each log event.
|
5251
|
+
- None of the log events in the batch can be more than 2 hours in the future.
|
5252
|
+
- None of the log events in the batch can be more than 14 days in the past. Also, none of the log events can be from
|
5253
|
+
earlier than the retention period of the log group.
|
5254
|
+
- The log events in the batch must be in chronological order by their timestamp. The timestamp is the time that the
|
5255
|
+
event occurred, expressed as the number of milliseconds after Jan 1, 1970 00:00:00 UTC. (In AWS Tools for PowerShell
|
5256
|
+
and the AWS SDK for .NET, the timestamp is specified in .NET format: yyyy-mm-ddThh:mm:ss. For example,
|
5257
|
+
2017-09-15T13:45:30.)
|
5258
|
+
- A batch of log events in a single request cannot span more than 24 hours. Otherwise, the operation fails.
|
5259
|
+
- Each log event can be no larger than 256 KB.
|
5260
|
+
- The maximum number of log events in a batch is 10,000.
|
5261
|
+
"""
|
5075
5262
|
|
5076
5263
|
|
5077
5264
|
##
|
5078
5265
|
|
5079
5266
|
|
5080
|
-
@
|
5081
|
-
|
5082
|
-
|
5083
|
-
|
5267
|
+
@dc.dataclass(frozen=True)
|
5268
|
+
class AwsLogEvent(AwsDataclass):
|
5269
|
+
message: str
|
5270
|
+
timestamp: int # milliseconds UTC
|
5084
5271
|
|
5085
|
-
created: float
|
5086
|
-
msecs: float
|
5087
|
-
relative_created: float
|
5088
5272
|
|
5089
|
-
|
5090
|
-
|
5091
|
-
|
5273
|
+
@dc.dataclass(frozen=True)
|
5274
|
+
class AwsPutLogEventsRequest(AwsDataclass):
|
5275
|
+
log_group_name: str
|
5276
|
+
log_stream_name: str
|
5277
|
+
log_events: ta.Sequence[AwsLogEvent]
|
5278
|
+
sequence_token: ta.Optional[str] = None
|
5092
5279
|
|
5093
|
-
# Before 3.13.0b1 this will be `time.time()`, a float of seconds. After that, it will be `time.time_ns()`, an
|
5094
|
-
# int.
|
5095
|
-
#
|
5096
|
-
# See:
|
5097
|
-
# - https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
|
5098
|
-
#
|
5099
|
-
if isinstance(x, float):
|
5100
|
-
return int(x * 1e9)
|
5101
|
-
else:
|
5102
|
-
return x
|
5103
5280
|
|
5104
|
-
|
5105
|
-
|
5106
|
-
|
5107
|
-
|
5108
|
-
|
5109
|
-
start_time_ns: ta.Optional[int] = None,
|
5110
|
-
) -> 'LoggingTimeFields':
|
5111
|
-
# https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
|
5112
|
-
created = time_ns / 1e9 # ns to float seconds
|
5113
|
-
|
5114
|
-
# Get the number of whole milliseconds (0-999) in the fractional part of seconds.
|
5115
|
-
# Eg: 1_677_903_920_999_998_503 ns --> 999_998_503 ns--> 999 ms
|
5116
|
-
# Convert to float by adding 0.0 for historical reasons. See gh-89047
|
5117
|
-
msecs = (time_ns % 1_000_000_000) // 1_000_000 + 0.0
|
5118
|
-
|
5119
|
-
# https://github.com/python/cpython/commit/1500a23f33f5a6d052ff1ef6383d9839928b8ff1
|
5120
|
-
if msecs == 999.0 and int(created) != time_ns // 1_000_000_000:
|
5121
|
-
# ns -> sec conversion can round up, e.g:
|
5122
|
-
# 1_677_903_920_999_999_900 ns --> 1_677_903_921.0 sec
|
5123
|
-
msecs = 0.0
|
5124
|
-
|
5125
|
-
if start_time_ns is None:
|
5126
|
-
start_time_ns = cls.get_std_start_time_ns()
|
5127
|
-
relative_created = (time_ns - start_time_ns) / 1e6
|
5128
|
-
|
5129
|
-
return cls(
|
5130
|
-
created=created,
|
5131
|
-
msecs=msecs,
|
5132
|
-
relative_created=relative_created,
|
5133
|
-
)
|
5134
|
-
|
5135
|
-
|
5136
|
-
##
|
5137
|
-
|
5138
|
-
|
5139
|
-
class UnexpectedLoggingStartTimeWarning(LoggingSetupWarning):
|
5140
|
-
pass
|
5141
|
-
|
5142
|
-
|
5143
|
-
def _check_logging_start_time() -> None:
|
5144
|
-
if (x := LoggingTimeFields.get_std_start_time_ns()) < (t := time.time()):
|
5145
|
-
import warnings # noqa
|
5146
|
-
|
5147
|
-
warnings.warn(
|
5148
|
-
f'Unexpected logging start time detected: '
|
5149
|
-
f'get_std_start_time_ns={x}, '
|
5150
|
-
f'time.time()={t}',
|
5151
|
-
UnexpectedLoggingStartTimeWarning,
|
5152
|
-
)
|
5153
|
-
|
5154
|
-
|
5155
|
-
_check_logging_start_time()
|
5156
|
-
|
5157
|
-
|
5158
|
-
########################################
|
5159
|
-
# ../../logs.py
|
5160
|
-
"""
|
5161
|
-
https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_PutLogEvents.html :
|
5162
|
-
- The maximum batch size is 1,048,576 bytes. This size is calculated as the sum of all event messages in UTF-8, plus 26
|
5163
|
-
bytes for each log event.
|
5164
|
-
- None of the log events in the batch can be more than 2 hours in the future.
|
5165
|
-
- None of the log events in the batch can be more than 14 days in the past. Also, none of the log events can be from
|
5166
|
-
earlier than the retention period of the log group.
|
5167
|
-
- The log events in the batch must be in chronological order by their timestamp. The timestamp is the time that the
|
5168
|
-
event occurred, expressed as the number of milliseconds after Jan 1, 1970 00:00:00 UTC. (In AWS Tools for PowerShell
|
5169
|
-
and the AWS SDK for .NET, the timestamp is specified in .NET format: yyyy-mm-ddThh:mm:ss. For example,
|
5170
|
-
2017-09-15T13:45:30.)
|
5171
|
-
- A batch of log events in a single request cannot span more than 24 hours. Otherwise, the operation fails.
|
5172
|
-
- Each log event can be no larger than 256 KB.
|
5173
|
-
- The maximum number of log events in a batch is 10,000.
|
5174
|
-
"""
|
5175
|
-
|
5176
|
-
|
5177
|
-
##
|
5178
|
-
|
5179
|
-
|
5180
|
-
@dc.dataclass(frozen=True)
|
5181
|
-
class AwsLogEvent(AwsDataclass):
|
5182
|
-
message: str
|
5183
|
-
timestamp: int # milliseconds UTC
|
5184
|
-
|
5185
|
-
|
5186
|
-
@dc.dataclass(frozen=True)
|
5187
|
-
class AwsPutLogEventsRequest(AwsDataclass):
|
5188
|
-
log_group_name: str
|
5189
|
-
log_stream_name: str
|
5190
|
-
log_events: ta.Sequence[AwsLogEvent]
|
5191
|
-
sequence_token: ta.Optional[str] = None
|
5192
|
-
|
5193
|
-
|
5194
|
-
@dc.dataclass(frozen=True)
|
5195
|
-
class AwsRejectedLogEventsInfo(AwsDataclass):
|
5196
|
-
expired_log_event_end_index: ta.Optional[int] = None
|
5197
|
-
too_new_log_event_start_index: ta.Optional[int] = None
|
5198
|
-
too_old_log_event_end_index: ta.Optional[int] = None
|
5281
|
+
@dc.dataclass(frozen=True)
|
5282
|
+
class AwsRejectedLogEventsInfo(AwsDataclass):
|
5283
|
+
expired_log_event_end_index: ta.Optional[int] = None
|
5284
|
+
too_new_log_event_start_index: ta.Optional[int] = None
|
5285
|
+
too_old_log_event_end_index: ta.Optional[int] = None
|
5199
5286
|
|
5200
5287
|
|
5201
5288
|
@dc.dataclass(frozen=True)
|
@@ -5366,68 +5453,36 @@ def load_config_file_obj(
|
|
5366
5453
|
|
5367
5454
|
|
5368
5455
|
class LoggingContext(Abstract):
|
5369
|
-
@property
|
5370
|
-
@abc.abstractmethod
|
5371
|
-
def level(self) -> NamedLogLevel:
|
5372
|
-
raise NotImplementedError
|
5373
|
-
|
5374
|
-
#
|
5375
|
-
|
5376
|
-
@property
|
5377
5456
|
@abc.abstractmethod
|
5378
|
-
def
|
5457
|
+
def get_info(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
|
5379
5458
|
raise NotImplementedError
|
5380
5459
|
|
5381
|
-
@
|
5382
|
-
|
5383
|
-
|
5384
|
-
raise NotImplementedError
|
5385
|
-
|
5386
|
-
#
|
5460
|
+
@ta.final
|
5461
|
+
def __getitem__(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
|
5462
|
+
return self.get_info(ty)
|
5387
5463
|
|
5388
|
-
@
|
5389
|
-
|
5390
|
-
|
5391
|
-
|
5464
|
+
@ta.final
|
5465
|
+
def must_get_info(self, ty: ta.Type[LoggingContextInfoT]) -> LoggingContextInfoT:
|
5466
|
+
if (info := self.get_info(ty)) is None:
|
5467
|
+
raise TypeError(f'LoggingContextInfo absent: {ty}')
|
5468
|
+
return info
|
5392
5469
|
|
5393
|
-
|
5394
|
-
@abc.abstractmethod
|
5395
|
-
def exc_info_tuple(self) -> ta.Optional[LoggingExcInfoTuple]:
|
5396
|
-
raise NotImplementedError
|
5470
|
+
##
|
5397
5471
|
|
5398
|
-
#
|
5399
5472
|
|
5473
|
+
class CaptureLoggingContext(LoggingContext, Abstract):
|
5400
5474
|
@abc.abstractmethod
|
5401
|
-
def
|
5402
|
-
|
5475
|
+
def set_basic(
|
5476
|
+
self,
|
5477
|
+
name: str,
|
5403
5478
|
|
5404
|
-
|
5405
|
-
|
5479
|
+
msg: ta.Union[str, tuple, LoggingMsgFn],
|
5480
|
+
args: tuple,
|
5481
|
+
) -> 'CaptureLoggingContext':
|
5406
5482
|
raise NotImplementedError
|
5407
5483
|
|
5408
5484
|
#
|
5409
5485
|
|
5410
|
-
@abc.abstractmethod
|
5411
|
-
def thread(self) -> ta.Optional[LoggingThreadInfo]:
|
5412
|
-
raise NotImplementedError
|
5413
|
-
|
5414
|
-
@abc.abstractmethod
|
5415
|
-
def process(self) -> ta.Optional[LoggingProcessInfo]:
|
5416
|
-
raise NotImplementedError
|
5417
|
-
|
5418
|
-
@abc.abstractmethod
|
5419
|
-
def multiprocessing(self) -> ta.Optional[LoggingMultiprocessingInfo]:
|
5420
|
-
raise NotImplementedError
|
5421
|
-
|
5422
|
-
@abc.abstractmethod
|
5423
|
-
def asyncio_task(self) -> ta.Optional[LoggingAsyncioTaskInfo]:
|
5424
|
-
raise NotImplementedError
|
5425
|
-
|
5426
|
-
|
5427
|
-
##
|
5428
|
-
|
5429
|
-
|
5430
|
-
class CaptureLoggingContext(LoggingContext, Abstract):
|
5431
5486
|
class AlreadyCapturedError(Exception):
|
5432
5487
|
pass
|
5433
5488
|
|
@@ -5458,80 +5513,50 @@ class CaptureLoggingContextImpl(CaptureLoggingContext):
|
|
5458
5513
|
|
5459
5514
|
exc_info: LoggingExcInfoArg = False,
|
5460
5515
|
|
5461
|
-
caller: ta.Union[
|
5516
|
+
caller: ta.Union[LoggingContextInfos.Caller, ta.Type[NOT_SET], None] = NOT_SET,
|
5462
5517
|
stack_offset: int = 0,
|
5463
5518
|
stack_info: bool = False,
|
5464
5519
|
) -> None:
|
5465
|
-
|
5466
|
-
|
5467
|
-
#
|
5520
|
+
# TODO: Name, Msg, Extra
|
5468
5521
|
|
5469
5522
|
if time_ns is None:
|
5470
5523
|
time_ns = time.time_ns()
|
5471
|
-
self._time_ns: int = time_ns
|
5472
5524
|
|
5473
|
-
|
5474
|
-
|
5475
|
-
|
5476
|
-
|
5477
|
-
|
5478
|
-
|
5479
|
-
else:
|
5480
|
-
exc_info = None
|
5481
|
-
elif exc_info is False:
|
5482
|
-
exc_info = None
|
5483
|
-
|
5484
|
-
if exc_info is not None:
|
5485
|
-
self._exc_info: ta.Optional[LoggingExcInfo] = exc_info
|
5486
|
-
if isinstance(exc_info, BaseException):
|
5487
|
-
self._exc_info_tuple: ta.Optional[LoggingExcInfoTuple] = (type(exc_info), exc_info, exc_info.__traceback__) # noqa
|
5488
|
-
else:
|
5489
|
-
self._exc_info_tuple = exc_info
|
5490
|
-
|
5491
|
-
#
|
5525
|
+
self._infos: ta.Dict[ta.Type[LoggingContextInfo], LoggingContextInfo] = {}
|
5526
|
+
self._set_info(
|
5527
|
+
LoggingContextInfos.Level.build(level),
|
5528
|
+
LoggingContextInfos.Time.build(time_ns),
|
5529
|
+
LoggingContextInfos.Exc.build(exc_info),
|
5530
|
+
)
|
5492
5531
|
|
5493
5532
|
if caller is not CaptureLoggingContextImpl.NOT_SET:
|
5494
|
-
self.
|
5533
|
+
self._infos[LoggingContextInfos.Caller] = caller
|
5495
5534
|
else:
|
5496
5535
|
self._stack_offset = stack_offset
|
5497
5536
|
self._stack_info = stack_info
|
5498
5537
|
|
5499
|
-
|
5500
|
-
|
5501
|
-
|
5502
|
-
|
5503
|
-
return self
|
5504
|
-
|
5505
|
-
#
|
5506
|
-
|
5507
|
-
@property
|
5508
|
-
def time_ns(self) -> int:
|
5509
|
-
return self._time_ns
|
5510
|
-
|
5511
|
-
_times: LoggingTimeFields
|
5512
|
-
|
5513
|
-
@property
|
5514
|
-
def times(self) -> LoggingTimeFields:
|
5515
|
-
try:
|
5516
|
-
return self._times
|
5517
|
-
except AttributeError:
|
5518
|
-
pass
|
5519
|
-
|
5520
|
-
times = self._times = LoggingTimeFields.build(self.time_ns)
|
5521
|
-
return times
|
5538
|
+
def _set_info(self, *infos: ta.Optional[LoggingContextInfo]) -> 'CaptureLoggingContextImpl':
|
5539
|
+
for info in infos:
|
5540
|
+
if info is not None:
|
5541
|
+
self._infos[type(info)] = info
|
5542
|
+
return self
|
5522
5543
|
|
5523
|
-
|
5544
|
+
def get_info(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
|
5545
|
+
return self._infos.get(ty)
|
5524
5546
|
|
5525
|
-
|
5526
|
-
_exc_info_tuple: ta.Optional[LoggingExcInfoTuple] = None
|
5547
|
+
##
|
5527
5548
|
|
5528
|
-
|
5529
|
-
|
5530
|
-
|
5549
|
+
def set_basic(
|
5550
|
+
self,
|
5551
|
+
name: str,
|
5531
5552
|
|
5532
|
-
|
5533
|
-
|
5534
|
-
|
5553
|
+
msg: ta.Union[str, tuple, LoggingMsgFn],
|
5554
|
+
args: tuple,
|
5555
|
+
) -> 'CaptureLoggingContextImpl':
|
5556
|
+
return self._set_info(
|
5557
|
+
LoggingContextInfos.Name(name),
|
5558
|
+
LoggingContextInfos.Msg.build(msg, *args),
|
5559
|
+
)
|
5535
5560
|
|
5536
5561
|
##
|
5537
5562
|
|
@@ -5545,74 +5570,28 @@ class CaptureLoggingContextImpl(CaptureLoggingContext):
|
|
5545
5570
|
|
5546
5571
|
_has_captured: bool = False
|
5547
5572
|
|
5548
|
-
_caller: ta.Optional[LoggingCaller]
|
5549
|
-
_source_file: ta.Optional[LoggingSourceFileInfo]
|
5550
|
-
|
5551
|
-
_thread: ta.Optional[LoggingThreadInfo]
|
5552
|
-
_process: ta.Optional[LoggingProcessInfo]
|
5553
|
-
_multiprocessing: ta.Optional[LoggingMultiprocessingInfo]
|
5554
|
-
_asyncio_task: ta.Optional[LoggingAsyncioTaskInfo]
|
5555
|
-
|
5556
5573
|
def capture(self) -> None:
|
5557
5574
|
if self._has_captured:
|
5558
5575
|
raise CaptureLoggingContextImpl.AlreadyCapturedError
|
5559
5576
|
self._has_captured = True
|
5560
5577
|
|
5561
|
-
if not
|
5562
|
-
self.
|
5578
|
+
if LoggingContextInfos.Caller not in self._infos:
|
5579
|
+
self._set_info(LoggingContextInfos.Caller.build(
|
5563
5580
|
self._stack_offset + 1,
|
5564
5581
|
stack_info=self._stack_info,
|
5565
|
-
)
|
5566
|
-
|
5567
|
-
if (caller := self._caller) is not None:
|
5568
|
-
self._source_file = LoggingSourceFileInfo.build(caller.file_path)
|
5569
|
-
else:
|
5570
|
-
self._source_file = None
|
5571
|
-
|
5572
|
-
self._thread = LoggingThreadInfo.build()
|
5573
|
-
self._process = LoggingProcessInfo.build()
|
5574
|
-
self._multiprocessing = LoggingMultiprocessingInfo.build()
|
5575
|
-
self._asyncio_task = LoggingAsyncioTaskInfo.build()
|
5576
|
-
|
5577
|
-
#
|
5578
|
-
|
5579
|
-
def caller(self) -> ta.Optional[LoggingCaller]:
|
5580
|
-
try:
|
5581
|
-
return self._caller
|
5582
|
-
except AttributeError:
|
5583
|
-
raise CaptureLoggingContext.NotCapturedError from None
|
5584
|
-
|
5585
|
-
def source_file(self) -> ta.Optional[LoggingSourceFileInfo]:
|
5586
|
-
try:
|
5587
|
-
return self._source_file
|
5588
|
-
except AttributeError:
|
5589
|
-
raise CaptureLoggingContext.NotCapturedError from None
|
5590
|
-
|
5591
|
-
#
|
5592
|
-
|
5593
|
-
def thread(self) -> ta.Optional[LoggingThreadInfo]:
|
5594
|
-
try:
|
5595
|
-
return self._thread
|
5596
|
-
except AttributeError:
|
5597
|
-
raise CaptureLoggingContext.NotCapturedError from None
|
5598
|
-
|
5599
|
-
def process(self) -> ta.Optional[LoggingProcessInfo]:
|
5600
|
-
try:
|
5601
|
-
return self._process
|
5602
|
-
except AttributeError:
|
5603
|
-
raise CaptureLoggingContext.NotCapturedError from None
|
5582
|
+
))
|
5604
5583
|
|
5605
|
-
|
5606
|
-
|
5607
|
-
|
5608
|
-
|
5609
|
-
raise CaptureLoggingContext.NotCapturedError from None
|
5584
|
+
if (caller := self[LoggingContextInfos.Caller]) is not None:
|
5585
|
+
self._set_info(LoggingContextInfos.SourceFile.build(
|
5586
|
+
caller.file_path,
|
5587
|
+
))
|
5610
5588
|
|
5611
|
-
|
5612
|
-
|
5613
|
-
|
5614
|
-
|
5615
|
-
|
5589
|
+
self._set_info(
|
5590
|
+
LoggingContextInfos.Thread.build(),
|
5591
|
+
LoggingContextInfos.Process.build(),
|
5592
|
+
LoggingContextInfos.Multiprocessing.build(),
|
5593
|
+
LoggingContextInfos.AsyncioTask.build(),
|
5594
|
+
)
|
5616
5595
|
|
5617
5596
|
|
5618
5597
|
########################################
|
@@ -5917,36 +5896,6 @@ class AnyLogger(Abstract, ta.Generic[T]):
|
|
5917
5896
|
|
5918
5897
|
##
|
5919
5898
|
|
5920
|
-
@classmethod
|
5921
|
-
def _prepare_msg_args(cls, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any) -> ta.Tuple[str, tuple]:
|
5922
|
-
if callable(msg):
|
5923
|
-
if args:
|
5924
|
-
raise TypeError(f'Must not provide both a message function and args: {msg=} {args=}')
|
5925
|
-
x = msg()
|
5926
|
-
if isinstance(x, str):
|
5927
|
-
return x, ()
|
5928
|
-
elif isinstance(x, tuple):
|
5929
|
-
if x:
|
5930
|
-
return x[0], x[1:]
|
5931
|
-
else:
|
5932
|
-
return '', ()
|
5933
|
-
else:
|
5934
|
-
raise TypeError(x)
|
5935
|
-
|
5936
|
-
elif isinstance(msg, tuple):
|
5937
|
-
if args:
|
5938
|
-
raise TypeError(f'Must not provide both a tuple message and args: {msg=} {args=}')
|
5939
|
-
if msg:
|
5940
|
-
return msg[0], msg[1:]
|
5941
|
-
else:
|
5942
|
-
return '', ()
|
5943
|
-
|
5944
|
-
elif isinstance(msg, str):
|
5945
|
-
return msg, args
|
5946
|
-
|
5947
|
-
else:
|
5948
|
-
raise TypeError(msg)
|
5949
|
-
|
5950
5899
|
@abc.abstractmethod
|
5951
5900
|
def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any, **kwargs: ta.Any) -> T: # noqa
|
5952
5901
|
raise NotImplementedError
|
@@ -5987,137 +5936,543 @@ class AsyncNopLogger(AnyNopLogger[ta.Awaitable[None]], AsyncLogger):
|
|
5987
5936
|
|
5988
5937
|
########################################
|
5989
5938
|
# ../../../../../omlish/logs/std/records.py
|
5939
|
+
"""
|
5940
|
+
TODO:
|
5941
|
+
- TypedDict?
|
5942
|
+
"""
|
5990
5943
|
|
5991
5944
|
|
5992
5945
|
##
|
5993
5946
|
|
5994
5947
|
|
5995
|
-
|
5996
|
-
#
|
5997
|
-
#
|
5998
|
-
#
|
5999
|
-
#
|
6000
|
-
# - https://github.com/python/cpython/blob/
|
6001
|
-
#
|
6002
|
-
#
|
6003
|
-
# - name: str
|
6004
|
-
# - level: int
|
6005
|
-
# - pathname: str - Confusingly referred to as `fn` before the LogRecord ctor. May be empty or "(unknown file)".
|
6006
|
-
# - lineno: int - May be 0.
|
6007
|
-
# - msg: str
|
6008
|
-
# - args: tuple | dict | 1-tuple[dict]
|
6009
|
-
# - exc_info: LoggingExcInfoTuple | None
|
6010
|
-
# - func: str | None = None -> funcName
|
6011
|
-
# - sinfo: str | None = None -> stack_info
|
6012
|
-
#
|
6013
|
-
KNOWN_STD_LOGGING_RECORD_ATTRS: ta.Dict[str, ta.Any] = dict(
|
6014
|
-
# Name of the logger used to log the call. Unmodified by ctor.
|
6015
|
-
name=str,
|
5948
|
+
class LoggingContextInfoRecordAdapters:
|
5949
|
+
# Ref:
|
5950
|
+
# - https://docs.python.org/3/library/logging.html#logrecord-attributes
|
5951
|
+
#
|
5952
|
+
# LogRecord:
|
5953
|
+
# - https://github.com/python/cpython/blob/39b2f82717a69dde7212bc39b673b0f55c99e6a3/Lib/logging/__init__.py#L276 (3.8) # noqa
|
5954
|
+
# - https://github.com/python/cpython/blob/f070f54c5f4a42c7c61d1d5d3b8f3b7203b4a0fb/Lib/logging/__init__.py#L286 (~3.14) # noqa
|
5955
|
+
#
|
6016
5956
|
|
6017
|
-
|
6018
|
-
|
6019
|
-
msg=str,
|
5957
|
+
def __new__(cls, *args, **kwargs): # noqa
|
5958
|
+
raise TypeError
|
6020
5959
|
|
6021
|
-
|
6022
|
-
|
6023
|
-
|
6024
|
-
|
5960
|
+
class Adapter(Abstract, ta.Generic[T]):
|
5961
|
+
@property
|
5962
|
+
@abc.abstractmethod
|
5963
|
+
def info_cls(self) -> ta.Type[LoggingContextInfo]:
|
5964
|
+
raise NotImplementedError
|
6025
5965
|
|
6026
|
-
|
5966
|
+
#
|
6027
5967
|
|
6028
|
-
|
6029
|
-
|
6030
|
-
|
5968
|
+
@ta.final
|
5969
|
+
class NOT_SET: # noqa
|
5970
|
+
def __new__(cls, *args, **kwargs): # noqa
|
5971
|
+
raise TypeError
|
6031
5972
|
|
6032
|
-
|
6033
|
-
|
5973
|
+
class RecordAttr(ta.NamedTuple):
|
5974
|
+
name: str
|
5975
|
+
type: ta.Any
|
5976
|
+
default: ta.Any
|
6034
5977
|
|
6035
|
-
|
5978
|
+
# @abc.abstractmethod
|
5979
|
+
record_attrs: ta.ClassVar[ta.Mapping[str, RecordAttr]]
|
6036
5980
|
|
6037
|
-
|
6038
|
-
|
6039
|
-
|
5981
|
+
@property
|
5982
|
+
@abc.abstractmethod
|
5983
|
+
def _record_attrs(self) -> ta.Union[
|
5984
|
+
ta.Mapping[str, ta.Any],
|
5985
|
+
ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]],
|
5986
|
+
]:
|
5987
|
+
raise NotImplementedError
|
6040
5988
|
|
6041
|
-
|
6042
|
-
filename=str,
|
5989
|
+
#
|
6043
5990
|
|
6044
|
-
|
6045
|
-
|
6046
|
-
|
5991
|
+
@abc.abstractmethod
|
5992
|
+
def context_to_record(self, ctx: LoggingContext) -> ta.Mapping[str, ta.Any]:
|
5993
|
+
raise NotImplementedError
|
6047
5994
|
|
6048
|
-
|
5995
|
+
#
|
6049
5996
|
|
6050
|
-
|
6051
|
-
|
5997
|
+
@abc.abstractmethod
|
5998
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[T]:
|
5999
|
+
raise NotImplementedError
|
6052
6000
|
|
6053
|
-
|
6054
|
-
exc_text=ta.Optional[str],
|
6001
|
+
#
|
6055
6002
|
|
6056
|
-
|
6003
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
6004
|
+
super().__init_subclass__(**kwargs)
|
6057
6005
|
|
6058
|
-
|
6059
|
-
|
6060
|
-
# unmodified. Mostly set, if requested, by `Logger.findCaller`, to `traceback.print_stack(f)`, but prepended with
|
6061
|
-
# the literal "Stack (most recent call last):\n", and stripped of exactly one trailing `\n` if present.
|
6062
|
-
stack_info=ta.Optional[str],
|
6006
|
+
if Abstract in cls.__bases__:
|
6007
|
+
return
|
6063
6008
|
|
6064
|
-
|
6065
|
-
|
6066
|
-
|
6009
|
+
if 'record_attrs' in cls.__dict__:
|
6010
|
+
raise TypeError(cls)
|
6011
|
+
if not isinstance(ra := cls.__dict__['_record_attrs'], collections.abc.Mapping):
|
6012
|
+
raise TypeError(ra)
|
6013
|
+
|
6014
|
+
rd: ta.Dict[str, LoggingContextInfoRecordAdapters.Adapter.RecordAttr] = {}
|
6015
|
+
for n, v in ra.items():
|
6016
|
+
if not n or not isinstance(n, str) or n in rd:
|
6017
|
+
raise AttributeError(n)
|
6018
|
+
if isinstance(v, tuple):
|
6019
|
+
t, d = v
|
6020
|
+
else:
|
6021
|
+
t, d = v, cls.NOT_SET
|
6022
|
+
rd[n] = cls.RecordAttr(
|
6023
|
+
name=n,
|
6024
|
+
type=t,
|
6025
|
+
default=d,
|
6026
|
+
)
|
6027
|
+
cls.record_attrs = rd
|
6067
6028
|
|
6068
|
-
|
6069
|
-
|
6070
|
-
|
6029
|
+
class RequiredAdapter(Adapter[T], Abstract):
|
6030
|
+
@property
|
6031
|
+
@abc.abstractmethod
|
6032
|
+
def _record_attrs(self) -> ta.Mapping[str, ta.Any]:
|
6033
|
+
raise NotImplementedError
|
6071
6034
|
|
6072
|
-
|
6035
|
+
#
|
6073
6036
|
|
6074
|
-
|
6075
|
-
|
6076
|
-
|
6077
|
-
|
6078
|
-
|
6079
|
-
|
6080
|
-
created=float,
|
6037
|
+
@ta.final
|
6038
|
+
def context_to_record(self, ctx: LoggingContext) -> ta.Mapping[str, ta.Any]:
|
6039
|
+
if (info := ctx.get_info(self.info_cls)) is not None:
|
6040
|
+
return self._info_to_record(info)
|
6041
|
+
else:
|
6042
|
+
raise TypeError # FIXME: fallback?
|
6081
6043
|
|
6082
|
-
|
6083
|
-
|
6044
|
+
@abc.abstractmethod
|
6045
|
+
def _info_to_record(self, info: T) -> ta.Mapping[str, ta.Any]:
|
6046
|
+
raise NotImplementedError
|
6084
6047
|
|
6085
|
-
|
6086
|
-
relativeCreated=float,
|
6048
|
+
#
|
6087
6049
|
|
6088
|
-
|
6050
|
+
@abc.abstractmethod
|
6051
|
+
def record_to_info(self, rec: logging.LogRecord) -> T:
|
6052
|
+
raise NotImplementedError
|
6089
6053
|
|
6090
|
-
|
6091
|
-
thread=ta.Optional[int],
|
6054
|
+
#
|
6092
6055
|
|
6093
|
-
|
6094
|
-
|
6056
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
6057
|
+
super().__init_subclass__(**kwargs)
|
6095
6058
|
|
6096
|
-
|
6059
|
+
if any(a.default is not cls.NOT_SET for a in cls.record_attrs.values()):
|
6060
|
+
raise TypeError(cls.record_attrs)
|
6061
|
+
|
6062
|
+
class OptionalAdapter(Adapter[T], Abstract, ta.Generic[T]):
|
6063
|
+
@property
|
6064
|
+
@abc.abstractmethod
|
6065
|
+
def _record_attrs(self) -> ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]:
|
6066
|
+
raise NotImplementedError
|
6067
|
+
|
6068
|
+
record_defaults: ta.ClassVar[ta.Mapping[str, ta.Any]]
|
6069
|
+
|
6070
|
+
#
|
6071
|
+
|
6072
|
+
@ta.final
|
6073
|
+
def context_to_record(self, ctx: LoggingContext) -> ta.Mapping[str, ta.Any]:
|
6074
|
+
if (info := ctx.get_info(self.info_cls)) is not None:
|
6075
|
+
return self._info_to_record(info)
|
6076
|
+
else:
|
6077
|
+
return self.record_defaults
|
6078
|
+
|
6079
|
+
@abc.abstractmethod
|
6080
|
+
def _info_to_record(self, info: T) -> ta.Mapping[str, ta.Any]:
|
6081
|
+
raise NotImplementedError
|
6082
|
+
|
6083
|
+
#
|
6084
|
+
|
6085
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
6086
|
+
super().__init_subclass__(**kwargs)
|
6087
|
+
|
6088
|
+
dd: ta.Dict[str, ta.Any] = {a.name: a.default for a in cls.record_attrs.values()}
|
6089
|
+
if any(d is cls.NOT_SET for d in dd.values()):
|
6090
|
+
raise TypeError(cls.record_attrs)
|
6091
|
+
cls.record_defaults = dd
|
6097
6092
|
|
6098
|
-
# Process name if available. Set to None if `logging.logMultiprocessing` is not truthy. Otherwise, set to
|
6099
|
-
# 'MainProcess', then `sys.modules.get('multiprocessing').current_process().name` if that works, otherwise remains
|
6100
|
-
# as 'MainProcess'.
|
6101
6093
|
#
|
6102
|
-
|
6094
|
+
|
6095
|
+
class Name(RequiredAdapter[LoggingContextInfos.Name]):
|
6096
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Name]] = LoggingContextInfos.Name
|
6097
|
+
|
6098
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
|
6099
|
+
# Name of the logger used to log the call. Unmodified by ctor.
|
6100
|
+
name=str,
|
6101
|
+
)
|
6102
|
+
|
6103
|
+
def _info_to_record(self, info: LoggingContextInfos.Name) -> ta.Mapping[str, ta.Any]:
|
6104
|
+
return dict(
|
6105
|
+
name=info.name,
|
6106
|
+
)
|
6107
|
+
|
6108
|
+
def record_to_info(self, rec: logging.LogRecord) -> LoggingContextInfos.Name:
|
6109
|
+
return LoggingContextInfos.Name(
|
6110
|
+
name=rec.name,
|
6111
|
+
)
|
6112
|
+
|
6113
|
+
class Level(RequiredAdapter[LoggingContextInfos.Level]):
|
6114
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Level]] = LoggingContextInfos.Level
|
6115
|
+
|
6116
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
|
6117
|
+
# Text logging level for the message ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'). Set to
|
6118
|
+
# `getLevelName(level)`.
|
6119
|
+
levelname=str,
|
6120
|
+
|
6121
|
+
# Numeric logging level for the message (DEBUG, INFO, WARNING, ERROR, CRITICAL). Unmodified by ctor.
|
6122
|
+
levelno=int,
|
6123
|
+
)
|
6124
|
+
|
6125
|
+
def _info_to_record(self, info: LoggingContextInfos.Level) -> ta.Mapping[str, ta.Any]:
|
6126
|
+
return dict(
|
6127
|
+
levelname=info.name,
|
6128
|
+
levelno=int(info.level),
|
6129
|
+
)
|
6130
|
+
|
6131
|
+
def record_to_info(self, rec: logging.LogRecord) -> LoggingContextInfos.Level:
|
6132
|
+
return LoggingContextInfos.Level.build(rec.levelno)
|
6133
|
+
|
6134
|
+
class Msg(RequiredAdapter[LoggingContextInfos.Msg]):
|
6135
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Msg]] = LoggingContextInfos.Msg
|
6136
|
+
|
6137
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
|
6138
|
+
# The format string passed in the original logging call. Merged with args to produce message, or an
|
6139
|
+
# arbitrary object (see Using arbitrary objects as messages). Unmodified by ctor.
|
6140
|
+
msg=str,
|
6141
|
+
|
6142
|
+
# The tuple of arguments merged into msg to produce message, or a dict whose values are used for the merge
|
6143
|
+
# (when there is only one argument, and it is a dictionary). Ctor will transform a 1-tuple containing a
|
6144
|
+
# Mapping into just the mapping, but is otherwise unmodified.
|
6145
|
+
args=ta.Union[tuple, dict, None],
|
6146
|
+
)
|
6147
|
+
|
6148
|
+
def _info_to_record(self, info: LoggingContextInfos.Msg) -> ta.Mapping[str, ta.Any]:
|
6149
|
+
return dict(
|
6150
|
+
msg=info.msg,
|
6151
|
+
args=info.args,
|
6152
|
+
)
|
6153
|
+
|
6154
|
+
def record_to_info(self, rec: logging.LogRecord) -> LoggingContextInfos.Msg:
|
6155
|
+
return LoggingContextInfos.Msg(
|
6156
|
+
msg=rec.msg,
|
6157
|
+
args=rec.args,
|
6158
|
+
)
|
6159
|
+
|
6160
|
+
# FIXME: handled specially - all unknown attrs on LogRecord
|
6161
|
+
# class Extra(Adapter[LoggingContextInfos.Extra]):
|
6162
|
+
# _record_attrs: ta.ClassVar[ta.Mapping[str, ta.Union[ta.Any, ta.Tuple[ta.Any, ta.Any]]]] = dict()
|
6103
6163
|
#
|
6104
|
-
#
|
6105
|
-
#
|
6164
|
+
# def info_to_record(self, info: ta.Optional[LoggingContextInfos.Extra]) -> ta.Mapping[str, ta.Any]:
|
6165
|
+
# # FIXME:
|
6166
|
+
# # if extra is not None:
|
6167
|
+
# # for key in extra:
|
6168
|
+
# # if (key in ["message", "asctime"]) or (key in rv.__dict__):
|
6169
|
+
# # raise KeyError("Attempt to overwrite %r in LogRecord" % key)
|
6170
|
+
# # rv.__dict__[key] = extra[key]
|
6171
|
+
# return dict()
|
6106
6172
|
#
|
6107
|
-
|
6173
|
+
# def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Extra]:
|
6174
|
+
# return None
|
6175
|
+
|
6176
|
+
class Time(RequiredAdapter[LoggingContextInfos.Time]):
|
6177
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Time]] = LoggingContextInfos.Time
|
6178
|
+
|
6179
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
|
6180
|
+
# Time when the LogRecord was created. Set to `time.time_ns() / 1e9` for >=3.13.0b1, otherwise simply
|
6181
|
+
# `time.time()`.
|
6182
|
+
#
|
6183
|
+
# See:
|
6184
|
+
# - https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
|
6185
|
+
# - https://github.com/python/cpython/commit/1500a23f33f5a6d052ff1ef6383d9839928b8ff1
|
6186
|
+
#
|
6187
|
+
created=float,
|
6188
|
+
|
6189
|
+
# Millisecond portion of the time when the LogRecord was created.
|
6190
|
+
msecs=float,
|
6191
|
+
|
6192
|
+
# Time in milliseconds when the LogRecord was created, relative to the time the logging module was loaded.
|
6193
|
+
relativeCreated=float,
|
6194
|
+
)
|
6108
6195
|
|
6109
|
-
|
6110
|
-
|
6111
|
-
|
6196
|
+
def _info_to_record(self, info: LoggingContextInfos.Time) -> ta.Mapping[str, ta.Any]:
|
6197
|
+
return dict(
|
6198
|
+
created=info.secs,
|
6199
|
+
msecs=info.msecs,
|
6200
|
+
relativeCreated=info.relative_secs,
|
6201
|
+
)
|
6112
6202
|
|
6113
|
-
|
6203
|
+
def record_to_info(self, rec: logging.LogRecord) -> LoggingContextInfos.Time:
|
6204
|
+
return LoggingContextInfos.Time.build(
|
6205
|
+
int(rec.created * 1e9),
|
6206
|
+
)
|
6114
6207
|
|
6115
|
-
|
6116
|
-
|
6117
|
-
|
6118
|
-
|
6208
|
+
class Exc(OptionalAdapter[LoggingContextInfos.Exc]):
|
6209
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Exc]] = LoggingContextInfos.Exc
|
6210
|
+
|
6211
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
|
6212
|
+
# Exception tuple (à la sys.exc_info) or, if no exception has occurred, None. Unmodified by ctor.
|
6213
|
+
exc_info=(ta.Optional[LoggingExcInfoTuple], None),
|
6214
|
+
|
6215
|
+
# Used to cache the traceback text. Simply set to None by ctor, later set by Formatter.format.
|
6216
|
+
exc_text=(ta.Optional[str], None),
|
6217
|
+
)
|
6218
|
+
|
6219
|
+
def _info_to_record(self, info: LoggingContextInfos.Exc) -> ta.Mapping[str, ta.Any]:
|
6220
|
+
return dict(
|
6221
|
+
exc_info=info.info_tuple,
|
6222
|
+
exc_text=None,
|
6223
|
+
)
|
6224
|
+
|
6225
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Exc]:
|
6226
|
+
# FIXME:
|
6227
|
+
# error: Argument 1 to "build" of "Exc" has incompatible type
|
6228
|
+
# "tuple[type[BaseException], BaseException, TracebackType | None] | tuple[None, None, None] | None"; expected # noqa
|
6229
|
+
# "BaseException | tuple[type[BaseException], BaseException, TracebackType | None] | bool | None" [arg-type] # noqa
|
6230
|
+
return LoggingContextInfos.Exc.build(rec.exc_info) # type: ignore[arg-type]
|
6119
6231
|
|
6120
|
-
|
6232
|
+
class Caller(OptionalAdapter[LoggingContextInfos.Caller]):
|
6233
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Caller]] = LoggingContextInfos.Caller
|
6234
|
+
|
6235
|
+
_UNKNOWN_PATH_NAME: ta.ClassVar[str] = '(unknown file)'
|
6236
|
+
_UNKNOWN_FUNC_NAME: ta.ClassVar[str] = '(unknown function)'
|
6237
|
+
|
6238
|
+
_STACK_INFO_PREFIX: ta.ClassVar[str] = 'Stack (most recent call last):\n'
|
6239
|
+
|
6240
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
|
6241
|
+
# Full pathname of the source file where the logging call was issued (if available). Unmodified by ctor. May
|
6242
|
+
# default to "(unknown file)" by Logger.findCaller / Logger._log.
|
6243
|
+
pathname=(str, _UNKNOWN_PATH_NAME),
|
6244
|
+
|
6245
|
+
# Source line number where the logging call was issued (if available). Unmodified by ctor. May default to 0
|
6246
|
+
# y Logger.findCaller / Logger._log.
|
6247
|
+
lineno=(int, 0),
|
6248
|
+
|
6249
|
+
# Name of function containing the logging call. Set by ctor to `func` arg, unmodified. May default to
|
6250
|
+
# "(unknown function)" by Logger.findCaller / Logger._log.
|
6251
|
+
funcName=(str, _UNKNOWN_FUNC_NAME),
|
6252
|
+
|
6253
|
+
# Stack frame information (where available) from the bottom of the stack in the current thread, up to and
|
6254
|
+
# including the stack frame of the logging call which resulted in the creation of this record. Set by ctor
|
6255
|
+
# to `sinfo` arg, unmodified. Mostly set, if requested, by `Logger.findCaller`, to
|
6256
|
+
# `traceback.print_stack(f)`, but prepended with the literal "Stack (most recent call last):\n", and
|
6257
|
+
# stripped of exactly one trailing `\n` if present.
|
6258
|
+
stack_info=(ta.Optional[str], None),
|
6259
|
+
)
|
6260
|
+
|
6261
|
+
def _info_to_record(self, caller: LoggingContextInfos.Caller) -> ta.Mapping[str, ta.Any]:
|
6262
|
+
if (sinfo := caller.stack_info) is not None:
|
6263
|
+
stack_info: ta.Optional[str] = '\n'.join([
|
6264
|
+
self._STACK_INFO_PREFIX,
|
6265
|
+
sinfo[1:] if sinfo.endswith('\n') else sinfo,
|
6266
|
+
])
|
6267
|
+
else:
|
6268
|
+
stack_info = None
|
6269
|
+
|
6270
|
+
return dict(
|
6271
|
+
pathname=caller.file_path,
|
6272
|
+
|
6273
|
+
lineno=caller.line_no,
|
6274
|
+
funcName=caller.func_name,
|
6275
|
+
|
6276
|
+
stack_info=stack_info,
|
6277
|
+
)
|
6278
|
+
|
6279
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Caller]:
|
6280
|
+
# FIXME: piecemeal?
|
6281
|
+
# FIXME: strip _STACK_INFO_PREFIX
|
6282
|
+
raise NotImplementedError
|
6283
|
+
|
6284
|
+
class SourceFile(Adapter[LoggingContextInfos.SourceFile]):
|
6285
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.SourceFile]] = LoggingContextInfos.SourceFile
|
6286
|
+
|
6287
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
|
6288
|
+
# Filename portion of pathname. Set to `os.path.basename(pathname)` if successful, otherwise defaults to
|
6289
|
+
# pathname.
|
6290
|
+
filename=str,
|
6291
|
+
|
6292
|
+
# Module (name portion of filename). Set to `os.path.splitext(filename)[0]`, otherwise defaults to
|
6293
|
+
# "Unknown module".
|
6294
|
+
module=str,
|
6295
|
+
)
|
6296
|
+
|
6297
|
+
_UNKNOWN_MODULE: ta.ClassVar[str] = 'Unknown module'
|
6298
|
+
|
6299
|
+
def context_to_record(self, ctx: LoggingContext) -> ta.Mapping[str, ta.Any]:
|
6300
|
+
if (info := ctx.get_info(LoggingContextInfos.SourceFile)) is not None:
|
6301
|
+
return dict(
|
6302
|
+
filename=info.file_name,
|
6303
|
+
module=info.module,
|
6304
|
+
)
|
6305
|
+
|
6306
|
+
if (caller := ctx.get_info(LoggingContextInfos.Caller)) is not None:
|
6307
|
+
return dict(
|
6308
|
+
filename=caller.file_path,
|
6309
|
+
module=self._UNKNOWN_MODULE,
|
6310
|
+
)
|
6311
|
+
|
6312
|
+
return dict(
|
6313
|
+
filename=LoggingContextInfoRecordAdapters.Caller._UNKNOWN_PATH_NAME, # noqa
|
6314
|
+
module=self._UNKNOWN_MODULE,
|
6315
|
+
)
|
6316
|
+
|
6317
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.SourceFile]:
|
6318
|
+
if not (
|
6319
|
+
rec.module is None or
|
6320
|
+
rec.module == self._UNKNOWN_MODULE
|
6321
|
+
):
|
6322
|
+
return LoggingContextInfos.SourceFile(
|
6323
|
+
file_name=rec.filename,
|
6324
|
+
module=rec.module, # FIXME: piecemeal?
|
6325
|
+
)
|
6326
|
+
|
6327
|
+
return None
|
6328
|
+
|
6329
|
+
class Thread(OptionalAdapter[LoggingContextInfos.Thread]):
|
6330
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Thread]] = LoggingContextInfos.Thread
|
6331
|
+
|
6332
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
|
6333
|
+
# Thread ID if available, and `logging.logThreads` is truthy.
|
6334
|
+
thread=(ta.Optional[int], None),
|
6335
|
+
|
6336
|
+
# Thread name if available, and `logging.logThreads` is truthy.
|
6337
|
+
threadName=(ta.Optional[str], None),
|
6338
|
+
)
|
6339
|
+
|
6340
|
+
def _info_to_record(self, info: LoggingContextInfos.Thread) -> ta.Mapping[str, ta.Any]:
|
6341
|
+
if logging.logThreads:
|
6342
|
+
return dict(
|
6343
|
+
thread=info.ident,
|
6344
|
+
threadName=info.name,
|
6345
|
+
)
|
6346
|
+
|
6347
|
+
return self.record_defaults
|
6348
|
+
|
6349
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Thread]:
|
6350
|
+
if (
|
6351
|
+
(ident := rec.thread) is not None and
|
6352
|
+
(name := rec.threadName) is not None
|
6353
|
+
):
|
6354
|
+
return LoggingContextInfos.Thread(
|
6355
|
+
ident=ident,
|
6356
|
+
native_id=None,
|
6357
|
+
name=name,
|
6358
|
+
)
|
6359
|
+
|
6360
|
+
return None
|
6361
|
+
|
6362
|
+
class Process(OptionalAdapter[LoggingContextInfos.Process]):
|
6363
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Process]] = LoggingContextInfos.Process
|
6364
|
+
|
6365
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
|
6366
|
+
# Process ID if available - that is, if `hasattr(os, 'getpid')` - and `logging.logProcesses` is truthy,
|
6367
|
+
# otherwise None.
|
6368
|
+
process=(ta.Optional[int], None),
|
6369
|
+
)
|
6370
|
+
|
6371
|
+
def _info_to_record(self, info: LoggingContextInfos.Process) -> ta.Mapping[str, ta.Any]:
|
6372
|
+
if logging.logProcesses:
|
6373
|
+
return dict(
|
6374
|
+
process=info.pid,
|
6375
|
+
)
|
6376
|
+
|
6377
|
+
return self.record_defaults
|
6378
|
+
|
6379
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Process]:
|
6380
|
+
if (
|
6381
|
+
(pid := rec.process) is not None
|
6382
|
+
):
|
6383
|
+
return LoggingContextInfos.Process(
|
6384
|
+
pid=pid,
|
6385
|
+
)
|
6386
|
+
|
6387
|
+
return None
|
6388
|
+
|
6389
|
+
class Multiprocessing(OptionalAdapter[LoggingContextInfos.Multiprocessing]):
|
6390
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Multiprocessing]] = LoggingContextInfos.Multiprocessing
|
6391
|
+
|
6392
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
|
6393
|
+
# Process name if available. Set to None if `logging.logMultiprocessing` is not truthy. Otherwise, set to
|
6394
|
+
# 'MainProcess', then `sys.modules.get('multiprocessing').current_process().name` if that works, otherwise
|
6395
|
+
# remains as 'MainProcess'.
|
6396
|
+
#
|
6397
|
+
# As noted by stdlib:
|
6398
|
+
#
|
6399
|
+
# Errors may occur if multiprocessing has not finished loading yet - e.g. if a custom import hook causes
|
6400
|
+
# third-party code to run when multiprocessing calls import. See issue 8200 for an example
|
6401
|
+
#
|
6402
|
+
processName=(ta.Optional[str], None),
|
6403
|
+
)
|
6404
|
+
|
6405
|
+
def _info_to_record(self, info: LoggingContextInfos.Multiprocessing) -> ta.Mapping[str, ta.Any]:
|
6406
|
+
if logging.logMultiprocessing:
|
6407
|
+
return dict(
|
6408
|
+
processName=info.process_name,
|
6409
|
+
)
|
6410
|
+
|
6411
|
+
return self.record_defaults
|
6412
|
+
|
6413
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Multiprocessing]:
|
6414
|
+
if (
|
6415
|
+
(process_name := rec.processName) is not None
|
6416
|
+
):
|
6417
|
+
return LoggingContextInfos.Multiprocessing(
|
6418
|
+
process_name=process_name,
|
6419
|
+
)
|
6420
|
+
|
6421
|
+
return None
|
6422
|
+
|
6423
|
+
class AsyncioTask(OptionalAdapter[LoggingContextInfos.AsyncioTask]):
|
6424
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.AsyncioTask]] = LoggingContextInfos.AsyncioTask
|
6425
|
+
|
6426
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Union[ta.Any, ta.Tuple[ta.Any, ta.Any]]]] = dict(
|
6427
|
+
# Absent <3.12, otherwise asyncio.Task name if available, and `logging.logAsyncioTasks` is truthy. Set to
|
6428
|
+
# `sys.modules.get('asyncio').current_task().get_name()`, otherwise None.
|
6429
|
+
taskName=(ta.Optional[str], None),
|
6430
|
+
)
|
6431
|
+
|
6432
|
+
def _info_to_record(self, info: LoggingContextInfos.AsyncioTask) -> ta.Mapping[str, ta.Any]:
|
6433
|
+
if getattr(logging, 'logAsyncioTasks', None): # Absent <3.12
|
6434
|
+
return dict(
|
6435
|
+
taskName=info.name,
|
6436
|
+
)
|
6437
|
+
|
6438
|
+
return self.record_defaults
|
6439
|
+
|
6440
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.AsyncioTask]:
|
6441
|
+
if (
|
6442
|
+
(name := getattr(rec, 'taskName', None)) is not None
|
6443
|
+
):
|
6444
|
+
return LoggingContextInfos.AsyncioTask(
|
6445
|
+
name=name,
|
6446
|
+
)
|
6447
|
+
|
6448
|
+
return None
|
6449
|
+
|
6450
|
+
|
6451
|
+
_LOGGING_CONTEXT_INFO_RECORD_ADAPTERS_: ta.Sequence[LoggingContextInfoRecordAdapters.Adapter] = [ # noqa
|
6452
|
+
LoggingContextInfoRecordAdapters.Name(),
|
6453
|
+
LoggingContextInfoRecordAdapters.Level(),
|
6454
|
+
LoggingContextInfoRecordAdapters.Msg(),
|
6455
|
+
LoggingContextInfoRecordAdapters.Time(),
|
6456
|
+
LoggingContextInfoRecordAdapters.Exc(),
|
6457
|
+
LoggingContextInfoRecordAdapters.Caller(),
|
6458
|
+
LoggingContextInfoRecordAdapters.SourceFile(),
|
6459
|
+
LoggingContextInfoRecordAdapters.Thread(),
|
6460
|
+
LoggingContextInfoRecordAdapters.Process(),
|
6461
|
+
LoggingContextInfoRecordAdapters.Multiprocessing(),
|
6462
|
+
LoggingContextInfoRecordAdapters.AsyncioTask(),
|
6463
|
+
]
|
6464
|
+
|
6465
|
+
_LOGGING_CONTEXT_INFO_RECORD_ADAPTERS: ta.Mapping[ta.Type[LoggingContextInfo], LoggingContextInfoRecordAdapters.Adapter] = { # noqa
|
6466
|
+
ad.info_cls: ad for ad in _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS_
|
6467
|
+
}
|
6468
|
+
|
6469
|
+
|
6470
|
+
##
|
6471
|
+
|
6472
|
+
|
6473
|
+
KNOWN_STD_LOGGING_RECORD_ATTR_SET: ta.FrozenSet[str] = frozenset(
|
6474
|
+
a for ad in _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS.values() for a in ad.record_attrs
|
6475
|
+
)
|
6121
6476
|
|
6122
6477
|
|
6123
6478
|
# Formatter:
|
@@ -6141,14 +6496,17 @@ KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTRS: ta.Dict[str, ta.Any] = dict(
|
|
6141
6496
|
KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTR_SET: ta.FrozenSet[str] = frozenset(KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTRS)
|
6142
6497
|
|
6143
6498
|
|
6144
|
-
##
|
6145
|
-
|
6146
|
-
|
6147
6499
|
class UnknownStdLoggingRecordAttrsWarning(LoggingSetupWarning):
|
6148
6500
|
pass
|
6149
6501
|
|
6150
6502
|
|
6151
6503
|
def _check_std_logging_record_attrs() -> None:
|
6504
|
+
if (
|
6505
|
+
len([a for ad in _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS.values() for a in ad.record_attrs]) !=
|
6506
|
+
len(KNOWN_STD_LOGGING_RECORD_ATTR_SET)
|
6507
|
+
):
|
6508
|
+
raise RuntimeError('Duplicate LoggingContextInfoRecordAdapter record attrs')
|
6509
|
+
|
6152
6510
|
rec_dct = dict(logging.makeLogRecord({}).__dict__)
|
6153
6511
|
|
6154
6512
|
if (unk_rec_fields := frozenset(rec_dct) - KNOWN_STD_LOGGING_RECORD_ATTR_SET):
|
@@ -6167,116 +6525,20 @@ _check_std_logging_record_attrs()
|
|
6167
6525
|
|
6168
6526
|
|
6169
6527
|
class LoggingContextLogRecord(logging.LogRecord):
|
6170
|
-
|
6171
|
-
|
6172
|
-
|
6173
|
-
|
6174
|
-
|
6175
|
-
|
6176
|
-
|
6177
|
-
|
6178
|
-
|
6179
|
-
|
6180
|
-
|
6181
|
-
|
6182
|
-
|
6183
|
-
|
6184
|
-
# msg,
|
6185
|
-
# args,
|
6186
|
-
# exc_info,
|
6187
|
-
# func=None,
|
6188
|
-
# sinfo=None,
|
6189
|
-
# **kwargs,
|
6190
|
-
*,
|
6191
|
-
name: str,
|
6192
|
-
msg: str,
|
6193
|
-
args: ta.Union[tuple, dict],
|
6194
|
-
|
6195
|
-
_logging_context: LoggingContext,
|
6196
|
-
) -> None:
|
6197
|
-
ctx = _logging_context
|
6198
|
-
|
6199
|
-
self.name: str = name
|
6200
|
-
|
6201
|
-
self.msg: str = msg
|
6202
|
-
|
6203
|
-
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L307
|
6204
|
-
if args and len(args) == 1 and isinstance(args[0], collections.abc.Mapping) and args[0]:
|
6205
|
-
args = args[0] # type: ignore[assignment]
|
6206
|
-
self.args: ta.Union[tuple, dict] = args
|
6207
|
-
|
6208
|
-
self.levelname: str = logging.getLevelName(ctx.level)
|
6209
|
-
self.levelno: int = ctx.level
|
6210
|
-
|
6211
|
-
if (caller := ctx.caller()) is not None:
|
6212
|
-
self.pathname: str = caller.file_path
|
6213
|
-
else:
|
6214
|
-
self.pathname = self._UNKNOWN_PATH_NAME
|
6215
|
-
|
6216
|
-
if (src_file := ctx.source_file()) is not None:
|
6217
|
-
self.filename: str = src_file.file_name
|
6218
|
-
self.module: str = src_file.module
|
6219
|
-
else:
|
6220
|
-
self.filename = self.pathname
|
6221
|
-
self.module = self._UNKNOWN_MODULE
|
6222
|
-
|
6223
|
-
self.exc_info: ta.Optional[LoggingExcInfoTuple] = ctx.exc_info_tuple
|
6224
|
-
self.exc_text: ta.Optional[str] = None
|
6225
|
-
|
6226
|
-
# If ctx.build_caller() was never called, we simply don't have a stack trace.
|
6227
|
-
if caller is not None:
|
6228
|
-
if (sinfo := caller.stack_info) is not None:
|
6229
|
-
self.stack_info: ta.Optional[str] = '\n'.join([
|
6230
|
-
self._STACK_INFO_PREFIX,
|
6231
|
-
sinfo[1:] if sinfo.endswith('\n') else sinfo,
|
6232
|
-
])
|
6233
|
-
else:
|
6234
|
-
self.stack_info = None
|
6235
|
-
|
6236
|
-
self.lineno: int = caller.line_no
|
6237
|
-
self.funcName: str = caller.name
|
6238
|
-
|
6239
|
-
else:
|
6240
|
-
self.stack_info = None
|
6241
|
-
|
6242
|
-
self.lineno = 0
|
6243
|
-
self.funcName = self._UNKNOWN_FUNC_NAME
|
6244
|
-
|
6245
|
-
times = ctx.times
|
6246
|
-
self.created: float = times.created
|
6247
|
-
self.msecs: float = times.msecs
|
6248
|
-
self.relativeCreated: float = times.relative_created
|
6249
|
-
|
6250
|
-
if logging.logThreads:
|
6251
|
-
thread = check.not_none(ctx.thread())
|
6252
|
-
self.thread: ta.Optional[int] = thread.ident
|
6253
|
-
self.threadName: ta.Optional[str] = thread.name
|
6254
|
-
else:
|
6255
|
-
self.thread = None
|
6256
|
-
self.threadName = None
|
6257
|
-
|
6258
|
-
if logging.logProcesses:
|
6259
|
-
process = check.not_none(ctx.process())
|
6260
|
-
self.process: ta.Optional[int] = process.pid
|
6261
|
-
else:
|
6262
|
-
self.process = None
|
6263
|
-
|
6264
|
-
if logging.logMultiprocessing:
|
6265
|
-
if (mp := ctx.multiprocessing()) is not None:
|
6266
|
-
self.processName: ta.Optional[str] = mp.process_name
|
6267
|
-
else:
|
6268
|
-
self.processName = None
|
6269
|
-
else:
|
6270
|
-
self.processName = None
|
6271
|
-
|
6272
|
-
# Absent <3.12
|
6273
|
-
if getattr(logging, 'logAsyncioTasks', None):
|
6274
|
-
if (at := ctx.asyncio_task()) is not None:
|
6275
|
-
self.taskName: ta.Optional[str] = at.name
|
6276
|
-
else:
|
6277
|
-
self.taskName = None
|
6278
|
-
else:
|
6279
|
-
self.taskName = None
|
6528
|
+
# LogRecord.__init__ args:
|
6529
|
+
# - name: str
|
6530
|
+
# - level: int
|
6531
|
+
# - pathname: str - Confusingly referred to as `fn` before the LogRecord ctor. May be empty or "(unknown file)".
|
6532
|
+
# - lineno: int - May be 0.
|
6533
|
+
# - msg: str
|
6534
|
+
# - args: tuple | dict | 1-tuple[dict]
|
6535
|
+
# - exc_info: LoggingExcInfoTuple | None
|
6536
|
+
# - func: str | None = None -> funcName
|
6537
|
+
# - sinfo: str | None = None -> stack_info
|
6538
|
+
|
6539
|
+
def __init__(self, *, _logging_context: LoggingContext) -> None: # noqa
|
6540
|
+
for ad in _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS_:
|
6541
|
+
self.__dict__.update(ad.context_to_record(_logging_context))
|
6280
6542
|
|
6281
6543
|
|
6282
6544
|
########################################
|
@@ -6303,21 +6565,20 @@ class StdLogger(Logger):
|
|
6303
6565
|
return self._std.getEffectiveLevel()
|
6304
6566
|
|
6305
6567
|
def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any) -> None:
|
6306
|
-
if not self.is_enabled_for(ctx.level):
|
6568
|
+
if not self.is_enabled_for(ctx.must_get_info(LoggingContextInfos.Level).level):
|
6307
6569
|
return
|
6308
6570
|
|
6309
|
-
ctx.
|
6310
|
-
|
6311
|
-
ms, args = self._prepare_msg_args(msg, *args)
|
6312
|
-
|
6313
|
-
rec = LoggingContextLogRecord(
|
6571
|
+
ctx.set_basic(
|
6314
6572
|
name=self._std.name,
|
6315
|
-
msg=ms,
|
6316
|
-
args=args,
|
6317
6573
|
|
6318
|
-
|
6574
|
+
msg=msg,
|
6575
|
+
args=args,
|
6319
6576
|
)
|
6320
6577
|
|
6578
|
+
ctx.capture()
|
6579
|
+
|
6580
|
+
rec = LoggingContextLogRecord(_logging_context=ctx)
|
6581
|
+
|
6321
6582
|
self._std.handle(rec)
|
6322
6583
|
|
6323
6584
|
|