ominfra 0.0.0.dev430__py3-none-any.whl → 0.0.0.dev431__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
- LoggingExcInfoTuple = ta.Tuple[ta.Type[BaseException], BaseException, ta.Optional[types.TracebackType]] # ta.TypeAlias
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/callers.py
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
- @logging_context_info
6107
+ def logging_context_info(cls):
6108
+ return cls
6109
+
6110
+
6220
6111
  @ta.final
6221
- class LoggingCaller(ta.NamedTuple):
6222
- file_path: str
6223
- line_no: int
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
- @classmethod
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
- # Yes, really.
6232
- # https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L204
6233
- # https://github.com/python/cpython/commit/5ca6d7469be53960843df39bb900e9c3359f127f
6234
- if 'importlib' in file_path and '_bootstrap' in file_path:
6235
- return True
6118
+ @logging_context_info
6119
+ @ta.final
6120
+ class Name(ta.NamedTuple):
6121
+ name: str
6236
6122
 
6237
- return False
6123
+ @logging_context_info
6124
+ @ta.final
6125
+ class Level(ta.NamedTuple):
6126
+ level: NamedLogLevel
6127
+ name: str
6238
6128
 
6239
- @classmethod
6240
- def find_frame(cls, ofs: int = 0) -> ta.Optional[types.FrameType]:
6241
- f: ta.Optional[types.FrameType] = sys._getframe(2 + ofs) # noqa
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
- while f is not None:
6244
- # NOTE: We don't check __file__ like stdlib since we may be running amalgamated - we rely on careful, manual
6245
- # stack_offset management.
6246
- if hasattr(f, 'f_code'):
6247
- return f
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
- f = f.f_back
6174
+ elif isinstance(msg, str):
6175
+ s, a = msg, args
6250
6176
 
