omdev 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.
@@ -130,6 +130,13 @@ U = ta.TypeVar('U')
130
130
  # ../../omlish/lite/timeouts.py
131
131
  TimeoutLike = ta.Union['Timeout', ta.Type['Timeout.DEFAULT'], ta.Iterable['TimeoutLike'], float, None] # ta.TypeAlias
132
132
 
133
+ # ../../omlish/logs/infos.py
134
+ LoggingMsgFn = ta.Callable[[], ta.Union[str, tuple]] # ta.TypeAlias
135
+ LoggingExcInfoTuple = ta.Tuple[ta.Type[BaseException], BaseException, ta.Optional[types.TracebackType]] # ta.TypeAlias
136
+ LoggingExcInfo = ta.Union[BaseException, LoggingExcInfoTuple] # ta.TypeAlias
137
+ LoggingExcInfoArg = ta.Union[LoggingExcInfo, bool, None] # ta.TypeAlias
138
+ LoggingContextInfo = ta.Any # ta.TypeAlias
139
+
133
140
  # ../../omlish/asyncs/asyncio/timeouts.py
134
141
  AwaitableT = ta.TypeVar('AwaitableT', bound=ta.Awaitable)
135
142
 
@@ -140,12 +147,7 @@ InjectorProviderFnMap = ta.Mapping['InjectorKey', 'InjectorProviderFn']
140
147
  InjectorBindingOrBindings = ta.Union['InjectorBinding', 'InjectorBindings']
141
148
 
142
149
  # ../../omlish/logs/contexts.py
143
- LoggingExcInfoTuple = ta.Tuple[ta.Type[BaseException], BaseException, ta.Optional[types.TracebackType]] # ta.TypeAlias
144
- LoggingExcInfo = ta.Union[BaseException, LoggingExcInfoTuple] # ta.TypeAlias
145
- LoggingExcInfoArg = ta.Union[LoggingExcInfo, bool, None] # ta.TypeAlias
146
-
147
- # ../../omlish/logs/base.py
148
- LoggingMsgFn = ta.Callable[[], ta.Union[str, tuple]] # ta.TypeAlias
150
+ LoggingContextInfoT = ta.TypeVar('LoggingContextInfoT', bound=LoggingContextInfo)
149
151
 
150
152
  # ../../omlish/subprocesses/base.py
151
153
  SubprocessChannelOption = ta.Literal['pipe', 'stdout', 'devnull'] # ta.TypeAlias
@@ -2959,124 +2961,6 @@ def typing_annotations_attr() -> str:
2959
2961
  return _TYPING_ANNOTATIONS_ATTR
2960
2962
 
2961
2963
 
2962
- ########################################
2963
- # ../../../omlish/logs/infos.py
2964
-
2965
-
2966
- ##
2967
-
2968
-
2969
- def logging_context_info(cls):
2970
- return cls
2971
-
2972
-
2973
- ##
2974
-
2975
-
2976
- @logging_context_info
2977
- @ta.final
2978
- class LoggingSourceFileInfo(ta.NamedTuple):
2979
- file_name: str
2980
- module: str
2981
-
2982
- @classmethod
2983
- def build(cls, file_path: ta.Optional[str]) -> ta.Optional['LoggingSourceFileInfo']:
2984
- if file_path is None:
2985
- return None
2986
-
2987
- # https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L331-L336 # noqa
2988
- try:
2989
- file_name = os.path.basename(file_path)
2990
- module = os.path.splitext(file_name)[0]
2991
- except (TypeError, ValueError, AttributeError):
2992
- return None
2993
-
2994
- return cls(
2995
- file_name=file_name,
2996
- module=module,
2997
- )
2998
-
2999
-
3000
- ##
3001
-
3002
-
3003
- @logging_context_info
3004
- @ta.final
3005
- class LoggingThreadInfo(ta.NamedTuple):
3006
- ident: int
3007
- native_id: ta.Optional[int]
3008
- name: str
3009
-
3010
- @classmethod
3011
- def build(cls) -> 'LoggingThreadInfo':
3012
- return cls(
3013
- ident=threading.get_ident(),
3014
- native_id=threading.get_native_id() if hasattr(threading, 'get_native_id') else None,
3015
- name=threading.current_thread().name,
3016
- )
3017
-
3018
-
3019
- ##
3020
-
3021
-
3022
- @logging_context_info
3023
- @ta.final
3024
- class LoggingProcessInfo(ta.NamedTuple):
3025
- pid: int
3026
-
3027
- @classmethod
3028
- def build(cls) -> 'LoggingProcessInfo':
3029
- return cls(
3030
- pid=os.getpid(),
3031
- )
3032
-
3033
-
3034
- ##
3035
-
3036
-
3037
- @logging_context_info
3038
- @ta.final
3039
- class LoggingMultiprocessingInfo(ta.NamedTuple):
3040
- process_name: str
3041
-
3042
- @classmethod
3043
- def build(cls) -> ta.Optional['LoggingMultiprocessingInfo']:
3044
- # https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L355-L364 # noqa
3045
- if (mp := sys.modules.get('multiprocessing')) is None:
3046
- return None
3047
-
3048
- return cls(
3049
- process_name=mp.current_process().name,
3050
- )
3051
-
3052
-
3053
- ##
3054
-
3055
-
3056
- @logging_context_info
3057
- @ta.final
3058
- class LoggingAsyncioTaskInfo(ta.NamedTuple):
3059
- name: str
3060
-
3061
- @classmethod
3062
- def build(cls) -> ta.Optional['LoggingAsyncioTaskInfo']:
3063
- # https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L372-L377 # noqa
3064
- if (asyncio := sys.modules.get('asyncio')) is None:
3065
- return None
3066
-
3067
- try:
3068
- task = asyncio.current_task()
3069
- except Exception: # noqa
3070
- return None
3071
-
3072
- if task is None:
3073
- return None
3074
-
3075
- return cls(
3076
- name=task.get_name(), # Always non-None
3077
- )
3078
-
3079
-
3080
2964
  ########################################
3081
2965
  # ../../../omlish/logs/levels.py
3082
2966
 
@@ -5560,74 +5444,362 @@ class PredicateTimeout(Timeout):
5560
5444
 
5561
5445
 
5562
5446
  ########################################
5563
- # ../../../omlish/logs/callers.py
5447
+ # ../../../omlish/logs/infos.py
5448
+ """
5449
+ TODO:
5450
+ - remove redundant info fields only present for std adaptation (Level.name, ...)
5451
+ """
5564
5452
 
5565
5453
 
5566
5454
  ##
5567
5455
 
5568
5456
 
5569
- @logging_context_info
5457
+ def logging_context_info(cls):
5458
+ return cls
5459
+
5460
+
5570
5461
  @ta.final
5571
- class LoggingCaller(ta.NamedTuple):
5572
- file_path: str
5573
- line_no: int
5574
- name: str
5575
- stack_info: ta.Optional[str]
5462
+ class LoggingContextInfos:
5463
+ def __new__(cls, *args, **kwargs): # noqa
5464
+ raise TypeError
5576
5465
 
5577
- @classmethod
5578
- def is_internal_frame(cls, frame: types.FrameType) -> bool:
5579
- file_path = os.path.normcase(frame.f_code.co_filename)
5466
+ #
5580
5467
 
5581
- # Yes, really.
5582
- # https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L204
5583
- # https://github.com/python/cpython/commit/5ca6d7469be53960843df39bb900e9c3359f127f
5584
- if 'importlib' in file_path and '_bootstrap' in file_path:
5585
- return True
5468
+ @logging_context_info
5469
+ @ta.final
5470
+ class Name(ta.NamedTuple):
5471
+ name: str
5586
5472
 
5587
- return False
5473
+ @logging_context_info
5474
+ @ta.final
5475
+ class Level(ta.NamedTuple):
5476
+ level: NamedLogLevel
5477
+ name: str
5588
5478
 
5589
- @classmethod
5590
- def find_frame(cls, ofs: int = 0) -> ta.Optional[types.FrameType]:
5591
- f: ta.Optional[types.FrameType] = sys._getframe(2 + ofs) # noqa
5479
+ @classmethod
5480
+ def build(cls, level: int) -> 'LoggingContextInfos.Level':
5481
+ nl: NamedLogLevel = level if level.__class__ is NamedLogLevel else NamedLogLevel(level) # type: ignore[assignment] # noqa
5482
+ return cls(
5483
+ level=nl,
5484
+ name=logging.getLevelName(nl),
5485
+ )
5486
+
5487
+ @logging_context_info
5488
+ @ta.final
5489
+ class Msg(ta.NamedTuple):
5490
+ msg: str
5491
+ args: ta.Union[tuple, ta.Mapping[ta.Any, ta.Any], None]
5492
+
5493
+ @classmethod
5494
+ def build(
5495
+ cls,
5496
+ msg: ta.Union[str, tuple, LoggingMsgFn],
5497
+ *args: ta.Any,
5498
+ ) -> 'LoggingContextInfos.Msg':
5499
+ s: str
5500
+ a: ta.Any
5501
+
5502
+ if callable(msg):
5503
+ if args:
5504
+ raise TypeError(f'Must not provide both a message function and args: {msg=} {args=}')
5505
+ x = msg()
5506
+ if isinstance(x, str):
5507
+ s, a = x, ()
5508
+ elif isinstance(x, tuple):
5509
+ if x:
5510
+ s, a = x[0], x[1:]
5511
+ else:
5512
+ s, a = '', ()
5513
+ else:
5514
+ raise TypeError(x)
5592
5515
 
