ominfra 0.0.0.dev430__py3-none-any.whl → 0.0.0.dev432__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ominfra/scripts/journald2aws.py +1007 -691
- ominfra/scripts/manage.py +995 -679
- ominfra/scripts/supervisor.py +993 -677
- {ominfra-0.0.0.dev430.dist-info → ominfra-0.0.0.dev432.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev430.dist-info → ominfra-0.0.0.dev432.dist-info}/RECORD +9 -9
- {ominfra-0.0.0.dev430.dist-info → ominfra-0.0.0.dev432.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev430.dist-info → ominfra-0.0.0.dev432.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev430.dist-info → ominfra-0.0.0.dev432.dist-info}/licenses/LICENSE +0 -0
- {ominfra-0.0.0.dev430.dist-info → ominfra-0.0.0.dev432.dist-info}/top_level.txt +0 -0
ominfra/scripts/supervisor.py
CHANGED
@@ -154,6 +154,13 @@ HttpHeaders = http.client.HTTPMessage # ta.TypeAlias
|
|
154
154
|
# ../../omlish/lite/maybes.py
|
155
155
|
U = ta.TypeVar('U')
|
156
156
|
|
157
|
+
# ../../omlish/logs/infos.py
|
158
|
+
LoggingMsgFn = ta.Callable[[], ta.Union[str, tuple]] # ta.TypeAlias
|
159
|
+
LoggingExcInfoTuple = ta.Tuple[ta.Type[BaseException], BaseException, ta.Optional[types.TracebackType]] # ta.TypeAlias
|
160
|
+
LoggingExcInfo = ta.Union[BaseException, LoggingExcInfoTuple] # ta.TypeAlias
|
161
|
+
LoggingExcInfoArg = ta.Union[LoggingExcInfo, bool, None] # ta.TypeAlias
|
162
|
+
LoggingContextInfo = ta.Any # ta.TypeAlias
|
163
|
+
|
157
164
|
# ../../omlish/http/handlers.py
|
158
165
|
HttpHandler = ta.Callable[['HttpHandlerRequest'], 'HttpHandlerResponse'] # ta.TypeAlias
|
159
166
|
HttpHandlerResponseData = ta.Union[bytes, 'HttpHandlerResponseStreamedData'] # ta.TypeAlias # noqa
|
@@ -165,16 +172,11 @@ InjectorProviderFnMap = ta.Mapping['InjectorKey', 'InjectorProviderFn']
|
|
165
172
|
InjectorBindingOrBindings = ta.Union['InjectorBinding', 'InjectorBindings']
|
166
173
|
|
167
174
|
# ../../omlish/logs/contexts.py
|
168
|
-
|
169
|
-
LoggingExcInfo = ta.Union[BaseException, LoggingExcInfoTuple] # ta.TypeAlias
|
170
|
-
LoggingExcInfoArg = ta.Union[LoggingExcInfo, bool, None] # ta.TypeAlias
|
175
|
+
LoggingContextInfoT = ta.TypeVar('LoggingContextInfoT', bound=LoggingContextInfo)
|
171
176
|
|
172
177
|
# ../../omlish/http/coro/server/server.py
|
173
178
|
CoroHttpServerFactory = ta.Callable[[SocketAddress], 'CoroHttpServer']
|
174
179
|
|
175
|
-
# ../../omlish/logs/base.py
|
176
|
-
LoggingMsgFn = ta.Callable[[], ta.Union[str, tuple]] # ta.TypeAlias
|
177
|
-
|
178
180
|
|
179
181
|
########################################
|
180
182
|
# ../errors.py
|
@@ -3074,124 +3076,6 @@ def typing_annotations_attr() -> str:
|
|
3074
3076
|
return _TYPING_ANNOTATIONS_ATTR
|
3075
3077
|
|
3076
3078
|
|
3077
|
-
########################################
|
3078
|
-
# ../../../omlish/logs/infos.py
|
3079
|
-
|
3080
|
-
|
3081
|
-
##
|
3082
|
-
|
3083
|
-
|
3084
|
-
def logging_context_info(cls):
|
3085
|
-
return cls
|
3086
|
-
|
3087
|
-
|
3088
|
-
##
|
3089
|
-
|
3090
|
-
|
3091
|
-
@logging_context_info
|
3092
|
-
@ta.final
|
3093
|
-
class LoggingSourceFileInfo(ta.NamedTuple):
|
3094
|
-
file_name: str
|
3095
|
-
module: str
|
3096
|
-
|
3097
|
-
@classmethod
|
3098
|
-
def build(cls, file_path: ta.Optional[str]) -> ta.Optional['LoggingSourceFileInfo']:
|
3099
|
-
if file_path is None:
|
3100
|
-
return None
|
3101
|
-
|
3102
|
-
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L331-L336 # noqa
|
3103
|
-
try:
|
3104
|
-
file_name = os.path.basename(file_path)
|
3105
|
-
module = os.path.splitext(file_name)[0]
|
3106
|
-
except (TypeError, ValueError, AttributeError):
|
3107
|
-
return None
|
3108
|
-
|
3109
|
-
return cls(
|
3110
|
-
file_name=file_name,
|
3111
|
-
module=module,
|
3112
|
-
)
|
3113
|
-
|
3114
|
-
|
3115
|
-
##
|
3116
|
-
|
3117
|
-
|
3118
|
-
@logging_context_info
|
3119
|
-
@ta.final
|
3120
|
-
class LoggingThreadInfo(ta.NamedTuple):
|
3121
|
-
ident: int
|
3122
|
-
native_id: ta.Optional[int]
|
3123
|
-
name: str
|
3124
|
-
|
3125
|
-
@classmethod
|
3126
|
-
def build(cls) -> 'LoggingThreadInfo':
|
3127
|
-
return cls(
|
3128
|
-
ident=threading.get_ident(),
|
3129
|
-
native_id=threading.get_native_id() if hasattr(threading, 'get_native_id') else None,
|
3130
|
-
name=threading.current_thread().name,
|
3131
|
-
)
|
3132
|
-
|
3133
|
-
|
3134
|
-
##
|
3135
|
-
|
3136
|
-
|
3137
|
-
@logging_context_info
|
3138
|
-
@ta.final
|
3139
|
-
class LoggingProcessInfo(ta.NamedTuple):
|
3140
|
-
pid: int
|
3141
|
-
|
3142
|
-
@classmethod
|
3143
|
-
def build(cls) -> 'LoggingProcessInfo':
|
3144
|
-
return cls(
|
3145
|
-
pid=os.getpid(),
|
3146
|
-
)
|
3147
|
-
|
3148
|
-
|
3149
|
-
##
|
3150
|
-
|
3151
|
-
|
3152
|
-
@logging_context_info
|
3153
|
-
@ta.final
|
3154
|
-
class LoggingMultiprocessingInfo(ta.NamedTuple):
|
3155
|
-
process_name: str
|
3156
|
-
|
3157
|
-
@classmethod
|
3158
|
-
def build(cls) -> ta.Optional['LoggingMultiprocessingInfo']:
|
3159
|
-
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L355-L364 # noqa
|
3160
|
-
if (mp := sys.modules.get('multiprocessing')) is None:
|
3161
|
-
return None
|
3162
|
-
|
3163
|
-
return cls(
|
3164
|
-
process_name=mp.current_process().name,
|
3165
|
-
)
|
3166
|
-
|
3167
|
-
|
3168
|
-
##
|
3169
|
-
|
3170
|
-
|
3171
|
-
@logging_context_info
|
3172
|
-
@ta.final
|
3173
|
-
class LoggingAsyncioTaskInfo(ta.NamedTuple):
|
3174
|
-
name: str
|
3175
|
-
|
3176
|
-
@classmethod
|
3177
|
-
def build(cls) -> ta.Optional['LoggingAsyncioTaskInfo']:
|
3178
|
-
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L372-L377 # noqa
|
3179
|
-
if (asyncio := sys.modules.get('asyncio')) is None:
|
3180
|
-
return None
|
3181
|
-
|
3182
|
-
try:
|
3183
|
-
task = asyncio.current_task()
|
3184
|
-
except Exception: # noqa
|
3185
|
-
return None
|
3186
|
-
|
3187
|
-
if task is None:
|
3188
|
-
return None
|
3189
|
-
|
3190
|
-
return cls(
|
3191
|
-
name=task.get_name(), # Always non-None
|
3192
|
-
)
|
3193
|
-
|
3194
|
-
|
3195
3079
|
########################################
|
3196
3080
|
# ../../../omlish/logs/levels.py
|
3197
3081
|
|
@@ -6210,74 +6094,362 @@ def check_lite_runtime_version() -> None:
|
|
6210
6094
|
|
6211
6095
|
|
6212
6096
|
########################################
|
6213
|
-
# ../../../omlish/logs/
|
6097
|
+
# ../../../omlish/logs/infos.py
|
6098
|
+
"""
|
6099
|
+
TODO:
|
6100
|
+
- remove redundant info fields only present for std adaptation (Level.name, ...)
|
6101
|
+
"""
|
6214
6102
|
|
6215
6103
|
|
6216
6104
|
##
|
6217
6105
|
|
6218
6106
|
|
6219
|
-
|
6107
|
+
def logging_context_info(cls):
|
6108
|
+
return cls
|
6109
|
+
|
6110
|
+
|
6220
6111
|
@ta.final
|
6221
|
-
class
|
6222
|
-
|
6223
|
-
|
6224
|
-
name: str
|
6225
|
-
stack_info: ta.Optional[str]
|
6112
|
+
class LoggingContextInfos:
|
6113
|
+
def __new__(cls, *args, **kwargs): # noqa
|
6114
|
+
raise TypeError
|
6226
6115
|
|
6227
|
-
|
6228
|
-
def is_internal_frame(cls, frame: types.FrameType) -> bool:
|
6229
|
-
file_path = os.path.normcase(frame.f_code.co_filename)
|
6116
|
+
#
|
6230
6117
|
|
6231
|
-
|
6232
|
-
|
6233
|
-
|
6234
|
-
|
6235
|
-
return True
|
6118
|
+
@logging_context_info
|
6119
|
+
@ta.final
|
6120
|
+
class Name(ta.NamedTuple):
|
6121
|
+
name: str
|
6236
6122
|
|
6237
|
-
|
6123
|
+
@logging_context_info
|
6124
|
+
@ta.final
|
6125
|
+
class Level(ta.NamedTuple):
|
6126
|
+
level: NamedLogLevel
|
6127
|
+
name: str
|
6238
6128
|
|
6239
|
-
|
6240
|
-
|
6241
|
-
|
6129
|
+
@classmethod
|
6130
|
+
def build(cls, level: int) -> 'LoggingContextInfos.Level':
|
6131
|
+
nl: NamedLogLevel = level if level.__class__ is NamedLogLevel else NamedLogLevel(level) # type: ignore[assignment] # noqa
|
6132
|
+
return cls(
|
6133
|
+
level=nl,
|
6134
|
+
name=logging.getLevelName(nl),
|
6135
|
+
)
|
6136
|
+
|
6137
|
+
@logging_context_info
|
6138
|
+
@ta.final
|
6139
|
+
class Msg(ta.NamedTuple):
|
6140
|
+
msg: str
|
6141
|
+
args: ta.Union[tuple, ta.Mapping[ta.Any, ta.Any], None]
|
6142
|
+
|
6143
|
+
@classmethod
|
6144
|
+
def build(
|
6145
|
+
cls,
|
6146
|
+
msg: ta.Union[str, tuple, LoggingMsgFn],
|
6147
|
+
*args: ta.Any,
|
6148
|
+
) -> 'LoggingContextInfos.Msg':
|
6149
|
+
s: str
|
6150
|
+
a: ta.Any
|
6151
|
+
|
6152
|
+
if callable(msg):
|
6153
|
+
if args:
|
6154
|
+
raise TypeError(f'Must not provide both a message function and args: {msg=} {args=}')
|
6155
|
+
x = msg()
|
6156
|
+
if isinstance(x, str):
|
6157
|
+
s, a = x, ()
|
6158
|
+
elif isinstance(x, tuple):
|
6159
|
+
if x:
|
6160
|
+
s, a = x[0], x[1:]
|
6161
|
+
else:
|
6162
|
+
s, a = '', ()
|
6163
|
+
else:
|
6164
|
+
raise TypeError(x)
|
6242
6165
|
|
6243
|
-
|
6244
|
-
|
6245
|
-
|
6246
|
-
|
6247
|
-
|
6166
|
+
elif isinstance(msg, tuple):
|
6167
|
+
if args:
|
6168
|
+
raise TypeError(f'Must not provide both a tuple message and args: {msg=} {args=}')
|
6169
|
+
if msg:
|
6170
|
+
s, a = msg[0], msg[1:]
|
6171
|
+
else:
|
6172
|
+
s, a = '', ()
|
6248
6173
|
|
6249
|
-
|
6174
|
+
elif isinstance(msg, str):
|
6175
|
+
s, a = msg, args
|
6250
6176
|
|
6251
|
-
|
6177
|
+
else:
|
6178
|
+
raise TypeError(msg)
|
6179
|
+
|
6180
|
+
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L307 # noqa
|
6181
|
+
if a and len(a) == 1 and isinstance(a[0], collections.abc.Mapping) and a[0]:
|
6182
|
+
a = a[0]
|
6183
|
+
|
6184
|
+
return cls(
|
6185
|
+
msg=s,
|
6186
|
+
args=a,
|
6187
|
+
)
|
6188
|
+
|
6189
|
+
@logging_context_info
|
6190
|
+
@ta.final
|
6191
|
+
class Extra(ta.NamedTuple):
|
6192
|
+
extra: ta.Mapping[ta.Any, ta.Any]
|
6193
|
+
|
6194
|
+
@logging_context_info
|
6195
|
+
@ta.final
|
6196
|
+
class Time(ta.NamedTuple):
|
6197
|
+
ns: int
|
6198
|
+
secs: float
|
6199
|
+
msecs: float
|
6200
|
+
relative_secs: float
|
6201
|
+
|
6202
|
+
@classmethod
|
6203
|
+
def get_std_start_ns(cls) -> int:
|
6204
|
+
x: ta.Any = logging._startTime # type: ignore[attr-defined] # noqa
|
6205
|
+
|
6206
|
+
# Before 3.13.0b1 this will be `time.time()`, a float of seconds. After that, it will be `time.time_ns()`,
|
6207
|
+
# an int.
|
6208
|
+
#
|
6209
|
+
# See:
|
6210
|
+
# - https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
|
6211
|
+
#
|
6212
|
+
if isinstance(x, float):
|
6213
|
+
return int(x * 1e9)
|
6214
|
+
else:
|
6215
|
+
return x
|
6216
|
+
|
6217
|
+
@classmethod
|
6218
|
+
def build(
|
6219
|
+
cls,
|
6220
|
+
ns: int,
|
6221
|
+
*,
|
6222
|
+
start_ns: ta.Optional[int] = None,
|
6223
|
+
) -> 'LoggingContextInfos.Time':
|
6224
|
+
# https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
|
6225
|
+
secs = ns / 1e9 # ns to float seconds
|
6226
|
+
|
6227
|
+
# Get the number of whole milliseconds (0-999) in the fractional part of seconds.
|
6228
|
+
# Eg: 1_677_903_920_999_998_503 ns --> 999_998_503 ns--> 999 ms
|
6229
|
+
# Convert to float by adding 0.0 for historical reasons. See gh-89047
|
6230
|
+
msecs = (ns % 1_000_000_000) // 1_000_000 + 0.0
|
6231
|
+
|
6232
|
+
# https://github.com/python/cpython/commit/1500a23f33f5a6d052ff1ef6383d9839928b8ff1
|
6233
|
+
if msecs == 999.0 and int(secs) != ns // 1_000_000_000:
|
6234
|
+
# ns -> sec conversion can round up, e.g:
|
6235
|
+
# 1_677_903_920_999_999_900 ns --> 1_677_903_921.0 sec
|
6236
|
+
msecs = 0.0
|
6237
|
+
|
6238
|
+
if start_ns is None:
|
6239
|
+
start_ns = cls.get_std_start_ns()
|
6240
|
+
relative_secs = (ns - start_ns) / 1e6
|
6241
|
+
|
6242
|
+
return cls(
|
6243
|
+
ns=ns,
|
6244
|
+
secs=secs,
|
6245
|
+
msecs=msecs,
|
6246
|
+
relative_secs=relative_secs,
|
6247
|
+
)
|
6248
|
+
|
6249
|
+
@logging_context_info
|
6250
|
+
@ta.final
|
6251
|
+
class Exc(ta.NamedTuple):
|
6252
|
+
info: LoggingExcInfo
|
6253
|
+
info_tuple: LoggingExcInfoTuple
|
6254
|
+
|
6255
|
+
@classmethod
|
6256
|
+
def build(
|
6257
|
+
cls,
|
6258
|
+
arg: LoggingExcInfoArg = False,
|
6259
|
+
) -> ta.Optional['LoggingContextInfos.Exc']:
|
6260
|
+
if arg is True:
|
6261
|
+
sys_exc_info = sys.exc_info()
|
6262
|
+
if sys_exc_info[0] is not None:
|
6263
|
+
arg = sys_exc_info
|
6264
|
+
else:
|
6265
|
+
arg = None
|
6266
|
+
elif arg is False:
|
6267
|
+
arg = None
|
6268
|
+
if arg is None:
|
6269
|
+
return None
|
6270
|
+
|
6271
|
+
info: LoggingExcInfo = arg
|
6272
|
+
if isinstance(info, BaseException):
|
6273
|
+
info_tuple: LoggingExcInfoTuple = (type(info), info, info.__traceback__) # noqa
|
6274
|
+
else:
|
6275
|
+
info_tuple = info
|
6276
|
+
|
6277
|
+
return cls(
|
6278
|
+
info=info,
|
6279
|
+
info_tuple=info_tuple,
|
6280
|
+
)
|
6281
|
+
|
6282
|
+
@logging_context_info
|
6283
|
+
@ta.final
|
6284
|
+
class Caller(ta.NamedTuple):
|
6285
|
+
file_path: str
|
6286
|
+
line_no: int
|
6287
|
+
func_name: str
|
6288
|
+
stack_info: ta.Optional[str]
|
6289
|
+
|
6290
|
+
@classmethod
|
6291
|
+
def is_internal_frame(cls, frame: types.FrameType) -> bool:
|
6292
|
+
file_path = os.path.normcase(frame.f_code.co_filename)
|
6293
|
+
|
6294
|
+
# Yes, really.
|
6295
|
+
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L204 # noqa
|
6296
|
+
# https://github.com/python/cpython/commit/5ca6d7469be53960843df39bb900e9c3359f127f
|
6297
|
+
if 'importlib' in file_path and '_bootstrap' in file_path:
|
6298
|
+
return True
|
6299
|
+
|
6300
|
+
return False
|
6301
|
+
|
6302
|
+
@classmethod
|
6303
|
+
def find_frame(cls, stack_offset: int = 0) -> ta.Optional[types.FrameType]:
|
6304
|
+
f: ta.Optional[types.FrameType] = sys._getframe(2 + stack_offset) # noqa
|
6305
|
+
|
6306
|
+
while f is not None:
|
6307
|
+
# NOTE: We don't check __file__ like stdlib since we may be running amalgamated - we rely on careful,
|
6308
|
+
# manual stack_offset management.
|
6309
|
+
if hasattr(f, 'f_code'):
|
6310
|
+
return f
|
6311
|
+
|
6312
|
+
f = f.f_back
|
6252
6313
|
|
6253
|
-
@classmethod
|
6254
|
-
def find(
|
6255
|
-
cls,
|
6256
|
-
ofs: int = 0,
|
6257
|
-
*,
|
6258
|
-
stack_info: bool = False,
|
6259
|
-
) -> ta.Optional['LoggingCaller']:
|
6260
|
-
if (f := cls.find_frame(ofs + 1)) is None:
|
6261
6314
|
return None
|
6262
6315
|
|
6263
|
-
|
6264
|
-
|
6265
|
-
|
6266
|
-
|
6267
|
-
|
6268
|
-
|
6269
|
-
|
6270
|
-
if
|
6271
|
-
|
6316
|
+
@classmethod
|
6317
|
+
def build(
|
6318
|
+
cls,
|
6319
|
+
stack_offset: int = 0,
|
6320
|
+
*,
|
6321
|
+
stack_info: bool = False,
|
6322
|
+
) -> ta.Optional['LoggingContextInfos.Caller']:
|
6323
|
+
if (f := cls.find_frame(stack_offset + 1)) is None:
|
6324
|
+
return None
|
6272
6325
|
|
6273
|
-
|
6274
|
-
|
6275
|
-
|
6276
|
-
|
6277
|
-
|
6326
|
+
# https://github.com/python/cpython/blob/08e9794517063c8cd92c48714071b1d3c60b71bd/Lib/logging/__init__.py#L1616-L1623 # noqa
|
6327
|
+
sinfo = None
|
6328
|
+
if stack_info:
|
6329
|
+
sio = io.StringIO()
|
6330
|
+
traceback.print_stack(f, file=sio)
|
6331
|
+
sinfo = sio.getvalue()
|
6332
|
+
sio.close()
|
6333
|
+
if sinfo[-1] == '\n':
|
6334
|
+
sinfo = sinfo[:-1]
|
6335
|
+
|
6336
|
+
return cls(
|
6337
|
+
file_path=f.f_code.co_filename,
|
6338
|
+
line_no=f.f_lineno or 0,
|
6339
|
+
func_name=f.f_code.co_name,
|
6340
|
+
stack_info=sinfo,
|
6341
|
+
)
|
6342
|
+
|
6343
|
+
@logging_context_info
|
6344
|
+
@ta.final
|
6345
|
+
class SourceFile(ta.NamedTuple):
|
6346
|
+
file_name: str
|
6347
|
+
module: str
|
6348
|
+
|
6349
|
+
@classmethod
|
6350
|
+
def build(cls, caller_file_path: ta.Optional[str]) -> ta.Optional['LoggingContextInfos.SourceFile']:
|
6351
|
+
if caller_file_path is None:
|
6352
|
+
return None
|
6353
|
+
|
6354
|
+
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L331-L336 # noqa
|
6355
|
+
try:
|
6356
|
+
file_name = os.path.basename(caller_file_path)
|
6357
|
+
module = os.path.splitext(file_name)[0]
|
6358
|
+
except (TypeError, ValueError, AttributeError):
|
6359
|
+
return None
|
6360
|
+
|
6361
|
+
return cls(
|
6362
|
+
file_name=file_name,
|
6363
|
+
module=module,
|
6364
|
+
)
|
6365
|
+
|
6366
|
+
@logging_context_info
|
6367
|
+
@ta.final
|
6368
|
+
class Thread(ta.NamedTuple):
|
6369
|
+
ident: int
|
6370
|
+
native_id: ta.Optional[int]
|
6371
|
+
name: str
|
6372
|
+
|
6373
|
+
@classmethod
|
6374
|
+
def build(cls) -> 'LoggingContextInfos.Thread':
|
6375
|
+
return cls(
|
6376
|
+
ident=threading.get_ident(),
|
6377
|
+
native_id=threading.get_native_id() if hasattr(threading, 'get_native_id') else None,
|
6378
|
+
name=threading.current_thread().name,
|
6379
|
+
)
|
6380
|
+
|
6381
|
+
@logging_context_info
|
6382
|
+
@ta.final
|
6383
|
+
class Process(ta.NamedTuple):
|
6384
|
+
pid: int
|
6385
|
+
|
6386
|
+
@classmethod
|
6387
|
+
def build(cls) -> 'LoggingContextInfos.Process':
|
6388
|
+
return cls(
|
6389
|
+
pid=os.getpid(),
|
6390
|
+
)
|
6391
|
+
|
6392
|
+
@logging_context_info
|
6393
|
+
@ta.final
|
6394
|
+
class Multiprocessing(ta.NamedTuple):
|
6395
|
+
process_name: str
|
6396
|
+
|
6397
|
+
@classmethod
|
6398
|
+
def build(cls) -> ta.Optional['LoggingContextInfos.Multiprocessing']:
|
6399
|
+
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L355-L364 # noqa
|
6400
|
+
if (mp := sys.modules.get('multiprocessing')) is None:
|
6401
|
+
return None
|
6402
|
+
|
6403
|
+
return cls(
|
6404
|
+
process_name=mp.current_process().name,
|
6405
|
+
)
|
6406
|
+
|
6407
|
+
@logging_context_info
|
6408
|
+
@ta.final
|
6409
|
+
class AsyncioTask(ta.NamedTuple):
|
6410
|
+
name: str
|
6411
|
+
|
6412
|
+
@classmethod
|
6413
|
+
def build(cls) -> ta.Optional['LoggingContextInfos.AsyncioTask']:
|
6414
|
+
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L372-L377 # noqa
|
6415
|
+
if (asyncio := sys.modules.get('asyncio')) is None:
|
6416
|
+
return None
|
6417
|
+
|
6418
|
+
try:
|
6419
|
+
task = asyncio.current_task()
|
6420
|
+
except Exception: # noqa
|
6421
|
+
return None
|
6422
|
+
|
6423
|
+
if task is None:
|
6424
|
+
return None
|
6425
|
+
|
6426
|
+
return cls(
|
6427
|
+
name=task.get_name(), # Always non-None
|
6428
|
+
)
|
6429
|
+
|
6430
|
+
|
6431
|
+
##
|
6432
|
+
|
6433
|
+
|
6434
|
+
class UnexpectedLoggingStartTimeWarning(LoggingSetupWarning):
|
6435
|
+
pass
|
6436
|
+
|
6437
|
+
|
6438
|
+
def _check_logging_start_time() -> None:
|
6439
|
+
if (x := LoggingContextInfos.Time.get_std_start_ns()) < (t := time.time()):
|
6440
|
+
import warnings # noqa
|
6441
|
+
|
6442
|
+
warnings.warn(
|
6443
|
+
f'Unexpected logging start time detected: '
|
6444
|
+
f'get_std_start_ns={x}, '
|
6445
|
+
f'time.time()={t}',
|
6446
|
+
UnexpectedLoggingStartTimeWarning,
|
6278
6447
|
)
|
6279
6448
|
|
6280
6449
|
|
6450
|
+
_check_logging_start_time()
|
6451
|
+
|
6452
|
+
|
6281
6453
|
########################################
|
6282
6454
|
# ../../../omlish/logs/protocols.py
|
6283
6455
|
|
@@ -6368,118 +6540,33 @@ class JsonLoggingFormatter(logging.Formatter):
|
|
6368
6540
|
|
6369
6541
|
|
6370
6542
|
########################################
|
6371
|
-
# ../../../omlish/
|
6543
|
+
# ../../../omlish/os/journald.py
|
6372
6544
|
|
6373
6545
|
|
6374
6546
|
##
|
6375
6547
|
|
6376
6548
|
|
6377
|
-
|
6378
|
-
|
6379
|
-
class LoggingTimeFields(ta.NamedTuple):
|
6380
|
-
"""Maps directly to stdlib `logging.LogRecord` fields, and must be kept in sync with it."""
|
6381
|
-
|
6382
|
-
created: float
|
6383
|
-
msecs: float
|
6384
|
-
relative_created: float
|
6549
|
+
class sd_iovec(ct.Structure): # noqa
|
6550
|
+
pass
|
6385
6551
|
|
6386
|
-
@classmethod
|
6387
|
-
def get_std_start_time_ns(cls) -> int:
|
6388
|
-
x: ta.Any = logging._startTime # type: ignore[attr-defined] # noqa
|
6389
6552
|
|
6390
|
-
|
6391
|
-
|
6392
|
-
|
6393
|
-
|
6394
|
-
# - https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
|
6395
|
-
#
|
6396
|
-
if isinstance(x, float):
|
6397
|
-
return int(x * 1e9)
|
6398
|
-
else:
|
6399
|
-
return x
|
6553
|
+
sd_iovec._fields_ = [
|
6554
|
+
('iov_base', ct.c_void_p), # Pointer to data.
|
6555
|
+
('iov_len', ct.c_size_t), # Length of data.
|
6556
|
+
]
|
6400
6557
|
|
6401
|
-
@classmethod
|
6402
|
-
def build(
|
6403
|
-
cls,
|
6404
|
-
time_ns: int,
|
6405
|
-
*,
|
6406
|
-
start_time_ns: ta.Optional[int] = None,
|
6407
|
-
) -> 'LoggingTimeFields':
|
6408
|
-
# https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
|
6409
|
-
created = time_ns / 1e9 # ns to float seconds
|
6410
|
-
|
6411
|
-
# Get the number of whole milliseconds (0-999) in the fractional part of seconds.
|
6412
|
-
# Eg: 1_677_903_920_999_998_503 ns --> 999_998_503 ns--> 999 ms
|
6413
|
-
# Convert to float by adding 0.0 for historical reasons. See gh-89047
|
6414
|
-
msecs = (time_ns % 1_000_000_000) // 1_000_000 + 0.0
|
6415
|
-
|
6416
|
-
# https://github.com/python/cpython/commit/1500a23f33f5a6d052ff1ef6383d9839928b8ff1
|
6417
|
-
if msecs == 999.0 and int(created) != time_ns // 1_000_000_000:
|
6418
|
-
# ns -> sec conversion can round up, e.g:
|
6419
|
-
# 1_677_903_920_999_999_900 ns --> 1_677_903_921.0 sec
|
6420
|
-
msecs = 0.0
|
6421
|
-
|
6422
|
-
if start_time_ns is None:
|
6423
|
-
start_time_ns = cls.get_std_start_time_ns()
|
6424
|
-
relative_created = (time_ns - start_time_ns) / 1e6
|
6425
6558
|
|
6426
|
-
|
6427
|
-
created=created,
|
6428
|
-
msecs=msecs,
|
6429
|
-
relative_created=relative_created,
|
6430
|
-
)
|
6559
|
+
##
|
6431
6560
|
|
6432
6561
|
|
6433
|
-
|
6562
|
+
@cached_nullary
|
6563
|
+
def sd_libsystemd() -> ta.Any:
|
6564
|
+
lib = ct.CDLL('libsystemd.so.0')
|
6434
6565
|
|
6566
|
+
lib.sd_journal_sendv.restype = ct.c_int
|
6567
|
+
lib.sd_journal_sendv.argtypes = [ct.POINTER(sd_iovec), ct.c_int]
|
6435
6568
|
|
6436
|
-
|
6437
|
-
pass
|
6438
|
-
|
6439
|
-
|
6440
|
-
def _check_logging_start_time() -> None:
|
6441
|
-
if (x := LoggingTimeFields.get_std_start_time_ns()) < (t := time.time()):
|
6442
|
-
import warnings # noqa
|
6443
|
-
|
6444
|
-
warnings.warn(
|
6445
|
-
f'Unexpected logging start time detected: '
|
6446
|
-
f'get_std_start_time_ns={x}, '
|
6447
|
-
f'time.time()={t}',
|
6448
|
-
UnexpectedLoggingStartTimeWarning,
|
6449
|
-
)
|
6450
|
-
|
6451
|
-
|
6452
|
-
_check_logging_start_time()
|
6453
|
-
|
6454
|
-
|
6455
|
-
########################################
|
6456
|
-
# ../../../omlish/os/journald.py
|
6457
|
-
|
6458
|
-
|
6459
|
-
##
|
6460
|
-
|
6461
|
-
|
6462
|
-
class sd_iovec(ct.Structure): # noqa
|
6463
|
-
pass
|
6464
|
-
|
6465
|
-
|
6466
|
-
sd_iovec._fields_ = [
|
6467
|
-
('iov_base', ct.c_void_p), # Pointer to data.
|
6468
|
-
('iov_len', ct.c_size_t), # Length of data.
|
6469
|
-
]
|
6470
|
-
|
6471
|
-
|
6472
|
-
##
|
6473
|
-
|
6474
|
-
|
6475
|
-
@cached_nullary
|
6476
|
-
def sd_libsystemd() -> ta.Any:
|
6477
|
-
lib = ct.CDLL('libsystemd.so.0')
|
6478
|
-
|
6479
|
-
lib.sd_journal_sendv.restype = ct.c_int
|
6480
|
-
lib.sd_journal_sendv.argtypes = [ct.POINTER(sd_iovec), ct.c_int]
|
6481
|
-
|
6482
|
-
return lib
|
6569
|
+
return lib
|
6483
6570
|
|
6484
6571
|
|
6485
6572
|
@cached_nullary
|
@@ -8490,68 +8577,46 @@ inj = InjectionApi()
|
|
8490
8577
|
|
8491
8578
|
|
8492
8579
|
class LoggingContext(Abstract):
|
8493
|
-
@property
|
8494
|
-
@abc.abstractmethod
|
8495
|
-
def level(self) -> NamedLogLevel:
|
8496
|
-
raise NotImplementedError
|
8497
|
-
|
8498
|
-
#
|
8499
|
-
|
8500
|
-
@property
|
8501
|
-
@abc.abstractmethod
|
8502
|
-
def time_ns(self) -> int:
|
8503
|
-
raise NotImplementedError
|
8504
|
-
|
8505
|
-
@property
|
8506
8580
|
@abc.abstractmethod
|
8507
|
-
def
|
8581
|
+
def get_info(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
|
8508
8582
|
raise NotImplementedError
|
8509
8583
|
|
8510
|
-
|
8511
|
-
|
8512
|
-
|
8513
|
-
@abc.abstractmethod
|
8514
|
-
def exc_info(self) -> ta.Optional[LoggingExcInfo]:
|
8515
|
-
raise NotImplementedError
|
8584
|
+
@ta.final
|
8585
|
+
def __getitem__(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
|
8586
|
+
return self.get_info(ty)
|
8516
8587
|
|
8517
|
-
@
|
8518
|
-
|
8519
|
-
|
8520
|
-
|
8588
|
+
@ta.final
|
8589
|
+
def must_get_info(self, ty: ta.Type[LoggingContextInfoT]) -> LoggingContextInfoT:
|
8590
|
+
if (info := self.get_info(ty)) is None:
|
8591
|
+
raise TypeError(f'LoggingContextInfo absent: {ty}')
|
8592
|
+
return info
|
8521
8593
|
|
8522
|
-
#
|
8523
8594
|
|
8524
|
-
|
8525
|
-
|
8526
|
-
|
8595
|
+
@ta.final
|
8596
|
+
class SimpleLoggingContext(LoggingContext):
|
8597
|
+
def __init__(self, *infos: LoggingContextInfo) -> None:
|
8598
|
+
self._infos: ta.Dict[ta.Type[LoggingContextInfo], LoggingContextInfo] = {type(i): i for i in infos}
|
8527
8599
|
|
8528
|
-
|
8529
|
-
|
8530
|
-
raise NotImplementedError
|
8600
|
+
def get_info(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
|
8601
|
+
return self._infos.get(ty)
|
8531
8602
|
|
8532
|
-
#
|
8533
8603
|
|
8534
|
-
|
8535
|
-
def thread(self) -> ta.Optional[LoggingThreadInfo]:
|
8536
|
-
raise NotImplementedError
|
8604
|
+
##
|
8537
8605
|
|
8538
|
-
@abc.abstractmethod
|
8539
|
-
def process(self) -> ta.Optional[LoggingProcessInfo]:
|
8540
|
-
raise NotImplementedError
|
8541
8606
|
|
8607
|
+
class CaptureLoggingContext(LoggingContext, Abstract):
|
8542
8608
|
@abc.abstractmethod
|
8543
|
-
def
|
8544
|
-
|
8609
|
+
def set_basic(
|
8610
|
+
self,
|
8611
|
+
name: str,
|
8545
8612
|
|
8546
|
-
|
8547
|
-
|
8613
|
+
msg: ta.Union[str, tuple, LoggingMsgFn],
|
8614
|
+
args: tuple,
|
8615
|
+
) -> 'CaptureLoggingContext':
|
8548
8616
|
raise NotImplementedError
|
8549
8617
|
|
8618
|
+
#
|
8550
8619
|
|
8551
|
-
##
|
8552
|
-
|
8553
|
-
|
8554
|
-
class CaptureLoggingContext(LoggingContext, Abstract):
|
8555
8620
|
class AlreadyCapturedError(Exception):
|
8556
8621
|
pass
|
8557
8622
|
|
@@ -8582,80 +8647,50 @@ class CaptureLoggingContextImpl(CaptureLoggingContext):
|
|
8582
8647
|
|
8583
8648
|
exc_info: LoggingExcInfoArg = False,
|
8584
8649
|
|
8585
|
-
caller: ta.Union[
|
8650
|
+
caller: ta.Union[LoggingContextInfos.Caller, ta.Type[NOT_SET], None] = NOT_SET,
|
8586
8651
|
stack_offset: int = 0,
|
8587
8652
|
stack_info: bool = False,
|
8588
8653
|
) -> None:
|
8589
|
-
|
8590
|
-
|
8591
|
-
#
|
8654
|
+
# TODO: Name, Msg, Extra
|
8592
8655
|
|
8593
8656
|
if time_ns is None:
|
8594
8657
|
time_ns = time.time_ns()
|
8595
|
-
self._time_ns: int = time_ns
|
8596
|
-
|
8597
|
-
#
|
8598
8658
|
|
8599
|
-
|
8600
|
-
|
8601
|
-
|
8602
|
-
|
8603
|
-
|
8604
|
-
|
8605
|
-
elif exc_info is False:
|
8606
|
-
exc_info = None
|
8607
|
-
|
8608
|
-
if exc_info is not None:
|
8609
|
-
self._exc_info: ta.Optional[LoggingExcInfo] = exc_info
|
8610
|
-
if isinstance(exc_info, BaseException):
|
8611
|
-
self._exc_info_tuple: ta.Optional[LoggingExcInfoTuple] = (type(exc_info), exc_info, exc_info.__traceback__) # noqa
|
8612
|
-
else:
|
8613
|
-
self._exc_info_tuple = exc_info
|
8614
|
-
|
8615
|
-
#
|
8659
|
+
self._infos: ta.Dict[ta.Type[LoggingContextInfo], LoggingContextInfo] = {}
|
8660
|
+
self._set_info(
|
8661
|
+
LoggingContextInfos.Level.build(level),
|
8662
|
+
LoggingContextInfos.Time.build(time_ns),
|
8663
|
+
LoggingContextInfos.Exc.build(exc_info),
|
8664
|
+
)
|
8616
8665
|
|
8617
8666
|
if caller is not CaptureLoggingContextImpl.NOT_SET:
|
8618
|
-
self.
|
8667
|
+
self._infos[LoggingContextInfos.Caller] = caller
|
8619
8668
|
else:
|
8620
8669
|
self._stack_offset = stack_offset
|
8621
8670
|
self._stack_info = stack_info
|
8622
8671
|
|
8623
|
-
|
8624
|
-
|
8625
|
-
|
8626
|
-
|
8627
|
-
return self
|
8628
|
-
|
8629
|
-
#
|
8630
|
-
|
8631
|
-
@property
|
8632
|
-
def time_ns(self) -> int:
|
8633
|
-
return self._time_ns
|
8634
|
-
|
8635
|
-
_times: LoggingTimeFields
|
8636
|
-
|
8637
|
-
@property
|
8638
|
-
def times(self) -> LoggingTimeFields:
|
8639
|
-
try:
|
8640
|
-
return self._times
|
8641
|
-
except AttributeError:
|
8642
|
-
pass
|
8643
|
-
|
8644
|
-
times = self._times = LoggingTimeFields.build(self.time_ns)
|
8645
|
-
return times
|
8672
|
+
def _set_info(self, *infos: ta.Optional[LoggingContextInfo]) -> 'CaptureLoggingContextImpl':
|
8673
|
+
for info in infos:
|
8674
|
+
if info is not None:
|
8675
|
+
self._infos[type(info)] = info
|
8676
|
+
return self
|
8646
8677
|
|
8647
|
-
|
8678
|
+
def get_info(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
|
8679
|
+
return self._infos.get(ty)
|
8648
8680
|
|
8649
|
-
|
8650
|
-
_exc_info_tuple: ta.Optional[LoggingExcInfoTuple] = None
|
8681
|
+
##
|
8651
8682
|
|
8652
|
-
|
8653
|
-
|
8654
|
-
|
8683
|
+
def set_basic(
|
8684
|
+
self,
|
8685
|
+
name: str,
|
8655
8686
|
|
8656
|
-
|
8657
|
-
|
8658
|
-
|
8687
|
+
msg: ta.Union[str, tuple, LoggingMsgFn],
|
8688
|
+
args: tuple,
|
8689
|
+
) -> 'CaptureLoggingContextImpl':
|
8690
|
+
return self._set_info(
|
8691
|
+
LoggingContextInfos.Name(name),
|
8692
|
+
LoggingContextInfos.Msg.build(msg, *args),
|
8693
|
+
)
|
8659
8694
|
|
8660
8695
|
##
|
8661
8696
|
|
@@ -8669,74 +8704,28 @@ class CaptureLoggingContextImpl(CaptureLoggingContext):
|
|
8669
8704
|
|
8670
8705
|
_has_captured: bool = False
|
8671
8706
|
|
8672
|
-
_caller: ta.Optional[LoggingCaller]
|
8673
|
-
_source_file: ta.Optional[LoggingSourceFileInfo]
|
8674
|
-
|
8675
|
-
_thread: ta.Optional[LoggingThreadInfo]
|
8676
|
-
_process: ta.Optional[LoggingProcessInfo]
|
8677
|
-
_multiprocessing: ta.Optional[LoggingMultiprocessingInfo]
|
8678
|
-
_asyncio_task: ta.Optional[LoggingAsyncioTaskInfo]
|
8679
|
-
|
8680
8707
|
def capture(self) -> None:
|
8681
8708
|
if self._has_captured:
|
8682
8709
|
raise CaptureLoggingContextImpl.AlreadyCapturedError
|
8683
8710
|
self._has_captured = True
|
8684
8711
|
|
8685
|
-
if not
|
8686
|
-
self.
|
8712
|
+
if LoggingContextInfos.Caller not in self._infos:
|
8713
|
+
self._set_info(LoggingContextInfos.Caller.build(
|
8687
8714
|
self._stack_offset + 1,
|
8688
8715
|
stack_info=self._stack_info,
|
8689
|
-
)
|
8690
|
-
|
8691
|
-
if (caller := self._caller) is not None:
|
8692
|
-
self._source_file = LoggingSourceFileInfo.build(caller.file_path)
|
8693
|
-
else:
|
8694
|
-
self._source_file = None
|
8695
|
-
|
8696
|
-
self._thread = LoggingThreadInfo.build()
|
8697
|
-
self._process = LoggingProcessInfo.build()
|
8698
|
-
self._multiprocessing = LoggingMultiprocessingInfo.build()
|
8699
|
-
self._asyncio_task = LoggingAsyncioTaskInfo.build()
|
8700
|
-
|
8701
|
-
#
|
8702
|
-
|
8703
|
-
def caller(self) -> ta.Optional[LoggingCaller]:
|
8704
|
-
try:
|
8705
|
-
return self._caller
|
8706
|
-
except AttributeError:
|
8707
|
-
raise CaptureLoggingContext.NotCapturedError from None
|
8708
|
-
|
8709
|
-
def source_file(self) -> ta.Optional[LoggingSourceFileInfo]:
|
8710
|
-
try:
|
8711
|
-
return self._source_file
|
8712
|
-
except AttributeError:
|
8713
|
-
raise CaptureLoggingContext.NotCapturedError from None
|
8714
|
-
|
8715
|
-
#
|
8716
|
-
|
8717
|
-
def thread(self) -> ta.Optional[LoggingThreadInfo]:
|
8718
|
-
try:
|
8719
|
-
return self._thread
|
8720
|
-
except AttributeError:
|
8721
|
-
raise CaptureLoggingContext.NotCapturedError from None
|
8722
|
-
|
8723
|
-
def process(self) -> ta.Optional[LoggingProcessInfo]:
|
8724
|
-
try:
|
8725
|
-
return self._process
|
8726
|
-
except AttributeError:
|
8727
|
-
raise CaptureLoggingContext.NotCapturedError from None
|
8716
|
+
))
|
8728
8717
|
|
8729
|
-
|
8730
|
-
|
8731
|
-
|
8732
|
-
|
8733
|
-
raise CaptureLoggingContext.NotCapturedError from None
|
8718
|
+
if (caller := self[LoggingContextInfos.Caller]) is not None:
|
8719
|
+
self._set_info(LoggingContextInfos.SourceFile.build(
|
8720
|
+
caller.file_path,
|
8721
|
+
))
|
8734
8722
|
|
8735
|
-
|
8736
|
-
|
8737
|
-
|
8738
|
-
|
8739
|
-
|
8723
|
+
self._set_info(
|
8724
|
+
LoggingContextInfos.Thread.build(),
|
8725
|
+
LoggingContextInfos.Process.build(),
|
8726
|
+
LoggingContextInfos.Multiprocessing.build(),
|
8727
|
+
LoggingContextInfos.AsyncioTask.build(),
|
8728
|
+
)
|
8740
8729
|
|
8741
8730
|
|
8742
8731
|
########################################
|
@@ -8813,10 +8802,14 @@ def _locking_logging_module_lock() -> ta.Iterator[None]:
|
|
8813
8802
|
def configure_standard_logging(
|
8814
8803
|
level: ta.Union[int, str] = logging.INFO,
|
8815
8804
|
*,
|
8816
|
-
json: bool = False,
|
8817
8805
|
target: ta.Optional[logging.Logger] = None,
|
8806
|
+
|
8818
8807
|
force: bool = False,
|
8808
|
+
|
8819
8809
|
handler_factory: ta.Optional[ta.Callable[[], logging.Handler]] = None,
|
8810
|
+
|
8811
|
+
formatter: ta.Optional[logging.Formatter] = None, # noqa
|
8812
|
+
json: bool = False,
|
8820
8813
|
) -> ta.Optional[StandardConfiguredLoggingHandler]:
|
8821
8814
|
with _locking_logging_module_lock():
|
8822
8815
|
if target is None:
|
@@ -8837,11 +8830,11 @@ def configure_standard_logging(
|
|
8837
8830
|
|
8838
8831
|
#
|
8839
8832
|
|
8840
|
-
formatter:
|
8841
|
-
|
8842
|
-
|
8843
|
-
|
8844
|
-
|
8833
|
+
if formatter is None:
|
8834
|
+
if json:
|
8835
|
+
formatter = JsonLoggingFormatter()
|
8836
|
+
else:
|
8837
|
+
formatter = StandardLoggingFormatter(StandardLoggingFormatter.build_log_format(STANDARD_LOG_FORMAT_PARTS)) # noqa
|
8845
8838
|
handler.setFormatter(formatter)
|
8846
8839
|
|
8847
8840
|
#
|
@@ -9762,36 +9755,6 @@ class AnyLogger(Abstract, ta.Generic[T]):
|
|
9762
9755
|
|
9763
9756
|
##
|
9764
9757
|
|
9765
|
-
@classmethod
|
9766
|
-
def _prepare_msg_args(cls, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any) -> ta.Tuple[str, tuple]:
|
9767
|
-
if callable(msg):
|
9768
|
-
if args:
|
9769
|
-
raise TypeError(f'Must not provide both a message function and args: {msg=} {args=}')
|
9770
|
-
x = msg()
|
9771
|
-
if isinstance(x, str):
|
9772
|
-
return x, ()
|
9773
|
-
elif isinstance(x, tuple):
|
9774
|
-
if x:
|
9775
|
-
return x[0], x[1:]
|
9776
|
-
else:
|
9777
|
-
return '', ()
|
9778
|
-
else:
|
9779
|
-
raise TypeError(x)
|
9780
|
-
|
9781
|
-
elif isinstance(msg, tuple):
|
9782
|
-
if args:
|
9783
|
-
raise TypeError(f'Must not provide both a tuple message and args: {msg=} {args=}')
|
9784
|
-
if msg:
|
9785
|
-
return msg[0], msg[1:]
|
9786
|
-
else:
|
9787
|
-
return '', ()
|
9788
|
-
|
9789
|
-
elif isinstance(msg, str):
|
9790
|
-
return msg, args
|
9791
|
-
|
9792
|
-
else:
|
9793
|
-
raise TypeError(msg)
|
9794
|
-
|
9795
9758
|
@abc.abstractmethod
|
9796
9759
|
def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any, **kwargs: ta.Any) -> T: # noqa
|
9797
9760
|
raise NotImplementedError
|
@@ -9832,144 +9795,560 @@ class AsyncNopLogger(AnyNopLogger[ta.Awaitable[None]], AsyncLogger):
|
|
9832
9795
|
|
9833
9796
|
########################################
|
9834
9797
|
# ../../../omlish/logs/std/records.py
|
9798
|
+
"""
|
9799
|
+
TODO:
|
9800
|
+
- TypedDict?
|
9801
|
+
"""
|
9835
9802
|
|
9836
9803
|
|
9837
9804
|
##
|
9838
9805
|
|
9839
9806
|
|
9840
|
-
|
9841
|
-
#
|
9842
|
-
#
|
9843
|
-
#
|
9844
|
-
#
|
9845
|
-
# - https://github.com/python/cpython/blob/
|
9846
|
-
#
|
9847
|
-
#
|
9848
|
-
|
9849
|
-
|
9850
|
-
|
9851
|
-
# - lineno: int - May be 0.
|
9852
|
-
# - msg: str
|
9853
|
-
# - args: tuple | dict | 1-tuple[dict]
|
9854
|
-
# - exc_info: LoggingExcInfoTuple | None
|
9855
|
-
# - func: str | None = None -> funcName
|
9856
|
-
# - sinfo: str | None = None -> stack_info
|
9857
|
-
#
|
9858
|
-
KNOWN_STD_LOGGING_RECORD_ATTRS: ta.Dict[str, ta.Any] = dict(
|
9859
|
-
# Name of the logger used to log the call. Unmodified by ctor.
|
9860
|
-
name=str,
|
9807
|
+
class LoggingContextInfoRecordAdapters:
|
9808
|
+
# Ref:
|
9809
|
+
# - https://docs.python.org/3/library/logging.html#logrecord-attributes
|
9810
|
+
#
|
9811
|
+
# LogRecord:
|
9812
|
+
# - https://github.com/python/cpython/blob/39b2f82717a69dde7212bc39b673b0f55c99e6a3/Lib/logging/__init__.py#L276 (3.8) # noqa
|
9813
|
+
# - https://github.com/python/cpython/blob/f070f54c5f4a42c7c61d1d5d3b8f3b7203b4a0fb/Lib/logging/__init__.py#L286 (~3.14) # noqa
|
9814
|
+
#
|
9815
|
+
|
9816
|
+
def __new__(cls, *args, **kwargs): # noqa
|
9817
|
+
raise TypeError
|
9861
9818
|
|
9862
|
-
|
9863
|
-
|
9864
|
-
|
9819
|
+
class Adapter(Abstract, ta.Generic[T]):
|
9820
|
+
@property
|
9821
|
+
@abc.abstractmethod
|
9822
|
+
def info_cls(self) -> ta.Type[LoggingContextInfo]:
|
9823
|
+
raise NotImplementedError
|
9865
9824
|
|
9866
|
-
|
9867
|
-
# there is only one argument, and it is a dictionary). Ctor will transform a 1-tuple containing a Mapping into just
|
9868
|
-
# the mapping, but is otherwise unmodified.
|
9869
|
-
args=ta.Union[tuple, dict],
|
9825
|
+
#
|
9870
9826
|
|
9871
|
-
|
9827
|
+
@ta.final
|
9828
|
+
class NOT_SET: # noqa
|
9829
|
+
def __new__(cls, *args, **kwargs): # noqa
|
9830
|
+
raise TypeError
|
9872
9831
|
|
9873
|
-
|
9874
|
-
|
9875
|
-
|
9832
|
+
class RecordAttr(ta.NamedTuple):
|
9833
|
+
name: str
|
9834
|
+
type: ta.Any
|
9835
|
+
default: ta.Any
|
9876
9836
|
|
9877
|
-
|
9878
|
-
|
9837
|
+
# @abc.abstractmethod
|
9838
|
+
record_attrs: ta.ClassVar[ta.Mapping[str, RecordAttr]]
|
9879
9839
|
|
9880
|
-
|
9840
|
+
@property
|
9841
|
+
@abc.abstractmethod
|
9842
|
+
def _record_attrs(self) -> ta.Union[
|
9843
|
+
ta.Mapping[str, ta.Any],
|
9844
|
+
ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]],
|
9845
|
+
]:
|
9846
|
+
raise NotImplementedError
|
9881
9847
|
|
9882
|
-
|
9883
|
-
# to "(unknown file)" by Logger.findCaller / Logger._log.
|
9884
|
-
pathname=str,
|
9848
|
+
#
|
9885
9849
|
|
9886
|
-
|
9887
|
-
|
9850
|
+
@abc.abstractmethod
|
9851
|
+
def context_to_record(self, ctx: LoggingContext) -> ta.Mapping[str, ta.Any]:
|
9852
|
+
raise NotImplementedError
|
9888
9853
|
|
9889
|
-
|
9890
|
-
# "Unknown module".
|
9891
|
-
module=str,
|
9854
|
+
#
|
9892
9855
|
|
9893
|
-
|
9856
|
+
@abc.abstractmethod
|
9857
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[T]:
|
9858
|
+
raise NotImplementedError
|
9894
9859
|
|
9895
|
-
|
9896
|
-
exc_info=ta.Optional[LoggingExcInfoTuple],
|
9860
|
+
#
|
9897
9861
|
|
9898
|
-
|
9899
|
-
|
9862
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
9863
|
+
super().__init_subclass__(**kwargs)
|
9900
9864
|
|
9901
|
-
|
9865
|
+
if Abstract in cls.__bases__:
|
9866
|
+
return
|
9902
9867
|
|
9903
|
-
|
9904
|
-
|
9905
|
-
|
9906
|
-
|
9907
|
-
|
9868
|
+
if 'record_attrs' in cls.__dict__:
|
9869
|
+
raise TypeError(cls)
|
9870
|
+
if not isinstance(ra := cls.__dict__['_record_attrs'], collections.abc.Mapping):
|
9871
|
+
raise TypeError(ra)
|
9872
|
+
|
9873
|
+
rd: ta.Dict[str, LoggingContextInfoRecordAdapters.Adapter.RecordAttr] = {}
|
9874
|
+
for n, v in ra.items():
|
9875
|
+
if not n or not isinstance(n, str) or n in rd:
|
9876
|
+
raise AttributeError(n)
|
9877
|
+
if isinstance(v, tuple):
|
9878
|
+
t, d = v
|
9879
|
+
else:
|
9880
|
+
t, d = v, cls.NOT_SET
|
9881
|
+
rd[n] = cls.RecordAttr(
|
9882
|
+
name=n,
|
9883
|
+
type=t,
|
9884
|
+
default=d,
|
9885
|
+
)
|
9886
|
+
cls.record_attrs = rd
|
9908
9887
|
|
9909
|
-
|
9910
|
-
|
9911
|
-
|
9888
|
+
class RequiredAdapter(Adapter[T], Abstract):
|
9889
|
+
@property
|
9890
|
+
@abc.abstractmethod
|
9891
|
+
def _record_attrs(self) -> ta.Mapping[str, ta.Any]:
|
9892
|
+
raise NotImplementedError
|
9912
9893
|
|
9913
|
-
|
9914
|
-
# "(unknown function)" by Logger.findCaller / Logger._log.
|
9915
|
-
funcName=str,
|
9894
|
+
#
|
9916
9895
|
|
9917
|
-
|
9896
|
+
@ta.final
|
9897
|
+
def context_to_record(self, ctx: LoggingContext) -> ta.Mapping[str, ta.Any]:
|
9898
|
+
if (info := ctx.get_info(self.info_cls)) is not None:
|
9899
|
+
return self._info_to_record(info)
|
9900
|
+
else:
|
9901
|
+
raise TypeError # FIXME: fallback?
|
9918
9902
|
|
9919
|
-
|
9920
|
-
|
9921
|
-
|
9922
|
-
# - https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
|
9923
|
-
# - https://github.com/python/cpython/commit/1500a23f33f5a6d052ff1ef6383d9839928b8ff1
|
9924
|
-
#
|
9925
|
-
created=float,
|
9903
|
+
@abc.abstractmethod
|
9904
|
+
def _info_to_record(self, info: T) -> ta.Mapping[str, ta.Any]:
|
9905
|
+
raise NotImplementedError
|
9926
9906
|
|
9927
|
-
|
9928
|
-
msecs=float,
|
9907
|
+
#
|
9929
9908
|
|
9930
|
-
|
9931
|
-
|
9909
|
+
@abc.abstractmethod
|
9910
|
+
def record_to_info(self, rec: logging.LogRecord) -> T:
|
9911
|
+
raise NotImplementedError
|
9932
9912
|
|
9933
|
-
|
9913
|
+
#
|
9934
9914
|
|
9935
|
-
|
9936
|
-
|
9915
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
9916
|
+
super().__init_subclass__(**kwargs)
|
9937
9917
|
|
9938
|
-
|
9939
|
-
|
9918
|
+
if any(a.default is not cls.NOT_SET for a in cls.record_attrs.values()):
|
9919
|
+
raise TypeError(cls.record_attrs)
|
9940
9920
|
|
9941
|
-
|
9921
|
+
class OptionalAdapter(Adapter[T], Abstract, ta.Generic[T]):
|
9922
|
+
@property
|
9923
|
+
@abc.abstractmethod
|
9924
|
+
def _record_attrs(self) -> ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]:
|
9925
|
+
raise NotImplementedError
|
9926
|
+
|
9927
|
+
record_defaults: ta.ClassVar[ta.Mapping[str, ta.Any]]
|
9928
|
+
|
9929
|
+
#
|
9930
|
+
|
9931
|
+
@ta.final
|
9932
|
+
def context_to_record(self, ctx: LoggingContext) -> ta.Mapping[str, ta.Any]:
|
9933
|
+
if (info := ctx.get_info(self.info_cls)) is not None:
|
9934
|
+
return self._info_to_record(info)
|
9935
|
+
else:
|
9936
|
+
return self.record_defaults
|
9937
|
+
|
9938
|
+
@abc.abstractmethod
|
9939
|
+
def _info_to_record(self, info: T) -> ta.Mapping[str, ta.Any]:
|
9940
|
+
raise NotImplementedError
|
9941
|
+
|
9942
|
+
#
|
9943
|
+
|
9944
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
9945
|
+
super().__init_subclass__(**kwargs)
|
9946
|
+
|
9947
|
+
dd: ta.Dict[str, ta.Any] = {a.name: a.default for a in cls.record_attrs.values()}
|
9948
|
+
if any(d is cls.NOT_SET for d in dd.values()):
|
9949
|
+
raise TypeError(cls.record_attrs)
|
9950
|
+
cls.record_defaults = dd
|
9942
9951
|
|
9943
|
-
# Process name if available. Set to None if `logging.logMultiprocessing` is not truthy. Otherwise, set to
|
9944
|
-
# 'MainProcess', then `sys.modules.get('multiprocessing').current_process().name` if that works, otherwise remains
|
9945
|
-
# as 'MainProcess'.
|
9946
9952
|
#
|
9947
|
-
|
9953
|
+
|
9954
|
+
class Name(RequiredAdapter[LoggingContextInfos.Name]):
|
9955
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Name]] = LoggingContextInfos.Name
|
9956
|
+
|
9957
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
|
9958
|
+
# Name of the logger used to log the call. Unmodified by ctor.
|
9959
|
+
name=str,
|
9960
|
+
)
|
9961
|
+
|
9962
|
+
def _info_to_record(self, info: LoggingContextInfos.Name) -> ta.Mapping[str, ta.Any]:
|
9963
|
+
return dict(
|
9964
|
+
name=info.name,
|
9965
|
+
)
|
9966
|
+
|
9967
|
+
def record_to_info(self, rec: logging.LogRecord) -> LoggingContextInfos.Name:
|
9968
|
+
return LoggingContextInfos.Name(
|
9969
|
+
name=rec.name,
|
9970
|
+
)
|
9971
|
+
|
9972
|
+
class Level(RequiredAdapter[LoggingContextInfos.Level]):
|
9973
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Level]] = LoggingContextInfos.Level
|
9974
|
+
|
9975
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
|
9976
|
+
# Text logging level for the message ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'). Set to
|
9977
|
+
# `getLevelName(level)`.
|
9978
|
+
levelname=str,
|
9979
|
+
|
9980
|
+
# Numeric logging level for the message (DEBUG, INFO, WARNING, ERROR, CRITICAL). Unmodified by ctor.
|
9981
|
+
levelno=int,
|
9982
|
+
)
|
9983
|
+
|
9984
|
+
def _info_to_record(self, info: LoggingContextInfos.Level) -> ta.Mapping[str, ta.Any]:
|
9985
|
+
return dict(
|
9986
|
+
levelname=info.name,
|
9987
|
+
levelno=int(info.level),
|
9988
|
+
)
|
9989
|
+
|
9990
|
+
def record_to_info(self, rec: logging.LogRecord) -> LoggingContextInfos.Level:
|
9991
|
+
return LoggingContextInfos.Level.build(rec.levelno)
|
9992
|
+
|
9993
|
+
class Msg(RequiredAdapter[LoggingContextInfos.Msg]):
|
9994
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Msg]] = LoggingContextInfos.Msg
|
9995
|
+
|
9996
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
|
9997
|
+
# The format string passed in the original logging call. Merged with args to produce message, or an
|
9998
|
+
# arbitrary object (see Using arbitrary objects as messages). Unmodified by ctor.
|
9999
|
+
msg=str,
|
10000
|
+
|
10001
|
+
# The tuple of arguments merged into msg to produce message, or a dict whose values are used for the merge
|
10002
|
+
# (when there is only one argument, and it is a dictionary). Ctor will transform a 1-tuple containing a
|
10003
|
+
# Mapping into just the mapping, but is otherwise unmodified.
|
10004
|
+
args=ta.Union[tuple, dict, None],
|
10005
|
+
)
|
10006
|
+
|
10007
|
+
def _info_to_record(self, info: LoggingContextInfos.Msg) -> ta.Mapping[str, ta.Any]:
|
10008
|
+
return dict(
|
10009
|
+
msg=info.msg,
|
10010
|
+
args=info.args,
|
10011
|
+
)
|
10012
|
+
|
10013
|
+
def record_to_info(self, rec: logging.LogRecord) -> LoggingContextInfos.Msg:
|
10014
|
+
return LoggingContextInfos.Msg(
|
10015
|
+
msg=rec.msg,
|
10016
|
+
args=rec.args,
|
10017
|
+
)
|
10018
|
+
|
10019
|
+
# FIXME: handled specially - all unknown attrs on LogRecord
|
10020
|
+
# class Extra(Adapter[LoggingContextInfos.Extra]):
|
10021
|
+
# _record_attrs: ta.ClassVar[ta.Mapping[str, ta.Union[ta.Any, ta.Tuple[ta.Any, ta.Any]]]] = dict()
|
9948
10022
|
#
|
9949
|
-
#
|
9950
|
-
#
|
10023
|
+
# def info_to_record(self, info: ta.Optional[LoggingContextInfos.Extra]) -> ta.Mapping[str, ta.Any]:
|
10024
|
+
# # FIXME:
|
10025
|
+
# # if extra is not None:
|
10026
|
+
# # for key in extra:
|
10027
|
+
# # if (key in ["message", "asctime"]) or (key in rv.__dict__):
|
10028
|
+
# # raise KeyError("Attempt to overwrite %r in LogRecord" % key)
|
10029
|
+
# # rv.__dict__[key] = extra[key]
|
10030
|
+
# return dict()
|
9951
10031
|
#
|
9952
|
-
|
10032
|
+
# def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Extra]:
|
10033
|
+
# return None
|
9953
10034
|
|
9954
|
-
|
9955
|
-
|
9956
|
-
process=ta.Optional[int],
|
10035
|
+
class Time(RequiredAdapter[LoggingContextInfos.Time]):
|
10036
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Time]] = LoggingContextInfos.Time
|
9957
10037
|
|
9958
|
-
|
10038
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
|
10039
|
+
# Time when the LogRecord was created. Set to `time.time_ns() / 1e9` for >=3.13.0b1, otherwise simply
|
10040
|
+
# `time.time()`.
|
10041
|
+
#
|
10042
|
+
# See:
|
10043
|
+
# - https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
|
10044
|
+
# - https://github.com/python/cpython/commit/1500a23f33f5a6d052ff1ef6383d9839928b8ff1
|
10045
|
+
#
|
10046
|
+
created=float,
|
9959
10047
|
|
9960
|
-
|
9961
|
-
|
9962
|
-
|
9963
|
-
|
10048
|
+
# Millisecond portion of the time when the LogRecord was created.
|
10049
|
+
msecs=float,
|
10050
|
+
|
10051
|
+
# Time in milliseconds when the LogRecord was created, relative to the time the logging module was loaded.
|
10052
|
+
relativeCreated=float,
|
10053
|
+
)
|
10054
|
+
|
10055
|
+
def _info_to_record(self, info: LoggingContextInfos.Time) -> ta.Mapping[str, ta.Any]:
|
10056
|
+
return dict(
|
10057
|
+
created=info.secs,
|
10058
|
+
msecs=info.msecs,
|
10059
|
+
relativeCreated=info.relative_secs,
|
10060
|
+
)
|
10061
|
+
|
10062
|
+
def record_to_info(self, rec: logging.LogRecord) -> LoggingContextInfos.Time:
|
10063
|
+
return LoggingContextInfos.Time.build(
|
10064
|
+
int(rec.created * 1e9),
|
10065
|
+
)
|
10066
|
+
|
10067
|
+
class Exc(OptionalAdapter[LoggingContextInfos.Exc]):
|
10068
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Exc]] = LoggingContextInfos.Exc
|
10069
|
+
|
10070
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
|
10071
|
+
# Exception tuple (à la sys.exc_info) or, if no exception has occurred, None. Unmodified by ctor.
|
10072
|
+
exc_info=(ta.Optional[LoggingExcInfoTuple], None),
|
10073
|
+
|
10074
|
+
# Used to cache the traceback text. Simply set to None by ctor, later set by Formatter.format.
|
10075
|
+
exc_text=(ta.Optional[str], None),
|
10076
|
+
)
|
10077
|
+
|
10078
|
+
def _info_to_record(self, info: LoggingContextInfos.Exc) -> ta.Mapping[str, ta.Any]:
|
10079
|
+
return dict(
|
10080
|
+
exc_info=info.info_tuple,
|
10081
|
+
exc_text=None,
|
10082
|
+
)
|
10083
|
+
|
10084
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Exc]:
|
10085
|
+
# FIXME:
|
10086
|
+
# error: Argument 1 to "build" of "Exc" has incompatible type
|
10087
|
+
# "tuple[type[BaseException], BaseException, TracebackType | None] | tuple[None, None, None] | None"; expected # noqa
|
10088
|
+
# "BaseException | tuple[type[BaseException], BaseException, TracebackType | None] | bool | None" [arg-type] # noqa
|
10089
|
+
return LoggingContextInfos.Exc.build(rec.exc_info) # type: ignore[arg-type]
|
9964
10090
|
|
9965
|
-
|
10091
|
+
class Caller(OptionalAdapter[LoggingContextInfos.Caller]):
|
10092
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Caller]] = LoggingContextInfos.Caller
|
10093
|
+
|
10094
|
+
_UNKNOWN_PATH_NAME: ta.ClassVar[str] = '(unknown file)'
|
10095
|
+
_UNKNOWN_FUNC_NAME: ta.ClassVar[str] = '(unknown function)'
|
10096
|
+
|
10097
|
+
_STACK_INFO_PREFIX: ta.ClassVar[str] = 'Stack (most recent call last):\n'
|
10098
|
+
|
10099
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
|
10100
|
+
# Full pathname of the source file where the logging call was issued (if available). Unmodified by ctor. May
|
10101
|
+
# default to "(unknown file)" by Logger.findCaller / Logger._log.
|
10102
|
+
pathname=(str, _UNKNOWN_PATH_NAME),
|
10103
|
+
|
10104
|
+
# Source line number where the logging call was issued (if available). Unmodified by ctor. May default to 0
|
10105
|
+
# y Logger.findCaller / Logger._log.
|
10106
|
+
lineno=(int, 0),
|
10107
|
+
|
10108
|
+
# Name of function containing the logging call. Set by ctor to `func` arg, unmodified. May default to
|
10109
|
+
# "(unknown function)" by Logger.findCaller / Logger._log.
|
10110
|
+
funcName=(str, _UNKNOWN_FUNC_NAME),
|
10111
|
+
|
10112
|
+
# Stack frame information (where available) from the bottom of the stack in the current thread, up to and
|
10113
|
+
# including the stack frame of the logging call which resulted in the creation of this record. Set by ctor
|
10114
|
+
# to `sinfo` arg, unmodified. Mostly set, if requested, by `Logger.findCaller`, to
|
10115
|
+
# `traceback.print_stack(f)`, but prepended with the literal "Stack (most recent call last):\n", and
|
10116
|
+
# stripped of exactly one trailing `\n` if present.
|
10117
|
+
stack_info=(ta.Optional[str], None),
|
10118
|
+
)
|
10119
|
+
|
10120
|
+
def _info_to_record(self, caller: LoggingContextInfos.Caller) -> ta.Mapping[str, ta.Any]:
|
10121
|
+
if (sinfo := caller.stack_info) is not None:
|
10122
|
+
stack_info: ta.Optional[str] = '\n'.join([
|
10123
|
+
self._STACK_INFO_PREFIX,
|
10124
|
+
sinfo[1:] if sinfo.endswith('\n') else sinfo,
|
10125
|
+
])
|
10126
|
+
else:
|
10127
|
+
stack_info = None
|
10128
|
+
|
10129
|
+
return dict(
|
10130
|
+
pathname=caller.file_path,
|
10131
|
+
|
10132
|
+
lineno=caller.line_no,
|
10133
|
+
funcName=caller.func_name,
|
10134
|
+
|
10135
|
+
stack_info=stack_info,
|
10136
|
+
)
|
10137
|
+
|
10138
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Caller]:
|
10139
|
+
# FIXME: piecemeal?
|
10140
|
+
if (
|
10141
|
+
rec.pathname != self._UNKNOWN_PATH_NAME and
|
10142
|
+
rec.lineno != 0 and
|
10143
|
+
rec.funcName != self._UNKNOWN_FUNC_NAME
|
10144
|
+
):
|
10145
|
+
if (sinfo := rec.stack_info) is not None and sinfo.startswith(self._STACK_INFO_PREFIX):
|
10146
|
+
sinfo = sinfo[len(self._STACK_INFO_PREFIX):]
|
10147
|
+
return LoggingContextInfos.Caller(
|
10148
|
+
file_path=rec.pathname,
|
10149
|
+
|
10150
|
+
line_no=rec.lineno,
|
10151
|
+
func_name=rec.funcName,
|
10152
|
+
|
10153
|
+
stack_info=sinfo,
|
10154
|
+
)
|
10155
|
+
|
10156
|
+
return None
|
10157
|
+
|
10158
|
+
class SourceFile(Adapter[LoggingContextInfos.SourceFile]):
|
10159
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.SourceFile]] = LoggingContextInfos.SourceFile
|
10160
|
+
|
10161
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
|
10162
|
+
# Filename portion of pathname. Set to `os.path.basename(pathname)` if successful, otherwise defaults to
|
10163
|
+
# pathname.
|
10164
|
+
filename=str,
|
10165
|
+
|
10166
|
+
# Module (name portion of filename). Set to `os.path.splitext(filename)[0]`, otherwise defaults to
|
10167
|
+
# "Unknown module".
|
10168
|
+
module=str,
|
10169
|
+
)
|
10170
|
+
|
10171
|
+
_UNKNOWN_MODULE: ta.ClassVar[str] = 'Unknown module'
|
10172
|
+
|
10173
|
+
def context_to_record(self, ctx: LoggingContext) -> ta.Mapping[str, ta.Any]:
|
10174
|
+
if (info := ctx.get_info(LoggingContextInfos.SourceFile)) is not None:
|
10175
|
+
return dict(
|
10176
|
+
filename=info.file_name,
|
10177
|
+
module=info.module,
|
10178
|
+
)
|
10179
|
+
|
10180
|
+
if (caller := ctx.get_info(LoggingContextInfos.Caller)) is not None:
|
10181
|
+
return dict(
|
10182
|
+
filename=caller.file_path,
|
10183
|
+
module=self._UNKNOWN_MODULE,
|
10184
|
+
)
|
10185
|
+
|
10186
|
+
return dict(
|
10187
|
+
filename=LoggingContextInfoRecordAdapters.Caller._UNKNOWN_PATH_NAME, # noqa
|
10188
|
+
module=self._UNKNOWN_MODULE,
|
10189
|
+
)
|
10190
|
+
|
10191
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.SourceFile]:
|
10192
|
+
if (
|
10193
|
+
rec.module is not None and
|
10194
|
+
rec.module != self._UNKNOWN_MODULE
|
10195
|
+
):
|
10196
|
+
return LoggingContextInfos.SourceFile(
|
10197
|
+
file_name=rec.filename,
|
10198
|
+
module=rec.module, # FIXME: piecemeal?
|
10199
|
+
)
|
10200
|
+
|
10201
|
+
return None
|
10202
|
+
|
10203
|
+
class Thread(OptionalAdapter[LoggingContextInfos.Thread]):
|
10204
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Thread]] = LoggingContextInfos.Thread
|
10205
|
+
|
10206
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
|
10207
|
+
# Thread ID if available, and `logging.logThreads` is truthy.
|
10208
|
+
thread=(ta.Optional[int], None),
|
10209
|
+
|
10210
|
+
# Thread name if available, and `logging.logThreads` is truthy.
|
10211
|
+
threadName=(ta.Optional[str], None),
|
10212
|
+
)
|
10213
|
+
|
10214
|
+
def _info_to_record(self, info: LoggingContextInfos.Thread) -> ta.Mapping[str, ta.Any]:
|
10215
|
+
if logging.logThreads:
|
10216
|
+
return dict(
|
10217
|
+
thread=info.ident,
|
10218
|
+
threadName=info.name,
|
10219
|
+
)
|
10220
|
+
|
10221
|
+
return self.record_defaults
|
10222
|
+
|
10223
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Thread]:
|
10224
|
+
if (
|
10225
|
+
(ident := rec.thread) is not None and
|
10226
|
+
(name := rec.threadName) is not None
|
10227
|
+
):
|
10228
|
+
return LoggingContextInfos.Thread(
|
10229
|
+
ident=ident,
|
10230
|
+
native_id=None,
|
10231
|
+
name=name,
|
10232
|
+
)
|
10233
|
+
|
10234
|
+
return None
|
10235
|
+
|
10236
|
+
class Process(OptionalAdapter[LoggingContextInfos.Process]):
|
10237
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Process]] = LoggingContextInfos.Process
|
10238
|
+
|
10239
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
|
10240
|
+
# Process ID if available - that is, if `hasattr(os, 'getpid')` - and `logging.logProcesses` is truthy,
|
10241
|
+
# otherwise None.
|
10242
|
+
process=(ta.Optional[int], None),
|
10243
|
+
)
|
10244
|
+
|
10245
|
+
def _info_to_record(self, info: LoggingContextInfos.Process) -> ta.Mapping[str, ta.Any]:
|
10246
|
+
if logging.logProcesses:
|
10247
|
+
return dict(
|
10248
|
+
process=info.pid,
|
10249
|
+
)
|
10250
|
+
|
10251
|
+
return self.record_defaults
|
10252
|
+
|
10253
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Process]:
|
10254
|
+
if (
|
10255
|
+
(pid := rec.process) is not None
|
10256
|
+
):
|
10257
|
+
return LoggingContextInfos.Process(
|
10258
|
+
pid=pid,
|
10259
|
+
)
|
10260
|
+
|
10261
|
+
return None
|
10262
|
+
|
10263
|
+
class Multiprocessing(OptionalAdapter[LoggingContextInfos.Multiprocessing]):
|
10264
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Multiprocessing]] = LoggingContextInfos.Multiprocessing
|
10265
|
+
|
10266
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
|
10267
|
+
# Process name if available. Set to None if `logging.logMultiprocessing` is not truthy. Otherwise, set to
|
10268
|
+
# 'MainProcess', then `sys.modules.get('multiprocessing').current_process().name` if that works, otherwise
|
10269
|
+
# remains as 'MainProcess'.
|
10270
|
+
#
|
10271
|
+
# As noted by stdlib:
|
10272
|
+
#
|
10273
|
+
# Errors may occur if multiprocessing has not finished loading yet - e.g. if a custom import hook causes
|
10274
|
+
# third-party code to run when multiprocessing calls import. See issue 8200 for an example
|
10275
|
+
#
|
10276
|
+
processName=(ta.Optional[str], None),
|
10277
|
+
)
|
10278
|
+
|
10279
|
+
def _info_to_record(self, info: LoggingContextInfos.Multiprocessing) -> ta.Mapping[str, ta.Any]:
|
10280
|
+
if logging.logMultiprocessing:
|
10281
|
+
return dict(
|
10282
|
+
processName=info.process_name,
|
10283
|
+
)
|
10284
|
+
|
10285
|
+
return self.record_defaults
|
10286
|
+
|
10287
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Multiprocessing]:
|
10288
|
+
if (
|
10289
|
+
(process_name := rec.processName) is not None
|
10290
|
+
):
|
10291
|
+
return LoggingContextInfos.Multiprocessing(
|
10292
|
+
process_name=process_name,
|
10293
|
+
)
|
10294
|
+
|
10295
|
+
return None
|
10296
|
+
|
10297
|
+
class AsyncioTask(OptionalAdapter[LoggingContextInfos.AsyncioTask]):
|
10298
|
+
info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.AsyncioTask]] = LoggingContextInfos.AsyncioTask
|
10299
|
+
|
10300
|
+
_record_attrs: ta.ClassVar[ta.Mapping[str, ta.Union[ta.Any, ta.Tuple[ta.Any, ta.Any]]]] = dict(
|
10301
|
+
# Absent <3.12, otherwise asyncio.Task name if available, and `logging.logAsyncioTasks` is truthy. Set to
|
10302
|
+
# `sys.modules.get('asyncio').current_task().get_name()`, otherwise None.
|
10303
|
+
taskName=(ta.Optional[str], None),
|
10304
|
+
)
|
10305
|
+
|
10306
|
+
def _info_to_record(self, info: LoggingContextInfos.AsyncioTask) -> ta.Mapping[str, ta.Any]:
|
10307
|
+
if getattr(logging, 'logAsyncioTasks', None): # Absent <3.12
|
10308
|
+
return dict(
|
10309
|
+
taskName=info.name,
|
10310
|
+
)
|
10311
|
+
|
10312
|
+
return self.record_defaults
|
10313
|
+
|
10314
|
+
def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.AsyncioTask]:
|
10315
|
+
if (
|
10316
|
+
(name := getattr(rec, 'taskName', None)) is not None
|
10317
|
+
):
|
10318
|
+
return LoggingContextInfos.AsyncioTask(
|
10319
|
+
name=name,
|
10320
|
+
)
|
10321
|
+
|
10322
|
+
return None
|
10323
|
+
|
10324
|
+
|
10325
|
+
_LOGGING_CONTEXT_INFO_RECORD_ADAPTERS_: ta.Sequence[LoggingContextInfoRecordAdapters.Adapter] = [ # noqa
|
10326
|
+
LoggingContextInfoRecordAdapters.Name(),
|
10327
|
+
LoggingContextInfoRecordAdapters.Level(),
|
10328
|
+
LoggingContextInfoRecordAdapters.Msg(),
|
10329
|
+
LoggingContextInfoRecordAdapters.Time(),
|
10330
|
+
LoggingContextInfoRecordAdapters.Exc(),
|
10331
|
+
LoggingContextInfoRecordAdapters.Caller(),
|
10332
|
+
LoggingContextInfoRecordAdapters.SourceFile(),
|
10333
|
+
LoggingContextInfoRecordAdapters.Thread(),
|
10334
|
+
LoggingContextInfoRecordAdapters.Process(),
|
10335
|
+
LoggingContextInfoRecordAdapters.Multiprocessing(),
|
10336
|
+
LoggingContextInfoRecordAdapters.AsyncioTask(),
|
10337
|
+
]
|
10338
|
+
|
10339
|
+
_LOGGING_CONTEXT_INFO_RECORD_ADAPTERS: ta.Mapping[ta.Type[LoggingContextInfo], LoggingContextInfoRecordAdapters.Adapter] = { # noqa
|
10340
|
+
ad.info_cls: ad for ad in _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS_
|
10341
|
+
}
|
10342
|
+
|
10343
|
+
|
10344
|
+
##
|
9966
10345
|
|
9967
10346
|
|
9968
10347
|
# Formatter:
|
9969
10348
|
# - https://github.com/python/cpython/blob/39b2f82717a69dde7212bc39b673b0f55c99e6a3/Lib/logging/__init__.py#L514 (3.8)
|
9970
10349
|
# - https://github.com/python/cpython/blob/f070f54c5f4a42c7c61d1d5d3b8f3b7203b4a0fb/Lib/logging/__init__.py#L554 (~3.14) # noqa
|
9971
10350
|
#
|
9972
|
-
|
10351
|
+
_KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTRS: ta.Dict[str, ta.Any] = dict(
|
9973
10352
|
# The logged message, computed as msg % args. Set to `record.getMessage()`.
|
9974
10353
|
message=str,
|
9975
10354
|
|
@@ -9983,20 +10362,31 @@ KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTRS: ta.Dict[str, ta.Any] = dict(
|
|
9983
10362
|
exc_text=ta.Optional[str],
|
9984
10363
|
)
|
9985
10364
|
|
9986
|
-
KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTR_SET: ta.FrozenSet[str] = frozenset(KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTRS)
|
9987
|
-
|
9988
10365
|
|
9989
10366
|
##
|
9990
10367
|
|
9991
10368
|
|
10369
|
+
_KNOWN_STD_LOGGING_RECORD_ATTR_SET: ta.FrozenSet[str] = frozenset(
|
10370
|
+
a for ad in _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS.values() for a in ad.record_attrs
|
10371
|
+
)
|
10372
|
+
|
10373
|
+
_KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTR_SET: ta.FrozenSet[str] = frozenset(_KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTRS)
|
10374
|
+
|
10375
|
+
|
9992
10376
|
class UnknownStdLoggingRecordAttrsWarning(LoggingSetupWarning):
|
9993
10377
|
pass
|
9994
10378
|
|
9995
10379
|
|
9996
10380
|
def _check_std_logging_record_attrs() -> None:
|
10381
|
+
if (
|
10382
|
+
len([a for ad in _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS.values() for a in ad.record_attrs]) !=
|
10383
|
+
len(_KNOWN_STD_LOGGING_RECORD_ATTR_SET)
|
10384
|
+
):
|
10385
|
+
raise RuntimeError('Duplicate LoggingContextInfoRecordAdapter record attrs')
|
10386
|
+
|
9997
10387
|
rec_dct = dict(logging.makeLogRecord({}).__dict__)
|
9998
10388
|
|
9999
|
-
if (unk_rec_fields := frozenset(rec_dct) -
|
10389
|
+
if (unk_rec_fields := frozenset(rec_dct) - _KNOWN_STD_LOGGING_RECORD_ATTR_SET):
|
10000
10390
|
import warnings # noqa
|
10001
10391
|
|
10002
10392
|
warnings.warn(
|
@@ -10012,116 +10402,43 @@ _check_std_logging_record_attrs()
|
|
10012
10402
|
|
10013
10403
|
|
10014
10404
|
class LoggingContextLogRecord(logging.LogRecord):
|
10015
|
-
|
10016
|
-
|
10017
|
-
|
10018
|
-
|
10019
|
-
|
10020
|
-
|
10021
|
-
|
10022
|
-
|
10023
|
-
|
10024
|
-
|
10025
|
-
# name,
|
10026
|
-
# level,
|
10027
|
-
# pathname,
|
10028
|
-
# lineno,
|
10029
|
-
# msg,
|
10030
|
-
# args,
|
10031
|
-
# exc_info,
|
10032
|
-
# func=None,
|
10033
|
-
# sinfo=None,
|
10034
|
-
# **kwargs,
|
10035
|
-
*,
|
10036
|
-
name: str,
|
10037
|
-
msg: str,
|
10038
|
-
args: ta.Union[tuple, dict],
|
10039
|
-
|
10040
|
-
_logging_context: LoggingContext,
|
10041
|
-
) -> None:
|
10042
|
-
ctx = _logging_context
|
10043
|
-
|
10044
|
-
self.name: str = name
|
10045
|
-
|
10046
|
-
self.msg: str = msg
|
10047
|
-
|
10048
|
-
# https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L307
|
10049
|
-
if args and len(args) == 1 and isinstance(args[0], collections.abc.Mapping) and args[0]:
|
10050
|
-
args = args[0] # type: ignore[assignment]
|
10051
|
-
self.args: ta.Union[tuple, dict] = args
|
10052
|
-
|
10053
|
-
self.levelname: str = logging.getLevelName(ctx.level)
|
10054
|
-
self.levelno: int = ctx.level
|
10055
|
-
|
10056
|
-
if (caller := ctx.caller()) is not None:
|
10057
|
-
self.pathname: str = caller.file_path
|
10058
|
-
else:
|
10059
|
-
self.pathname = self._UNKNOWN_PATH_NAME
|
10060
|
-
|
10061
|
-
if (src_file := ctx.source_file()) is not None:
|
10062
|
-
self.filename: str = src_file.file_name
|
10063
|
-
self.module: str = src_file.module
|
10064
|
-
else:
|
10065
|
-
self.filename = self.pathname
|
10066
|
-
self.module = self._UNKNOWN_MODULE
|
10405
|
+
# LogRecord.__init__ args:
|
10406
|
+
# - name: str
|
10407
|
+
# - level: int
|
10408
|
+
# - pathname: str - Confusingly referred to as `fn` before the LogRecord ctor. May be empty or "(unknown file)".
|
10409
|
+
# - lineno: int - May be 0.
|
10410
|
+
# - msg: str
|
10411
|
+
# - args: tuple | dict | 1-tuple[dict]
|
10412
|
+
# - exc_info: LoggingExcInfoTuple | None
|
10413
|
+
# - func: str | None = None -> funcName
|
10414
|
+
# - sinfo: str | None = None -> stack_info
|
10067
10415
|
|
10068
|
-
|
10069
|
-
self.
|
10416
|
+
def __init__(self, *, _logging_context: LoggingContext) -> None: # noqa
|
10417
|
+
self._logging_context = _logging_context
|
10070
10418
|
|
10071
|
-
|
10072
|
-
|
10073
|
-
if (sinfo := caller.stack_info) is not None:
|
10074
|
-
self.stack_info: ta.Optional[str] = '\n'.join([
|
10075
|
-
self._STACK_INFO_PREFIX,
|
10076
|
-
sinfo[1:] if sinfo.endswith('\n') else sinfo,
|
10077
|
-
])
|
10078
|
-
else:
|
10079
|
-
self.stack_info = None
|
10419
|
+
for ad in _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS_:
|
10420
|
+
self.__dict__.update(ad.context_to_record(_logging_context))
|
10080
10421
|
|
10081
|
-
self.lineno: int = caller.line_no
|
10082
|
-
self.funcName: str = caller.name
|
10083
10422
|
|
10084
|
-
|
10085
|
-
self.stack_info = None
|
10086
|
-
|
10087
|
-
self.lineno = 0
|
10088
|
-
self.funcName = self._UNKNOWN_FUNC_NAME
|
10423
|
+
##
|
10089
10424
|
|
10090
|
-
times = ctx.times
|
10091
|
-
self.created: float = times.created
|
10092
|
-
self.msecs: float = times.msecs
|
10093
|
-
self.relativeCreated: float = times.relative_created
|
10094
10425
|
|
10095
|
-
|
10096
|
-
|
10097
|
-
|
10098
|
-
|
10099
|
-
|
10100
|
-
self.thread = None
|
10101
|
-
self.threadName = None
|
10426
|
+
@ta.final
|
10427
|
+
class LogRecordLoggingContext(LoggingContext):
|
10428
|
+
def __init__(self, rec: logging.LogRecord) -> None:
|
10429
|
+
if isinstance(rec, LoggingContextLogRecord):
|
10430
|
+
raise TypeError(rec)
|
10102
10431
|
|
10103
|
-
|
10104
|
-
process = check.not_none(ctx.process())
|
10105
|
-
self.process: ta.Optional[int] = process.pid
|
10106
|
-
else:
|
10107
|
-
self.process = None
|
10432
|
+
self._rec = rec
|
10108
10433
|
|
10109
|
-
|
10110
|
-
|
10111
|
-
|
10112
|
-
|
10113
|
-
|
10114
|
-
else:
|
10115
|
-
self.processName = None
|
10434
|
+
self._infos: ta.Dict[ta.Type[LoggingContextInfo], LoggingContextInfo] = {
|
10435
|
+
type(info): info
|
10436
|
+
for ad in _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS_
|
10437
|
+
if (info := ad.record_to_info(rec)) is not None
|
10438
|
+
}
|
10116
10439
|
|
10117
|
-
|
10118
|
-
|
10119
|
-
if (at := ctx.asyncio_task()) is not None:
|
10120
|
-
self.taskName: ta.Optional[str] = at.name
|
10121
|
-
else:
|
10122
|
-
self.taskName = None
|
10123
|
-
else:
|
10124
|
-
self.taskName = None
|
10440
|
+
def get_info(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
|
10441
|
+
return self._infos.get(ty)
|
10125
10442
|
|
10126
10443
|
|
10127
10444
|
########################################
|
@@ -10421,21 +10738,20 @@ class StdLogger(Logger):
|
|
10421
10738
|
return self._std.getEffectiveLevel()
|
10422
10739
|
|
10423
10740
|
def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any) -> None:
|
10424
|
-
if not self.is_enabled_for(ctx.level):
|
10741
|
+
if not self.is_enabled_for(ctx.must_get_info(LoggingContextInfos.Level).level):
|
10425
10742
|
return
|
10426
10743
|
|
10427
|
-
ctx.
|
10428
|
-
|
10429
|
-
ms, args = self._prepare_msg_args(msg, *args)
|
10430
|
-
|
10431
|
-
rec = LoggingContextLogRecord(
|
10744
|
+
ctx.set_basic(
|
10432
10745
|
name=self._std.name,
|
10433
|
-
msg=ms,
|
10434
|
-
args=args,
|
10435
10746
|
|
10436
|
-
|
10747
|
+
msg=msg,
|
10748
|
+
args=args,
|
10437
10749
|
)
|
10438
10750
|
|
10751
|
+
ctx.capture()
|
10752
|
+
|
10753
|
+
rec = LoggingContextLogRecord(_logging_context=ctx)
|
10754
|
+
|
10439
10755
|
self._std.handle(rec)
|
10440
10756
|
|
10441
10757
|
|