6251
- return None
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
- # https://github.com/python/cpython/blob/08e9794517063c8cd92c48714071b1d3c60b71bd/Lib/logging/__init__.py#L1616-L1623 # noqa
6264
- sinfo = None
6265
- if stack_info:
6266
- sio = io.StringIO()
6267
- traceback.print_stack(f, file=sio)
6268
- sinfo = sio.getvalue()
6269
- sio.close()
6270
- if sinfo[-1] == '\n':
6271
- sinfo = sinfo[:-1]
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
- return cls(
6274
- file_path=f.f_code.co_filename,
6275
- line_no=f.f_lineno or 0,
6276
- name=f.f_code.co_name,
6277
- stack_info=sinfo,
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/logs/times.py
6543
+ # ../../../omlish/os/journald.py
6372
6544
 
6373
6545
 
6374
6546
  ##
6375
6547
 
6376
6548
 
6377
- @logging_context_info
6378
- @ta.final
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
- # Before 3.13.0b1 this will be `time.time()`, a float of seconds. After that, it will be `time.time_ns()`, an
6391
- # int.
6392
- #
6393
- # See:
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
- return cls(
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
- class UnexpectedLoggingStartTimeWarning(LoggingSetupWarning):
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,36 @@ 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
8580
  @abc.abstractmethod
8502
- def time_ns(self) -> int:
8581
+ def get_info(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
8503
8582
  raise NotImplementedError
8504
8583
 
8505
- @property
8506
- @abc.abstractmethod
8507
- def times(self) -> LoggingTimeFields:
8508
- raise NotImplementedError
8509
-
8510
- #
8584
+ @ta.final
8585
+ def __getitem__(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
8586
+ return self.get_info(ty)
8511
8587
 
8512
- @property
8513
- @abc.abstractmethod
8514
- def exc_info(self) -> ta.Optional[LoggingExcInfo]:
8515
- raise NotImplementedError
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
8516
8593
 
8517
- @property
8518
- @abc.abstractmethod
8519
- def exc_info_tuple(self) -> ta.Optional[LoggingExcInfoTuple]:
8520
- raise NotImplementedError
8594
+ ##
8521
8595
 
8522
- #
8523
8596
 
8597
+ class CaptureLoggingContext(LoggingContext, Abstract):
8524
8598
  @abc.abstractmethod
8525
- def caller(self) -> ta.Optional[LoggingCaller]:
8526
- raise NotImplementedError
8599
+ def set_basic(
8600
+ self,
8601
+ name: str,
8527
8602
 
8528
- @abc.abstractmethod
8529
- def source_file(self) -> ta.Optional[LoggingSourceFileInfo]:
8603
+ msg: ta.Union[str, tuple, LoggingMsgFn],
8604
+ args: tuple,
8605
+ ) -> 'CaptureLoggingContext':
8530
8606
  raise NotImplementedError
8531
8607
 
8532
8608
  #
8533
8609
 
8534
- @abc.abstractmethod
8535
- def thread(self) -> ta.Optional[LoggingThreadInfo]:
8536
- raise NotImplementedError
8537
-
8538
- @abc.abstractmethod
8539
- def process(self) -> ta.Optional[LoggingProcessInfo]:
8540
- raise NotImplementedError
8541
-
8542
- @abc.abstractmethod
8543
- def multiprocessing(self) -> ta.Optional[LoggingMultiprocessingInfo]:
8544
- raise NotImplementedError
8545
-
8546
- @abc.abstractmethod
8547
- def asyncio_task(self) -> ta.Optional[LoggingAsyncioTaskInfo]:
8548
- raise NotImplementedError
8549
-
8550
-
8551
- ##
8552
-
8553
-
8554
- class CaptureLoggingContext(LoggingContext, Abstract):
8555
8610
  class AlreadyCapturedError(Exception):
8556
8611
  pass
8557
8612
 
@@ -8582,80 +8637,50 @@ class CaptureLoggingContextImpl(CaptureLoggingContext):
8582
8637
 
8583
8638
  exc_info: LoggingExcInfoArg = False,
8584
8639
 
8585
- caller: ta.Union[LoggingCaller, ta.Type[NOT_SET], None] = NOT_SET,
8640
+ caller: ta.Union[LoggingContextInfos.Caller, ta.Type[NOT_SET], None] = NOT_SET,
8586
8641
  stack_offset: int = 0,
8587
8642
  stack_info: bool = False,
8588
8643
  ) -> None:
8589
- self._level: NamedLogLevel = level if level.__class__ is NamedLogLevel else NamedLogLevel(level) # type: ignore[assignment] # noqa
8590
-
8591
- #
8644
+ # TODO: Name, Msg, Extra
8592
8645
 
8593
8646
  if time_ns is None:
8594
8647
  time_ns = time.time_ns()
8595
- self._time_ns: int = time_ns
8596
-
8597
- #
8598
-
8599
- if exc_info is True:
8600
- sys_exc_info = sys.exc_info()
8601
- if sys_exc_info[0] is not None:
8602
- exc_info = sys_exc_info
8603
- else:
8604
- exc_info = None
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
8648
 
8615
- #
8649
+ self._infos: ta.Dict[ta.Type[LoggingContextInfo], LoggingContextInfo] = {}
8650
+ self._set_info(
8651
+ LoggingContextInfos.Level.build(level),
8652
+ LoggingContextInfos.Time.build(time_ns),
8653
+ LoggingContextInfos.Exc.build(exc_info),
8654
+ )
8616
8655
 
8617
8656
  if caller is not CaptureLoggingContextImpl.NOT_SET:
8618
- self._caller = caller # type: ignore[assignment]
8657
+ self._infos[LoggingContextInfos.Caller] = caller
8619
8658
  else:
8620
8659
  self._stack_offset = stack_offset
8621
8660
  self._stack_info = stack_info
8622
8661
 
8623
- ##
8624
-
8625
- @property
8626
- def level(self) -> NamedLogLevel:
8627
- return self._level
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
8662
+ def _set_info(self, *infos: ta.Optional[LoggingContextInfo]) -> 'CaptureLoggingContextImpl':
8663
+ for info in infos:
8664
+ if info is not None:
8665
+ self._infos[type(info)] = info
8666
+ return self
8646
8667
 
8647
- #
8668
+ def get_info(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
8669
+ return self._infos.get(ty)
8648
8670
 
8649
- _exc_info: ta.Optional[LoggingExcInfo] = None
8650
- _exc_info_tuple: ta.Optional[LoggingExcInfoTuple] = None
8671
+ ##
8651
8672
 
8652
- @property
8653
- def exc_info(self) -> ta.Optional[LoggingExcInfo]:
8654
- return self._exc_info
8673
+ def set_basic(
8674
+ self,
8675
+ name: str,
8655
8676
 
8656
- @property
8657
- def exc_info_tuple(self) -> ta.Optional[LoggingExcInfoTuple]:
8658
- return self._exc_info_tuple
8677
+ msg: ta.Union[str, tuple, LoggingMsgFn],
8678
+ args: tuple,
8679
+ ) -> 'CaptureLoggingContextImpl':
8680
+ return self._set_info(
8681
+ LoggingContextInfos.Name(name),
8682
+ LoggingContextInfos.Msg.build(msg, *args),
8683
+ )
8659
8684
 
8660
8685
  ##
8661
8686
 
@@ -8669,74 +8694,28 @@ class CaptureLoggingContextImpl(CaptureLoggingContext):
8669
8694
 
8670
8695
  _has_captured: bool = False
8671
8696
 
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
8697
  def capture(self) -> None:
8681
8698
  if self._has_captured:
8682
8699
  raise CaptureLoggingContextImpl.AlreadyCapturedError
8683
8700
  self._has_captured = True
8684
8701
 
8685
- if not hasattr(self, '_caller'):
8686
- self._caller = LoggingCaller.find(
8702
+ if LoggingContextInfos.Caller not in self._infos:
8703
+ self._set_info(LoggingContextInfos.Caller.build(
8687
8704
  self._stack_offset + 1,
8688
8705
  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
8706
+ ))
8728
8707
 
8729
- def multiprocessing(self) -> ta.Optional[LoggingMultiprocessingInfo]:
8730
- try:
8731
- return self._multiprocessing
8732
- except AttributeError:
8733
- raise CaptureLoggingContext.NotCapturedError from None
8708
+ if (caller := self[LoggingContextInfos.Caller]) is not None:
8709
+ self._set_info(LoggingContextInfos.SourceFile.build(
8710
+ caller.file_path,
8711
+ ))
8734
8712
 
8735
- def asyncio_task(self) -> ta.Optional[LoggingAsyncioTaskInfo]:
8736
- try:
8737
- return self._asyncio_task
8738
- except AttributeError:
8739
- raise CaptureLoggingContext.NotCapturedError from None
8713
+ self._set_info(
8714
+ LoggingContextInfos.Thread.build(),
8715
+ LoggingContextInfos.Process.build(),
8716
+ LoggingContextInfos.Multiprocessing.build(),
8717
+ LoggingContextInfos.AsyncioTask.build(),
8718
+ )
8740
8719
 
8741
8720
 
8742
8721
  ########################################
@@ -9762,36 +9741,6 @@ class AnyLogger(Abstract, ta.Generic[T]):
9762
9741
 
9763
9742
  ##
9764
9743
 
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
9744
  @abc.abstractmethod
9796
9745
  def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any, **kwargs: ta.Any) -> T: # noqa
9797
9746
  raise NotImplementedError
@@ -9832,137 +9781,543 @@ class AsyncNopLogger(AnyNopLogger[ta.Awaitable[None]], AsyncLogger):
9832
9781
 
9833
9782
  ########################################
9834
9783
  # ../../../omlish/logs/std/records.py
9784
+ """
9785
+ TODO:
9786
+ - TypedDict?
9787
+ """
9835
9788
 
9836
9789
 
9837
9790
  ##
9838
9791
 
9839
9792
 
9840
- # Ref:
9841
- # - https://docs.python.org/3/library/logging.html#logrecord-attributes
9842
- #
9843
- # LogRecord:
9844
- # - https://github.com/python/cpython/blob/39b2f82717a69dde7212bc39b673b0f55c99e6a3/Lib/logging/__init__.py#L276 (3.8)
9845
- # - https://github.com/python/cpython/blob/f070f54c5f4a42c7c61d1d5d3b8f3b7203b4a0fb/Lib/logging/__init__.py#L286 (~3.14) # noqa
9846
- #
9847
- # LogRecord.__init__ args:
9848
- # - name: str
9849
- # - level: int
9850
- # - pathname: str - Confusingly referred to as `fn` before the LogRecord ctor. May be empty or "(unknown file)".
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,
9793
+ class LoggingContextInfoRecordAdapters:
9794
+ # Ref:
9795
+ # - https://docs.python.org/3/library/logging.html#logrecord-attributes
9796
+ #
9797
+ # LogRecord:
9798
+ # - https://github.com/python/cpython/blob/39b2f82717a69dde7212bc39b673b0f55c99e6a3/Lib/logging/__init__.py#L276 (3.8) # noqa
9799
+ # - https://github.com/python/cpython/blob/f070f54c5f4a42c7c61d1d5d3b8f3b7203b4a0fb/Lib/logging/__init__.py#L286 (~3.14) # noqa
9800
+ #
9861
9801
 
9862
- # The format string passed in the original logging call. Merged with args to produce message, or an arbitrary object
9863
- # (see Using arbitrary objects as messages). Unmodified by ctor.
9864
- msg=str,
9802
+ def __new__(cls, *args, **kwargs): # noqa
9803
+ raise TypeError
9865
9804
 
9866
- # The tuple of arguments merged into msg to produce message, or a dict whose values are used for the merge (when
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],
9805
+ class Adapter(Abstract, ta.Generic[T]):
9806
+ @property
9807
+ @abc.abstractmethod
9808
+ def info_cls(self) -> ta.Type[LoggingContextInfo]:
9809
+ raise NotImplementedError
9870
9810
 
9871
- #
9811
+ #
9872
9812
 
9873
- # Text logging level for the message ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'). Set to
9874
- # `getLevelName(level)`.
9875
- levelname=str,
9813
+ @ta.final
9814
+ class NOT_SET: # noqa
9815
+ def __new__(cls, *args, **kwargs): # noqa
9816
+ raise TypeError
9876
9817
 
9877
- # Numeric logging level for the message (DEBUG, INFO, WARNING, ERROR, CRITICAL). Unmodified by ctor.
9878
- levelno=int,
9818
+ class RecordAttr(ta.NamedTuple):
9819
+ name: str
9820
+ type: ta.Any
9821
+ default: ta.Any
9879
9822
 
9880
- #
9823
+ # @abc.abstractmethod
9824
+ record_attrs: ta.ClassVar[ta.Mapping[str, RecordAttr]]
9881
9825
 
9882
- # Full pathname of the source file where the logging call was issued (if available). Unmodified by ctor. May default
9883
- # to "(unknown file)" by Logger.findCaller / Logger._log.
9884
- pathname=str,
9826
+ @property
9827
+ @abc.abstractmethod
9828
+ def _record_attrs(self) -> ta.Union[
9829
+ ta.Mapping[str, ta.Any],
9830
+ ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]],
9831
+ ]:
9832
+ raise NotImplementedError
9885
9833
 
9886
- # Filename portion of pathname. Set to `os.path.basename(pathname)` if successful, otherwise defaults to pathname.
9887
- filename=str,
9834
+ #
9888
9835
 
9889
- # Module (name portion of filename). Set to `os.path.splitext(filename)[0]`, otherwise defaults to
9890
- # "Unknown module".
9891
- module=str,
9836
+ @abc.abstractmethod
9837
+ def context_to_record(self, ctx: LoggingContext) -> ta.Mapping[str, ta.Any]:
9838
+ raise NotImplementedError
9892
9839
 
9893
- #
9840
+ #
9894
9841
 
9895
- # Exception tuple (à la sys.exc_info) or, if no exception has occurred, None. Unmodified by ctor.
9896
- exc_info=ta.Optional[LoggingExcInfoTuple],
9842
+ @abc.abstractmethod
9843
+ def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[T]:
9844
+ raise NotImplementedError
9897
9845
 
9898
- # Used to cache the traceback text. Simply set to None by ctor, later set by Formatter.format.
9899
- exc_text=ta.Optional[str],
9846
+ #
9900
9847
 
9901
- #
9848
+ def __init_subclass__(cls, **kwargs: ta.Any) -> None:
9849
+ super().__init_subclass__(**kwargs)
9902
9850
 
9903
- # Stack frame information (where available) from the bottom of the stack in the current thread, up to and including
9904
- # the stack frame of the logging call which resulted in the creation of this record. Set by ctor to `sinfo` arg,
9905
- # unmodified. Mostly set, if requested, by `Logger.findCaller`, to `traceback.print_stack(f)`, but prepended with
9906
- # the literal "Stack (most recent call last):\n", and stripped of exactly one trailing `\n` if present.
9907
- stack_info=ta.Optional[str],
9851
+ if Abstract in cls.__bases__:
9852
+ return
9908
9853
 
9909
- # Source line number where the logging call was issued (if available). Unmodified by ctor. May default to 0 by
9910
- # Logger.findCaller / Logger._log.
9911
- lineno=int,
9854
+ if 'record_attrs' in cls.__dict__:
9855
+ raise TypeError(cls)
9856
+ if not isinstance(ra := cls.__dict__['_record_attrs'], collections.abc.Mapping):
9857
+ raise TypeError(ra)
9858
+
9859
+ rd: ta.Dict[str, LoggingContextInfoRecordAdapters.Adapter.RecordAttr] = {}
9860
+ for n, v in ra.items():
9861
+ if not n or not isinstance(n, str) or n in rd:
9862
+ raise AttributeError(n)
9863
+ if isinstance(v, tuple):
9864
+ t, d = v
9865
+ else:
9866
+ t, d = v, cls.NOT_SET
9867
+ rd[n] = cls.RecordAttr(
9868
+ name=n,
9869
+ type=t,
9870
+ default=d,
9871
+ )
9872
+ cls.record_attrs = rd
9912
9873
 
9913
- # Name of function containing the logging call. Set by ctor to `func` arg, unmodified. May default to
9914
- # "(unknown function)" by Logger.findCaller / Logger._log.
9915
- funcName=str,
9874
+ class RequiredAdapter(Adapter[T], Abstract):
9875
+ @property
9876
+ @abc.abstractmethod
9877
+ def _record_attrs(self) -> ta.Mapping[str, ta.Any]:
9878
+ raise NotImplementedError
9916
9879
 
9917
- #
9880
+ #
9918
9881
 
9919
- # Time when the LogRecord was created. Set to `time.time_ns() / 1e9` for >=3.13.0b1, otherwise simply `time.time()`.
9920
- #
9921
- # See:
9922
- # - https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
9923
- # - https://github.com/python/cpython/commit/1500a23f33f5a6d052ff1ef6383d9839928b8ff1
9924
- #
9925
- created=float,
9882
+ @ta.final
9883
+ def context_to_record(self, ctx: LoggingContext) -> ta.Mapping[str, ta.Any]:
9884
+ if (info := ctx.get_info(self.info_cls)) is not None:
9885
+ return self._info_to_record(info)
9886
+ else:
9887
+ raise TypeError # FIXME: fallback?
9926
9888
 
9927
- # Millisecond portion of the time when the LogRecord was created.
9928
- msecs=float,
9889
+ @abc.abstractmethod
9890
+ def _info_to_record(self, info: T) -> ta.Mapping[str, ta.Any]:
9891
+ raise NotImplementedError
9929
9892
 
9930
- # Time in milliseconds when the LogRecord was created, relative to the time the logging module was loaded.
9931
- relativeCreated=float,
9893
+ #
9932
9894
 
9933
- #
9895
+ @abc.abstractmethod
9896
+ def record_to_info(self, rec: logging.LogRecord) -> T:
9897
+ raise NotImplementedError
9934
9898
 
9935
- # Thread ID if available, and `logging.logThreads` is truthy.
9936
- thread=ta.Optional[int],
9899
+ #
9937
9900
 
9938
- # Thread name if available, and `logging.logThreads` is truthy.
9939
- threadName=ta.Optional[str],
9901
+ def __init_subclass__(cls, **kwargs: ta.Any) -> None:
9902
+ super().__init_subclass__(**kwargs)
9940
9903
 
9941
- #
9904
+ if any(a.default is not cls.NOT_SET for a in cls.record_attrs.values()):
9905
+ raise TypeError(cls.record_attrs)
9906
+
9907
+ class OptionalAdapter(Adapter[T], Abstract, ta.Generic[T]):
9908
+ @property
9909
+ @abc.abstractmethod
9910
+ def _record_attrs(self) -> ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]:
9911
+ raise NotImplementedError
9912
+
9913
+ record_defaults: ta.ClassVar[ta.Mapping[str, ta.Any]]
9914
+
9915
+ #
9916
+
9917
+ @ta.final
9918
+ def context_to_record(self, ctx: LoggingContext) -> ta.Mapping[str, ta.Any]:
9919
+ if (info := ctx.get_info(self.info_cls)) is not None:
9920
+ return self._info_to_record(info)
9921
+ else:
9922
+ return self.record_defaults
9923
+
9924
+ @abc.abstractmethod
9925
+ def _info_to_record(self, info: T) -> ta.Mapping[str, ta.Any]:
9926
+ raise NotImplementedError
9927
+
9928
+ #
9929
+
9930
+ def __init_subclass__(cls, **kwargs: ta.Any) -> None:
9931
+ super().__init_subclass__(**kwargs)
9932
+
9933
+ dd: ta.Dict[str, ta.Any] = {a.name: a.default for a in cls.record_attrs.values()}
9934
+ if any(d is cls.NOT_SET for d in dd.values()):
9935
+ raise TypeError(cls.record_attrs)
9936
+ cls.record_defaults = dd
9942
9937
 
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
9938
  #