5593
- while f is not None:
5594
- # NOTE: We don't check __file__ like stdlib since we may be running amalgamated - we rely on careful, manual
5595
- # stack_offset management.
5596
- if hasattr(f, 'f_code'):
5597
- return f
5516
+ elif isinstance(msg, tuple):
5517
+ if args:
5518
+ raise TypeError(f'Must not provide both a tuple message and args: {msg=} {args=}')
5519
+ if msg:
5520
+ s, a = msg[0], msg[1:]
5521
+ else:
5522
+ s, a = '', ()
5598
5523
 
5599
- f = f.f_back
5524
+ elif isinstance(msg, str):
5525
+ s, a = msg, args
5600
5526
 
5601
- return None
5527
+ else:
5528
+ raise TypeError(msg)
5529
+
5530
+ # https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L307 # noqa
5531
+ if a and len(a) == 1 and isinstance(a[0], collections.abc.Mapping) and a[0]:
5532
+ a = a[0]
5533
+
5534
+ return cls(
5535
+ msg=s,
5536
+ args=a,
5537
+ )
5538
+
5539
+ @logging_context_info
5540
+ @ta.final
5541
+ class Extra(ta.NamedTuple):
5542
+ extra: ta.Mapping[ta.Any, ta.Any]
5543
+
5544
+ @logging_context_info
5545
+ @ta.final
5546
+ class Time(ta.NamedTuple):
5547
+ ns: int
5548
+ secs: float
5549
+ msecs: float
5550
+ relative_secs: float
5551
+
5552
+ @classmethod
5553
+ def get_std_start_ns(cls) -> int:
5554
+ x: ta.Any = logging._startTime # type: ignore[attr-defined] # noqa
5555
+
5556
+ # Before 3.13.0b1 this will be `time.time()`, a float of seconds. After that, it will be `time.time_ns()`,
5557
+ # an int.
5558
+ #
5559
+ # See:
5560
+ # - https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
5561
+ #
5562
+ if isinstance(x, float):
5563
+ return int(x * 1e9)
5564
+ else:
5565
+ return x
5566
+
5567
+ @classmethod
5568
+ def build(
5569
+ cls,
5570
+ ns: int,
5571
+ *,
5572
+ start_ns: ta.Optional[int] = None,
5573
+ ) -> 'LoggingContextInfos.Time':
5574
+ # https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
5575
+ secs = ns / 1e9 # ns to float seconds
5576
+
5577
+ # Get the number of whole milliseconds (0-999) in the fractional part of seconds.
5578
+ # Eg: 1_677_903_920_999_998_503 ns --> 999_998_503 ns--> 999 ms
5579
+ # Convert to float by adding 0.0 for historical reasons. See gh-89047
5580
+ msecs = (ns % 1_000_000_000) // 1_000_000 + 0.0
5581
+
5582
+ # https://github.com/python/cpython/commit/1500a23f33f5a6d052ff1ef6383d9839928b8ff1
5583
+ if msecs == 999.0 and int(secs) != ns // 1_000_000_000:
5584
+ # ns -> sec conversion can round up, e.g:
5585
+ # 1_677_903_920_999_999_900 ns --> 1_677_903_921.0 sec
5586
+ msecs = 0.0
5587
+
5588
+ if start_ns is None:
5589
+ start_ns = cls.get_std_start_ns()
5590
+ relative_secs = (ns - start_ns) / 1e6
5591
+
5592
+ return cls(
5593
+ ns=ns,
5594
+ secs=secs,
5595
+ msecs=msecs,
5596
+ relative_secs=relative_secs,
5597
+ )
5598
+
5599
+ @logging_context_info
5600
+ @ta.final
5601
+ class Exc(ta.NamedTuple):
5602
+ info: LoggingExcInfo
5603
+ info_tuple: LoggingExcInfoTuple
5604
+
5605
+ @classmethod
5606
+ def build(
5607
+ cls,
5608
+ arg: LoggingExcInfoArg = False,
5609
+ ) -> ta.Optional['LoggingContextInfos.Exc']:
5610
+ if arg is True:
5611
+ sys_exc_info = sys.exc_info()
5612
+ if sys_exc_info[0] is not None:
5613
+ arg = sys_exc_info
5614
+ else:
5615
+ arg = None
5616
+ elif arg is False:
5617
+ arg = None
5618
+ if arg is None:
5619
+ return None
5620
+
5621
+ info: LoggingExcInfo = arg
5622
+ if isinstance(info, BaseException):
5623
+ info_tuple: LoggingExcInfoTuple = (type(info), info, info.__traceback__) # noqa
5624
+ else:
5625
+ info_tuple = info
5626
+
5627
+ return cls(
5628
+ info=info,
5629
+ info_tuple=info_tuple,
5630
+ )
5631
+
5632
+ @logging_context_info
5633
+ @ta.final
5634
+ class Caller(ta.NamedTuple):
5635
+ file_path: str
5636
+ line_no: int
5637
+ func_name: str
5638
+ stack_info: ta.Optional[str]
5639
+
5640
+ @classmethod
5641
+ def is_internal_frame(cls, frame: types.FrameType) -> bool:
5642
+ file_path = os.path.normcase(frame.f_code.co_filename)
5643
+
5644
+ # Yes, really.
5645
+ # https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L204 # noqa
5646
+ # https://github.com/python/cpython/commit/5ca6d7469be53960843df39bb900e9c3359f127f
5647
+ if 'importlib' in file_path and '_bootstrap' in file_path:
5648
+ return True
5649
+
5650
+ return False
5651
+
5652
+ @classmethod
5653
+ def find_frame(cls, stack_offset: int = 0) -> ta.Optional[types.FrameType]:
5654
+ f: ta.Optional[types.FrameType] = sys._getframe(2 + stack_offset) # noqa
5655
+
5656
+ while f is not None:
5657
+ # NOTE: We don't check __file__ like stdlib since we may be running amalgamated - we rely on careful,
5658
+ # manual stack_offset management.
5659
+ if hasattr(f, 'f_code'):
5660
+ return f
5661
+
5662
+ f = f.f_back
5602
5663
 
5603
- @classmethod
5604
- def find(
5605
- cls,
5606
- ofs: int = 0,
5607
- *,
5608
- stack_info: bool = False,
5609
- ) -> ta.Optional['LoggingCaller']:
5610
- if (f := cls.find_frame(ofs + 1)) is None:
5611
5664
  return None
5612
5665
 
5613
- # https://github.com/python/cpython/blob/08e9794517063c8cd92c48714071b1d3c60b71bd/Lib/logging/__init__.py#L1616-L1623 # noqa
5614
- sinfo = None
5615
- if stack_info:
5616
- sio = io.StringIO()
5617
- traceback.print_stack(f, file=sio)
5618
- sinfo = sio.getvalue()
5619
- sio.close()
5620
- if sinfo[-1] == '\n':
5621
- sinfo = sinfo[:-1]
5666
+ @classmethod
5667
+ def build(
5668
+ cls,
5669
+ stack_offset: int = 0,
5670
+ *,
5671
+ stack_info: bool = False,
5672
+ ) -> ta.Optional['LoggingContextInfos.Caller']:
5673
+ if (f := cls.find_frame(stack_offset + 1)) is None:
5674
+ return None
5622
5675
 
