omdev 0.0.0.dev430__py3-none-any.whl → 0.0.0.dev432__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- omdev/scripts/ci.py +991 -675
- omdev/scripts/interp.py +10 -6
- omdev/scripts/pyproject.py +979 -663
- {omdev-0.0.0.dev430.dist-info → omdev-0.0.0.dev432.dist-info}/METADATA +2 -2
- {omdev-0.0.0.dev430.dist-info → omdev-0.0.0.dev432.dist-info}/RECORD +9 -9
- {omdev-0.0.0.dev430.dist-info → omdev-0.0.0.dev432.dist-info}/WHEEL +0 -0
- {omdev-0.0.0.dev430.dist-info → omdev-0.0.0.dev432.dist-info}/entry_points.txt +0 -0
- {omdev-0.0.0.dev430.dist-info → omdev-0.0.0.dev432.dist-info}/licenses/LICENSE +0 -0
- {omdev-0.0.0.dev430.dist-info → omdev-0.0.0.dev432.dist-info}/top_level.txt +0 -0
omdev/scripts/ci.py
CHANGED
@@ -126,6 +126,13 @@ U = ta.TypeVar('U')
|
|
126
126
|
# ../../omlish/lite/timeouts.py
|
127
127
|
TimeoutLike = ta.Union['Timeout', ta.Type['Timeout.DEFAULT'], ta.Iterable['TimeoutLike'], float, None] # ta.TypeAlias
|
128
128
|
|
129
|
+
# ../../omlish/logs/infos.py
|
130
|
+
LoggingMsgFn = ta.Callable[[], ta.Union[str, tuple]] # ta.TypeAlias
|
131
|
+
LoggingExcInfoTuple = ta.Tuple[ta.Type[BaseException], BaseException, ta.Optional[types.TracebackType]] # ta.TypeAlias
|
132
|
+
LoggingExcInfo = ta.Union[BaseException, LoggingExcInfoTuple] # ta.TypeAlias
|
133
|
+
LoggingExcInfoArg = ta.Union[LoggingExcInfo, bool, None] # ta.TypeAlias
|
134
|
+
LoggingContextInfo = ta.Any # ta.TypeAlias
|
135
|
+
|
129
136
|
# ../../omlish/sockets/bind.py
|
130
137
|
SocketBinderT = ta.TypeVar('SocketBinderT', bound='SocketBinder')
|
131
138
|
SocketBinderConfigT = ta.TypeVar('SocketBinderConfigT', bound='SocketBinder.Config')
|
@@ -149,9 +156,7 @@ InjectorProviderFnMap = ta.Mapping['InjectorKey', 'InjectorProviderFn']
|
|
149
156
|
InjectorBindingOrBindings = ta.Union['InjectorBinding', 'InjectorBindings']
|
150
157
|
|
151
158
|
# ../../omlish/logs/contexts.py
|
152
|
-
|
153
|
-
LoggingExcInfo = ta.Union[BaseException, LoggingExcInfoTuple] # ta.TypeAlias
|
154
|
-
LoggingExcInfoArg = ta.Union[LoggingExcInfo, bool, None] # ta.TypeAlias
|
159
|
+
LoggingContextInfoT = ta.TypeVar('LoggingContextInfoT', bound=LoggingContextInfo)
|
155
160
|
|
156
161
|
# ../../omlish/sockets/server/handlers.py
|
157
162
|
SocketServerHandler = ta.Callable[['SocketAndAddress'], None] # ta.TypeAlias
|
@@ -162,9 +167,6 @@ DataServerTargetT = ta.TypeVar('DataServerTargetT', bound='DataServerTarget')
|
|
162
167
|
# ../../omlish/http/coro/server/server.py
|
163
168
|
CoroHttpServerFactory = ta.Callable[[SocketAddress], 'CoroHttpServer']
|
164
169
|
|
165
|
-
# ../../omlish/logs/base.py
|
166
|
-
LoggingMsgFn = ta.Callable[[], ta.Union[str, tuple]] # ta.TypeAlias
|
167
|
-
|
168
170
|
# ../../omlish/subprocesses/base.py
|
169
171
|
SubprocessChannelOption = ta.Literal['pipe', 'stdout', 'devnull'] # ta.TypeAlias
|
170
172
|
|
@@ -1800,124 +1802,6 @@ def format_num_bytes(num_bytes: int) -> str:
|
|
1800
1802
|
return f'{num_bytes / 1024 ** (len(FORMAT_NUM_BYTES_SUFFIXES) - 1):.2f}{FORMAT_NUM_BYTES_SUFFIXES[-1]}'
|
1801
1803
|
|
1802
1804
|
|
1803
|
-
########################################
|
1804
|
-
# ../../../omlish/logs/infos.py
|
1805
|
-
|
1806
|
-
|
1807
|
-
##
|
1808
|
-
|
1809
|
-
|
1810
|
-
def logging_context_info(cls):
|
1811
|
-
return cls
|
1812
|
-
|
1813
|
-
|
1814
|
-
##
|
1815
|
-
|
1816
|
-
|
1817
|
-
@logging_context_info
|
1818
|
-
@ta.final
|
1819
|
-
class LoggingSourceFileInfo(ta.NamedTuple):
|
1820
|
-
file_name: str
|
1821
|
-
module: str
|
1822
|
-
|
1823
|
-
@classmethod
|
1824
|
-
def build(cls, file_path: ta.Optional[str]) -> ta.Optional['LoggingSourceFileInfo']:
|
1825
|
-
if file_path is None:
|
1826
|
-
return None
|
1827
|
-
|
1828
|
-
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L331-L336 # noqa
|
1829
|
-
try:
|
1830
|
-
file_name = os.path.basename(file_path)
|
1831
|
-
module = os.path.splitext(file_name)[0]
|
1832
|
-
except (TypeError, ValueError, AttributeError):
|
1833
|
-
return None
|
1834
|
-
|
1835
|
-
return cls(
|
1836
|
-
file_name=file_name,
|
1837
|
-
module=module,
|
1838
|
-
)
|
1839
|
-
|
1840
|
-
|
1841
|
-
##
|
1842
|
-
|
1843
|
-
|
1844
|
-
@logging_context_info
|
1845
|
-
@ta.final
|
1846
|
-
class LoggingThreadInfo(ta.NamedTuple):
|
1847
|
-
ident: int
|
1848
|
-
native_id: ta.Optional[int]
|
1849
|
-
name: str
|
1850
|
-
|
1851
|
-
@classmethod
|
1852
|
-
def build(cls) -> 'LoggingThreadInfo':
|
1853
|
-
return cls(
|
1854
|
-
ident=threading.get_ident(),
|
1855
|
-
native_id=threading.get_native_id() if hasattr(threading, 'get_native_id') else None,
|
1856
|
-
name=threading.current_thread().name,
|
1857
|
-
)
|
1858
|
-
|
1859
|
-
|
1860
|
-
##
|
1861
|
-
|
1862
|
-
|
1863
|
-
@logging_context_info
|
1864
|
-
@ta.final
|
1865
|
-
class LoggingProcessInfo(ta.NamedTuple):
|
1866
|
-
pid: int
|
1867
|
-
|
1868
|
-
@classmethod
|
1869
|
-
def build(cls) -> 'LoggingProcessInfo':
|
1870
|
-
return cls(
|
1871
|
-
pid=os.getpid(),
|
1872
|
-
)
|
1873
|
-
|
1874
|
-
|
1875
|
-
##
|
1876
|
-
|
1877
|
-
|
1878
|
-
@logging_context_info
|
1879
|
-
@ta.final
|
1880
|
-
class LoggingMultiprocessingInfo(ta.NamedTuple):
|
1881
|
-
process_name: str
|
1882
|
-
|
1883
|
-
@classmethod
|
1884
|
-
def build(cls) -> ta.Optional['LoggingMultiprocessingInfo']:
|
1885
|
-
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L355-L364 # noqa
|
1886
|
-
if (mp := sys.modules.get('multiprocessing')) is None:
|
1887
|
-
return None
|
1888
|
-
|
1889
|
-
return cls(
|
1890
|
-
process_name=mp.current_process().name,
|
1891
|
-
)
|
1892
|
-
|
1893
|
-
|
1894
|
-
##
|
1895
|
-
|
1896
|
-
|
1897
|
-
@logging_context_info
|
1898
|
-
@ta.final
|
1899
|
-
class LoggingAsyncioTaskInfo(ta.NamedTuple):
|
1900
|
-
name: str
|
1901
|
-
|
1902
|
-
@classmethod
|
1903
|
-
def build(cls) -> ta.Optional['LoggingAsyncioTaskInfo']:
|
1904
|
-
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L372-L377 # noqa
|
1905
|
-
if (asyncio := sys.modules.get('asyncio')) is None:
|
1906
|
-
return None
|
1907
|
-
|
1908
|
-
try:
|
1909
|
-
task = asyncio.current_task()
|
1910
|
-
except Exception: # noqa
|
1911
|
-
return None
|
1912
|
-
|
1913
|
-
if task is None:
|
1914
|
-
return None
|
1915
|
-
|
1916
|
-
return cls(
|
1917
|
-
name=task.get_name(), # Always non-None
|
1918
|
-
)
|
1919
|
-
|
1920
|
-
|
1921
1805
|
########################################
|
1922
1806
|
# ../../../omlish/logs/levels.py
|
1923
1807
|
|
@@ -4980,74 +4864,362 @@ class PredicateTimeout(Timeout):
|
|
4980
4864
|
|
4981
4865
|
|
4982
4866
|
########################################
|
4983
|
-
# ../../../omlish/logs/
|
4867
|
+
# ../../../omlish/logs/infos.py
|
4868
|
+
"""
|
4869
|
+
TODO:
|
4870
|
+
- remove redundant info fields only present for std adaptation (Level.name, ...)
|
4871
|
+
"""
|
4984
4872
|
|
4985
4873
|
|
4986
4874
|
##
|
4987
4875
|
|
4988
4876
|
|
4989
|
-
|
4877
|
+
def logging_context_info(cls):
|
4878
|
+
return cls
|
4879
|
+
|
4880
|
+
|
4990
4881
|
@ta.final
|
4991
|
-
class
|
4992
|
-
|
4993
|
-
|
4994
|
-
name: str
|
4995
|
-
stack_info: ta.Optional[str]
|
4882
|
+
class LoggingContextInfos:
|
4883
|
+
def __new__(cls, *args, **kwargs): # noqa
|
4884
|
+
raise TypeError
|
4996
4885
|
|
4997
|
-
|
4998
|
-
def is_internal_frame(cls, frame: types.FrameType) -> bool:
|
4999
|
-
file_path = os.path.normcase(frame.f_code.co_filename)
|
4886
|
+
#
|
5000
4887
|
|
5001
|
-
|
5002
|
-
|
5003
|
-
|
5004
|
-
|
5005
|
-
return True
|
4888
|
+
@logging_context_info
|
4889
|
+
@ta.final
|
4890
|
+
class Name(ta.NamedTuple):
|
4891
|
+
name: str
|
5006
4892
|
|
5007
|
-
|
4893
|
+
@logging_context_info
|
4894
|
+
@ta.final
|
4895
|
+
class Level(ta.NamedTuple):
|
4896
|
+
level: NamedLogLevel
|
4897
|
+
name: str
|
5008
4898
|
|
5009
|
-
|
5010
|
-
|
5011
|
-
|
4899
|
+
@classmethod
|
4900
|
+
def build(cls, level: int) -> 'LoggingContextInfos.Level':
|
4901
|
+
nl: NamedLogLevel = level if level.__class__ is NamedLogLevel else NamedLogLevel(level) # type: ignore[assignment] # noqa
|
4902
|
+
return cls(
|
4903
|
+
level=nl,
|
4904
|
+
name=logging.getLevelName(nl),
|
4905
|
+
)
|
4906
|
+
|
4907
|
+
@logging_context_info
|
4908
|
+
@ta.final
|
4909
|
+
class Msg(ta.NamedTuple):
|
4910
|
+
msg: str
|
4911
|
+
args: ta.Union[tuple, ta.Mapping[ta.Any, ta.Any], None]
|
5012
4912
|
|
5013
|
-
|
5014
|
-
|
5015
|
-
|
5016
|
-
|
5017
|
-
|
4913
|
+
@classmethod
|
4914
|
+
def build(
|
4915
|
+
cls,
|
4916
|
+
msg: ta.Union[str, tuple, LoggingMsgFn],
|
4917
|
+
*args: ta.Any,
|
4918
|
+
) -> 'LoggingContextInfos.Msg':
|
4919
|
+
s: str
|
4920
|
+
a: ta.Any
|
4921
|
+
|
4922
|
+
if callable(msg):
|
4923
|
+
if args:
|
4924
|
+
raise TypeError(f'Must not provide both a message function and args: {msg=} {args=}')
|
4925
|
+
x = msg()
|
4926
|
+
if isinstance(x, str):
|
4927
|
+
s, a = x, ()
|
4928
|
+
elif isinstance(x, tuple):
|
4929
|
+
if x:
|
4930
|
+
s, a = x[0], x[1:]
|
4931
|
+
else:
|
4932
|
+
s, a = '', ()
|
4933
|
+
else:
|
4934
|
+
raise TypeError(x)
|
5018
4935
|
|
5019
|
-
|
4936
|
+
elif isinstance(msg, tuple):
|
4937
|
+
if args:
|
4938
|
+
raise TypeError(f'Must not provide both a tuple message and args: {msg=} {args=}')
|
4939
|
+
if msg:
|
4940
|
+
s, a = msg[0], msg[1:]
|
4941
|
+
else:
|
4942
|
+
s, a = '', ()
|
5020
4943
|
|
5021
|
-
|
4944
|
+
elif isinstance(msg, str):
|
4945
|
+
s, a = msg, args
|
4946
|
+
|
4947
|
+
else:
|
4948
|
+
raise TypeError(msg)
|
4949
|
+
|
4950
|
+
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L307 # noqa
|
4951
|
+
if a and len(a) == 1 and isinstance(a[0], collections.abc.Mapping) and a[0]:
|
4952
|
+
a = a[0]
|
4953
|
+
|
4954
|
+
return cls(
|
4955
|
+
msg=s,
|
4956
|
+
args=a,
|
4957
|
+
)
|
4958
|
+
|
4959
|
+
@logging_context_info
|
4960
|
+
@ta.final
|
4961
|
+
class Extra(ta.NamedTuple):
|
4962
|
+
extra: ta.Mapping[ta.Any, ta.Any]
|
4963
|
+
|
4964
|
+
@logging_context_info
|
4965
|
+
@ta.final
|
4966
|
+
class Time(ta.NamedTuple):
|
4967
|
+
ns: int
|
4968
|
+
secs: float
|
4969
|
+
msecs: float
|
4970
|
+
relative_secs: float
|
4971
|
+
|
4972
|
+
@classmethod
|
4973
|
+
def get_std_start_ns(cls) -> int:
|
4974
|
+
x: ta.Any = logging._startTime # type: ignore[attr-defined] # noqa
|
4975
|
+
|
4976
|
+
# Before 3.13.0b1 this will be `time.time()`, a float of seconds. After that, it will be `time.time_ns()`,
|
4977
|
+
# an int.
|
4978
|
+
#
|
4979
|
+
# See:
|
4980
|
+
# - https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
|
4981
|
+
#
|
4982
|
+
if isinstance(x, float):
|
4983
|
+
return int(x * 1e9)
|
4984
|
+
else:
|
4985
|
+
return x
|
4986
|
+
|
4987
|
+
@classmethod
|
4988
|
+
def build(
|
4989
|
+
cls,
|
4990
|
+
ns: int,
|
4991
|
+
*,
|
4992
|
+
start_ns: ta.Optional[int] = None,
|
4993
|
+
) -> 'LoggingContextInfos.Time':
|
4994
|
+
# https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
|
4995
|
+
secs = ns / 1e9 # ns to float seconds
|
4996
|
+
|
4997
|
+
# Get the number of whole milliseconds (0-999) in the fractional part of seconds.
|
4998
|
+
# Eg: 1_677_903_920_999_998_503 ns --> 999_998_503 ns--> 999 ms
|
4999
|
+
# Convert to float by adding 0.0 for historical reasons. See gh-89047
|
5000
|
+
msecs = (ns % 1_000_000_000) // 1_000_000 + 0.0
|
5001
|
+
|
5002
|
+
# https://github.com/python/cpython/commit/1500a23f33f5a6d052ff1ef6383d9839928b8ff1
|
5003
|
+
if msecs == 999.0 and int(secs) != ns // 1_000_000_000:
|
5004
|
+
# ns -> sec conversion can round up, e.g:
|
5005
|
+
# 1_677_903_920_999_999_900 ns --> 1_677_903_921.0 sec
|
5006
|
+
msecs = 0.0
|
5007
|
+
|
5008
|
+
if start_ns is None:
|
5009
|
+
start_ns = cls.get_std_start_ns()
|
5010
|
+
relative_secs = (ns - start_ns) / 1e6
|
5011
|
+
|
5012
|
+
return cls(
|
5013
|
+
ns=ns,
|
5014
|
+
secs=secs,
|
5015
|
+
msecs=msecs,
|
5016
|
+
relative_secs=relative_secs,
|
5017
|
+
)
|
5018
|
+
|
5019
|
+
@logging_context_info
|
5020
|
+
@ta.final
|
5021
|
+
class Exc(ta.NamedTuple):
|
5022
|
+
info: LoggingExcInfo
|
5023
|
+
info_tuple: LoggingExcInfoTuple
|
5024
|
+
|
5025
|
+
@classmethod
|
5026
|
+
def build(
|
5027
|
+
cls,
|
5028
|
+
arg: LoggingExcInfoArg = False,
|
5029
|
+
) -> ta.Optional['LoggingContextInfos.Exc']:
|
5030
|
+
if arg is True:
|
5031
|
+
sys_exc_info = sys.exc_info()
|
5032
|
+
if sys_exc_info[0] is not None:
|
5033
|
+
arg = sys_exc_info
|
5034
|
+
else:
|
5035
|
+
arg = None
|
5036
|
+
elif arg is False:
|
5037
|
+
arg = None
|
5038
|
+
if arg is None:
|
5039
|
+
return None
|
5040
|
+
|
5041
|
+
info: LoggingExcInfo = arg
|
5042
|
+
if isinstance(info, BaseException):
|
5043
|
+
info_tuple: LoggingExcInfoTuple = (type(info), info, info.__traceback__) # noqa
|
5044
|
+
else:
|
5045
|
+
info_tuple = info
|
5046
|
+
|
5047
|
+
return cls(
|
5048
|
+
info=info,
|
5049
|
+
info_tuple=info_tuple,
|
5050
|
+
)
|
5051
|
+
|
5052
|
+
@logging_context_info
|
5053
|
+
@ta.final
|
5054
|
+
class Caller(ta.NamedTuple):
|
5055
|
+
file_path: str
|
5056
|
+
line_no: int
|
5057
|
+
func_name: str
|
5058
|
+
stack_info: ta.Optional[str]
|
5059
|
+
|
5060
|
+
@classmethod
|
5061
|
+
def is_internal_frame(cls, frame: types.FrameType) -> bool:
|
5062
|
+
file_path = os.path.normcase(frame.f_code.co_filename)
|
5063
|
+
|
5064
|
+
# Yes, really.
|
5065
|
+
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L204 # noqa
|
5066
|
+
# https://github.com/python/cpython/commit/5ca6d7469be53960843df39bb900e9c3359f127f
|
5067
|
+
if 'importlib' in file_path and '_bootstrap' in file_path:
|
5068
|
+
return True
|
5069
|
+
|
5070
|
+
return False
|
5071
|
+
|
5072
|
+
@classmethod
|
5073
|
+
def find_frame(cls, stack_offset: int = 0) -> ta.Optional[types.FrameType]:
|
5074
|
+
f: ta.Optional[types.FrameType] = sys._getframe(2 + stack_offset) # noqa
|
5075
|
+
|
5076
|
+
while f is not None:
|
5077
|
+
# NOTE: We don't check __file__ like stdlib since we may be running amalgamated - we rely on careful,
|
5078
|
+
# manual stack_offset management.
|
5079
|
+
if hasattr(f, 'f_code'):
|
5080
|
+
return f
|
5081
|
+
|
5082
|
+
f = f.f_back
|
5022
5083
|
|
5023
|
-
@classmethod
|
5024
|
-
def find(
|
5025
|
-
cls,
|
5026
|
-
ofs: int = 0,
|
5027
|
-
*,
|
5028
|
-
stack_info: bool = False,
|
5029
|
-
) -> ta.Optional['LoggingCaller']:
|
5030
|
-
if (f := cls.find_frame(ofs + 1)) is None:
|
5031
5084
|
return None
|
5032
5085
|
|
5033
|
-
|
5034
|
-
|
5035
|
-
|
5036
|
-
|
5037
|
-
|
5038
|
-
|
5039
|
-
|
5040
|
-
if
|
5041
|
-
|
5086
|
+
@classmethod
|
5087
|
+
def build(
|
5088
|
+
cls,
|
5089
|
+
stack_offset: int = 0,
|
5090
|
+
*,
|
5091
|
+
stack_info: bool = False,
|
5092
|
+
) -> ta.Optional['LoggingContextInfos.Caller']:
|
5093
|
+
if (f := cls.find_frame(stack_offset + 1)) is None:
|
5094
|
+
return None
|
5095
|
+
|
5096
|
+
# https://github.com/python/cpython/blob/08e9794517063c8cd92c48714071b1d3c60b71bd/Lib/logging/__init__.py#L1616-L1623 # noqa
|
5097
|
+
sinfo = None
|
5098
|
+
if stack_info:
|
5099
|
+
sio = io.StringIO()
|
5100
|
+
traceback.print_stack(f, file=sio)
|
5101
|
+
sinfo = sio.getvalue()
|
5102
|
+
sio.close()
|
5103
|
+
if sinfo[-1] == '\n':
|
5104
|
+
sinfo = sinfo[:-1]
|
5042
5105
|
|
5043
|
-
|
5044
|
-
|
5045
|
-
|
5046
|
-
|
5047
|
-
|
5106
|
+
return cls(
|
5107
|
+
file_path=f.f_code.co_filename,
|
5108
|
+
line_no=f.f_lineno or 0,
|
5109
|
+
func_name=f.f_code.co_name,
|
5110
|
+
stack_info=sinfo,
|
5111
|
+
)
|
5112
|
+
|
5113
|
+
@logging_context_info
|
5114
|
+
@ta.final
|
5115
|
+
class SourceFile(ta.NamedTuple):
|
5116
|
+
file_name: str
|
5117
|
+
module: str
|
5118
|
+
|
5119
|
+
@classmethod
|
5120
|
+
def build(cls, caller_file_path: ta.Optional[str]) -> ta.Optional['LoggingContextInfos.SourceFile']:
|
5121
|
+
if caller_file_path is None:
|
5122
|
+
return None
|
5123
|
+
|
5124
|
+
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L331-L336 # noqa
|
5125
|
+
try:
|
5126
|
+
file_name = os.path.basename(caller_file_path)
|
5127
|
+
module = os.path.splitext(file_name)[0]
|
5128
|
+
except (TypeError, ValueError, AttributeError):
|
5129
|
+
return None
|
5130
|
+
|
5131
|
+
return cls(
|
5132
|
+
file_name=file_name,
|
5133
|
+
module=module,
|
5134
|
+
)
|
5135
|
+
|
5136
|
+
@logging_context_info
|
5137
|
+
@ta.final
|
5138
|
+
class Thread(ta.NamedTuple):
|
5139
|
+
ident: int
|
5140
|
+
native_id: ta.Optional[int]
|
5141
|
+
name: str
|
5142
|
+
|
5143
|
+
@classmethod
|
5144
|
+
def build(cls) -> 'LoggingContextInfos.Thread':
|
5145
|
+
return cls(
|
5146
|
+
ident=threading.get_ident(),
|
5147
|
+
native_id=threading.get_native_id() if hasattr(threading, 'get_native_id') else None,
|
5148
|
+
name=threading.current_thread().name,
|
5149
|
+
)
|
5150
|
+
|
5151
|
+
@logging_context_info
|
5152
|
+
@ta.final
|
5153
|
+
class Process(ta.NamedTuple):
|
5154
|
+
pid: int
|
5155
|
+
|
5156
|
+
@classmethod
|
5157
|
+
def build(cls) -> 'LoggingContextInfos.Process':
|
5158
|
+
return cls(
|
5159
|
+
pid=os.getpid(),
|
5160
|
+
)
|
5161
|
+
|
5162
|
+
@logging_context_info
|
5163
|
+
@ta.final
|
5164
|
+
class Multiprocessing(ta.NamedTuple):
|
5165
|
+
process_name: str
|
5166
|
+
|
5167
|
+
@classmethod
|
5168
|
+
def build(cls) -> ta.Optional['LoggingContextInfos.Multiprocessing']:
|
5169
|
+
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L355-L364 # noqa
|
5170
|
+
if (mp := sys.modules.get('multiprocessing')) is None:
|
5171
|
+
return None
|
5172
|
+
|
5173
|
+
return cls(
|
5174
|
+
process_name=mp.current_process().name,
|
5175
|
+
)
|
5176
|
+
|
5177
|
+
@logging_context_info
|
5178
|
+
@ta.final
|
5179
|
+
class AsyncioTask(ta.NamedTuple):
|
5180
|
+
name: str
|
5181
|
+
|
5182
|
+
@classmethod
|
5183
|
+
def build(cls) -> ta.Optional['LoggingContextInfos.AsyncioTask']:
|
5184
|
+
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L372-L377 # noqa
|
5185
|
+
if (asyncio := sys.modules.get('asyncio')) is None:
|
5186
|
+
return None
|
5187
|
+
|
5188
|
+
try:
|
5189
|
+
task = asyncio.current_task()
|
5190
|
+
except Exception: # noqa
|
5191
|
+
return None
|
5192
|
+
|
5193
|
+
if task is None:
|
5194
|
+
return None
|
5195
|
+
|
5196
|
+
return cls(
|
5197
|
+
name=task.get_name(), # Always non-None
|
5198
|
+
)
|
5199
|
+
|
5200
|
+
|
5201
|
+
##
|
5202
|
+
|
5203
|
+
|
5204
|
+
class UnexpectedLoggingStartTimeWarning(LoggingSetupWarning):
|
5205
|
+
pass
|
5206
|
+
|
5207
|
+
|
5208
|
+
def _check_logging_start_time() -> None:
|
5209
|
+
if (x := LoggingContextInfos.Time.get_std_start_ns()) < (t := time.time()):
|
5210
|
+
import warnings # noqa
|
5211
|
+
|
5212
|
+
warnings.warn(
|
5213
|
+
f'Unexpected logging start time detected: '
|
5214
|
+
f'get_std_start_ns={x}, '
|
5215
|
+
f'time.time()={t}',
|
5216
|
+
UnexpectedLoggingStartTimeWarning,
|
5048
5217
|
)
|
5049
5218
|
|
5050
5219
|
|
5220
|
+
_check_logging_start_time()
|
5221
|
+
|
5222
|
+
|
5051
5223
|
########################################
|
5052
5224
|
# ../../../omlish/logs/protocols.py
|
5053
5225
|
|
@@ -5138,110 +5310,25 @@ class JsonLoggingFormatter(logging.Formatter):
|
|
5138
5310
|
|
5139
5311
|
|
5140
5312
|
########################################
|
5141
|
-
# ../../../omlish/
|
5313
|
+
# ../../../omlish/os/temp.py
|
5142
5314
|
|
5143
5315
|
|
5144
5316
|
##
|
5145
5317
|
|
5146
5318
|
|
5147
|
-
|
5148
|
-
|
5149
|
-
|
5150
|
-
|
5319
|
+
def make_temp_file(**kwargs: ta.Any) -> str:
|
5320
|
+
file_fd, file = tempfile.mkstemp(**kwargs)
|
5321
|
+
os.close(file_fd)
|
5322
|
+
return file
|
5151
5323
|
|
5152
|
-
created: float
|
5153
|
-
msecs: float
|
5154
|
-
relative_created: float
|
5155
5324
|
|
5156
|
-
|
5157
|
-
|
5158
|
-
|
5159
|
-
|
5160
|
-
|
5161
|
-
|
5162
|
-
|
5163
|
-
# See:
|
5164
|
-
# - https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
|
5165
|
-
#
|
5166
|
-
if isinstance(x, float):
|
5167
|
-
return int(x * 1e9)
|
5168
|
-
else:
|
5169
|
-
return x
|
5170
|
-
|
5171
|
-
@classmethod
|
5172
|
-
def build(
|
5173
|
-
cls,
|
5174
|
-
time_ns: int,
|
5175
|
-
*,
|
5176
|
-
start_time_ns: ta.Optional[int] = None,
|
5177
|
-
) -> 'LoggingTimeFields':
|
5178
|
-
# https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
|
5179
|
-
created = time_ns / 1e9 # ns to float seconds
|
5180
|
-
|
5181
|
-
# Get the number of whole milliseconds (0-999) in the fractional part of seconds.
|
5182
|
-
# Eg: 1_677_903_920_999_998_503 ns --> 999_998_503 ns--> 999 ms
|
5183
|
-
# Convert to float by adding 0.0 for historical reasons. See gh-89047
|
5184
|
-
msecs = (time_ns % 1_000_000_000) // 1_000_000 + 0.0
|
5185
|
-
|
5186
|
-
# https://github.com/python/cpython/commit/1500a23f33f5a6d052ff1ef6383d9839928b8ff1
|
5187
|
-
if msecs == 999.0 and int(created) != time_ns // 1_000_000_000:
|
5188
|
-
# ns -> sec conversion can round up, e.g:
|
5189
|
-
# 1_677_903_920_999_999_900 ns --> 1_677_903_921.0 sec
|
5190
|
-
msecs = 0.0
|
5191
|
-
|
5192
|
-
if start_time_ns is None:
|
5193
|
-
start_time_ns = cls.get_std_start_time_ns()
|
5194
|
-
relative_created = (time_ns - start_time_ns) / 1e6
|
5195
|
-
|
5196
|
-
return cls(
|
5197
|
-
created=created,
|
5198
|
-
msecs=msecs,
|
5199
|
-
relative_created=relative_created,
|
5200
|
-
)
|
5201
|
-
|
5202
|
-
|
5203
|
-
##
|
5204
|
-
|
5205
|
-
|
5206
|
-
class UnexpectedLoggingStartTimeWarning(LoggingSetupWarning):
|
5207
|
-
pass
|
5208
|
-
|
5209
|
-
|
5210
|
-
def _check_logging_start_time() -> None:
|
5211
|
-
if (x := LoggingTimeFields.get_std_start_time_ns()) < (t := time.time()):
|
5212
|
-
import warnings # noqa
|
5213
|
-
|
5214
|
-
warnings.warn(
|
5215
|
-
f'Unexpected logging start time detected: '
|
5216
|
-
f'get_std_start_time_ns={x}, '
|
5217
|
-
f'time.time()={t}',
|
5218
|
-
UnexpectedLoggingStartTimeWarning,
|
5219
|
-
)
|
5220
|
-
|
5221
|
-
|
5222
|
-
_check_logging_start_time()
|
5223
|
-
|
5224
|
-
|
5225
|
-
########################################
|
5226
|
-
# ../../../omlish/os/temp.py
|
5227
|
-
|
5228
|
-
|
5229
|
-
##
|
5230
|
-
|
5231
|
-
|
5232
|
-
def make_temp_file(**kwargs: ta.Any) -> str:
|
5233
|
-
file_fd, file = tempfile.mkstemp(**kwargs)
|
5234
|
-
os.close(file_fd)
|
5235
|
-
return file
|
5236
|
-
|
5237
|
-
|
5238
|
-
@contextlib.contextmanager
|
5239
|
-
def temp_file_context(**kwargs: ta.Any) -> ta.Iterator[str]:
|
5240
|
-
path = make_temp_file(**kwargs)
|
5241
|
-
try:
|
5242
|
-
yield path
|
5243
|
-
finally:
|
5244
|
-
unlink_if_exists(path)
|
5325
|
+
@contextlib.contextmanager
|
5326
|
+
def temp_file_context(**kwargs: ta.Any) -> ta.Iterator[str]:
|
5327
|
+
path = make_temp_file(**kwargs)
|
5328
|
+
try:
|
5329
|
+
yield path
|
5330
|
+
finally:
|
5331
|
+
unlink_if_exists(path)
|
5245
5332
|
|
5246
5333
|
|
5247
5334
|
@contextlib.contextmanager
|
@@ -7552,68 +7639,46 @@ inj = InjectionApi()
|
|
7552
7639
|
|
7553
7640
|
|
7554
7641
|
class LoggingContext(Abstract):
|
7555
|
-
@property
|
7556
7642
|
@abc.abstractmethod
|
7557
|
-
def
|
7643
|
+
def get_info(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
|
7558
7644
|
raise NotImplementedError
|
7559
7645
|
|
7560
|
-
|
7561
|
-
|
7562
|
-
|
7563
|
-
@abc.abstractmethod
|
7564
|
-
def time_ns(self) -> int:
|
7565
|
-
raise NotImplementedError
|
7566
|
-
|
7567
|
-
@property
|
7568
|
-
@abc.abstractmethod
|
7569
|
-
def times(self) -> LoggingTimeFields:
|
7570
|
-
raise NotImplementedError
|
7646
|
+
@ta.final
|
7647
|
+
def __getitem__(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
|
7648
|
+
return self.get_info(ty)
|
7571
7649
|
|
7572
|
-
|
7650
|
+
@ta.final
|
7651
|
+
def must_get_info(self, ty: ta.Type[LoggingContextInfoT]) -> LoggingContextInfoT:
|
7652
|
+
if (info := self.get_info(ty)) is None:
|
7653
|
+
raise TypeError(f'LoggingContextInfo absent: {ty}')
|
7654
|
+
return info
|
7573
7655
|
|
7574
|
-
@property
|
7575
|
-
@abc.abstractmethod
|
7576
|
-
def exc_info(self) -> ta.Optional[LoggingExcInfo]:
|
7577
|
-
raise NotImplementedError
|
7578
7656
|
|
7579
|
-
|
7580
|
-
|
7581
|
-
def
|
7582
|
-
|
7657
|
+
@ta.final
|
7658
|
+
class SimpleLoggingContext(LoggingContext):
|
7659
|
+
def __init__(self, *infos: LoggingContextInfo) -> None:
|
7660
|
+
self._infos: ta.Dict[ta.Type[LoggingContextInfo], LoggingContextInfo] = {type(i): i for i in infos}
|
7583
7661
|
|
7584
|
-
|
7662
|
+
def get_info(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
|
7663
|
+
return self._infos.get(ty)
|
7585
7664
|
|
7586
|
-
@abc.abstractmethod
|
7587
|
-
def caller(self) -> ta.Optional[LoggingCaller]:
|
7588
|
-
raise NotImplementedError
|
7589
|
-
|
7590
|
-
@abc.abstractmethod
|
7591
|
-
def source_file(self) -> ta.Optional[LoggingSourceFileInfo]:
|
7592
|
-
raise NotImplementedError
|
7593
7665
|
|
7594
|
-
|
7666
|
+
##
|
7595
7667
|
|
7596
|
-
@abc.abstractmethod
|
7597
|
-
def thread(self) -> ta.Optional[LoggingThreadInfo]:
|
7598
|
-
raise NotImplementedError
|
7599
|
-
|
7600
|
-
@abc.abstractmethod
|
7601
|
-
def process(self) -> ta.Optional[LoggingProcessInfo]:
|
7602
|
-
raise NotImplementedError
|
7603
7668
|
|
7669
|
+
class CaptureLoggingContext(LoggingContext, Abstract):
|
7604
7670
|
@abc.abstractmethod
|
7605
|
-
def
|
7606
|
-
|
7671
|
+
def set_basic(
|
7672
|
+
self,
|
7673
|
+
name: str,
|
7607
7674
|
|
7608
|
-
|
7609
|
-
|
7675
|
+
msg: ta.Union[str, tuple, LoggingMsgFn],
|
7676
|
+
args: tuple,
|
7677
|
+
) -> 'CaptureLoggingContext':
|
7610
7678
|
raise NotImplementedError
|
7611
7679
|
|
7680
|
+
#
|
7612
7681
|
|
7613
|
-
##
|
7614
|
-
|
7615
|
-
|
7616
|
-
class CaptureLoggingContext(LoggingContext, Abstract):
|
7617
7682
|
class AlreadyCapturedError(Exception):
|
7618
7683
|
pass
|
7619
7684
|
|
@@ -7644,80 +7709,50 @@ class CaptureLoggingContextImpl(CaptureLoggingContext):
|
|
7644
7709
|
|
7645
7710
|
exc_info: LoggingExcInfoArg = False,
|
7646
7711
|
|
7647
|
-
caller: ta.Union[
|
7712
|
+
caller: ta.Union[LoggingContextInfos.Caller, ta.Type[NOT_SET], None] = NOT_SET,
|
7648
7713
|
stack_offset: int = 0,
|
7649
7714
|
stack_info: bool = False,
|
7650
7715
|
) -> None:
|
7651
|
-
|
7652
|
-
|
7653
|
-
#
|
7716
|
+
# TODO: Name, Msg, Extra
|
7654
7717
|
|
7655
7718
|
if time_ns is None:
|
7656
7719
|
time_ns = time.time_ns()
|
7657
|
-
self._time_ns: int = time_ns
|
7658
7720
|
|
7659
|
-
|
7660
|
-
|
7661
|
-
|
7662
|
-
|
7663
|
-
|
7664
|
-
|
7665
|
-
else:
|
7666
|
-
exc_info = None
|
7667
|
-
elif exc_info is False:
|
7668
|
-
exc_info = None
|
7669
|
-
|
7670
|
-
if exc_info is not None:
|
7671
|
-
self._exc_info: ta.Optional[LoggingExcInfo] = exc_info
|
7672
|
-
if isinstance(exc_info, BaseException):
|
7673
|
-
self._exc_info_tuple: ta.Optional[LoggingExcInfoTuple] = (type(exc_info), exc_info, exc_info.__traceback__) # noqa
|
7674
|
-
else:
|
7675
|
-
self._exc_info_tuple = exc_info
|
7676
|
-
|
7677
|
-
#
|
7721
|
+
self._infos: ta.Dict[ta.Type[LoggingContextInfo], LoggingContextInfo] = {}
|
7722
|
+
self._set_info(
|
7723
|
+
LoggingContextInfos.Level.build(level),
|
7724
|
+
LoggingContextInfos.Time.build(time_ns),
|
7725
|
+
LoggingContextInfos.Exc.build(exc_info),
|
7726
|
+
)
|
7678
7727
|
|
7679
7728
|
if caller is not CaptureLoggingContextImpl.NOT_SET:
|
7680
|
-
self.
|
7729
|
+
self._infos[LoggingContextInfos.Caller] = caller
|
7681
7730
|
else:
|
7682
7731
|
self._stack_offset = stack_offset
|
7683
7732
|
self._stack_info = stack_info
|
7684
7733
|
|
7685
|
-
|
7686
|
-
|
7687
|
-
|
7688
|
-
|
7689
|
-
return self
|
7690
|
-
|
7691
|
-
#
|
7692
|
-
|
7693
|
-
@property
|
7694
|
-
def time_ns(self) -> int:
|
7695
|
-
return self._time_ns
|
7696
|
-
|
7697
|
-
_times: LoggingTimeFields
|
7698
|
-
|
7699
|
-
@property
|
7700
|
-
def times(self) -> LoggingTimeFields:
|
7701
|
-
try:
|
7702
|
-
return self._times
|
7703
|
-
except AttributeError:
|
7704
|
-
pass
|
7705
|
-
|
7706
|
-
times = self._times = LoggingTimeFields.build(self.time_ns)
|
7707
|
-
return times
|
7734
|
+
def _set_info(self, *infos: ta.Optional[LoggingContextInfo]) -> 'CaptureLoggingContextImpl':
|
7735
|
+
for info in infos:
|
7736
|
+
if info is not None:
|
7737
|
+
self._infos[type(info)] = info
|
7738
|
+
return self
|
7708
7739
|
|
7709
|
-
|
7740
|
+
def get_info(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
|
7741
|
+
return self._infos.get(ty)
|
7710
7742
|
|
7711
|
-
|
7712
|
-
_exc_info_tuple: ta.Optional[LoggingExcInfoTuple] = None
|
7743
|
+
##
|
7713
7744
|
|
7714
|
-
|
7715
|
-
|
7716
|
-
|
7745
|
+
def set_basic(
|
7746
|
+
self,
|
7747
|
+
name: str,
|
7717
7748
|
|
7718
|
-
|
7719
|
-
|
7720
|
-
|
7749
|
+
msg: ta.Union[str, tuple, LoggingMsgFn],
|
7750
|
+
args: tuple,
|
7751
|
+
) -> 'CaptureLoggingContextImpl':
|
7752
|
+
return self._set_info(
|
7753
|
+
LoggingContextInfos.Name(name),
|
7754
|
+
LoggingContextInfos.Msg.build(msg, *args),
|
7755
|
+
)
|
7721
7756
|
|
7722
7757
|
##
|
7723
7758
|
|
@@ -7731,74 +7766,28 @@ class CaptureLoggingContextImpl(CaptureLoggingContext):
|
|
7731
7766
|
|
7732
7767
|
_has_captured: bool = False
|
7733
7768
|
|
7734
|
-
_caller: ta.Optional[LoggingCaller]
|
7735
|
-
_source_file: ta.Optional[LoggingSourceFileInfo]
|
7736
|
-
|
7737
|
-
_thread: ta.Optional[LoggingThreadInfo]
|
7738
|
-
_process: ta.Optional[LoggingProcessInfo]
|
7739
|
-
_multiprocessing: ta.Optional[LoggingMultiprocessingInfo]
|
7740
|
-
_asyncio_task: ta.Optional[LoggingAsyncioTaskInfo]
|
7741
|
-
|
7742
7769
|
def capture(self) -> None:
|
7743
7770
|
if self._has_captured:
|
7744
7771
|
raise CaptureLoggingContextImpl.AlreadyCapturedError
|
7745
7772
|
self._has_captured = True
|
7746
7773
|
|
7747
|
-
if not
|
7748
|
-
self.
|
7774
|
+
if LoggingContextInfos.Caller not in self._infos:
|
7775
|
+
self._set_info(LoggingContextInfos.Caller.build(
|
7749
7776
|
self._stack_offset + 1,
|
7750
7777
|
stack_info=self._stack_info,
|
7751
|
-
)
|
7752
|
-
|
7753
|
-
if (caller := self._caller) is not None:
|
7754
|
-
self._source_file = LoggingSourceFileInfo.build(caller.file_path)
|
7755
|
-
else:
|
7756
|
-
self._source_file = None
|
7757
|
-
|
7758
|
-
self._thread = LoggingThreadInfo.build()
|
7759
|
-
self._process = LoggingProcessInfo.build()
|
7760
|
-
self._multiprocessing = LoggingMultiprocessingInfo.build()
|
7761
|
-
self._asyncio_task = LoggingAsyncioTaskInfo.build()
|
7762
|
-
|
7763
|
-
#
|
7764
|
-
|
7765
|
-
def caller(self) -> ta.Optional[LoggingCaller]:
|
7766
|
-
try:
|
7767
|
-
return self._caller
|
7768
|
-
except AttributeError:
|
7769
|
-
raise CaptureLoggingContext.NotCapturedError from None
|
7770
|
-
|
7771
|
-
def source_file(self) -> ta.Optional[LoggingSourceFileInfo]:
|
7772
|
-
try:
|
7773
|
-
return self._source_file
|
7774
|
-
except AttributeError:
|
7775
|
-
raise CaptureLoggingContext.NotCapturedError from None
|
7776
|
-
|
7777
|
-
#
|
7778
|
-
|
7779
|
-
def thread(self) -> ta.Optional[LoggingThreadInfo]:
|
7780
|
-
try:
|
7781
|
-
return self._thread
|
7782
|
-
except AttributeError:
|
7783
|
-
raise CaptureLoggingContext.NotCapturedError from None
|
7784
|
-
|
7785
|
-
def process(self) -> ta.Optional[LoggingProcessInfo]:
|
7786
|
-
try:
|
7787
|
-
return self._process
|
7788
|
-
except AttributeError:
|
7789
|
-
raise CaptureLoggingContext.NotCapturedError from None
|
7778
|
+
))
|
7790
7779
|
|
7791
|
-
|
7792
|
-
|
7793
|
-
|
7794
|
-
|
7795
|
-
raise CaptureLoggingContext.NotCapturedError from None
|
7780
|
+
if (caller := self[LoggingContextInfos.Caller]) is not None:
|
7781
|
+
self._set_info(LoggingContextInfos.SourceFile.build(
|
7782
|
+
caller.file_path,
|
7783
|
+
))
|
7796
7784
|
|
7797
|
-
|
7798
|
-
|
7799
|
-
|
7800
|
-
|
7801
|
-
|
7785
|
+
self._set_info(
|
7786
|
+
LoggingContextInfos.Thread.build(),
|
7787
|
+
LoggingContextInfos.Process.build(),
|
7788
|
+
LoggingContextInfos.Multiprocessing.build(),
|
7789
|
+
LoggingContextInfos.AsyncioTask.build(),
|
7790
|
+
)
|
7802
7791
|
|
7803
7792
|
|
7804
7793
|
########################################
|
@@ -7875,10 +7864,14 @@ def _locking_logging_module_lock() -> ta.Iterator[None]:
|
|
7875
7864
|
def configure_standard_logging(
|
7876
7865
|
level: ta.Union[int, str] = logging.INFO,
|
7877
7866
|
*,
|
7878
|
-
json: bool = False,
|
7879
7867
|
target: ta.Optional[logging.Logger] = None,
|
7868
|
+
|
7880
7869
|
force: bool = False,
|
7870
|
+
|
7881
7871
|
handler_factory: ta.Optional[ta.Callable[[], logging.Handler]] = None,
|
7872
|
+
|
7873
|
+
formatter: ta.Optional[logging.Formatter] = None, # noqa
|
7874
|
+
json: bool = False,
|
7882
7875
|
) -> ta.Optional[StandardConfiguredLoggingHandler]:
|
7883
7876
|
with _locking_logging_module_lock():
|
7884
7877
|
if target is None:
|
@@ -7899,11 +7892,11 @@ def configure_standard_logging(
|
|
7899
7892
|
|
7900
7893
|
#
|
7901
7894
|
|
7902
|
-
formatter:
|
7903
|
-
|
7904
|
-
|
7905
|
-
|
7906
|
-
|
7895
|
+
if formatter is None:
|
7896
|
+
if json:
|
7897
|
+
formatter = JsonLoggingFormatter()
|
7898
|
+
else:
|
7899
|
+
formatter = StandardLoggingFormatter(StandardLoggingFormatter.build_log_format(STANDARD_LOG_FORMAT_PARTS)) # noqa
|
7907
7900
|
handler.setFormatter(formatter)
|
7908
7901
|
|
7909
7902
|
#
|
@@ -9615,36 +9608,6 @@ class AnyLogger(Abstract, ta.Generic[T]):
|
|
9615
9608
|
|
9616
9609
|
##
|
9617
9610
|
|
9618
|
-
@classmethod
|
9619
|
-
def _prepare_msg_args(cls, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any) -> ta.Tuple[str, tuple]:
|
9620
|
-
if callable(msg):
|
9621
|
-
if args:
|
9622
|
-
raise TypeError(f'Must not provide both a message function and args: {msg=} {args=}')
|
9623
|
-
x = msg()
|
9624
|
-
if isinstance(x, str):
|
9625
|
-
return x, ()
|
9626
|
-
elif isinstance(x, tuple):
|
9627
|
-
if x:
|
9628
|
-
return x[0], x[1:]
|
9629
|
-
else:
|
9630
|
-
return '', ()
|
9631
|
-
else:
|
9632
|
-
raise TypeError(x)
|
9633
|
-
|
9634
|
-
elif isinstance(msg, tuple):
|
9635
|
-
if args:
|
9636
|
-
raise TypeError(f'Must not provide both a tuple message and args: {msg=} {args=}')
|
9637
|
-
if msg:
|
9638
|
-
return msg[0], msg[1:]
|
9639
|
-
else:
|
9640
|
-
return '', ()
|
9641
|
-
|
9642
|
-
elif isinstance(msg, str):
|
9643
|
-
return msg, args
|
9644
|
-
|
9645
|
-
else:
|
9646
|
-
raise TypeError(msg)
|
9647
|
-
|
9648
9611
|
@abc.abstractmethod
|
9649
9612
|
def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any, **kwargs: ta.Any) -> T: # noqa
|
9650
9613
|
raise NotImplementedError
|
@@ -9685,144 +9648,560 @@ class AsyncNopLogger(AnyNopLogger[ta.Awaitable[None]], AsyncLogger):
|
|
9685
9648
|
|
9686
9649
|
########################################
|
9687
9650
|
# ../../../omlish/logs/std/records.py
|
9651
|
+
"""
|
9652
|
+
TODO:
|
9653
|
+
- TypedDict?
|
9654
|
+
"""
|
9688
9655
|
|
9689
9656
|
|
9690
9657
|
##
|
9691
9658
|
|
9692
9659
|
|
9693
|
-
|
9694
|
-
#
|
9695
|
-
#
|
9696
|
-
#
|
9697
|
-
#
|
9698
|
-
# - https://github.com/python/cpython/blob/
|
9699
|
-
#
|
9700
|
-
#
|
9701
|
-
# - name: str
|
9702
|
-
# - level: int
|
9703
|
-
# - pathname: str - Confusingly referred to as `fn` before the LogRecord ctor. May be empty or "(unknown file)".
|
9704
|
-
# - lineno: int - May be 0.
|
9705
|
-
# - msg: str
|
9706
|
-
# - args: tuple | dict | 1-tuple[dict]
|
9707
|
-
# - exc_info: LoggingExcInfoTuple | None
|
9708
|
-
# - func: str | None = None -> funcName
|
9709
|
-
# - sinfo: str | None = None -> stack_info
|
9710
|
-
#
|
9711
|
-
KNOWN_STD_LOGGING_RECORD_ATTRS: ta.Dict[str, ta.Any] = dict(
|
9712
|
-
# Name of the logger used to log the call. Unmodified by ctor.
|
9713
|
-
name=str,
|
9660
|
+
class LoggingContextInfoRecordAdapters:
|
9661
|
+
# Ref:
|
9662
|
+
# - https://docs.python.org/3/library/logging.html#logrecord-attributes
|
9663
|
+
#
|
9664
|
+
# LogRecord:
|
9665
|
+
# - https://github.com/python/cpython/blob/39b2f82717a69dde7212bc39b673b0f55c99e6a3/Lib/logging/__init__.py#L276 (3.8) # noqa
|
9666
|
+
# - https://github.com/python/cpython/blob/f070f54c5f4a42c7c61d1d5d3b8f3b7203b4a0fb/Lib/logging/__init__.py#L286 (~3.14) # noqa
|
9667
|
+
#
|
9714
9668
|
|
9715
|
-
|
9716
|
-
|
9717
|
-
msg=str,
|
9669
|
+
def __new__(cls, *args, **kwargs): # noqa
|
9670
|
+
raise TypeError
|
9718
9671
|
|
9719
|
-
|
9720
|
-
|
9721
|
-
|
9722
|
-
|
9672
|
+
class Adapter(Abstract, ta.Generic[T]):
|
9673
|
+
@property
|
9674
|
+
@abc.abstractmethod
|
9675
|
+
def info_cls(self) -> ta.Type[LoggingContextInfo]:
|
9676
|
+
raise NotImplementedError
|
9723
9677
|
|
9724
|
-
|
9678
|
+
#
|
9725
9679
|
|
9726
|
-
|
9727
|
-
|
9728
|
-
|
9680
|
+
@ta.final
|
9681
|
+
class NOT_SET: # noqa
|
9682
|
+
def __new__(cls, *args, **kwargs): # noqa
|
9683
|
+
raise TypeError
|
9729
9684
|
|
9730
|
-
|
9731
|
-
|
9685
|
+
class RecordAttr(ta.NamedTuple):
|
9686
|
+
name: str
|
9687
|
+
type: ta.Any
|
9688
|
+
default: ta.Any
|
9732
9689
|
|
9733
|
-
|
9690
|
+
# @abc.abstractmethod
|
9691
|
+
record_attrs: ta.ClassVar[ta.Mapping[str, RecordAttr]]
|
9734
9692
|
|
9735
|
-
|
9736
|
-
|
9737
|
-
|
9693
|
+
@property
|
9694
|
+
@abc.abstractmethod
|
9695
|
+
def _record_attrs(self) -> ta.Union[
|
9696
|
+
ta.Mapping[str, ta.Any],
|
9697
|
+
ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]],
|
9698
|
+
]:
|
9699
|
+
raise NotImplementedError
|
9738
9700
|
|
9739
|
-
|
9740
|
-
filename=str,
|
9701
|
+
#
|
9741
9702
|
|
9742
|
-
|
9743
|
-
|
9744
|
-
|
9703
|
+
@abc.abstractmethod
|
9704
|
+
def context_to_record(self, ctx: LoggingContext) -> ta.Mapping[str, ta.Any]:
|
9705
|
+
raise NotImplementedError
|
9745
9706
|
|
9746
|
-
|
9707
|
+
#
|
9747
9708
|
|
9748
|
-
|
9749
|
-
|
9709
|
+
@abc.abstractmethod
|
9710
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[T]:
|
9711
|
+
raise NotImplementedError
|
9750
9712
|
|
9751
|
-
|
9752
|
-
exc_text=ta.Optional[str],
|
9713
|
+
#
|
9753
9714
|
|
9754
|
-
|
9715
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
9716
|
+
super().__init_subclass__(**kwargs)
|
9755
9717
|
|
9756
|
-
|
9757
|
-
|
9758
|
-
# unmodified. Mostly set, if requested, by `Logger.findCaller`, to `traceback.print_stack(f)`, but prepended with
|
9759
|
-
# the literal "Stack (most recent call last):\n", and stripped of exactly one trailing `\n` if present.
|
9760
|
-
stack_info=ta.Optional[str],
|
9718
|
+
if Abstract in cls.__bases__:
|
9719
|
+
return
|
9761
9720
|
|
9762
|
-
|
9763
|
-
|
9764
|
-
|
9721
|
+
if 'record_attrs' in cls.__dict__:
|
9722
|
+
raise TypeError(cls)
|
9723
|
+
if not isinstance(ra := cls.__dict__['_record_attrs'], collections.abc.Mapping):
|
9724
|
+
raise TypeError(ra)
|
9725
|
+
|
9726
|
+
rd: ta.Dict[str, LoggingContextInfoRecordAdapters.Adapter.RecordAttr] = {}
|
9727
|
+
for n, v in ra.items():
|
9728
|
+
if not n or not isinstance(n, str) or n in rd:
|
9729
|
+
raise AttributeError(n)
|
9730
|
+
if isinstance(v, tuple):
|
9731
|
+
t, d = v
|
9732
|
+
else:
|
9733
|
+
t, d = v, cls.NOT_SET
|
9734
|
+
rd[n] = cls.RecordAttr(
|
9735
|
+
name=n,
|
9736
|
+
type=t,
|
9737
|
+
default=d,
|
9738
|
+
)
|
9739
|
+
cls.record_attrs = rd
|
9765
9740
|
|
9766
|
-
|
9767
|
-
|
9768
|
-
|
9741
|
+
class RequiredAdapter(Adapter[T], Abstract):
|
9742
|
+
@property
|
9743
|
+
@abc.abstractmethod
|
9744
|
+
def _record_attrs(self) -> ta.Mapping[str, ta.Any]:
|
9745
|
+
raise NotImplementedError
|
9769
9746
|
|
9770
|
-
|
9747
|
+
#
|
9771
9748
|
|
9772
|
-
|
9773
|
-
|
9774
|
-
|
9775
|
-
|
9776
|
-
|
9777
|
-
|
9778
|
-
created=float,
|
9749
|
+
@ta.final
|
9750
|
+
def context_to_record(self, ctx: LoggingContext) -> ta.Mapping[str, ta.Any]:
|
9751
|
+
if (info := ctx.get_info(self.info_cls)) is not None:
|
9752
|
+
return self._info_to_record(info)
|
9753
|
+
else:
|
9754
|
+
raise TypeError # FIXME: fallback?
|
9779
9755
|
|
9780
|
-
|
9781
|
-
|
9756
|
+
@abc.abstractmethod
|
9757
|
+
def _info_to_record(self, info: T) -> ta.Mapping[str, ta.Any]:
|
9758
|
+
raise NotImplementedError
|
9782
9759
|
|
9783
|
-
|
9784
|
-
relativeCreated=float,
|
9760
|
+
#
|
9785
9761
|
|
9786
|
-
|
9762
|
+
@abc.abstractmethod
|
9763
|
+
def record_to_info(self, rec: logging.LogRecord) -> T:
|
9764
|
+
raise NotImplementedError
|
9787
9765
|
|
9788
|
-
|
9789
|
-
thread=ta.Optional[int],
|
9766
|
+
#
|
9790
9767
|
|
9791
|
-
|
9792
|
-
|
9768
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
9769
|
+
super().__init_subclass__(**kwargs)
|
9793
9770
|
|
9794
|
-
|
9771
|
+
if any(a.default is not cls.NOT_SET for a in cls.record_attrs.values()):
|
9772
|
+
raise TypeError(cls.record_attrs)
|
9773
|
+
|
9774
|
+
class OptionalAdapter(Adapter[T], Abstract, ta.Generic[T]):
|
9775
|
+
@property
|
9776
|
+
@abc.abstractmethod
|
9777
|
+
def _record_attrs(self) -> ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]:
|
9778
|
+
raise NotImplementedError
|
9779
|
+
|
9780
|
+
record_defaults: ta.ClassVar[ta.Mapping[str, ta.Any]]
|
9781
|
+
|
9782
|
+
#
|
9783
|
+
|
9784
|
+
@ta.final
|
9785
|
+
def context_to_record(self, ctx: LoggingContext) -> ta.Mapping[str, ta.Any]:
|
9786
|
+
if (info := ctx.get_info(self.info_cls)) is not None:
|
9787
|
+
return self._info_to_record(info)
|
9788
|
+
else:
|
9789
|
+
return self.record_defaults
|
9790
|
+
|
9791
|
+
@abc.abstractmethod
|
9792
|
+
def _info_to_record(self, info: T) -> ta.Mapping[str, ta.Any]:
|
9793
|
+
raise NotImplementedError
|
9794
|
+
|
9795
|
+
#
|
9796
|
+
|
9797
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
9798
|
+
super().__init_subclass__(**kwargs)
|
9799
|
+
|
9800
|
+
dd: ta.Dict[str, ta.Any] = {a.name: a.default for a in cls.record_attrs.values()}
|
9801
|
+
if any(d is cls.NOT_SET for d in dd.values()):
|
9802
|
+
raise TypeError(cls.record_attrs)
|
9803
|
+
cls.record_defaults = dd
|
9795
9804
|
|
9796
|
-
# Process name if available. Set to None if `logging.logMultiprocessing` is not truthy. Otherwise, set to
|
9797
|
-
# 'MainProcess', then `sys.modules.get('multiprocessing').current_process().name` if that works, otherwise remains
|
9798
|
-
# as 'MainProcess'.
|
9799
9805
|
#
|
9800
|
-
|
9806
|
+
|
9807
|
+
class Name(RequiredAdapter[LoggingContextInfos.Name]):
|
9808
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Name]] = LoggingContextInfos.Name
|
9809
|
+
|
9810
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
|
9811
|
+
# Name of the logger used to log the call. Unmodified by ctor.
|
9812
|
+
name=str,
|
9813
|
+
)
|
9814
|
+
|
9815
|
+
def _info_to_record(self, info: LoggingContextInfos.Name) -> ta.Mapping[str, ta.Any]:
|
9816
|
+
return dict(
|
9817
|
+
name=info.name,
|
9818
|
+
)
|
9819
|
+
|
9820
|
+
def record_to_info(self, rec: logging.LogRecord) -> LoggingContextInfos.Name:
|
9821
|
+
return LoggingContextInfos.Name(
|
9822
|
+
name=rec.name,
|
9823
|
+
)
|
9824
|
+
|
9825
|
+
class Level(RequiredAdapter[LoggingContextInfos.Level]):
|
9826
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Level]] = LoggingContextInfos.Level
|
9827
|
+
|
9828
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
|
9829
|
+
# Text logging level for the message ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'). Set to
|
9830
|
+
# `getLevelName(level)`.
|
9831
|
+
levelname=str,
|
9832
|
+
|
9833
|
+
# Numeric logging level for the message (DEBUG, INFO, WARNING, ERROR, CRITICAL). Unmodified by ctor.
|
9834
|
+
levelno=int,
|
9835
|
+
)
|
9836
|
+
|
9837
|
+
def _info_to_record(self, info: LoggingContextInfos.Level) -> ta.Mapping[str, ta.Any]:
|
9838
|
+
return dict(
|
9839
|
+
levelname=info.name,
|
9840
|
+
levelno=int(info.level),
|
9841
|
+
)
|
9842
|
+
|
9843
|
+
def record_to_info(self, rec: logging.LogRecord) -> LoggingContextInfos.Level:
|
9844
|
+
return LoggingContextInfos.Level.build(rec.levelno)
|
9845
|
+
|
9846
|
+
class Msg(RequiredAdapter[LoggingContextInfos.Msg]):
|
9847
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Msg]] = LoggingContextInfos.Msg
|
9848
|
+
|
9849
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
|
9850
|
+
# The format string passed in the original logging call. Merged with args to produce message, or an
|
9851
|
+
# arbitrary object (see Using arbitrary objects as messages). Unmodified by ctor.
|
9852
|
+
msg=str,
|
9853
|
+
|
9854
|
+
# The tuple of arguments merged into msg to produce message, or a dict whose values are used for the merge
|
9855
|
+
# (when there is only one argument, and it is a dictionary). Ctor will transform a 1-tuple containing a
|
9856
|
+
# Mapping into just the mapping, but is otherwise unmodified.
|
9857
|
+
args=ta.Union[tuple, dict, None],
|
9858
|
+
)
|
9859
|
+
|
9860
|
+
def _info_to_record(self, info: LoggingContextInfos.Msg) -> ta.Mapping[str, ta.Any]:
|
9861
|
+
return dict(
|
9862
|
+
msg=info.msg,
|
9863
|
+
args=info.args,
|
9864
|
+
)
|
9865
|
+
|
9866
|
+
def record_to_info(self, rec: logging.LogRecord) -> LoggingContextInfos.Msg:
|
9867
|
+
return LoggingContextInfos.Msg(
|
9868
|
+
msg=rec.msg,
|
9869
|
+
args=rec.args,
|
9870
|
+
)
|
9871
|
+
|
9872
|
+
# FIXME: handled specially - all unknown attrs on LogRecord
|
9873
|
+
# class Extra(Adapter[LoggingContextInfos.Extra]):
|
9874
|
+
# _record_attrs: ta.ClassVar[ta.Mapping[str, ta.Union[ta.Any, ta.Tuple[ta.Any, ta.Any]]]] = dict()
|
9801
9875
|
#
|
9802
|
-
#
|
9803
|
-
#
|
9876
|
+
# def info_to_record(self, info: ta.Optional[LoggingContextInfos.Extra]) -> ta.Mapping[str, ta.Any]:
|
9877
|
+
# # FIXME:
|
9878
|
+
# # if extra is not None:
|
9879
|
+
# # for key in extra:
|
9880
|
+
# # if (key in ["message", "asctime"]) or (key in rv.__dict__):
|
9881
|
+
# # raise KeyError("Attempt to overwrite %r in LogRecord" % key)
|
9882
|
+
# # rv.__dict__[key] = extra[key]
|
9883
|
+
# return dict()
|
9804
9884
|
#
|
9805
|
-
|
9885
|
+
# def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Extra]:
|
9886
|
+
# return None
|
9806
9887
|
|
9807
|
-
|
9808
|
-
|
9809
|
-
process=ta.Optional[int],
|
9888
|
+
class Time(RequiredAdapter[LoggingContextInfos.Time]):
|
9889
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Time]] = LoggingContextInfos.Time
|
9810
9890
|
|
9811
|
-
|
9891
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
|
9892
|
+
# Time when the LogRecord was created. Set to `time.time_ns() / 1e9` for >=3.13.0b1, otherwise simply
|
9893
|
+
# `time.time()`.
|
9894
|
+
#
|
9895
|
+
# See:
|
9896
|
+
# - https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
|
9897
|
+
# - https://github.com/python/cpython/commit/1500a23f33f5a6d052ff1ef6383d9839928b8ff1
|
9898
|
+
#
|
9899
|
+
created=float,
|
9812
9900
|
|
9813
|
-
|
9814
|
-
|
9815
|
-
|
9816
|
-
|
9901
|
+
# Millisecond portion of the time when the LogRecord was created.
|
9902
|
+
msecs=float,
|
9903
|
+
|
9904
|
+
# Time in milliseconds when the LogRecord was created, relative to the time the logging module was loaded.
|
9905
|
+
relativeCreated=float,
|
9906
|
+
)
|
9907
|
+
|
9908
|
+
def _info_to_record(self, info: LoggingContextInfos.Time) -> ta.Mapping[str, ta.Any]:
|
9909
|
+
return dict(
|
9910
|
+
created=info.secs,
|
9911
|
+
msecs=info.msecs,
|
9912
|
+
relativeCreated=info.relative_secs,
|
9913
|
+
)
|
9914
|
+
|
9915
|
+
def record_to_info(self, rec: logging.LogRecord) -> LoggingContextInfos.Time:
|
9916
|
+
return LoggingContextInfos.Time.build(
|
9917
|
+
int(rec.created * 1e9),
|
9918
|
+
)
|
9919
|
+
|
9920
|
+
class Exc(OptionalAdapter[LoggingContextInfos.Exc]):
|
9921
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Exc]] = LoggingContextInfos.Exc
|
9922
|
+
|
9923
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
|
9924
|
+
# Exception tuple (à la sys.exc_info) or, if no exception has occurred, None. Unmodified by ctor.
|
9925
|
+
exc_info=(ta.Optional[LoggingExcInfoTuple], None),
|
9926
|
+
|
9927
|
+
# Used to cache the traceback text. Simply set to None by ctor, later set by Formatter.format.
|
9928
|
+
exc_text=(ta.Optional[str], None),
|
9929
|
+
)
|
9930
|
+
|
9931
|
+
def _info_to_record(self, info: LoggingContextInfos.Exc) -> ta.Mapping[str, ta.Any]:
|
9932
|
+
return dict(
|
9933
|
+
exc_info=info.info_tuple,
|
9934
|
+
exc_text=None,
|
9935
|
+
)
|
9936
|
+
|
9937
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Exc]:
|
9938
|
+
# FIXME:
|
9939
|
+
# error: Argument 1 to "build" of "Exc" has incompatible type
|
9940
|
+
# "tuple[type[BaseException], BaseException, TracebackType | None] | tuple[None, None, None] | None"; expected # noqa
|
9941
|
+
# "BaseException | tuple[type[BaseException], BaseException, TracebackType | None] | bool | None" [arg-type] # noqa
|
9942
|
+
return LoggingContextInfos.Exc.build(rec.exc_info) # type: ignore[arg-type]
|
9943
|
+
|
9944
|
+
class Caller(OptionalAdapter[LoggingContextInfos.Caller]):
|
9945
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Caller]] = LoggingContextInfos.Caller
|
9946
|
+
|
9947
|
+
_UNKNOWN_PATH_NAME: ta.ClassVar[str] = '(unknown file)'
|
9948
|
+
_UNKNOWN_FUNC_NAME: ta.ClassVar[str] = '(unknown function)'
|
9949
|
+
|
9950
|
+
_STACK_INFO_PREFIX: ta.ClassVar[str] = 'Stack (most recent call last):\n'
|
9951
|
+
|
9952
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
|
9953
|
+
# Full pathname of the source file where the logging call was issued (if available). Unmodified by ctor. May
|
9954
|
+
# default to "(unknown file)" by Logger.findCaller / Logger._log.
|
9955
|
+
pathname=(str, _UNKNOWN_PATH_NAME),
|
9956
|
+
|
9957
|
+
# Source line number where the logging call was issued (if available). Unmodified by ctor. May default to 0
|
9958
|
+
# y Logger.findCaller / Logger._log.
|
9959
|
+
lineno=(int, 0),
|
9960
|
+
|
9961
|
+
# Name of function containing the logging call. Set by ctor to `func` arg, unmodified. May default to
|
9962
|
+
# "(unknown function)" by Logger.findCaller / Logger._log.
|
9963
|
+
funcName=(str, _UNKNOWN_FUNC_NAME),
|
9964
|
+
|
9965
|
+
# Stack frame information (where available) from the bottom of the stack in the current thread, up to and
|
9966
|
+
# including the stack frame of the logging call which resulted in the creation of this record. Set by ctor
|
9967
|
+
# to `sinfo` arg, unmodified. Mostly set, if requested, by `Logger.findCaller`, to
|
9968
|
+
# `traceback.print_stack(f)`, but prepended with the literal "Stack (most recent call last):\n", and
|
9969
|
+
# stripped of exactly one trailing `\n` if present.
|
9970
|
+
stack_info=(ta.Optional[str], None),
|
9971
|
+
)
|
9972
|
+
|
9973
|
+
def _info_to_record(self, caller: LoggingContextInfos.Caller) -> ta.Mapping[str, ta.Any]:
|
9974
|
+
if (sinfo := caller.stack_info) is not None:
|
9975
|
+
stack_info: ta.Optional[str] = '\n'.join([
|
9976
|
+
self._STACK_INFO_PREFIX,
|
9977
|
+
sinfo[1:] if sinfo.endswith('\n') else sinfo,
|
9978
|
+
])
|
9979
|
+
else:
|
9980
|
+
stack_info = None
|
9981
|
+
|
9982
|
+
return dict(
|
9983
|
+
pathname=caller.file_path,
|
9984
|
+
|
9985
|
+
lineno=caller.line_no,
|
9986
|
+
funcName=caller.func_name,
|
9987
|
+
|
9988
|
+
stack_info=stack_info,
|
9989
|
+
)
|
9990
|
+
|
9991
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Caller]:
|
9992
|
+
# FIXME: piecemeal?
|
9993
|
+
if (
|
9994
|
+
rec.pathname != self._UNKNOWN_PATH_NAME and
|
9995
|
+
rec.lineno != 0 and
|
9996
|
+
rec.funcName != self._UNKNOWN_FUNC_NAME
|
9997
|
+
):
|
9998
|
+
if (sinfo := rec.stack_info) is not None and sinfo.startswith(self._STACK_INFO_PREFIX):
|
9999
|
+
sinfo = sinfo[len(self._STACK_INFO_PREFIX):]
|
10000
|
+
return LoggingContextInfos.Caller(
|
10001
|
+
file_path=rec.pathname,
|
10002
|
+
|
10003
|
+
line_no=rec.lineno,
|
10004
|
+
func_name=rec.funcName,
|
10005
|
+
|
10006
|
+
stack_info=sinfo,
|
10007
|
+
)
|
10008
|
+
|
10009
|
+
return None
|
10010
|
+
|
10011
|
+
class SourceFile(Adapter[LoggingContextInfos.SourceFile]):
|
10012
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.SourceFile]] = LoggingContextInfos.SourceFile
|
10013
|
+
|
10014
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
|
10015
|
+
# Filename portion of pathname. Set to `os.path.basename(pathname)` if successful, otherwise defaults to
|
10016
|
+
# pathname.
|
10017
|
+
filename=str,
|
10018
|
+
|
10019
|
+
# Module (name portion of filename). Set to `os.path.splitext(filename)[0]`, otherwise defaults to
|
10020
|
+
# "Unknown module".
|
10021
|
+
module=str,
|
10022
|
+
)
|
10023
|
+
|
10024
|
+
_UNKNOWN_MODULE: ta.ClassVar[str] = 'Unknown module'
|
10025
|
+
|
10026
|
+
def context_to_record(self, ctx: LoggingContext) -> ta.Mapping[str, ta.Any]:
|
10027
|
+
if (info := ctx.get_info(LoggingContextInfos.SourceFile)) is not None:
|
10028
|
+
return dict(
|
10029
|
+
filename=info.file_name,
|
10030
|
+
module=info.module,
|
10031
|
+
)
|
10032
|
+
|
10033
|
+
if (caller := ctx.get_info(LoggingContextInfos.Caller)) is not None:
|
10034
|
+
return dict(
|
10035
|
+
filename=caller.file_path,
|
10036
|
+
module=self._UNKNOWN_MODULE,
|
10037
|
+
)
|
10038
|
+
|
10039
|
+
return dict(
|
10040
|
+
filename=LoggingContextInfoRecordAdapters.Caller._UNKNOWN_PATH_NAME, # noqa
|
10041
|
+
module=self._UNKNOWN_MODULE,
|
10042
|
+
)
|
10043
|
+
|
10044
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.SourceFile]:
|
10045
|
+
if (
|
10046
|
+
rec.module is not None and
|
10047
|
+
rec.module != self._UNKNOWN_MODULE
|
10048
|
+
):
|
10049
|
+
return LoggingContextInfos.SourceFile(
|
10050
|
+
file_name=rec.filename,
|
10051
|
+
module=rec.module, # FIXME: piecemeal?
|
10052
|
+
)
|
10053
|
+
|
10054
|
+
return None
|
10055
|
+
|
10056
|
+
class Thread(OptionalAdapter[LoggingContextInfos.Thread]):
|
10057
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Thread]] = LoggingContextInfos.Thread
|
10058
|
+
|
10059
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
|
10060
|
+
# Thread ID if available, and `logging.logThreads` is truthy.
|
10061
|
+
thread=(ta.Optional[int], None),
|
10062
|
+
|
10063
|
+
# Thread name if available, and `logging.logThreads` is truthy.
|
10064
|
+
threadName=(ta.Optional[str], None),
|
10065
|
+
)
|
10066
|
+
|
10067
|
+
def _info_to_record(self, info: LoggingContextInfos.Thread) -> ta.Mapping[str, ta.Any]:
|
10068
|
+
if logging.logThreads:
|
10069
|
+
return dict(
|
10070
|
+
thread=info.ident,
|
10071
|
+
threadName=info.name,
|
10072
|
+
)
|
10073
|
+
|
10074
|
+
return self.record_defaults
|
10075
|
+
|
10076
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Thread]:
|
10077
|
+
if (
|
10078
|
+
(ident := rec.thread) is not None and
|
10079
|
+
(name := rec.threadName) is not None
|
10080
|
+
):
|
10081
|
+
return LoggingContextInfos.Thread(
|
10082
|
+
ident=ident,
|
10083
|
+
native_id=None,
|
10084
|
+
name=name,
|
10085
|
+
)
|
10086
|
+
|
10087
|
+
return None
|
10088
|
+
|
10089
|
+
class Process(OptionalAdapter[LoggingContextInfos.Process]):
|
10090
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Process]] = LoggingContextInfos.Process
|
10091
|
+
|
10092
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
|
10093
|
+
# Process ID if available - that is, if `hasattr(os, 'getpid')` - and `logging.logProcesses` is truthy,
|
10094
|
+
# otherwise None.
|
10095
|
+
process=(ta.Optional[int], None),
|
10096
|
+
)
|
10097
|
+
|
10098
|
+
def _info_to_record(self, info: LoggingContextInfos.Process) -> ta.Mapping[str, ta.Any]:
|
10099
|
+
if logging.logProcesses:
|
10100
|
+
return dict(
|
10101
|
+
process=info.pid,
|
10102
|
+
)
|
10103
|
+
|
10104
|
+
return self.record_defaults
|
10105
|
+
|
10106
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Process]:
|
10107
|
+
if (
|
10108
|
+
(pid := rec.process) is not None
|
10109
|
+
):
|
10110
|
+
return LoggingContextInfos.Process(
|
10111
|
+
pid=pid,
|
10112
|
+
)
|
10113
|
+
|
10114
|
+
return None
|
10115
|
+
|
10116
|
+
class Multiprocessing(OptionalAdapter[LoggingContextInfos.Multiprocessing]):
|
10117
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Multiprocessing]] = LoggingContextInfos.Multiprocessing
|
10118
|
+
|
10119
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
|
10120
|
+
# Process name if available. Set to None if `logging.logMultiprocessing` is not truthy. Otherwise, set to
|
10121
|
+
# 'MainProcess', then `sys.modules.get('multiprocessing').current_process().name` if that works, otherwise
|
10122
|
+
# remains as 'MainProcess'.
|
10123
|
+
#
|
10124
|
+
# As noted by stdlib:
|
10125
|
+
#
|
10126
|
+
# Errors may occur if multiprocessing has not finished loading yet - e.g. if a custom import hook causes
|
10127
|
+
# third-party code to run when multiprocessing calls import. See issue 8200 for an example
|
10128
|
+
#
|
10129
|
+
processName=(ta.Optional[str], None),
|
10130
|
+
)
|
10131
|
+
|
10132
|
+
def _info_to_record(self, info: LoggingContextInfos.Multiprocessing) -> ta.Mapping[str, ta.Any]:
|
10133
|
+
if logging.logMultiprocessing:
|
10134
|
+
return dict(
|
10135
|
+
processName=info.process_name,
|
10136
|
+
)
|
10137
|
+
|
10138
|
+
return self.record_defaults
|
10139
|
+
|
10140
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Multiprocessing]:
|
10141
|
+
if (
|
10142
|
+
(process_name := rec.processName) is not None
|
10143
|
+
):
|
10144
|
+
return LoggingContextInfos.Multiprocessing(
|
10145
|
+
process_name=process_name,
|
10146
|
+
)
|
10147
|
+
|
10148
|
+
return None
|
10149
|
+
|
10150
|
+
class AsyncioTask(OptionalAdapter[LoggingContextInfos.AsyncioTask]):
|
10151
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.AsyncioTask]] = LoggingContextInfos.AsyncioTask
|
10152
|
+
|
10153
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Union[ta.Any, ta.Tuple[ta.Any, ta.Any]]]] = dict(
|
10154
|
+
# Absent <3.12, otherwise asyncio.Task name if available, and `logging.logAsyncioTasks` is truthy. Set to
|
10155
|
+
# `sys.modules.get('asyncio').current_task().get_name()`, otherwise None.
|
10156
|
+
taskName=(ta.Optional[str], None),
|
10157
|
+
)
|
10158
|
+
|
10159
|
+
def _info_to_record(self, info: LoggingContextInfos.AsyncioTask) -> ta.Mapping[str, ta.Any]:
|
10160
|
+
if getattr(logging, 'logAsyncioTasks', None): # Absent <3.12
|
10161
|
+
return dict(
|
10162
|
+
taskName=info.name,
|
10163
|
+
)
|
10164
|
+
|
10165
|
+
return self.record_defaults
|
10166
|
+
|
10167
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.AsyncioTask]:
|
10168
|
+
if (
|
10169
|
+
(name := getattr(rec, 'taskName', None)) is not None
|
10170
|
+
):
|
10171
|
+
return LoggingContextInfos.AsyncioTask(
|
10172
|
+
name=name,
|
10173
|
+
)
|
10174
|
+
|
10175
|
+
return None
|
10176
|
+
|
10177
|
+
|
10178
|
+
_LOGGING_CONTEXT_INFO_RECORD_ADAPTERS_: ta.Sequence[LoggingContextInfoRecordAdapters.Adapter] = [ # noqa
|
10179
|
+
LoggingContextInfoRecordAdapters.Name(),
|
10180
|
+
LoggingContextInfoRecordAdapters.Level(),
|
10181
|
+
LoggingContextInfoRecordAdapters.Msg(),
|
10182
|
+
LoggingContextInfoRecordAdapters.Time(),
|
10183
|
+
LoggingContextInfoRecordAdapters.Exc(),
|
10184
|
+
LoggingContextInfoRecordAdapters.Caller(),
|
10185
|
+
LoggingContextInfoRecordAdapters.SourceFile(),
|
10186
|
+
LoggingContextInfoRecordAdapters.Thread(),
|
10187
|
+
LoggingContextInfoRecordAdapters.Process(),
|
10188
|
+
LoggingContextInfoRecordAdapters.Multiprocessing(),
|
10189
|
+
LoggingContextInfoRecordAdapters.AsyncioTask(),
|
10190
|
+
]
|
9817
10191
|
|
9818
|
-
|
10192
|
+
_LOGGING_CONTEXT_INFO_RECORD_ADAPTERS: ta.Mapping[ta.Type[LoggingContextInfo], LoggingContextInfoRecordAdapters.Adapter] = { # noqa
|
10193
|
+
ad.info_cls: ad for ad in _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS_
|
10194
|
+
}
|
10195
|
+
|
10196
|
+
|
10197
|
+
##
|
9819
10198
|
|
9820
10199
|
|
9821
10200
|
# Formatter:
|
9822
10201
|
# - https://github.com/python/cpython/blob/39b2f82717a69dde7212bc39b673b0f55c99e6a3/Lib/logging/__init__.py#L514 (3.8)
|
9823
10202
|
# - https://github.com/python/cpython/blob/f070f54c5f4a42c7c61d1d5d3b8f3b7203b4a0fb/Lib/logging/__init__.py#L554 (~3.14) # noqa
|
9824
10203
|
#
|
9825
|
-
|
10204
|
+
_KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTRS: ta.Dict[str, ta.Any] = dict(
|
9826
10205
|
# The logged message, computed as msg % args. Set to `record.getMessage()`.
|
9827
10206
|
message=str,
|
9828
10207
|
|
@@ -9836,20 +10215,31 @@ KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTRS: ta.Dict[str, ta.Any] = dict(
|
|
9836
10215
|
exc_text=ta.Optional[str],
|
9837
10216
|
)
|
9838
10217
|
|
9839
|
-
KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTR_SET: ta.FrozenSet[str] = frozenset(KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTRS)
|
9840
|
-
|
9841
10218
|
|
9842
10219
|
##
|
9843
10220
|
|
9844
10221
|
|
10222
|
+
_KNOWN_STD_LOGGING_RECORD_ATTR_SET: ta.FrozenSet[str] = frozenset(
|
10223
|
+
a for ad in _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS.values() for a in ad.record_attrs
|
10224
|
+
)
|
10225
|
+
|
10226
|
+
_KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTR_SET: ta.FrozenSet[str] = frozenset(_KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTRS)
|
10227
|
+
|
10228
|
+
|
9845
10229
|
class UnknownStdLoggingRecordAttrsWarning(LoggingSetupWarning):
|
9846
10230
|
pass
|
9847
10231
|
|
9848
10232
|
|
9849
10233
|
def _check_std_logging_record_attrs() -> None:
|
10234
|
+
if (
|
10235
|
+
len([a for ad in _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS.values() for a in ad.record_attrs]) !=
|
10236
|
+
len(_KNOWN_STD_LOGGING_RECORD_ATTR_SET)
|
10237
|
+
):
|
10238
|
+
raise RuntimeError('Duplicate LoggingContextInfoRecordAdapter record attrs')
|
10239
|
+
|
9850
10240
|
rec_dct = dict(logging.makeLogRecord({}).__dict__)
|
9851
10241
|
|
9852
|
-
if (unk_rec_fields := frozenset(rec_dct) -
|
10242
|
+
if (unk_rec_fields := frozenset(rec_dct) - _KNOWN_STD_LOGGING_RECORD_ATTR_SET):
|
9853
10243
|
import warnings # noqa
|
9854
10244
|
|
9855
10245
|
warnings.warn(
|
@@ -9865,116 +10255,43 @@ _check_std_logging_record_attrs()
|
|
9865
10255
|
|
9866
10256
|
|
9867
10257
|
class LoggingContextLogRecord(logging.LogRecord):
|
9868
|
-
|
9869
|
-
|
9870
|
-
|
9871
|
-
|
9872
|
-
|
9873
|
-
|
9874
|
-
|
9875
|
-
|
9876
|
-
|
9877
|
-
|
9878
|
-
# name,
|
9879
|
-
# level,
|
9880
|
-
# pathname,
|
9881
|
-
# lineno,
|
9882
|
-
# msg,
|
9883
|
-
# args,
|
9884
|
-
# exc_info,
|
9885
|
-
# func=None,
|
9886
|
-
# sinfo=None,
|
9887
|
-
# **kwargs,
|
9888
|
-
*,
|
9889
|
-
name: str,
|
9890
|
-
msg: str,
|
9891
|
-
args: ta.Union[tuple, dict],
|
9892
|
-
|
9893
|
-
_logging_context: LoggingContext,
|
9894
|
-
) -> None:
|
9895
|
-
ctx = _logging_context
|
10258
|
+
# LogRecord.__init__ args:
|
10259
|
+
# - name: str
|
10260
|
+
# - level: int
|
10261
|
+
# - pathname: str - Confusingly referred to as `fn` before the LogRecord ctor. May be empty or "(unknown file)".
|
10262
|
+
# - lineno: int - May be 0.
|
10263
|
+
# - msg: str
|
10264
|
+
# - args: tuple | dict | 1-tuple[dict]
|
10265
|
+
# - exc_info: LoggingExcInfoTuple | None
|
10266
|
+
# - func: str | None = None -> funcName
|
10267
|
+
# - sinfo: str | None = None -> stack_info
|
9896
10268
|
|
9897
|
-
|
10269
|
+
def __init__(self, *, _logging_context: LoggingContext) -> None: # noqa
|
10270
|
+
self._logging_context = _logging_context
|
9898
10271
|
|
9899
|
-
|
9900
|
-
|
9901
|
-
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L307
|
9902
|
-
if args and len(args) == 1 and isinstance(args[0], collections.abc.Mapping) and args[0]:
|
9903
|
-
args = args[0] # type: ignore[assignment]
|
9904
|
-
self.args: ta.Union[tuple, dict] = args
|
9905
|
-
|
9906
|
-
self.levelname: str = logging.getLevelName(ctx.level)
|
9907
|
-
self.levelno: int = ctx.level
|
9908
|
-
|
9909
|
-
if (caller := ctx.caller()) is not None:
|
9910
|
-
self.pathname: str = caller.file_path
|
9911
|
-
else:
|
9912
|
-
self.pathname = self._UNKNOWN_PATH_NAME
|
9913
|
-
|
9914
|
-
if (src_file := ctx.source_file()) is not None:
|
9915
|
-
self.filename: str = src_file.file_name
|
9916
|
-
self.module: str = src_file.module
|
9917
|
-
else:
|
9918
|
-
self.filename = self.pathname
|
9919
|
-
self.module = self._UNKNOWN_MODULE
|
10272
|
+
for ad in _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS_:
|
10273
|
+
self.__dict__.update(ad.context_to_record(_logging_context))
|
9920
10274
|
|
9921
|
-
self.exc_info: ta.Optional[LoggingExcInfoTuple] = ctx.exc_info_tuple
|
9922
|
-
self.exc_text: ta.Optional[str] = None
|
9923
10275
|
|
9924
|
-
|
9925
|
-
if caller is not None:
|
9926
|
-
if (sinfo := caller.stack_info) is not None:
|
9927
|
-
self.stack_info: ta.Optional[str] = '\n'.join([
|
9928
|
-
self._STACK_INFO_PREFIX,
|
9929
|
-
sinfo[1:] if sinfo.endswith('\n') else sinfo,
|
9930
|
-
])
|
9931
|
-
else:
|
9932
|
-
self.stack_info = None
|
9933
|
-
|
9934
|
-
self.lineno: int = caller.line_no
|
9935
|
-
self.funcName: str = caller.name
|
9936
|
-
|
9937
|
-
else:
|
9938
|
-
self.stack_info = None
|
9939
|
-
|
9940
|
-
self.lineno = 0
|
9941
|
-
self.funcName = self._UNKNOWN_FUNC_NAME
|
10276
|
+
##
|
9942
10277
|
|
9943
|
-
times = ctx.times
|
9944
|
-
self.created: float = times.created
|
9945
|
-
self.msecs: float = times.msecs
|
9946
|
-
self.relativeCreated: float = times.relative_created
|
9947
10278
|
|
9948
|
-
|
9949
|
-
|
9950
|
-
|
9951
|
-
|
9952
|
-
|
9953
|
-
self.thread = None
|
9954
|
-
self.threadName = None
|
10279
|
+
@ta.final
|
10280
|
+
class LogRecordLoggingContext(LoggingContext):
|
10281
|
+
def __init__(self, rec: logging.LogRecord) -> None:
|
10282
|
+
if isinstance(rec, LoggingContextLogRecord):
|
10283
|
+
raise TypeError(rec)
|
9955
10284
|
|
9956
|
-
|
9957
|
-
process = check.not_none(ctx.process())
|
9958
|
-
self.process: ta.Optional[int] = process.pid
|
9959
|
-
else:
|
9960
|
-
self.process = None
|
10285
|
+
self._rec = rec
|
9961
10286
|
|
9962
|
-
|
9963
|
-
|
9964
|
-
|
9965
|
-
|
9966
|
-
|
9967
|
-
else:
|
9968
|
-
self.processName = None
|
10287
|
+
self._infos: ta.Dict[ta.Type[LoggingContextInfo], LoggingContextInfo] = {
|
10288
|
+
type(info): info
|
10289
|
+
for ad in _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS_
|
10290
|
+
if (info := ad.record_to_info(rec)) is not None
|
10291
|
+
}
|
9969
10292
|
|
9970
|
-
|
9971
|
-
|
9972
|
-
if (at := ctx.asyncio_task()) is not None:
|
9973
|
-
self.taskName: ta.Optional[str] = at.name
|
9974
|
-
else:
|
9975
|
-
self.taskName = None
|
9976
|
-
else:
|
9977
|
-
self.taskName = None
|
10293
|
+
def get_info(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
|
10294
|
+
return self._infos.get(ty)
|
9978
10295
|
|
9979
10296
|
|
9980
10297
|
########################################
|
@@ -11194,21 +11511,20 @@ class StdLogger(Logger):
|
|
11194
11511
|
return self._std.getEffectiveLevel()
|
11195
11512
|
|
11196
11513
|
def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any) -> None:
|
11197
|
-
if not self.is_enabled_for(ctx.level):
|
11514
|
+
if not self.is_enabled_for(ctx.must_get_info(LoggingContextInfos.Level).level):
|
11198
11515
|
return
|
11199
11516
|
|
11200
|
-
ctx.
|
11201
|
-
|
11202
|
-
ms, args = self._prepare_msg_args(msg, *args)
|
11203
|
-
|
11204
|
-
rec = LoggingContextLogRecord(
|
11517
|
+
ctx.set_basic(
|
11205
11518
|
name=self._std.name,
|
11206
|
-
msg=ms,
|
11207
|
-
args=args,
|
11208
11519
|
|
11209
|
-
|
11520
|
+
msg=msg,
|
11521
|
+
args=args,
|
11210
11522
|
)
|
11211
11523
|
|
11524
|
+
ctx.capture()
|
11525
|
+
|
11526
|
+
rec = LoggingContextLogRecord(_logging_context=ctx)
|
11527
|
+
|
11212
11528
|
self._std.handle(rec)
|
11213
11529
|
|
11214
11530
|
|