9947
- # As noted by stdlib:
9939
+
9940
+ class Name(RequiredAdapter[LoggingContextInfos.Name]):
9941
+ info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Name]] = LoggingContextInfos.Name
9942
+
9943
+ _record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
9944
+ # Name of the logger used to log the call. Unmodified by ctor.
9945
+ name=str,
9946
+ )
9947
+
9948
+ def _info_to_record(self, info: LoggingContextInfos.Name) -> ta.Mapping[str, ta.Any]:
9949
+ return dict(
9950
+ name=info.name,
9951
+ )
9952
+
9953
+ def record_to_info(self, rec: logging.LogRecord) -> LoggingContextInfos.Name:
9954
+ return LoggingContextInfos.Name(
9955
+ name=rec.name,
9956
+ )
9957
+
9958
+ class Level(RequiredAdapter[LoggingContextInfos.Level]):
9959
+ info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Level]] = LoggingContextInfos.Level
9960
+
9961
+ _record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
9962
+ # Text logging level for the message ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'). Set to
9963
+ # `getLevelName(level)`.
9964
+ levelname=str,
9965
+
9966
+ # Numeric logging level for the message (DEBUG, INFO, WARNING, ERROR, CRITICAL). Unmodified by ctor.
9967
+ levelno=int,
9968
+ )
9969
+
9970
+ def _info_to_record(self, info: LoggingContextInfos.Level) -> ta.Mapping[str, ta.Any]:
9971
+ return dict(
9972
+ levelname=info.name,
9973
+ levelno=int(info.level),
9974
+ )
9975
+
9976
+ def record_to_info(self, rec: logging.LogRecord) -> LoggingContextInfos.Level:
9977
+ return LoggingContextInfos.Level.build(rec.levelno)
9978
+
9979
+ class Msg(RequiredAdapter[LoggingContextInfos.Msg]):
9980
+ info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Msg]] = LoggingContextInfos.Msg
9981
+
9982
+ _record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
9983
+ # The format string passed in the original logging call. Merged with args to produce message, or an
9984
+ # arbitrary object (see Using arbitrary objects as messages). Unmodified by ctor.
9985
+ msg=str,
9986
+
9987
+ # The tuple of arguments merged into msg to produce message, or a dict whose values are used for the merge
9988
+ # (when there is only one argument, and it is a dictionary). Ctor will transform a 1-tuple containing a
9989
+ # Mapping into just the mapping, but is otherwise unmodified.
9990
+ args=ta.Union[tuple, dict, None],
9991
+ )
9992
+
9993
+ def _info_to_record(self, info: LoggingContextInfos.Msg) -> ta.Mapping[str, ta.Any]:
9994
+ return dict(
9995
+ msg=info.msg,
9996
+ args=info.args,
9997
+ )
9998
+
9999
+ def record_to_info(self, rec: logging.LogRecord) -> LoggingContextInfos.Msg:
10000
+ return LoggingContextInfos.Msg(
10001
+ msg=rec.msg,
10002
+ args=rec.args,
10003
+ )
10004
+
10005
+ # FIXME: handled specially - all unknown attrs on LogRecord
10006
+ # class Extra(Adapter[LoggingContextInfos.Extra]):
10007
+ # _record_attrs: ta.ClassVar[ta.Mapping[str, ta.Union[ta.Any, ta.Tuple[ta.Any, ta.Any]]]] = dict()
9948
10008
  #