5623
- return cls(
5624
- file_path=f.f_code.co_filename,
5625
- line_no=f.f_lineno or 0,
5626
- name=f.f_code.co_name,
5627
- stack_info=sinfo,
5676
+ # https://github.com/python/cpython/blob/08e9794517063c8cd92c48714071b1d3c60b71bd/Lib/logging/__init__.py#L1616-L1623 # noqa
5677
+ sinfo = None
5678
+ if stack_info:
5679
+ sio = io.StringIO()
5680
+ traceback.print_stack(f, file=sio)
5681
+ sinfo = sio.getvalue()
5682
+ sio.close()
5683
+ if sinfo[-1] == '\n':
5684
+ sinfo = sinfo[:-1]
5685
+
5686
+ return cls(
5687
+ file_path=f.f_code.co_filename,
5688
+ line_no=f.f_lineno or 0,
5689
+ func_name=f.f_code.co_name,
5690
+ stack_info=sinfo,
5691
+ )
5692
+
5693
+ @logging_context_info
5694
+ @ta.final
5695
+ class SourceFile(ta.NamedTuple):
5696
+ file_name: str
5697
+ module: str
5698
+
5699
+ @classmethod
5700
+ def build(cls, caller_file_path: ta.Optional[str]) -> ta.Optional['LoggingContextInfos.SourceFile']:
5701
+ if caller_file_path is None:
5702
+ return None
5703
+
5704
+ # https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L331-L336 # noqa
5705
+ try:
5706
+ file_name = os.path.basename(caller_file_path)
5707
+ module = os.path.splitext(file_name)[0]
5708
+ except (TypeError, ValueError, AttributeError):
5709
+ return None
5710
+
5711
+ return cls(
5712
+ file_name=file_name,
5713
+ module=module,
5714
+ )
5715
+
5716
+ @logging_context_info
5717
+ @ta.final
5718
+ class Thread(ta.NamedTuple):
5719
+ ident: int
5720
+ native_id: ta.Optional[int]
5721
+ name: str
5722
+
5723
+ @classmethod
5724
+ def build(cls) -> 'LoggingContextInfos.Thread':
5725
+ return cls(
5726
+ ident=threading.get_ident(),
5727
+ native_id=threading.get_native_id() if hasattr(threading, 'get_native_id') else None,
5728
+ name=threading.current_thread().name,
5729
+ )
5730
+
5731
+ @logging_context_info
5732
+ @ta.final
5733
+ class Process(ta.NamedTuple):
5734
+ pid: int
5735
+
5736
+ @classmethod
5737
+ def build(cls) -> 'LoggingContextInfos.Process':
5738
+ return cls(
5739
+ pid=os.getpid(),
5740
+ )
5741
+
5742
+ @logging_context_info
5743
+ @ta.final
5744
+ class Multiprocessing(ta.NamedTuple):
5745
+ process_name: str
5746
+
5747
+ @classmethod
5748
+ def build(cls) -> ta.Optional['LoggingContextInfos.Multiprocessing']:
5749
+ # https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L355-L364 # noqa
5750
+ if (mp := sys.modules.get('multiprocessing')) is None:
5751
+ return None
5752
+
5753
+ return cls(
5754
+ process_name=mp.current_process().name,
5755
+ )
5756
+
5757
+ @logging_context_info
5758
+ @ta.final
5759
+ class AsyncioTask(ta.NamedTuple):
5760
+ name: str
5761
+
5762
+ @classmethod
5763
+ def build(cls) -> ta.Optional['LoggingContextInfos.AsyncioTask']:
5764
+ # https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L372-L377 # noqa
5765
+ if (asyncio := sys.modules.get('asyncio')) is None:
5766
+ return None
5767
+
5768
+ try:
5769
+ task = asyncio.current_task()
5770
+ except Exception: # noqa
5771
+ return None
5772
+
5773
+ if task is None:
5774
+ return None
5775
+
5776
+ return cls(
5777
+ name=task.get_name(), # Always non-None
5778
+ )
5779
+
5780
+
5781
+ ##
5782
+
5783
+
5784
+ class UnexpectedLoggingStartTimeWarning(LoggingSetupWarning):
5785
+ pass
5786
+
5787
+
5788
+ def _check_logging_start_time() -> None:
5789
+ if (x := LoggingContextInfos.Time.get_std_start_ns()) < (t := time.time()):
5790
+ import warnings # noqa
5791
+
5792
+ warnings.warn(
5793
+ f'Unexpected logging start time detected: '
5794
+ f'get_std_start_ns={x}, '
5795
+ f'time.time()={t}',
5796
+ UnexpectedLoggingStartTimeWarning,
5628
5797
  )
5629
5798
 
5630
5799
 
5800
+ _check_logging_start_time()
5801
+
5802
+
5631
5803
  ########################################
5632
5804
  # ../../../omlish/logs/protocols.py
5633
5805
 
@@ -5717,91 +5889,6 @@ class JsonLoggingFormatter(logging.Formatter):
5717
5889
  return self._json_dumps(dct)
5718
5890
 
5719
5891
 
5720
- ########################################
5721
- # ../../../omlish/logs/times.py
5722
-
5723
-
5724
- ##
5725
-
5726
-
5727
- @logging_context_info
5728
- @ta.final
5729
- class LoggingTimeFields(ta.NamedTuple):
5730
- """Maps directly to stdlib `logging.LogRecord` fields, and must be kept in sync with it."""
5731
-
5732
- created: float
5733
- msecs: float
5734
- relative_created: float
5735
-
5736
- @classmethod
5737
- def get_std_start_time_ns(cls) -> int:
5738
- x: ta.Any = logging._startTime # type: ignore[attr-defined] # noqa
5739
-
5740
- # Before 3.13.0b1 this will be `time.time()`, a float of seconds. After that, it will be `time.time_ns()`, an
5741
- # int.
5742
- #
5743
- # See:
5744
- # - https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
5745
- #
5746
- if isinstance(x, float):
5747
- return int(x * 1e9)
5748
- else:
5749
- return x
5750
-
5751
- @classmethod
5752
- def build(
5753
- cls,
5754
- time_ns: int,
5755
- *,
5756
- start_time_ns: ta.Optional[int] = None,
5757
- ) -> 'LoggingTimeFields':
5758
- # https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
5759
- created = time_ns / 1e9 # ns to float seconds
5760
-
5761
- # Get the number of whole milliseconds (0-999) in the fractional part of seconds.
5762
- # Eg: 1_677_903_920_999_998_503 ns --> 999_998_503 ns--> 999 ms
5763
- # Convert to float by adding 0.0 for historical reasons. See gh-89047
5764
- msecs = (time_ns % 1_000_000_000) // 1_000_000 + 0.0
5765
-
5766
- # https://github.com/python/cpython/commit/1500a23f33f5a6d052ff1ef6383d9839928b8ff1
5767
- if msecs == 999.0 and int(created) != time_ns // 1_000_000_000:
5768
- # ns -> sec conversion can round up, e.g:
5769
- # 1_677_903_920_999_999_900 ns --> 1_677_903_921.0 sec
5770
- msecs = 0.0
5771
-
5772
- if start_time_ns is None:
5773
- start_time_ns = cls.get_std_start_time_ns()
5774
- relative_created = (time_ns - start_time_ns) / 1e6
5775
-
5776
- return cls(
5777
- created=created,
5778
- msecs=msecs,
5779
- relative_created=relative_created,
5780
- )
5781
-
5782
-
5783
- ##
5784
-
5785
-
5786
- class UnexpectedLoggingStartTimeWarning(LoggingSetupWarning):
5787
- pass
5788
-
5789
-
5790
- def _check_logging_start_time() -> None:
5791
- if (x := LoggingTimeFields.get_std_start_time_ns()) < (t := time.time()):
5792
- import warnings # noqa
5793
-
5794
- warnings.warn(
5795
- f'Unexpected logging start time detected: '
5796
- f'get_std_start_time_ns={x}, '
5797
- f'time.time()={t}',
5798
- UnexpectedLoggingStartTimeWarning,
5799
- )
5800
-
5801
-
5802
- _check_logging_start_time()
5803
-
5804
-
5805
5892
  ########################################
5806
5893
  # ../../interp/types.py
5807
5894
 
@@ -7015,68 +7102,36 @@ inj = InjectionApi()
7015
7102
 
7016
7103
 
7017
7104
  class LoggingContext(Abstract):
7018
- @property
7019
7105
  @abc.abstractmethod
7020
- def level(self) -> NamedLogLevel:
7106
+ def get_info(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
7021
7107
  raise NotImplementedError
7022
7108
 
7023
- #
7109
+ @ta.final
7110
+ def __getitem__(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
7111
+ return self.get_info(ty)
7024
7112
 
7025
- @property
7026
- @abc.abstractmethod
7027
- def time_ns(self) -> int:
7028
- raise NotImplementedError
7113
+ @ta.final
7114
+ def must_get_info(self, ty: ta.Type[LoggingContextInfoT]) -> LoggingContextInfoT:
7115
+ if (info := self.get_info(ty)) is None:
7116
+ raise TypeError(f'LoggingContextInfo absent: {ty}')
7117
+ return info
7029
7118
 
7030
- @property
7031
- @abc.abstractmethod
7032
- def times(self) -> LoggingTimeFields:
7033
- raise NotImplementedError
7034
-
7035
- #
7036
-
7037
- @property
7038
- @abc.abstractmethod
7039
- def exc_info(self) -> ta.Optional[LoggingExcInfo]:
7040
- raise NotImplementedError
7119
+ ##
7041
7120
 
7042
- @property
7043
- @abc.abstractmethod
7044
- def exc_info_tuple(self) -> ta.Optional[LoggingExcInfoTuple]:
7045
- raise NotImplementedError
7046
-
7047
- #
7048
7121
 
7122
+ class CaptureLoggingContext(LoggingContext, Abstract):
7049
7123
  @abc.abstractmethod
7050
- def caller(self) -> ta.Optional[LoggingCaller]:
7051
- raise NotImplementedError
7124
+ def set_basic(
7125
+ self,
7126
+ name: str,
7052
7127
 
7053
- @abc.abstractmethod
7054
- def source_file(self) -> ta.Optional[LoggingSourceFileInfo]:
7128
+ msg: ta.Union[str, tuple, LoggingMsgFn],
7129
+ args: tuple,
7130
+ ) -> 'CaptureLoggingContext':
7055
7131
  raise NotImplementedError
7056
7132
 
7057
7133
  #
7058
7134
 
7059
- @abc.abstractmethod
7060
- def thread(self) -> ta.Optional[LoggingThreadInfo]:
7061
- raise NotImplementedError
7062
-
7063
- @abc.abstractmethod
7064
- def process(self) -> ta.Optional[LoggingProcessInfo]:
7065
- raise NotImplementedError
7066
-
7067
- @abc.abstractmethod
7068
- def multiprocessing(self) -> ta.Optional[LoggingMultiprocessingInfo]:
7069
- raise NotImplementedError
7070
-
7071
- @abc.abstractmethod
7072
- def asyncio_task(self) -> ta.Optional[LoggingAsyncioTaskInfo]:
7073
- raise NotImplementedError
7074
-
7075
-
7076
- ##
7077
-
7078
-
7079
- class CaptureLoggingContext(LoggingContext, Abstract):
7080
7135
  class AlreadyCapturedError(Exception):
7081
7136
  pass
7082
7137
 
@@ -7107,80 +7162,50 @@ class CaptureLoggingContextImpl(CaptureLoggingContext):
7107
7162
 
7108
7163
  exc_info: LoggingExcInfoArg = False,
7109
7164
 
7110
- caller: ta.Union[LoggingCaller, ta.Type[NOT_SET], None] = NOT_SET,
7165
+ caller: ta.Union[LoggingContextInfos.Caller, ta.Type[NOT_SET], None] = NOT_SET,
7111
7166
  stack_offset: int = 0,
7112
7167
  stack_info: bool = False,
7113
7168
  ) -> None:
7114
- self._level: NamedLogLevel = level if level.__class__ is NamedLogLevel else NamedLogLevel(level) # type: ignore[assignment] # noqa
7115
-
7116
- #
7169
+ # TODO: Name, Msg, Extra
7117
7170
 
7118
7171
  if time_ns is None:
7119
7172
  time_ns = time.time_ns()
7120
- self._time_ns: int = time_ns
7121
-
7122
- #
7123
-
7124
- if exc_info is True:
7125
- sys_exc_info = sys.exc_info()
7126
- if sys_exc_info[0] is not None:
7127
- exc_info = sys_exc_info
7128
- else:
7129
- exc_info = None
7130
- elif exc_info is False:
7131
- exc_info = None
7132
-
7133
- if exc_info is not None:
7134
- self._exc_info: ta.Optional[LoggingExcInfo] = exc_info
7135
- if isinstance(exc_info, BaseException):
7136
- self._exc_info_tuple: ta.Optional[LoggingExcInfoTuple] = (type(exc_info), exc_info, exc_info.__traceback__) # noqa
7137
- else:
7138
- self._exc_info_tuple = exc_info
7139
7173
 
7140
- #
7174
+ self._infos: ta.Dict[ta.Type[LoggingContextInfo], LoggingContextInfo] = {}
7175
+ self._set_info(
7176
+ LoggingContextInfos.Level.build(level),
7177
+ LoggingContextInfos.Time.build(time_ns),
7178
+ LoggingContextInfos.Exc.build(exc_info),
7179
+ )
7141
7180
 
7142
7181
  if caller is not CaptureLoggingContextImpl.NOT_SET:
7143
- self._caller = caller # type: ignore[assignment]
7182
+ self._infos[LoggingContextInfos.Caller] = caller
7144
7183
  else:
7145
7184
  self._stack_offset = stack_offset
7146
7185
  self._stack_info = stack_info
7147
7186
 
7148
- ##
7149
-
7150
- @property
7151
- def level(self) -> NamedLogLevel:
7152
- return self._level
7153
-
7154
- #
7155
-
7156
- @property
7157
- def time_ns(self) -> int:
7158
- return self._time_ns
7159
-
7160
- _times: LoggingTimeFields
7161
-
7162
- @property
7163
- def times(self) -> LoggingTimeFields:
7164
- try:
7165
- return self._times
7166
- except AttributeError:
7167
- pass
7168
-
7169
- times = self._times = LoggingTimeFields.build(self.time_ns)
7170
- return times
7187
+ def _set_info(self, *infos: ta.Optional[LoggingContextInfo]) -> 'CaptureLoggingContextImpl':
7188
+ for info in infos:
7189
+ if info is not None:
7190
+ self._infos[type(info)] = info
7191
+ return self
7171
7192
 
7172
- #
7193
+ def get_info(self, ty: ta.Type[LoggingContextInfoT]) -> ta.Optional[LoggingContextInfoT]:
7194
+ return self._infos.get(ty)
7173
7195
 
7174
- _exc_info: ta.Optional[LoggingExcInfo] = None
7175
- _exc_info_tuple: ta.Optional[LoggingExcInfoTuple] = None
7196
+ ##
7176
7197
 
7177
- @property
7178
- def exc_info(self) -> ta.Optional[LoggingExcInfo]:
7179
- return self._exc_info
7198
+ def set_basic(
7199
+ self,
7200
+ name: str,
7180
7201
 
7181
- @property
7182
- def exc_info_tuple(self) -> ta.Optional[LoggingExcInfoTuple]:
7183
- return self._exc_info_tuple
7202
+ msg: ta.Union[str, tuple, LoggingMsgFn],
7203
+ args: tuple,
7204
+ ) -> 'CaptureLoggingContextImpl':
7205
+ return self._set_info(
7206
+ LoggingContextInfos.Name(name),
7207
+ LoggingContextInfos.Msg.build(msg, *args),
7208
+ )
7184
7209
 
7185
7210
  ##
7186
7211
 
@@ -7194,74 +7219,28 @@ class CaptureLoggingContextImpl(CaptureLoggingContext):
7194
7219
 
7195
7220
  _has_captured: bool = False
7196
7221
 
7197
- _caller: ta.Optional[LoggingCaller]
7198
- _source_file: ta.Optional[LoggingSourceFileInfo]
7199
-
7200
- _thread: ta.Optional[LoggingThreadInfo]
7201
- _process: ta.Optional[LoggingProcessInfo]
7202
- _multiprocessing: ta.Optional[LoggingMultiprocessingInfo]
7203
- _asyncio_task: ta.Optional[LoggingAsyncioTaskInfo]
7204
-
7205
7222
  def capture(self) -> None:
7206
7223
  if self._has_captured:
7207
7224
  raise CaptureLoggingContextImpl.AlreadyCapturedError
7208
7225
  self._has_captured = True
7209
7226
 
7210
- if not hasattr(self, '_caller'):
7211
- self._caller = LoggingCaller.find(
7227
+ if LoggingContextInfos.Caller not in self._infos:
7228
+ self._set_info(LoggingContextInfos.Caller.build(
7212
7229
  self._stack_offset + 1,
7213
7230
  stack_info=self._stack_info,
7214
- )
7215
-
7216
- if (caller := self._caller) is not None:
7217
- self._source_file = LoggingSourceFileInfo.build(caller.file_path)
7218
- else:
7219
- self._source_file = None
7220
-
7221
- self._thread = LoggingThreadInfo.build()
7222
- self._process = LoggingProcessInfo.build()
7223
- self._multiprocessing = LoggingMultiprocessingInfo.build()
7224
- self._asyncio_task = LoggingAsyncioTaskInfo.build()
7225
-
7226
- #
7227
-
7228
- def caller(self) -> ta.Optional[LoggingCaller]:
7229
- try:
7230
- return self._caller
7231
- except AttributeError:
7232
- raise CaptureLoggingContext.NotCapturedError from None
7233
-
7234
- def source_file(self) -> ta.Optional[LoggingSourceFileInfo]:
7235
- try:
7236
- return self._source_file
7237
- except AttributeError:
7238
- raise CaptureLoggingContext.NotCapturedError from None
7239
-
7240
- #
7241
-
7242
- def thread(self) -> ta.Optional[LoggingThreadInfo]:
7243
- try:
7244
- return self._thread
7245
- except AttributeError:
7246
- raise CaptureLoggingContext.NotCapturedError from None
7247
-
7248
- def process(self) -> ta.Optional[LoggingProcessInfo]:
7249
- try:
7250
- return self._process
7251
- except AttributeError:
7252
- raise CaptureLoggingContext.NotCapturedError from None
7231
+ ))
7253
7232
 
7254
- def multiprocessing(self) -> ta.Optional[LoggingMultiprocessingInfo]:
7255
- try:
7256
- return self._multiprocessing
7257
- except AttributeError:
7258
- raise CaptureLoggingContext.NotCapturedError from None
7233
+ if (caller := self[LoggingContextInfos.Caller]) is not None:
7234
+ self._set_info(LoggingContextInfos.SourceFile.build(
7235
+ caller.file_path,
7236
+ ))
7259
7237
 
7260
- def asyncio_task(self) -> ta.Optional[LoggingAsyncioTaskInfo]:
7261
- try:
7262
- return self._asyncio_task
7263
- except AttributeError:
7264
- raise CaptureLoggingContext.NotCapturedError from None
7238
+ self._set_info(
7239
+ LoggingContextInfos.Thread.build(),
7240
+ LoggingContextInfos.Process.build(),
7241
+ LoggingContextInfos.Multiprocessing.build(),
7242
+ LoggingContextInfos.AsyncioTask.build(),
7243
+ )
7265
7244
 
7266
7245
 
7267
7246
  ########################################
@@ -7760,36 +7739,6 @@ class AnyLogger(Abstract, ta.Generic[T]):
7760
7739
 
7761
7740
  ##
7762
7741
 
7763
- @classmethod
7764
- def _prepare_msg_args(cls, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any) -> ta.Tuple[str, tuple]:
7765
- if callable(msg):
7766
- if args:
7767
- raise TypeError(f'Must not provide both a message function and args: {msg=} {args=}')
7768
- x = msg()
7769
- if isinstance(x, str):
7770
- return x, ()
7771
- elif isinstance(x, tuple):
7772
- if x:
7773
- return x[0], x[1:]
7774
- else:
7775
- return '', ()
7776
- else:
7777
- raise TypeError(x)
7778
-
7779
- elif isinstance(msg, tuple):
7780
- if args:
7781
- raise TypeError(f'Must not provide both a tuple message and args: {msg=} {args=}')
7782
- if msg:
7783
- return msg[0], msg[1:]
7784
- else:
7785
- return '', ()
7786
-
7787
- elif isinstance(msg, str):
7788
- return msg, args
7789
-
7790
- else:
7791
- raise TypeError(msg)
7792
-
7793
7742
  @abc.abstractmethod
7794
7743
  def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any, **kwargs: ta.Any) -> T: # noqa
7795
7744
  raise NotImplementedError
@@ -7830,137 +7779,543 @@ class AsyncNopLogger(AnyNopLogger[ta.Awaitable[None]], AsyncLogger):
7830
7779
 
7831
7780
  ########################################
7832
7781
  # ../../../omlish/logs/std/records.py
7782
+ """
7783
+ TODO:
7784
+ - TypedDict?
7785
+ """
7833
7786
 
7834
7787
 
7835
7788
  ##
7836
7789
 
7837
7790
 
7838
- # Ref:
7839
- # - https://docs.python.org/3/library/logging.html#logrecord-attributes
7840
- #
7841
- # LogRecord:
7842
- # - https://github.com/python/cpython/blob/39b2f82717a69dde7212bc39b673b0f55c99e6a3/Lib/logging/__init__.py#L276 (3.8)
7843
- # - https://github.com/python/cpython/blob/f070f54c5f4a42c7c61d1d5d3b8f3b7203b4a0fb/Lib/logging/__init__.py#L286 (~3.14) # noqa
7844
- #
7845
- # LogRecord.__init__ args:
7846
- # - name: str
7847
- # - level: int
7848
- # - pathname: str - Confusingly referred to as `fn` before the LogRecord ctor. May be empty or "(unknown file)".
7849
- # - lineno: int - May be 0.
7850
- # - msg: str
7851
- # - args: tuple | dict | 1-tuple[dict]
7852
- # - exc_info: LoggingExcInfoTuple | None
7853
- # - func: str | None = None -> funcName
7854
- # - sinfo: str | None = None -> stack_info
7855
- #
7856
- KNOWN_STD_LOGGING_RECORD_ATTRS: ta.Dict[str, ta.Any] = dict(
7857
- # Name of the logger used to log the call. Unmodified by ctor.
7858
- name=str,
7791
+ class LoggingContextInfoRecordAdapters:
7792
+ # Ref:
7793
+ # - https://docs.python.org/3/library/logging.html#logrecord-attributes
7794
+ #
7795
+ # LogRecord:
7796
+ # - https://github.com/python/cpython/blob/39b2f82717a69dde7212bc39b673b0f55c99e6a3/Lib/logging/__init__.py#L276 (3.8) # noqa
7797
+ # - https://github.com/python/cpython/blob/f070f54c5f4a42c7c61d1d5d3b8f3b7203b4a0fb/Lib/logging/__init__.py#L286 (~3.14) # noqa
7798
+ #
7859
7799
 
7860
- # The format string passed in the original logging call. Merged with args to produce message, or an arbitrary object
7861
- # (see Using arbitrary objects as messages). Unmodified by ctor.
7862
- msg=str,
7800
+ def __new__(cls, *args, **kwargs): # noqa
7801
+ raise TypeError
7863
7802
 
7864
- # The tuple of arguments merged into msg to produce message, or a dict whose values are used for the merge (when
7865
- # there is only one argument, and it is a dictionary). Ctor will transform a 1-tuple containing a Mapping into just
7866
- # the mapping, but is otherwise unmodified.
7867
- args=ta.Union[tuple, dict],
7803
+ class Adapter(Abstract, ta.Generic[T]):
7804
+ @property
7805
+ @abc.abstractmethod
7806
+ def info_cls(self) -> ta.Type[LoggingContextInfo]:
7807
+ raise NotImplementedError
7868
7808
 
7869
- #
7809
+ #
7870
7810
 
7871
- # Text logging level for the message ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'). Set to
7872
- # `getLevelName(level)`.
7873
- levelname=str,
7811
+ @ta.final
7812
+ class NOT_SET: # noqa
7813
+ def __new__(cls, *args, **kwargs): # noqa
7814
+ raise TypeError
7874
7815
 
7875
- # Numeric logging level for the message (DEBUG, INFO, WARNING, ERROR, CRITICAL). Unmodified by ctor.
7876
- levelno=int,
7816
+ class RecordAttr(ta.NamedTuple):
7817
+ name: str
7818
+ type: ta.Any
7819
+ default: ta.Any
7877
7820
 
7878
- #
7821
+ # @abc.abstractmethod
7822
+ record_attrs: ta.ClassVar[ta.Mapping[str, RecordAttr]]
7879
7823
 
7880
- # Full pathname of the source file where the logging call was issued (if available). Unmodified by ctor. May default
7881
- # to "(unknown file)" by Logger.findCaller / Logger._log.
7882
- pathname=str,
7824
+ @property
7825
+ @abc.abstractmethod
7826
+ def _record_attrs(self) -> ta.Union[
7827
+ ta.Mapping[str, ta.Any],
7828
+ ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]],
7829
+ ]:
7830
+ raise NotImplementedError
7883
7831
 