9949
- # Errors may occur if multiprocessing has not finished loading yet - e.g. if a custom import hook causes
9950
- # third-party code to run when multiprocessing calls import. See issue 8200 for an example
10009
+ # def info_to_record(self, info: ta.Optional[LoggingContextInfos.Extra]) -> ta.Mapping[str, ta.Any]:
10010
+ # # FIXME:
10011
+ # # if extra is not None:
10012
+ # # for key in extra:
10013
+ # # if (key in ["message", "asctime"]) or (key in rv.__dict__):
10014
+ # # raise KeyError("Attempt to overwrite %r in LogRecord" % key)
10015
+ # # rv.__dict__[key] = extra[key]
10016
+ # return dict()
9951
10017
  #
9952
- processName=ta.Optional[str],
10018
+ # def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Extra]:
10019
+ # return None
9953
10020
 
9954
- # Process ID if available - that is, if `hasattr(os, 'getpid')` - and `logging.logProcesses` is truthy, otherwise
9955
- # None.
9956
- process=ta.Optional[int],
10021
+ class Time(RequiredAdapter[LoggingContextInfos.Time]):
10022
+ info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Time]] = LoggingContextInfos.Time
9957
10023
 
9958
- #
10024
+ _record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
10025
+ # Time when the LogRecord was created. Set to `time.time_ns() / 1e9` for >=3.13.0b1, otherwise simply
10026
+ # `time.time()`.
10027
+ #
10028
+ # See:
10029
+ # - https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
10030
+ # - https://github.com/python/cpython/commit/1500a23f33f5a6d052ff1ef6383d9839928b8ff1
10031
+ #
10032
+ created=float,
9959
10033
 
9960
- # Absent <3.12, otherwise asyncio.Task name if available, and `logging.logAsyncioTasks` is truthy. Set to
9961
- # `sys.modules.get('asyncio').current_task().get_name()`, otherwise None.
9962
- taskName=ta.Optional[str],
9963
- )
10034
+ # Millisecond portion of the time when the LogRecord was created.
10035
+ msecs=float,
10036
+
10037
+ # Time in milliseconds when the LogRecord was created, relative to the time the logging module was loaded.
10038
+ relativeCreated=float,
10039
+ )
10040
+
10041
+ def _info_to_record(self, info: LoggingContextInfos.Time) -> ta.Mapping[str, ta.Any]:
10042
+ return dict(
10043
+ created=info.secs,
10044
+ msecs=info.msecs,
10045
+ relativeCreated=info.relative_secs,
10046
+ )
10047
+
10048
+ def record_to_info(self, rec: logging.LogRecord) -> LoggingContextInfos.Time:
10049
+ return LoggingContextInfos.Time.build(
10050
+ int(rec.created * 1e9),
10051
+ )
10052
+
10053
+ class Exc(OptionalAdapter[LoggingContextInfos.Exc]):
10054
+ info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Exc]] = LoggingContextInfos.Exc
10055
+
10056
+ _record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
10057
+ # Exception tuple (à la sys.exc_info) or, if no exception has occurred, None. Unmodified by ctor.
10058
+ exc_info=(ta.Optional[LoggingExcInfoTuple], None),
10059
+
10060
+ # Used to cache the traceback text. Simply set to None by ctor, later set by Formatter.format.
10061
+ exc_text=(ta.Optional[str], None),
10062
+ )
10063
+
10064
+ def _info_to_record(self, info: LoggingContextInfos.Exc) -> ta.Mapping[str, ta.Any]:
10065
+ return dict(
10066
+ exc_info=info.info_tuple,
10067
+ exc_text=None,
10068
+ )
10069
+
10070
+ def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Exc]:
10071
+ # FIXME:
10072
+ # error: Argument 1 to "build" of "Exc" has incompatible type
10073
+ # "tuple[type[BaseException], BaseException, TracebackType | None] | tuple[None, None, None] | None"; expected # noqa
10074
+ # "BaseException | tuple[type[BaseException], BaseException, TracebackType | None] | bool | None" [arg-type] # noqa
10075
+ return LoggingContextInfos.Exc.build(rec.exc_info) # type: ignore[arg-type]
10076
+
10077
+ class Caller(OptionalAdapter[LoggingContextInfos.Caller]):
10078
+ info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Caller]] = LoggingContextInfos.Caller
10079
+
10080
+ _UNKNOWN_PATH_NAME: ta.ClassVar[str] = '(unknown file)'
10081
+ _UNKNOWN_FUNC_NAME: ta.ClassVar[str] = '(unknown function)'
9964
10082
 