7884
- # Filename portion of pathname. Set to `os.path.basename(pathname)` if successful, otherwise defaults to pathname.
7885
- filename=str,
7832
+ #
7886
7833
 
7887
- # Module (name portion of filename). Set to `os.path.splitext(filename)[0]`, otherwise defaults to
7888
- # "Unknown module".
7889
- module=str,
7834
+ @abc.abstractmethod
7835
+ def context_to_record(self, ctx: LoggingContext) -> ta.Mapping[str, ta.Any]:
7836
+ raise NotImplementedError
7890
7837
 
7891
- #
7838
+ #
7892
7839
 
7893
- # Exception tuple (à la sys.exc_info) or, if no exception has occurred, None. Unmodified by ctor.
7894
- exc_info=ta.Optional[LoggingExcInfoTuple],
7840
+ @abc.abstractmethod
7841
+ def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[T]:
7842
+ raise NotImplementedError
7895
7843
 
7896
- # Used to cache the traceback text. Simply set to None by ctor, later set by Formatter.format.
7897
- exc_text=ta.Optional[str],
7844
+ #
7898
7845
 
7899
- #
7846
+ def __init_subclass__(cls, **kwargs: ta.Any) -> None:
7847
+ super().__init_subclass__(**kwargs)
7900
7848
 