9965
- KNOWN_STD_LOGGING_RECORD_ATTR_SET: ta.FrozenSet[str] = frozenset(KNOWN_STD_LOGGING_RECORD_ATTRS)
10083
+ _STACK_INFO_PREFIX: ta.ClassVar[str] = 'Stack (most recent call last):\n'
10084
+
10085
+ _record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
10086
+ # Full pathname of the source file where the logging call was issued (if available). Unmodified by ctor. May
10087
+ # default to "(unknown file)" by Logger.findCaller / Logger._log.
10088
+ pathname=(str, _UNKNOWN_PATH_NAME),
10089
+
10090
+ # Source line number where the logging call was issued (if available). Unmodified by ctor. May default to 0
10091
+ # y Logger.findCaller / Logger._log.
10092
+ lineno=(int, 0),
10093
+
10094
+ # Name of function containing the logging call. Set by ctor to `func` arg, unmodified. May default to
10095
+ # "(unknown function)" by Logger.findCaller / Logger._log.
10096
+ funcName=(str, _UNKNOWN_FUNC_NAME),
10097
+
10098
+ # Stack frame information (where available) from the bottom of the stack in the current thread, up to and
10099
+ # including the stack frame of the logging call which resulted in the creation of this record. Set by ctor
10100
+ # to `sinfo` arg, unmodified. Mostly set, if requested, by `Logger.findCaller`, to
10101
+ # `traceback.print_stack(f)`, but prepended with the literal "Stack (most recent call last):\n", and
10102
+ # stripped of exactly one trailing `\n` if present.
10103
+ stack_info=(ta.Optional[str], None),
10104
+ )
10105
+
10106
+ def _info_to_record(self, caller: LoggingContextInfos.Caller) -> ta.Mapping[str, ta.Any]:
10107
+ if (sinfo := caller.stack_info) is not None:
10108
+ stack_info: ta.Optional[str] = '\n'.join([
10109
+ self._STACK_INFO_PREFIX,
10110
+ sinfo[1:] if sinfo.endswith('\n') else sinfo,
10111
+ ])
10112
+ else:
10113
+ stack_info = None
10114
+
10115
+ return dict(
10116
+ pathname=caller.file_path,
10117
+
10118
+ lineno=caller.line_no,
10119
+ funcName=caller.func_name,
10120
+
10121
+ stack_info=stack_info,
10122
+ )
10123
+
10124
+ def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Caller]:
10125
+ # FIXME: piecemeal?
10126
+ # FIXME: strip _STACK_INFO_PREFIX
10127
+ raise NotImplementedError
10128
+
10129
+ class SourceFile(Adapter[LoggingContextInfos.SourceFile]):
10130
+ info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.SourceFile]] = LoggingContextInfos.SourceFile
10131
+
10132
+ _record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
10133
+ # Filename portion of pathname. Set to `os.path.basename(pathname)` if successful, otherwise defaults to
10134
+ # pathname.
10135
+ filename=str,
10136
+
10137
+ # Module (name portion of filename). Set to `os.path.splitext(filename)[0]`, otherwise defaults to
10138
+ # "Unknown module".
10139
+ module=str,
10140
+ )
10141
+
10142
+ _UNKNOWN_MODULE: ta.ClassVar[str] = 'Unknown module'
10143
+
10144
+ def context_to_record(self, ctx: LoggingContext) -> ta.Mapping[str, ta.Any]:
10145
+ if (info := ctx.get_info(LoggingContextInfos.SourceFile)) is not None:
10146
+ return dict(
10147
+ filename=info.file_name,
10148
+ module=info.module,
10149
+ )
10150
+
10151
+ if (caller := ctx.get_info(LoggingContextInfos.Caller)) is not None:
10152
+ return dict(
10153
+ filename=caller.file_path,
10154
+ module=self._UNKNOWN_MODULE,
10155
+ )
10156
+
10157
+ return dict(
10158
+ filename=LoggingContextInfoRecordAdapters.Caller._UNKNOWN_PATH_NAME, # noqa
10159
+ module=self._UNKNOWN_MODULE,
10160
+ )
10161
+
10162
+ def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.SourceFile]:
10163
+ if not (
10164
+ rec.module is None or
10165
+ rec.module == self._UNKNOWN_MODULE
10166
+ ):
10167
+ return LoggingContextInfos.SourceFile(
10168
+ file_name=rec.filename,
10169
+ module=rec.module, # FIXME: piecemeal?
10170
+ )
10171
+
10172
+ return None
10173
+
10174
+ class Thread(OptionalAdapter[LoggingContextInfos.Thread]):
10175
+ info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Thread]] = LoggingContextInfos.Thread
10176
+
10177
+ _record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
10178
+ # Thread ID if available, and `logging.logThreads` is truthy.
10179
+ thread=(ta.Optional[int], None),
10180
+
10181
+ # Thread name if available, and `logging.logThreads` is truthy.
10182
+ threadName=(ta.Optional[str], None),
10183
+ )
10184
+
10185
+ def _info_to_record(self, info: LoggingContextInfos.Thread) -> ta.Mapping[str, ta.Any]:
10186
+ if logging.logThreads:
10187
+ return dict(
10188
+ thread=info.ident,
10189
+ threadName=info.name,
10190
+ )
10191
+
10192
+ return self.record_defaults
10193
+
10194
+ def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Thread]:
10195
+ if (
10196
+ (ident := rec.thread) is not None and
10197
+ (name := rec.threadName) is not None
10198
+ ):
10199
+ return LoggingContextInfos.Thread(
10200
+ ident=ident,
10201
+ native_id=None,
10202
+ name=name,
10203
+ )
10204
+
10205
+ return None
10206
+
10207
+ class Process(OptionalAdapter[LoggingContextInfos.Process]):
10208
+ info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Process]] = LoggingContextInfos.Process
10209
+
10210
+ _record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
10211
+ # Process ID if available - that is, if `hasattr(os, 'getpid')` - and `logging.logProcesses` is truthy,
10212
+ # otherwise None.
10213
+ process=(ta.Optional[int], None),
10214
+ )
10215
+
10216
+ def _info_to_record(self, info: LoggingContextInfos.Process) -> ta.Mapping[str, ta.Any]:
10217
+ if logging.logProcesses:
10218
+ return dict(
10219
+ process=info.pid,
10220
+ )
10221
+
10222
+ return self.record_defaults
10223
+
10224
+ def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Process]:
10225
+ if (
10226
+ (pid := rec.process) is not None
10227
+ ):
10228
+ return LoggingContextInfos.Process(
10229
+ pid=pid,
10230
+ )
10231
+
10232
+ return None
10233
+
10234
+ class Multiprocessing(OptionalAdapter[LoggingContextInfos.Multiprocessing]):
10235
+ info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Multiprocessing]] = LoggingContextInfos.Multiprocessing
10236
+
10237
+ _record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
10238
+ # Process name if available. Set to None if `logging.logMultiprocessing` is not truthy. Otherwise, set to
10239
+ # 'MainProcess', then `sys.modules.get('multiprocessing').current_process().name` if that works, otherwise
10240
+ # remains as 'MainProcess'.
10241
+ #
10242
+ # As noted by stdlib:
10243
+ #
10244
+ # Errors may occur if multiprocessing has not finished loading yet - e.g. if a custom import hook causes
10245
+ # third-party code to run when multiprocessing calls import. See issue 8200 for an example
10246
+ #
10247
+ processName=(ta.Optional[str], None),
10248
+ )
10249
+
10250
+ def _info_to_record(self, info: LoggingContextInfos.Multiprocessing) -> ta.Mapping[str, ta.Any]:
10251
+ if logging.logMultiprocessing:
10252
+ return dict(
10253
+ processName=info.process_name,
10254
+ )
10255
+
10256
+ return self.record_defaults
10257
+
10258
+ def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Multiprocessing]:
10259
+ if (
10260
+ (process_name := rec.processName) is not None
10261
+ ):
10262
+ return LoggingContextInfos.Multiprocessing(
10263
+ process_name=process_name,
10264
+ )
10265
+
10266
+ return None
10267
+
10268
+ class AsyncioTask(OptionalAdapter[LoggingContextInfos.AsyncioTask]):
10269
+ info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.AsyncioTask]] = LoggingContextInfos.AsyncioTask
10270
+
10271
+ _record_attrs: ta.ClassVar[ta.Mapping[str, ta.Union[ta.Any, ta.Tuple[ta.Any, ta.Any]]]] = dict(
10272
+ # Absent <3.12, otherwise asyncio.Task name if available, and `logging.logAsyncioTasks` is truthy. Set to
10273
+ # `sys.modules.get('asyncio').current_task().get_name()`, otherwise None.
10274
+ taskName=(ta.Optional[str], None),
10275
+ )
10276
+
10277
+ def _info_to_record(self, info: LoggingContextInfos.AsyncioTask) -> ta.Mapping[str, ta.Any]:
10278
+ if getattr(logging, 'logAsyncioTasks', None): # Absent <3.12
10279
+ return dict(
10280
+ taskName=info.name,
10281
+ )
10282
+
10283
+ return self.record_defaults
10284
+
10285
+ def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.AsyncioTask]:
10286
+ if (
10287
+ (name := getattr(rec, 'taskName', None)) is not None
10288
+ ):
10289
+ return LoggingContextInfos.AsyncioTask(
10290
+ name=name,
10291
+ )
10292
+
10293
+ return None
10294
+
10295
+
10296
+ _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS_: ta.Sequence[LoggingContextInfoRecordAdapters.Adapter] = [ # noqa
10297
+ LoggingContextInfoRecordAdapters.Name(),
10298
+ LoggingContextInfoRecordAdapters.Level(),
10299
+ LoggingContextInfoRecordAdapters.Msg(),
10300
+ LoggingContextInfoRecordAdapters.Time(),
10301
+ LoggingContextInfoRecordAdapters.Exc(),
10302
+ LoggingContextInfoRecordAdapters.Caller(),
10303
+ LoggingContextInfoRecordAdapters.SourceFile(),
10304
+ LoggingContextInfoRecordAdapters.Thread(),
10305
+ LoggingContextInfoRecordAdapters.Process(),
10306
+ LoggingContextInfoRecordAdapters.Multiprocessing(),
10307
+ LoggingContextInfoRecordAdapters.AsyncioTask(),
10308
+ ]
10309
+
10310
+ _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS: ta.Mapping[ta.Type[LoggingContextInfo], LoggingContextInfoRecordAdapters.Adapter] = { # noqa
10311
+ ad.info_cls: ad for ad in _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS_
10312
+ }
10313
+
10314
+
10315
+ ##
10316
+
10317
+
10318
+ KNOWN_STD_LOGGING_RECORD_ATTR_SET: ta.FrozenSet[str] = frozenset(
10319
+ a for ad in _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS.values() for a in ad.record_attrs
10320
+ )
9966
10321
 
9967
10322
 
9968
10323
  # Formatter:
@@ -9986,14 +10341,17 @@ KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTRS: ta.Dict[str, ta.Any] = dict(
9986
10341
  KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTR_SET: ta.FrozenSet[str] = frozenset(KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTRS)
9987
10342
 
9988
10343
 
9989
- ##
9990
-
9991
-
9992
10344
  class UnknownStdLoggingRecordAttrsWarning(LoggingSetupWarning):
9993
10345
  pass
9994
10346
 
9995
10347
 
9996
10348
  def _check_std_logging_record_attrs() -> None:
10349
+ if (
10350
+ len([a for ad in _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS.values() for a in ad.record_attrs]) !=
10351
+ len(KNOWN_STD_LOGGING_RECORD_ATTR_SET)
10352
+ ):
10353
+ raise RuntimeError('Duplicate LoggingContextInfoRecordAdapter record attrs')
10354
+
9997
10355
  rec_dct = dict(logging.makeLogRecord({}).__dict__)
9998
10356
 
9999
10357
  if (unk_rec_fields := frozenset(rec_dct) - KNOWN_STD_LOGGING_RECORD_ATTR_SET):
@@ -10012,116 +10370,20 @@ _check_std_logging_record_attrs()
10012
10370
 
10013
10371
 
10014
10372
  class LoggingContextLogRecord(logging.LogRecord):
10015
- _SHOULD_ADD_TASK_NAME: ta.ClassVar[bool] = sys.version_info >= (3, 12)
10016
-
10017
- _UNKNOWN_PATH_NAME: ta.ClassVar[str] = '(unknown file)'
10018
- _UNKNOWN_FUNC_NAME: ta.ClassVar[str] = '(unknown function)'
10019
- _UNKNOWN_MODULE: ta.ClassVar[str] = 'Unknown module'
10020
-
10021
- _STACK_INFO_PREFIX: ta.ClassVar[str] = 'Stack (most recent call last):\n'
10022
-
10023
- def __init__( # noqa
10024
- self,
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
10067
-
10068
- self.exc_info: ta.Optional[LoggingExcInfoTuple] = ctx.exc_info_tuple
10069
- self.exc_text: ta.Optional[str] = None
10070
-
10071
- # If ctx.build_caller() was never called, we simply don't have a stack trace.
10072
- if caller is not None:
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
10080
-
10081
- self.lineno: int = caller.line_no
10082
- self.funcName: str = caller.name
10083
-
10084
- else:
10085
- self.stack_info = None
10086
-
10087
- self.lineno = 0
10088
- self.funcName = self._UNKNOWN_FUNC_NAME
10089
-
10090
- times = ctx.times
10091
- self.created: float = times.created
10092
- self.msecs: float = times.msecs
10093
- self.relativeCreated: float = times.relative_created
10094
-
10095
- if logging.logThreads:
10096
- thread = check.not_none(ctx.thread())
10097
- self.thread: ta.Optional[int] = thread.ident
10098
- self.threadName: ta.Optional[str] = thread.name
10099
- else:
10100
- self.thread = None
10101
- self.threadName = None
10102
-
10103
- if logging.logProcesses:
10104
- process = check.not_none(ctx.process())
10105
- self.process: ta.Optional[int] = process.pid
10106
- else:
10107
- self.process = None
10108
-
10109
- if logging.logMultiprocessing:
10110
- if (mp := ctx.multiprocessing()) is not None:
10111
- self.processName: ta.Optional[str] = mp.process_name
10112
- else:
10113
- self.processName = None
10114
- else:
10115
- self.processName = None
10116
-
10117
- # Absent <3.12
10118
- if getattr(logging, 'logAsyncioTasks', None):
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
10373
+ # LogRecord.__init__ args:
10374
+ # - name: str
10375
+ # - level: int
10376
+ # - pathname: str - Confusingly referred to as `fn` before the LogRecord ctor. May be empty or "(unknown file)".
10377
+ # - lineno: int - May be 0.
10378
+ # - msg: str
10379
+ # - args: tuple | dict | 1-tuple[dict]
10380
+ # - exc_info: LoggingExcInfoTuple | None
10381
+ # - func: str | None = None -> funcName
10382
+ # - sinfo: str | None = None -> stack_info
10383
+
10384
+ def __init__(self, *, _logging_context: LoggingContext) -> None: # noqa
10385
+ for ad in _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS_:
10386
+ self.__dict__.update(ad.context_to_record(_logging_context))
10125
10387
 
10126
10388
 
10127
10389
  ########################################
@@ -10421,21 +10683,20 @@ class StdLogger(Logger):
10421
10683
  return self._std.getEffectiveLevel()
10422
10684
 
10423
10685
  def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any) -> None:
10424
- if not self.is_enabled_for(ctx.level):
10686
+ if not self.is_enabled_for(ctx.must_get_info(LoggingContextInfos.Level).level):
10425
10687
  return
10426
10688
 
10427
- ctx.capture()
10428
-
10429
- ms, args = self._prepare_msg_args(msg, *args)
10430
-
10431
- rec = LoggingContextLogRecord(
10689
+ ctx.set_basic(
10432
10690
  name=self._std.name,
10433
- msg=ms,
10434
- args=args,
10435
10691
 
10436
- _logging_context=ctx,
10692
+ msg=msg,
10693
+ args=args,
10437
10694
  )
10438
10695
 
10696
+ ctx.capture()
10697
+
10698
+ rec = LoggingContextLogRecord(_logging_context=ctx)
10699
+
10439
10700
  self._std.handle(rec)
10440
10701
 
10441
10702