7901
- # Stack frame information (where available) from the bottom of the stack in the current thread, up to and including
7902
- # the stack frame of the logging call which resulted in the creation of this record. Set by ctor to `sinfo` arg,
7903
- # unmodified. Mostly set, if requested, by `Logger.findCaller`, to `traceback.print_stack(f)`, but prepended with
7904
- # the literal "Stack (most recent call last):\n", and stripped of exactly one trailing `\n` if present.
7905
- stack_info=ta.Optional[str],
7849
+ if Abstract in cls.__bases__:
7850
+ return
7906
7851
 
7907
- # Source line number where the logging call was issued (if available). Unmodified by ctor. May default to 0 by
7908
- # Logger.findCaller / Logger._log.
7909
- lineno=int,
7852
+ if 'record_attrs' in cls.__dict__:
7853
+ raise TypeError(cls)
7854
+ if not isinstance(ra := cls.__dict__['_record_attrs'], collections.abc.Mapping):
7855
+ raise TypeError(ra)
7856
+
7857
+ rd: ta.Dict[str, LoggingContextInfoRecordAdapters.Adapter.RecordAttr] = {}
7858
+ for n, v in ra.items():
7859
+ if not n or not isinstance(n, str) or n in rd:
7860
+ raise AttributeError(n)
7861
+ if isinstance(v, tuple):
7862
+ t, d = v
7863
+ else:
7864
+ t, d = v, cls.NOT_SET
7865
+ rd[n] = cls.RecordAttr(
7866
+ name=n,
7867
+ type=t,
7868
+ default=d,
7869
+ )
7870
+ cls.record_attrs = rd
7910
7871
 
7911
- # Name of function containing the logging call. Set by ctor to `func` arg, unmodified. May default to
7912
- # "(unknown function)" by Logger.findCaller / Logger._log.
7913
- funcName=str,
7872
+ class RequiredAdapter(Adapter[T], Abstract):
7873
+ @property
7874
+ @abc.abstractmethod
7875
+ def _record_attrs(self) -> ta.Mapping[str, ta.Any]:
7876
+ raise NotImplementedError
7914
7877
 
7915
- #
7878
+ #
7916
7879
 
7917
- # Time when the LogRecord was created. Set to `time.time_ns() / 1e9` for >=3.13.0b1, otherwise simply `time.time()`.
7918
- #
7919
- # See:
7920
- # - https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
7921
- # - https://github.com/python/cpython/commit/1500a23f33f5a6d052ff1ef6383d9839928b8ff1
7922
- #
7923
- created=float,
7880
+ @ta.final
7881
+ def context_to_record(self, ctx: LoggingContext) -> ta.Mapping[str, ta.Any]:
7882
+ if (info := ctx.get_info(self.info_cls)) is not None:
7883
+ return self._info_to_record(info)
7884
+ else:
7885
+ raise TypeError # FIXME: fallback?
7924
7886
 
7925
- # Millisecond portion of the time when the LogRecord was created.
7926
- msecs=float,
7887
+ @abc.abstractmethod
7888
+ def _info_to_record(self, info: T) -> ta.Mapping[str, ta.Any]:
7889
+ raise NotImplementedError
7927
7890
 
7928
- # Time in milliseconds when the LogRecord was created, relative to the time the logging module was loaded.
7929
- relativeCreated=float,
7891
+ #
7930
7892
 
7931
- #
7893
+ @abc.abstractmethod
7894
+ def record_to_info(self, rec: logging.LogRecord) -> T:
7895
+ raise NotImplementedError
7932
7896
 
7933
- # Thread ID if available, and `logging.logThreads` is truthy.
7934
- thread=ta.Optional[int],
7897
+ #
7935
7898
 
7936
- # Thread name if available, and `logging.logThreads` is truthy.
7937
- threadName=ta.Optional[str],
7899
+ def __init_subclass__(cls, **kwargs: ta.Any) -> None:
7900
+ super().__init_subclass__(**kwargs)
7938
7901
 
7939
- #
7902
+ if any(a.default is not cls.NOT_SET for a in cls.record_attrs.values()):
7903
+ raise TypeError(cls.record_attrs)
7904
+
7905
+ class OptionalAdapter(Adapter[T], Abstract, ta.Generic[T]):
7906
+ @property
7907
+ @abc.abstractmethod
7908
+ def _record_attrs(self) -> ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]:
7909
+ raise NotImplementedError
7910
+
7911
+ record_defaults: ta.ClassVar[ta.Mapping[str, ta.Any]]
7912
+
7913
+ #
7914
+
7915
+ @ta.final
7916
+ def context_to_record(self, ctx: LoggingContext) -> ta.Mapping[str, ta.Any]:
7917
+ if (info := ctx.get_info(self.info_cls)) is not None:
7918
+ return self._info_to_record(info)
7919
+ else:
7920
+ return self.record_defaults
7921
+
7922
+ @abc.abstractmethod
7923
+ def _info_to_record(self, info: T) -> ta.Mapping[str, ta.Any]:
7924
+ raise NotImplementedError
7925
+
7926
+ #
7927
+
7928
+ def __init_subclass__(cls, **kwargs: ta.Any) -> None:
7929
+ super().__init_subclass__(**kwargs)
7930
+
7931
+ dd: ta.Dict[str, ta.Any] = {a.name: a.default for a in cls.record_attrs.values()}
7932
+ if any(d is cls.NOT_SET for d in dd.values()):
7933
+ raise TypeError(cls.record_attrs)
7934
+ cls.record_defaults = dd
7940
7935
 
7941
- # Process name if available. Set to None if `logging.logMultiprocessing` is not truthy. Otherwise, set to
7942
- # 'MainProcess', then `sys.modules.get('multiprocessing').current_process().name` if that works, otherwise remains
7943
- # as 'MainProcess'.
7944
7936
  #
7945
- # As noted by stdlib:
7937
+
7938
+ class Name(RequiredAdapter[LoggingContextInfos.Name]):
7939
+ info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Name]] = LoggingContextInfos.Name
7940
+
7941
+ _record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
7942
+ # Name of the logger used to log the call. Unmodified by ctor.
7943
+ name=str,
7944
+ )
7945
+
7946
+ def _info_to_record(self, info: LoggingContextInfos.Name) -> ta.Mapping[str, ta.Any]:
7947
+ return dict(
7948
+ name=info.name,
7949
+ )
7950
+
7951
+ def record_to_info(self, rec: logging.LogRecord) -> LoggingContextInfos.Name:
7952
+ return LoggingContextInfos.Name(
7953
+ name=rec.name,
7954
+ )
7955
+
7956
+ class Level(RequiredAdapter[LoggingContextInfos.Level]):
7957
+ info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Level]] = LoggingContextInfos.Level
7958
+
7959
+ _record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
7960
+ # Text logging level for the message ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'). Set to
7961
+ # `getLevelName(level)`.
7962
+ levelname=str,
7963
+
7964
+ # Numeric logging level for the message (DEBUG, INFO, WARNING, ERROR, CRITICAL). Unmodified by ctor.
7965
+ levelno=int,
7966
+ )
7967
+
7968
+ def _info_to_record(self, info: LoggingContextInfos.Level) -> ta.Mapping[str, ta.Any]:
7969
+ return dict(
7970
+ levelname=info.name,
7971
+ levelno=int(info.level),
7972
+ )
7973
+
7974
+ def record_to_info(self, rec: logging.LogRecord) -> LoggingContextInfos.Level:
7975
+ return LoggingContextInfos.Level.build(rec.levelno)
7976
+
7977
+ class Msg(RequiredAdapter[LoggingContextInfos.Msg]):
7978
+ info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Msg]] = LoggingContextInfos.Msg
7979
+
7980
+ _record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
7981
+ # The format string passed in the original logging call. Merged with args to produce message, or an
7982
+ # arbitrary object (see Using arbitrary objects as messages). Unmodified by ctor.
7983
+ msg=str,
7984
+
7985
+ # The tuple of arguments merged into msg to produce message, or a dict whose values are used for the merge
7986
+ # (when there is only one argument, and it is a dictionary). Ctor will transform a 1-tuple containing a
7987
+ # Mapping into just the mapping, but is otherwise unmodified.
7988
+ args=ta.Union[tuple, dict, None],
7989
+ )
7990
+
7991
+ def _info_to_record(self, info: LoggingContextInfos.Msg) -> ta.Mapping[str, ta.Any]:
7992
+ return dict(
7993
+ msg=info.msg,
7994
+ args=info.args,
7995
+ )
7996
+
7997
+ def record_to_info(self, rec: logging.LogRecord) -> LoggingContextInfos.Msg:
7998
+ return LoggingContextInfos.Msg(
7999
+ msg=rec.msg,
8000
+ args=rec.args,
8001
+ )
8002
+
8003
+ # FIXME: handled specially - all unknown attrs on LogRecord
8004
+ # class Extra(Adapter[LoggingContextInfos.Extra]):
8005
+ # _record_attrs: ta.ClassVar[ta.Mapping[str, ta.Union[ta.Any, ta.Tuple[ta.Any, ta.Any]]]] = dict()
7946
8006
  #
7947
- # Errors may occur if multiprocessing has not finished loading yet - e.g. if a custom import hook causes
7948
- # third-party code to run when multiprocessing calls import. See issue 8200 for an example
8007
+ # def info_to_record(self, info: ta.Optional[LoggingContextInfos.Extra]) -> ta.Mapping[str, ta.Any]:
8008
+ # # FIXME:
8009
+ # # if extra is not None:
8010
+ # # for key in extra:
8011
+ # # if (key in ["message", "asctime"]) or (key in rv.__dict__):
8012
+ # # raise KeyError("Attempt to overwrite %r in LogRecord" % key)
8013
+ # # rv.__dict__[key] = extra[key]
8014
+ # return dict()
7949
8015
  #
7950
- processName=ta.Optional[str],
8016
+ # def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Extra]:
8017
+ # return None
8018
+
8019
+ class Time(RequiredAdapter[LoggingContextInfos.Time]):
8020
+ info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Time]] = LoggingContextInfos.Time
8021
+
8022
+ _record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
8023
+ # Time when the LogRecord was created. Set to `time.time_ns() / 1e9` for >=3.13.0b1, otherwise simply
8024
+ # `time.time()`.
8025
+ #
8026
+ # See:
8027
+ # - https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
8028
+ # - https://github.com/python/cpython/commit/1500a23f33f5a6d052ff1ef6383d9839928b8ff1
8029
+ #
8030
+ created=float,
8031
+
8032
+ # Millisecond portion of the time when the LogRecord was created.
8033
+ msecs=float,
8034
+
8035
+ # Time in milliseconds when the LogRecord was created, relative to the time the logging module was loaded.
8036
+ relativeCreated=float,
8037
+ )
7951
8038
 
7952
- # Process ID if available - that is, if `hasattr(os, 'getpid')` - and `logging.logProcesses` is truthy, otherwise
7953
- # None.
7954
- process=ta.Optional[int],
8039
+ def _info_to_record(self, info: LoggingContextInfos.Time) -> ta.Mapping[str, ta.Any]:
8040
+ return dict(
8041
+ created=info.secs,
8042
+ msecs=info.msecs,
8043
+ relativeCreated=info.relative_secs,
8044
+ )
7955
8045
 
7956
- #
8046
+ def record_to_info(self, rec: logging.LogRecord) -> LoggingContextInfos.Time:
8047
+ return LoggingContextInfos.Time.build(
8048
+ int(rec.created * 1e9),
8049
+ )
7957
8050
 
7958
- # Absent <3.12, otherwise asyncio.Task name if available, and `logging.logAsyncioTasks` is truthy. Set to
7959
- # `sys.modules.get('asyncio').current_task().get_name()`, otherwise None.
7960
- taskName=ta.Optional[str],
7961
- )
8051
+ class Exc(OptionalAdapter[LoggingContextInfos.Exc]):
8052
+ info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Exc]] = LoggingContextInfos.Exc
8053
+
8054
+ _record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
8055
+ # Exception tuple (à la sys.exc_info) or, if no exception has occurred, None. Unmodified by ctor.
8056
+ exc_info=(ta.Optional[LoggingExcInfoTuple], None),
8057
+
8058
+ # Used to cache the traceback text. Simply set to None by ctor, later set by Formatter.format.
8059
+ exc_text=(ta.Optional[str], None),
8060
+ )
8061
+
8062
+ def _info_to_record(self, info: LoggingContextInfos.Exc) -> ta.Mapping[str, ta.Any]:
8063
+ return dict(
8064
+ exc_info=info.info_tuple,
8065
+ exc_text=None,
8066
+ )
8067
+
8068
+ def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Exc]:
8069
+ # FIXME:
8070
+ # error: Argument 1 to "build" of "Exc" has incompatible type
8071
+ # "tuple[type[BaseException], BaseException, TracebackType | None] | tuple[None, None, None] | None"; expected # noqa
8072
+ # "BaseException | tuple[type[BaseException], BaseException, TracebackType | None] | bool | None" [arg-type] # noqa
8073
+ return LoggingContextInfos.Exc.build(rec.exc_info) # type: ignore[arg-type]
8074
+
8075
+ class Caller(OptionalAdapter[LoggingContextInfos.Caller]):
8076
+ info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Caller]] = LoggingContextInfos.Caller
8077
+
8078
+ _UNKNOWN_PATH_NAME: ta.ClassVar[str] = '(unknown file)'
8079
+ _UNKNOWN_FUNC_NAME: ta.ClassVar[str] = '(unknown function)'
8080
+
8081
+ _STACK_INFO_PREFIX: ta.ClassVar[str] = 'Stack (most recent call last):\n'
8082
+
8083
+ _record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
8084
+ # Full pathname of the source file where the logging call was issued (if available). Unmodified by ctor. May
8085
+ # default to "(unknown file)" by Logger.findCaller / Logger._log.
8086
+ pathname=(str, _UNKNOWN_PATH_NAME),
8087
+
8088
+ # Source line number where the logging call was issued (if available). Unmodified by ctor. May default to 0
8089
+ # y Logger.findCaller / Logger._log.
8090
+ lineno=(int, 0),
8091
+
8092
+ # Name of function containing the logging call. Set by ctor to `func` arg, unmodified. May default to
8093
+ # "(unknown function)" by Logger.findCaller / Logger._log.
8094
+ funcName=(str, _UNKNOWN_FUNC_NAME),
8095
+
8096
+ # Stack frame information (where available) from the bottom of the stack in the current thread, up to and
8097
+ # including the stack frame of the logging call which resulted in the creation of this record. Set by ctor
8098
+ # to `sinfo` arg, unmodified. Mostly set, if requested, by `Logger.findCaller`, to
8099
+ # `traceback.print_stack(f)`, but prepended with the literal "Stack (most recent call last):\n", and
8100
+ # stripped of exactly one trailing `\n` if present.
8101
+ stack_info=(ta.Optional[str], None),
8102
+ )
8103
+
8104
+ def _info_to_record(self, caller: LoggingContextInfos.Caller) -> ta.Mapping[str, ta.Any]:
8105
+ if (sinfo := caller.stack_info) is not None:
8106
+ stack_info: ta.Optional[str] = '\n'.join([
8107
+ self._STACK_INFO_PREFIX,
8108
+ sinfo[1:] if sinfo.endswith('\n') else sinfo,
8109
+ ])
8110
+ else:
8111
+ stack_info = None
8112
+
8113
+ return dict(
8114
+ pathname=caller.file_path,
8115
+
8116
+ lineno=caller.line_no,
8117
+ funcName=caller.func_name,
8118
+
8119
+ stack_info=stack_info,
8120
+ )
8121
+
8122
+ def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Caller]:
8123
+ # FIXME: piecemeal?
8124
+ # FIXME: strip _STACK_INFO_PREFIX
8125
+ raise NotImplementedError
8126
+
8127
+ class SourceFile(Adapter[LoggingContextInfos.SourceFile]):
8128
+ info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.SourceFile]] = LoggingContextInfos.SourceFile
8129
+
8130
+ _record_attrs: ta.ClassVar[ta.Mapping[str, ta.Any]] = dict(
8131
+ # Filename portion of pathname. Set to `os.path.basename(pathname)` if successful, otherwise defaults to
8132
+ # pathname.
8133
+ filename=str,
8134
+
8135
+ # Module (name portion of filename). Set to `os.path.splitext(filename)[0]`, otherwise defaults to
8136
+ # "Unknown module".
8137
+ module=str,
8138
+ )
8139
+
8140
+ _UNKNOWN_MODULE: ta.ClassVar[str] = 'Unknown module'
8141
+
8142
+ def context_to_record(self, ctx: LoggingContext) -> ta.Mapping[str, ta.Any]:
8143
+ if (info := ctx.get_info(LoggingContextInfos.SourceFile)) is not None:
8144
+ return dict(
8145
+ filename=info.file_name,
8146
+ module=info.module,
8147
+ )
8148
+
8149
+ if (caller := ctx.get_info(LoggingContextInfos.Caller)) is not None:
8150
+ return dict(
8151
+ filename=caller.file_path,
8152
+ module=self._UNKNOWN_MODULE,
8153
+ )
8154
+
8155
+ return dict(
8156
+ filename=LoggingContextInfoRecordAdapters.Caller._UNKNOWN_PATH_NAME, # noqa
8157
+ module=self._UNKNOWN_MODULE,
8158
+ )
8159
+
8160
+ def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.SourceFile]:
8161
+ if not (
8162
+ rec.module is None or
8163
+ rec.module == self._UNKNOWN_MODULE
8164
+ ):
8165
+ return LoggingContextInfos.SourceFile(
8166
+ file_name=rec.filename,
8167
+ module=rec.module, # FIXME: piecemeal?
8168
+ )
8169
+
8170
+ return None
8171
+
8172
+ class Thread(OptionalAdapter[LoggingContextInfos.Thread]):
8173
+ info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Thread]] = LoggingContextInfos.Thread
8174
+
8175
+ _record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
8176
+ # Thread ID if available, and `logging.logThreads` is truthy.
8177
+ thread=(ta.Optional[int], None),
8178
+
8179
+ # Thread name if available, and `logging.logThreads` is truthy.
8180
+ threadName=(ta.Optional[str], None),
8181
+ )
8182
+
8183
+ def _info_to_record(self, info: LoggingContextInfos.Thread) -> ta.Mapping[str, ta.Any]:
8184
+ if logging.logThreads:
8185
+ return dict(
8186
+ thread=info.ident,
8187
+ threadName=info.name,
8188
+ )
8189
+
8190
+ return self.record_defaults
8191
+
8192
+ def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Thread]:
8193
+ if (
8194
+ (ident := rec.thread) is not None and
8195
+ (name := rec.threadName) is not None
8196
+ ):
8197
+ return LoggingContextInfos.Thread(
8198
+ ident=ident,
8199
+ native_id=None,
8200
+ name=name,
8201
+ )
8202
+
8203
+ return None
8204
+
8205
+ class Process(OptionalAdapter[LoggingContextInfos.Process]):
8206
+ info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Process]] = LoggingContextInfos.Process
8207
+
8208
+ _record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
8209
+ # Process ID if available - that is, if `hasattr(os, 'getpid')` - and `logging.logProcesses` is truthy,
8210
+ # otherwise None.
8211
+ process=(ta.Optional[int], None),
8212
+ )
8213
+
8214
+ def _info_to_record(self, info: LoggingContextInfos.Process) -> ta.Mapping[str, ta.Any]:
8215
+ if logging.logProcesses:
8216
+ return dict(
8217
+ process=info.pid,
8218
+ )
8219
+
8220
+ return self.record_defaults
8221
+
8222
+ def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Process]:
8223
+ if (
8224
+ (pid := rec.process) is not None
8225
+ ):
8226
+ return LoggingContextInfos.Process(
8227
+ pid=pid,
8228
+ )
8229
+
8230
+ return None
8231
+
8232
+ class Multiprocessing(OptionalAdapter[LoggingContextInfos.Multiprocessing]):
8233
+ info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.Multiprocessing]] = LoggingContextInfos.Multiprocessing
8234
+
8235
+ _record_attrs: ta.ClassVar[ta.Mapping[str, ta.Tuple[ta.Any, ta.Any]]] = dict(
8236
+ # Process name if available. Set to None if `logging.logMultiprocessing` is not truthy. Otherwise, set to
8237
+ # 'MainProcess', then `sys.modules.get('multiprocessing').current_process().name` if that works, otherwise
8238
+ # remains as 'MainProcess'.
8239
+ #
8240
+ # As noted by stdlib:
8241
+ #
8242
+ # Errors may occur if multiprocessing has not finished loading yet - e.g. if a custom import hook causes
8243
+ # third-party code to run when multiprocessing calls import. See issue 8200 for an example
8244
+ #
8245
+ processName=(ta.Optional[str], None),
8246
+ )
7962
8247
 
7963
- KNOWN_STD_LOGGING_RECORD_ATTR_SET: ta.FrozenSet[str] = frozenset(KNOWN_STD_LOGGING_RECORD_ATTRS)
8248
+ def _info_to_record(self, info: LoggingContextInfos.Multiprocessing) -> ta.Mapping[str, ta.Any]:
8249
+ if logging.logMultiprocessing:
8250
+ return dict(
8251
+ processName=info.process_name,
8252
+ )
8253
+
8254
+ return self.record_defaults
8255
+
8256
+ def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.Multiprocessing]:
8257
+ if (
8258
+ (process_name := rec.processName) is not None
8259
+ ):
8260
+ return LoggingContextInfos.Multiprocessing(
8261
+ process_name=process_name,
8262
+ )
8263
+
8264
+ return None
8265
+
8266
+ class AsyncioTask(OptionalAdapter[LoggingContextInfos.AsyncioTask]):
8267
+ info_cls: ta.ClassVar[ta.Type[LoggingContextInfos.AsyncioTask]] = LoggingContextInfos.AsyncioTask
8268
+
8269
+ _record_attrs: ta.ClassVar[ta.Mapping[str, ta.Union[ta.Any, ta.Tuple[ta.Any, ta.Any]]]] = dict(
8270
+ # Absent <3.12, otherwise asyncio.Task name if available, and `logging.logAsyncioTasks` is truthy. Set to
8271
+ # `sys.modules.get('asyncio').current_task().get_name()`, otherwise None.
8272
+ taskName=(ta.Optional[str], None),
8273
+ )
8274
+
8275
+ def _info_to_record(self, info: LoggingContextInfos.AsyncioTask) -> ta.Mapping[str, ta.Any]:
8276
+ if getattr(logging, 'logAsyncioTasks', None): # Absent <3.12
8277
+ return dict(
8278
+ taskName=info.name,
8279
+ )
8280
+
8281
+ return self.record_defaults
8282
+
8283
+ def record_to_info(self, rec: logging.LogRecord) -> ta.Optional[LoggingContextInfos.AsyncioTask]:
8284
+ if (
8285
+ (name := getattr(rec, 'taskName', None)) is not None
8286
+ ):
8287
+ return LoggingContextInfos.AsyncioTask(
8288
+ name=name,
8289
+ )
8290
+
8291
+ return None
8292
+
8293
+
8294
+ _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS_: ta.Sequence[LoggingContextInfoRecordAdapters.Adapter] = [ # noqa
8295
+ LoggingContextInfoRecordAdapters.Name(),
8296
+ LoggingContextInfoRecordAdapters.Level(),
8297
+ LoggingContextInfoRecordAdapters.Msg(),
8298
+ LoggingContextInfoRecordAdapters.Time(),
8299
+ LoggingContextInfoRecordAdapters.Exc(),
8300
+ LoggingContextInfoRecordAdapters.Caller(),
8301
+ LoggingContextInfoRecordAdapters.SourceFile(),
8302
+ LoggingContextInfoRecordAdapters.Thread(),
8303
+ LoggingContextInfoRecordAdapters.Process(),
8304
+ LoggingContextInfoRecordAdapters.Multiprocessing(),
8305
+ LoggingContextInfoRecordAdapters.AsyncioTask(),
8306
+ ]
8307
+
8308
+ _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS: ta.Mapping[ta.Type[LoggingContextInfo], LoggingContextInfoRecordAdapters.Adapter] = { # noqa
8309
+ ad.info_cls: ad for ad in _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS_
8310
+ }
8311
+
8312
+
8313
+ ##
8314
+
8315
+
8316
+ KNOWN_STD_LOGGING_RECORD_ATTR_SET: ta.FrozenSet[str] = frozenset(
8317
+ a for ad in _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS.values() for a in ad.record_attrs
8318
+ )
7964
8319
 
7965
8320
 
7966
8321
  # Formatter:
@@ -7984,14 +8339,17 @@ KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTRS: ta.Dict[str, ta.Any] = dict(
7984
8339
  KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTR_SET: ta.FrozenSet[str] = frozenset(KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTRS)
7985
8340
 
7986
8341
 
7987
- ##
7988
-
7989
-
7990
8342
  class UnknownStdLoggingRecordAttrsWarning(LoggingSetupWarning):
7991
8343
  pass
7992
8344
 
7993
8345
 
7994
8346
  def _check_std_logging_record_attrs() -> None:
8347
+ if (
8348
+ len([a for ad in _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS.values() for a in ad.record_attrs]) !=
8349
+ len(KNOWN_STD_LOGGING_RECORD_ATTR_SET)
8350
+ ):
8351
+ raise RuntimeError('Duplicate LoggingContextInfoRecordAdapter record attrs')
8352
+
7995
8353
  rec_dct = dict(logging.makeLogRecord({}).__dict__)
7996
8354
 
7997
8355
  if (unk_rec_fields := frozenset(rec_dct) - KNOWN_STD_LOGGING_RECORD_ATTR_SET):
@@ -8010,116 +8368,20 @@ _check_std_logging_record_attrs()
8010
8368
 
8011
8369
 
8012
8370
  class LoggingContextLogRecord(logging.LogRecord):
8013
- _SHOULD_ADD_TASK_NAME: ta.ClassVar[bool] = sys.version_info >= (3, 12)
8014
-
8015
- _UNKNOWN_PATH_NAME: ta.ClassVar[str] = '(unknown file)'
8016
- _UNKNOWN_FUNC_NAME: ta.ClassVar[str] = '(unknown function)'
8017
- _UNKNOWN_MODULE: ta.ClassVar[str] = 'Unknown module'
8018
-
8019
- _STACK_INFO_PREFIX: ta.ClassVar[str] = 'Stack (most recent call last):\n'
8020
-
8021
- def __init__( # noqa
8022
- self,
8023
- # name,
8024
- # level,
8025
- # pathname,
8026
- # lineno,
8027
- # msg,
8028
- # args,
8029
- # exc_info,
8030
- # func=None,
8031
- # sinfo=None,
8032
- # **kwargs,
8033
- *,
8034
- name: str,
8035
- msg: str,
8036
- args: ta.Union[tuple, dict],
8037
-
8038
- _logging_context: LoggingContext,
8039
- ) -> None:
8040
- ctx = _logging_context
8041
-
8042
- self.name: str = name
8043
-
8044
- self.msg: str = msg
8045
-
8046
- # https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L307
8047
- if args and len(args) == 1 and isinstance(args[0], collections.abc.Mapping) and args[0]:
8048
- args = args[0] # type: ignore[assignment]
8049
- self.args: ta.Union[tuple, dict] = args
8050
-
8051
- self.levelname: str = logging.getLevelName(ctx.level)
8052
- self.levelno: int = ctx.level
8053
-
8054
- if (caller := ctx.caller()) is not None:
8055
- self.pathname: str = caller.file_path
8056
- else:
8057
- self.pathname = self._UNKNOWN_PATH_NAME
8058
-
8059
- if (src_file := ctx.source_file()) is not None:
8060
- self.filename: str = src_file.file_name
8061
- self.module: str = src_file.module
8062
- else:
8063
- self.filename = self.pathname
8064
- self.module = self._UNKNOWN_MODULE
8065
-
8066
- self.exc_info: ta.Optional[LoggingExcInfoTuple] = ctx.exc_info_tuple
8067
- self.exc_text: ta.Optional[str] = None
8068
-
8069
- # If ctx.build_caller() was never called, we simply don't have a stack trace.
8070
- if caller is not None:
8071
- if (sinfo := caller.stack_info) is not None:
8072
- self.stack_info: ta.Optional[str] = '\n'.join([
8073
- self._STACK_INFO_PREFIX,
8074
- sinfo[1:] if sinfo.endswith('\n') else sinfo,
8075
- ])
8076
- else:
8077
- self.stack_info = None
8078
-
8079
- self.lineno: int = caller.line_no
8080
- self.funcName: str = caller.name
8081
-
8082
- else:
8083
- self.stack_info = None
8084
-
8085
- self.lineno = 0
8086
- self.funcName = self._UNKNOWN_FUNC_NAME
8087
-
8088
- times = ctx.times
8089
- self.created: float = times.created
8090
- self.msecs: float = times.msecs
8091
- self.relativeCreated: float = times.relative_created
8092
-
8093
- if logging.logThreads:
8094
- thread = check.not_none(ctx.thread())
8095
- self.thread: ta.Optional[int] = thread.ident
8096
- self.threadName: ta.Optional[str] = thread.name
8097
- else:
8098
- self.thread = None
8099
- self.threadName = None
8100
-
8101
- if logging.logProcesses:
8102
- process = check.not_none(ctx.process())
8103
- self.process: ta.Optional[int] = process.pid
8104
- else:
8105
- self.process = None
8106
-
8107
- if logging.logMultiprocessing:
8108
- if (mp := ctx.multiprocessing()) is not None:
8109
- self.processName: ta.Optional[str] = mp.process_name
8110
- else:
8111
- self.processName = None
8112
- else:
8113
- self.processName = None
8114
-
8115
- # Absent <3.12
8116
- if getattr(logging, 'logAsyncioTasks', None):
8117
- if (at := ctx.asyncio_task()) is not None:
8118
- self.taskName: ta.Optional[str] = at.name
8119
- else:
8120
- self.taskName = None
8121
- else:
8122
- self.taskName = None
8371
+ # LogRecord.__init__ args:
8372
+ # - name: str
8373
+ # - level: int
8374
+ # - pathname: str - Confusingly referred to as `fn` before the LogRecord ctor. May be empty or "(unknown file)".
8375
+ # - lineno: int - May be 0.
8376
+ # - msg: str
8377
+ # - args: tuple | dict | 1-tuple[dict]
8378
+ # - exc_info: LoggingExcInfoTuple | None
8379
+ # - func: str | None = None -> funcName
8380
+ # - sinfo: str | None = None -> stack_info
8381
+
8382
+ def __init__(self, *, _logging_context: LoggingContext) -> None: # noqa
8383
+ for ad in _LOGGING_CONTEXT_INFO_RECORD_ADAPTERS_:
8384
+ self.__dict__.update(ad.context_to_record(_logging_context))
8123
8385
 
8124
8386
 
8125
8387
  ########################################
@@ -8446,21 +8708,20 @@ class StdLogger(Logger):
8446
8708
  return self._std.getEffectiveLevel()
8447
8709
 
8448
8710
  def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any) -> None:
8449
- if not self.is_enabled_for(ctx.level):
8711
+ if not self.is_enabled_for(ctx.must_get_info(LoggingContextInfos.Level).level):
8450
8712
  return
8451
8713
 
8452
- ctx.capture()
8453
-
8454
- ms, args = self._prepare_msg_args(msg, *args)
8455
-
8456
- rec = LoggingContextLogRecord(
8714
+ ctx.set_basic(
8457
8715
  name=self._std.name,
8458
- msg=ms,
8459
- args=args,
8460
8716
 
8461
- _logging_context=ctx,
8717
+ msg=msg,
8718
+ args=args,
8462
8719
  )
8463
8720
 
8721
+ ctx.capture()
8722
+
8723
+ rec = LoggingContextLogRecord(_logging_context=ctx)
8724
+
8464
8725
  self._std.handle(rec)
8465
8726
 
8466
8727