omlish 0.0.0.dev424__py3-none-any.whl → 0.0.0.dev426__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.
Files changed (126) hide show
  1. omlish/__about__.py +3 -3
  2. omlish/c3.py +4 -1
  3. omlish/configs/processing/flattening.py +1 -1
  4. omlish/configs/processing/merging.py +8 -6
  5. omlish/dataclasses/impl/concerns/doc.py +1 -1
  6. omlish/dataclasses/impl/configs.py +2 -1
  7. omlish/diag/_pycharm/runhack.py +1 -1
  8. omlish/diag/procfs.py +2 -2
  9. omlish/formats/json/stream/lexing.py +69 -14
  10. omlish/formats/json/stream/parsing.py +1 -1
  11. omlish/formats/json/stream/utils.py +3 -0
  12. omlish/formats/json5/streams.py +22 -0
  13. omlish/formats/logfmt.py +8 -2
  14. omlish/funcs/genmachine.py +1 -1
  15. omlish/http/sse.py +1 -1
  16. omlish/inject/impl/injector.py +1 -1
  17. omlish/inject/impl/multis.py +2 -2
  18. omlish/inject/impl/providers.py +0 -4
  19. omlish/inject/impl/proxy.py +0 -2
  20. omlish/inject/scopes.py +0 -4
  21. omlish/io/buffers.py +1 -1
  22. omlish/lang/__init__.py +26 -14
  23. omlish/lang/asyncs.py +12 -0
  24. omlish/lang/{attrs.py → attrstorage.py} +15 -15
  25. omlish/lang/cached/property.py +2 -2
  26. omlish/lang/classes/simple.py +26 -4
  27. omlish/lang/collections.py +1 -1
  28. omlish/lang/functions.py +0 -11
  29. omlish/lang/iterables.py +2 -2
  30. omlish/lang/lazyglobals.py +27 -5
  31. omlish/lang/maysync.py +2 -2
  32. omlish/lifecycles/contextmanagers.py +1 -2
  33. omlish/lifecycles/controller.py +1 -2
  34. omlish/lite/asyncs.py +20 -0
  35. omlish/lite/attrops.py +332 -0
  36. omlish/lite/cached.py +1 -1
  37. omlish/lite/maybes.py +2 -0
  38. omlish/lite/strings.py +0 -7
  39. omlish/lite/timing.py +6 -3
  40. omlish/logs/all.py +25 -32
  41. omlish/logs/base.py +248 -0
  42. omlish/logs/callers.py +21 -15
  43. omlish/logs/infos.py +105 -0
  44. omlish/logs/levels.py +64 -0
  45. omlish/logs/modules.py +10 -0
  46. omlish/logs/protocols.py +31 -0
  47. omlish/logs/standard.py +12 -11
  48. omlish/logs/std/adapters.py +41 -0
  49. omlish/logs/std/configs.py +29 -0
  50. omlish/logs/{filters.py → std/filters.py} +1 -1
  51. omlish/logs/{handlers.py → std/handlers.py} +1 -1
  52. omlish/logs/{json.py → std/json.py} +2 -2
  53. omlish/logs/{proxy.py → std/proxy.py} +3 -3
  54. omlish/logs/std/records.py +286 -0
  55. omlish/logs/times.py +86 -0
  56. omlish/logs/typed/bindings.py +24 -0
  57. omlish/logs/utils.py +60 -4
  58. omlish/logs/warnings.py +8 -0
  59. omlish/manifests/loading.py +1 -1
  60. omlish/os/atomics.py +1 -1
  61. omlish/os/journald.py +3 -3
  62. omlish/reflect/types.py +22 -0
  63. omlish/testing/pytest/plugins/skips.py +0 -4
  64. {omlish-0.0.0.dev424.dist-info → omlish-0.0.0.dev426.dist-info}/METADATA +2 -2
  65. {omlish-0.0.0.dev424.dist-info → omlish-0.0.0.dev426.dist-info}/RECORD +72 -114
  66. omlish/defs.py +0 -216
  67. omlish/dispatch/_dispatch2.py +0 -69
  68. omlish/dispatch/_dispatch3.py +0 -108
  69. omlish/dynamic.py +0 -219
  70. omlish/formats/json/Json.g4 +0 -77
  71. omlish/formats/json/_antlr/JsonLexer.py +0 -109
  72. omlish/formats/json/_antlr/JsonListener.py +0 -61
  73. omlish/formats/json/_antlr/JsonParser.py +0 -457
  74. omlish/formats/json/_antlr/JsonVisitor.py +0 -42
  75. omlish/io/trampoline.py +0 -289
  76. omlish/lite/logs.py +0 -4
  77. omlish/lite/reprs.py +0 -85
  78. omlish/logs/abc.py +0 -319
  79. omlish/logs/color.py +0 -27
  80. omlish/logs/configs.py +0 -29
  81. omlish/logs/protocol.py +0 -218
  82. omlish/logs/timing.py +0 -58
  83. omlish/specs/irc/__init__.py +0 -0
  84. omlish/specs/irc/messages/__init__.py +0 -0
  85. omlish/specs/irc/messages/base.py +0 -49
  86. omlish/specs/irc/messages/formats.py +0 -92
  87. omlish/specs/irc/messages/messages.py +0 -774
  88. omlish/specs/irc/messages/parsing.py +0 -98
  89. omlish/specs/irc/numerics/__init__.py +0 -0
  90. omlish/specs/irc/numerics/formats.py +0 -97
  91. omlish/specs/irc/numerics/numerics.py +0 -865
  92. omlish/specs/irc/numerics/types.py +0 -59
  93. omlish/specs/irc/protocol/LICENSE +0 -11
  94. omlish/specs/irc/protocol/__init__.py +0 -61
  95. omlish/specs/irc/protocol/consts.py +0 -6
  96. omlish/specs/irc/protocol/errors.py +0 -30
  97. omlish/specs/irc/protocol/message.py +0 -21
  98. omlish/specs/irc/protocol/nuh.py +0 -55
  99. omlish/specs/irc/protocol/parsing.py +0 -158
  100. omlish/specs/irc/protocol/rendering.py +0 -153
  101. omlish/specs/irc/protocol/tags.py +0 -102
  102. omlish/specs/irc/protocol/utils.py +0 -30
  103. omlish/specs/proto/Protobuf3.g4 +0 -396
  104. omlish/specs/proto/__init__.py +0 -0
  105. omlish/specs/proto/_antlr/Protobuf3Lexer.py +0 -340
  106. omlish/specs/proto/_antlr/Protobuf3Listener.py +0 -448
  107. omlish/specs/proto/_antlr/Protobuf3Parser.py +0 -3909
  108. omlish/specs/proto/_antlr/Protobuf3Visitor.py +0 -257
  109. omlish/specs/proto/_antlr/__init__.py +0 -0
  110. omlish/specs/proto/nodes.py +0 -54
  111. omlish/specs/proto/parsing.py +0 -97
  112. omlish/sql/parsing/Minisql.g4 +0 -292
  113. omlish/sql/parsing/__init__.py +0 -0
  114. omlish/sql/parsing/_antlr/MinisqlLexer.py +0 -322
  115. omlish/sql/parsing/_antlr/MinisqlListener.py +0 -511
  116. omlish/sql/parsing/_antlr/MinisqlParser.py +0 -3763
  117. omlish/sql/parsing/_antlr/MinisqlVisitor.py +0 -292
  118. omlish/sql/parsing/_antlr/__init__.py +0 -0
  119. omlish/sql/parsing/parsing.py +0 -119
  120. /omlish/{.manifests.json → .omlish-manifests.json} +0 -0
  121. /omlish/{formats/json/_antlr → logs/std}/__init__.py +0 -0
  122. /omlish/logs/{noisy.py → std/noisy.py} +0 -0
  123. {omlish-0.0.0.dev424.dist-info → omlish-0.0.0.dev426.dist-info}/WHEEL +0 -0
  124. {omlish-0.0.0.dev424.dist-info → omlish-0.0.0.dev426.dist-info}/entry_points.txt +0 -0
  125. {omlish-0.0.0.dev424.dist-info → omlish-0.0.0.dev426.dist-info}/licenses/LICENSE +0 -0
  126. {omlish-0.0.0.dev424.dist-info → omlish-0.0.0.dev426.dist-info}/top_level.txt +0 -0
omlish/logs/standard.py CHANGED
@@ -2,6 +2,7 @@
2
2
  # @omlish-lite
3
3
  """
4
4
  TODO:
5
+ - !! move to std !!
5
6
  - structured
6
7
  - prefixed
7
8
  - debug
@@ -12,9 +13,9 @@ import datetime
12
13
  import logging
13
14
  import typing as ta
14
15
 
15
- from .filters import TidLogFilter
16
- from .json import JsonLogFormatter
17
- from .proxy import ProxyLogHandler
16
+ from .std.filters import TidLoggingFilter
17
+ from .std.json import JsonLoggingFormatter
18
+ from .std.proxy import ProxyLoggingHandler
18
19
 
19
20
 
20
21
  ##
@@ -31,7 +32,7 @@ STANDARD_LOG_FORMAT_PARTS = [
31
32
  ]
32
33
 
33
34
 
34
- class StandardLogFormatter(logging.Formatter):
35
+ class StandardLoggingFormatter(logging.Formatter):
35
36
  @staticmethod
36
37
  def build_log_format(parts: ta.Iterable[ta.Tuple[str, str]]) -> str:
37
38
  return ' '.join(v for k, v in parts)
@@ -50,7 +51,7 @@ class StandardLogFormatter(logging.Formatter):
50
51
  ##
51
52
 
52
53
 
53
- class StandardConfiguredLogHandler(ProxyLogHandler):
54
+ class StandardConfiguredLoggingHandler(ProxyLoggingHandler):
54
55
  def __init_subclass__(cls, **kwargs):
55
56
  raise TypeError('This class serves only as a marker and should not be subclassed.')
56
57
 
@@ -83,7 +84,7 @@ def configure_standard_logging(
83
84
  target: ta.Optional[logging.Logger] = None,
84
85
  force: bool = False,
85
86
  handler_factory: ta.Optional[ta.Callable[[], logging.Handler]] = None,
86
- ) -> ta.Optional[StandardConfiguredLogHandler]:
87
+ ) -> ta.Optional[StandardConfiguredLoggingHandler]:
87
88
  with _locking_logging_module_lock():
88
89
  if target is None:
89
90
  target = logging.root
@@ -91,7 +92,7 @@ def configure_standard_logging(
91
92
  #
92
93
 
93
94
  if not force:
94
- if any(isinstance(h, StandardConfiguredLogHandler) for h in list(target.handlers)):
95
+ if any(isinstance(h, StandardConfiguredLoggingHandler) for h in list(target.handlers)):
95
96
  return None
96
97
 
97
98
  #
@@ -105,14 +106,14 @@ def configure_standard_logging(
105
106
 
106
107
  formatter: logging.Formatter
107
108
  if json:
108
- formatter = JsonLogFormatter()
109
+ formatter = JsonLoggingFormatter()
109
110
  else:
110
- formatter = StandardLogFormatter(StandardLogFormatter.build_log_format(STANDARD_LOG_FORMAT_PARTS))
111
+ formatter = StandardLoggingFormatter(StandardLoggingFormatter.build_log_format(STANDARD_LOG_FORMAT_PARTS))
111
112
  handler.setFormatter(formatter)
112
113
 
113
114
  #
114
115
 
115
- handler.addFilter(TidLogFilter())
116
+ handler.addFilter(TidLoggingFilter())
116
117
 
117
118
  #
118
119
 
@@ -125,4 +126,4 @@ def configure_standard_logging(
125
126
 
126
127
  #
127
128
 
128
- return StandardConfiguredLogHandler(handler)
129
+ return StandardConfiguredLoggingHandler(handler)
@@ -0,0 +1,41 @@
1
+ # @omlish-lite
2
+ import logging
3
+ import typing as ta
4
+
5
+ from ..base import Logger
6
+ from ..base import LoggingContext
7
+ from ..base import LogLevel
8
+ from .records import LoggingContextLogRecord
9
+
10
+
11
+ ##
12
+
13
+
14
+ class StdLogger(Logger):
15
+ def __init__(self, std: logging.Logger) -> None:
16
+ super().__init__()
17
+
18
+ self._std = std
19
+
20
+ @property
21
+ def std(self) -> logging.Logger:
22
+ return self._std
23
+
24
+ def get_effective_level(self) -> LogLevel:
25
+ return self._std.getEffectiveLevel()
26
+
27
+ def _log(self, ctx: LoggingContext, msg: str, *args: ta.Any) -> None:
28
+ if not self.is_enabled_for(ctx.level):
29
+ return
30
+
31
+ ctx.build_caller()
32
+
33
+ rec = LoggingContextLogRecord(
34
+ name=self._std.name,
35
+ msg=msg,
36
+ args=args,
37
+
38
+ _logging_context=ctx,
39
+ )
40
+
41
+ self._std.handle(rec)
@@ -0,0 +1,29 @@
1
+ # ruff: noqa: UP006 UP045
2
+ # @omlish-lite
3
+ """
4
+ https://docs.python.org/3/howto/logging.html#configuring-logging
5
+ https://docs.python.org/3/library/logging.config.html#logging-config-dictschema
6
+ """
7
+ import dataclasses as dc
8
+ import typing as ta
9
+
10
+
11
+ FilterDictLoggingConfig = ta.Dict[str, ta.Any] # ta.TypeAlias
12
+ FormatterDictLoggingConfig = ta.Dict[str, ta.Any] # ta.TypeAlias
13
+ HandlerDictLoggingConfig = ta.Dict[str, ta.Any] # ta.TypeAlias
14
+ LoggerDictLoggingConfig = ta.Dict[str, ta.Any] # ta.TypeAlias
15
+
16
+
17
+ ##
18
+
19
+
20
+ @dc.dataclass()
21
+ class DictLoggingConfig:
22
+ version: int = 1
23
+ incremental: bool = False
24
+ disable_existing_loggers: bool = False
25
+ filters: ta.Dict[str, FilterDictLoggingConfig] = dc.field(default_factory=dict)
26
+ formatters: ta.Dict[str, FormatterDictLoggingConfig] = dc.field(default_factory=dict)
27
+ handlers: ta.Dict[str, HandlerDictLoggingConfig] = dc.field(default_factory=dict)
28
+ loggers: ta.Dict[str, LoggerDictLoggingConfig] = dc.field(default_factory=dict)
29
+ root: ta.Optional[LoggerDictLoggingConfig] = None
@@ -7,7 +7,7 @@ import threading
7
7
  ##
8
8
 
9
9
 
10
- class TidLogFilter(logging.Filter):
10
+ class TidLoggingFilter(logging.Filter):
11
11
  def filter(self, record):
12
12
  # FIXME: handle better - missing from wasm and cosmos
13
13
  if hasattr(threading, 'get_native_id'):
@@ -7,7 +7,7 @@ import typing as ta
7
7
  ##
8
8
 
9
9
 
10
- class ListHandler(logging.Handler):
10
+ class ListLoggingHandler(logging.Handler):
11
11
  def __init__(self) -> None:
12
12
  super().__init__()
13
13
 
@@ -7,13 +7,13 @@ TODO:
7
7
  import logging
8
8
  import typing as ta
9
9
 
10
- from ..lite.json import json_dumps_compact
10
+ from ...lite.json import json_dumps_compact
11
11
 
12
12
 
13
13
  ##
14
14
 
15
15
 
16
- class JsonLogFormatter(logging.Formatter):
16
+ class JsonLoggingFormatter(logging.Formatter):
17
17
  KEYS: ta.Mapping[str, bool] = {
18
18
  'name': False,
19
19
  'msg': False,
@@ -6,7 +6,7 @@ import logging
6
6
  ##
7
7
 
8
8
 
9
- class ProxyLogFilterer(logging.Filterer):
9
+ class ProxyLoggingFilterer(logging.Filterer):
10
10
  def __init__(self, underlying: logging.Filterer) -> None: # noqa
11
11
  self._underlying = underlying
12
12
 
@@ -32,9 +32,9 @@ class ProxyLogFilterer(logging.Filterer):
32
32
  return self._underlying.filter(record)
33
33
 
34
34
 
35
- class ProxyLogHandler(ProxyLogFilterer, logging.Handler):
35
+ class ProxyLoggingHandler(ProxyLoggingFilterer, logging.Handler):
36
36
  def __init__(self, underlying: logging.Handler) -> None: # noqa
37
- ProxyLogFilterer.__init__(self, underlying)
37
+ ProxyLoggingFilterer.__init__(self, underlying)
38
38
 
39
39
  _underlying: logging.Handler
40
40
 
@@ -0,0 +1,286 @@
1
+ # ruff: noqa: UP006 UP007 UP045
2
+ # @omlish-lite
3
+ import collections.abc
4
+ import logging
5
+ import sys
6
+ import typing as ta
7
+
8
+ from ..base import LoggingContext
9
+ from ..base import LoggingExcInfoTuple
10
+ from ..warnings import LoggingSetupWarning
11
+
12
+
13
+ ##
14
+
15
+
16
+ # Ref:
17
+ # - https://docs.python.org/3/library/logging.html#logrecord-attributes
18
+ #
19
+ # LogRecord:
20
+ # - https://github.com/python/cpython/blob/39b2f82717a69dde7212bc39b673b0f55c99e6a3/Lib/logging/__init__.py#L276 (3.8)
21
+ # - https://github.com/python/cpython/blob/f070f54c5f4a42c7c61d1d5d3b8f3b7203b4a0fb/Lib/logging/__init__.py#L286 (~3.14) # noqa
22
+ #
23
+ # LogRecord.__init__ args:
24
+ # - name: str
25
+ # - level: int
26
+ # - pathname: str - Confusingly referred to as `fn` before the LogRecord ctor. May be empty or "(unknown file)".
27
+ # - lineno: int - May be 0.
28
+ # - msg: str
29
+ # - args: tuple | dict | 1-tuple[dict]
30
+ # - exc_info: LoggingExcInfoTuple | None
31
+ # - func: str | None = None -> funcName
32
+ # - sinfo: str | None = None -> stack_info
33
+ #
34
+ KNOWN_STD_LOGGING_RECORD_ATTRS: ta.Dict[str, ta.Any] = dict(
35
+ # Name of the logger used to log the call. Unmodified by ctor.
36
+ name=str,
37
+
38
+ # The format string passed in the original logging call. Merged with args to produce message, or an arbitrary object
39
+ # (see Using arbitrary objects as messages). Unmodified by ctor.
40
+ msg=str,
41
+
42
+ # The tuple of arguments merged into msg to produce message, or a dict whose values are used for the merge (when
43
+ # there is only one argument, and it is a dictionary). Ctor will transform a 1-tuple containing a Mapping into just
44
+ # the mapping, but is otherwise unmodified.
45
+ args=ta.Union[tuple, dict],
46
+
47
+ #
48
+
49
+ # Text logging level for the message ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'). Set to
50
+ # `getLevelName(level)`.
51
+ levelname=str,
52
+
53
+ # Numeric logging level for the message (DEBUG, INFO, WARNING, ERROR, CRITICAL). Unmodified by ctor.
54
+ levelno=int,
55
+
56
+ #
57
+
58
+ # Full pathname of the source file where the logging call was issued (if available). Unmodified by ctor. May default
59
+ # to "(unknown file)" by Logger.findCaller / Logger._log.
60
+ pathname=str,
61
+
62
+ # Filename portion of pathname. Set to `os.path.basename(pathname)` if successful, otherwise defaults to pathname.
63
+ filename=str,
64
+
65
+ # Module (name portion of filename). Set to `os.path.splitext(filename)[0]`, otherwise defaults to
66
+ # "Unknown module".
67
+ module=str,
68
+
69
+ #
70
+
71
+ # Exception tuple (à la sys.exc_info) or, if no exception has occurred, None. Unmodified by ctor.
72
+ exc_info=ta.Optional[LoggingExcInfoTuple],
73
+
74
+ # Used to cache the traceback text. Simply set to None by ctor, later set by Formatter.format.
75
+ exc_text=ta.Optional[str],
76
+
77
+ #
78
+
79
+ # Stack frame information (where available) from the bottom of the stack in the current thread, up to and including
80
+ # the stack frame of the logging call which resulted in the creation of this record. Set by ctor to `sinfo` arg,
81
+ # unmodified. Mostly set, if requested, by `Logger.findCaller`, to `traceback.print_stack(f)`, but prepended with
82
+ # the literal "Stack (most recent call last):\n", and stripped of exactly one trailing `\n` if present.
83
+ stack_info=ta.Optional[str],
84
+
85
+ # Source line number where the logging call was issued (if available). Unmodified by ctor. May default to 0 by
86
+ # Logger.findCaller / Logger._log.
87
+ lineno=int,
88
+
89
+ # Name of function containing the logging call. Set by ctor to `func` arg, unmodified. May default to
90
+ # "(unknown function)" by Logger.findCaller / Logger._log.
91
+ funcName=str,
92
+
93
+ #
94
+
95
+ # Time when the LogRecord was created. Set to `time.time_ns() / 1e9` for >=3.13.0b1, otherwise simply `time.time()`.
96
+ #
97
+ # See:
98
+ # - https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
99
+ # - https://github.com/python/cpython/commit/1500a23f33f5a6d052ff1ef6383d9839928b8ff1
100
+ #
101
+ created=float,
102
+
103
+ # Millisecond portion of the time when the LogRecord was created.
104
+ msecs=float,
105
+
106
+ # Time in milliseconds when the LogRecord was created, relative to the time the logging module was loaded.
107
+ relativeCreated=float,
108
+
109
+ #
110
+
111
+ # Thread ID if available, and `logging.logThreads` is truthy.
112
+ thread=ta.Optional[int],
113
+
114
+ # Thread name if available, and `logging.logThreads` is truthy.
115
+ threadName=ta.Optional[str],
116
+
117
+ #
118
+
119
+ # Process name if available. Set to None if `logging.logMultiprocessing` is not truthy. Otherwise, set to
120
+ # 'MainProcess', then `sys.modules.get('multiprocessing').current_process().name` if that works, otherwise remains
121
+ # as 'MainProcess'.
122
+ #
123
+ # As noted by stdlib:
124
+ #
125
+ # Errors may occur if multiprocessing has not finished loading yet - e.g. if a custom import hook causes
126
+ # third-party code to run when multiprocessing calls import. See issue 8200 for an example
127
+ #
128
+ processName=ta.Optional[str],
129
+
130
+ # Process ID if available - that is, if `hasattr(os, 'getpid')` - and `logging.logProcesses` is truthy, otherwise
131
+ # None.
132
+ process=ta.Optional[int],
133
+
134
+ #
135
+
136
+ # Absent <3.12, otherwise asyncio.Task name if available, and `logging.logAsyncioTasks` is truthy. Set to
137
+ # `sys.modules.get('asyncio').current_task().get_name()`, otherwise None.
138
+ taskName=ta.Optional[str],
139
+ )
140
+
141
+ KNOWN_STD_LOGGING_RECORD_ATTR_SET: ta.FrozenSet[str] = frozenset(KNOWN_STD_LOGGING_RECORD_ATTRS)
142
+
143
+
144
+ # Formatter:
145
+ # - https://github.com/python/cpython/blob/39b2f82717a69dde7212bc39b673b0f55c99e6a3/Lib/logging/__init__.py#L514 (3.8)
146
+ # - https://github.com/python/cpython/blob/f070f54c5f4a42c7c61d1d5d3b8f3b7203b4a0fb/Lib/logging/__init__.py#L554 (~3.14) # noqa
147
+ #
148
+ KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTRS: ta.Dict[str, ta.Any] = dict(
149
+ # The logged message, computed as msg % args. Set to `record.getMessage()`.
150
+ message=str,
151
+
152
+ # Human-readable time when the LogRecord was created. By default this is of the form '2003-07-08 16:49:45,896' (the
153
+ # numbers after the comma are millisecond portion of the time). Set to `self.formatTime(record, self.datefmt)` if
154
+ # `self.usesTime()`, otherwise unset.
155
+ asctime=str,
156
+
157
+ # Used to cache the traceback text. If unset (falsey) on the record and `exc_info` is truthy, set to
158
+ # `self.formatException(record.exc_info)` - otherwise unmodified.
159
+ exc_text=ta.Optional[str],
160
+ )
161
+
162
+ KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTR_SET: ta.FrozenSet[str] = frozenset(KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTRS)
163
+
164
+
165
+ ##
166
+
167
+
168
+ class UnknownStdLoggingRecordAttrsWarning(LoggingSetupWarning):
169
+ pass
170
+
171
+
172
+ def _check_std_logging_record_attrs() -> None:
173
+ rec_dct = dict(logging.makeLogRecord({}).__dict__)
174
+
175
+ if (unk_rec_fields := frozenset(rec_dct) - KNOWN_STD_LOGGING_RECORD_ATTR_SET):
176
+ import warnings # noqa
177
+
178
+ warnings.warn(
179
+ f'Unknown log record attrs detected: {sorted(unk_rec_fields)!r}',
180
+ UnknownStdLoggingRecordAttrsWarning,
181
+ )
182
+
183
+
184
+ _check_std_logging_record_attrs()
185
+
186
+
187
+ ##
188
+
189
+
190
+ class LoggingContextLogRecord(logging.LogRecord):
191
+ _SHOULD_ADD_TASK_NAME: ta.ClassVar[bool] = sys.version_info >= (3, 12)
192
+
193
+ _UNKNOWN_PATH_NAME: ta.ClassVar[str] = '(unknown file)'
194
+ _UNKNOWN_FUNC_NAME: ta.ClassVar[str] = '(unknown function)'
195
+ _UNKNOWN_MODULE: ta.ClassVar[str] = 'Unknown module'
196
+
197
+ _STACK_INFO_PREFIX: ta.ClassVar[str] = 'Stack (most recent call last):\n'
198
+
199
+ def __init__( # noqa
200
+ self,
201
+ # name,
202
+ # level,
203
+ # pathname,
204
+ # lineno,
205
+ # msg,
206
+ # args,
207
+ # exc_info,
208
+ # func=None,
209
+ # sinfo=None,
210
+ # **kwargs,
211
+ *,
212
+ name: str,
213
+ msg: str,
214
+ args: ta.Union[tuple, dict],
215
+
216
+ _logging_context: LoggingContext,
217
+ ) -> None:
218
+ ctx = _logging_context
219
+
220
+ self.name: str = name
221
+
222
+ self.msg: str = msg
223
+
224
+ # https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L307
225
+ if args and len(args) == 1 and isinstance(args[0], collections.abc.Mapping) and args[0]:
226
+ args = args[0] # type: ignore[assignment]
227
+ self.args: ta.Union[tuple, dict] = args
228
+
229
+ self.levelname: str = logging.getLevelName(ctx.level)
230
+ self.levelno: int = ctx.level
231
+
232
+ if (caller := ctx.caller()) is not None:
233
+ self.pathname: str = caller.file_path
234
+ else:
235
+ self.pathname = self._UNKNOWN_PATH_NAME
236
+
237
+ if (src_file := ctx.source_file()) is not None:
238
+ self.filename: str = src_file.file_name
239
+ self.module: str = src_file.module
240
+ else:
241
+ self.filename = self.pathname
242
+ self.module = self._UNKNOWN_MODULE
243
+
244
+ self.exc_info: ta.Optional[LoggingExcInfoTuple] = ctx.exc_info_tuple
245
+ self.exc_text: ta.Optional[str] = None
246
+
247
+ # If ctx.build_caller() was never called, we simply don't have a stack trace.
248
+ if caller is not None:
249
+ if (sinfo := caller.stack_info) is not None:
250
+ self.stack_info: ta.Optional[str] = '\n'.join([
251
+ self._STACK_INFO_PREFIX,
252
+ sinfo[1:] if sinfo.endswith('\n') else sinfo,
253
+ ])
254
+ else:
255
+ self.stack_info = None
256
+
257
+ self.lineno: int = caller.line_no
258
+ self.funcName: str = caller.name
259
+
260
+ else:
261
+ self.stack_info = None
262
+
263
+ self.lineno = 0
264
+ self.funcName = self._UNKNOWN_FUNC_NAME
265
+
266
+ times = ctx.times
267
+ self.created: float = times.created
268
+ self.msecs: float = times.msecs
269
+ self.relativeCreated: float = times.relative_created
270
+
271
+ thread = ctx.thread
272
+ self.thread: ta.Optional[int] = thread.ident
273
+ self.threadName: ta.Optional[str] = thread.name
274
+
275
+ process = ctx.process
276
+ self.process: ta.Optional[int] = process.pid
277
+
278
+ if (mp := ctx.multiprocessing) is not None:
279
+ self.processName: ta.Optional[str] = mp.process_name
280
+ else:
281
+ self.processName = None
282
+
283
+ if (at := ctx.asyncio_task) is not None:
284
+ self.taskName: ta.Optional[str] = at.name
285
+ else:
286
+ self.taskName = None
omlish/logs/times.py ADDED
@@ -0,0 +1,86 @@
1
+ # ruff: noqa: UP045
2
+ # @omlish-lite
3
+ import logging
4
+ import time
5
+ import typing as ta
6
+
7
+ from .warnings import LoggingSetupWarning
8
+
9
+
10
+ ##
11
+
12
+
13
+ class LoggingTimeFields(ta.NamedTuple):
14
+ """Maps directly to stdlib `logging.LogRecord` fields, and must be kept in sync with it."""
15
+
16
+ created: float
17
+ msecs: float
18
+ relative_created: float
19
+
20
+ @classmethod
21
+ def get_std_start_time_ns(cls) -> int:
22
+ x: ta.Any = logging._startTime # type: ignore[attr-defined] # noqa
23
+
24
+ # Before 3.13.0b1 this will be `time.time()`, a float of seconds. After that, it will be `time.time_ns()`, an
25
+ # int.
26
+ #
27
+ # See:
28
+ # - https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
29
+ #
30
+ if isinstance(x, float):
31
+ return int(x * 1e9)
32
+ else:
33
+ return x
34
+
35
+ @classmethod
36
+ def build(
37
+ cls,
38
+ time_ns: int,
39
+ *,
40
+ start_time_ns: ta.Optional[int] = None,
41
+ ) -> 'LoggingTimeFields':
42
+ # https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
43
+ created = time_ns / 1e9 # ns to float seconds
44
+
45
+ # Get the number of whole milliseconds (0-999) in the fractional part of seconds.
46
+ # Eg: 1_677_903_920_999_998_503 ns --> 999_998_503 ns--> 999 ms
47
+ # Convert to float by adding 0.0 for historical reasons. See gh-89047
48
+ msecs = (time_ns % 1_000_000_000) // 1_000_000 + 0.0
49
+
50
+ # https://github.com/python/cpython/commit/1500a23f33f5a6d052ff1ef6383d9839928b8ff1
51
+ if msecs == 999.0 and int(created) != time_ns // 1_000_000_000:
52
+ # ns -> sec conversion can round up, e.g:
53
+ # 1_677_903_920_999_999_900 ns --> 1_677_903_921.0 sec
54
+ msecs = 0.0
55
+
56
+ if start_time_ns is None:
57
+ start_time_ns = cls.get_std_start_time_ns()
58
+ relative_created = (time_ns - start_time_ns) / 1e6
59
+
60
+ return cls(
61
+ created,
62
+ msecs,
63
+ relative_created,
64
+ )
65
+
66
+
67
+ ##
68
+
69
+
70
+ class UnexpectedLoggingStartTimeWarning(LoggingSetupWarning):
71
+ pass
72
+
73
+
74
+ def _check_logging_start_time() -> None:
75
+ if (x := LoggingTimeFields.get_std_start_time_ns()) < (t := time.time()):
76
+ import warnings # noqa
77
+
78
+ warnings.warn(
79
+ f'Unexpected logging start time detected: '
80
+ f'get_std_start_time_ns={x}, '
81
+ f'time.time()={t}',
82
+ UnexpectedLoggingStartTimeWarning,
83
+ )
84
+
85
+
86
+ _check_logging_start_time()
@@ -448,6 +448,30 @@ _AS_TYPED_LOGGER_BINDINGS_FIELD_VALUE_DIRECT_TYPES: ta.Tuple[type, ...] = (
448
448
  )
449
449
 
450
450
 
451
+ # @ta.final
452
+ # class TypedLoggerBindingsBuilder:
453
+ # def __init__(
454
+ # self,
455
+ # *,
456
+ # add_default_keys: bool = False,
457
+ # default_key_filter: ta.Optional[ta.Callable[[str], bool]] = None,
458
+ #
459
+ # add_default_values: bool = False,
460
+ # default_value_filter: ta.Optional[ta.Callable[[ta.Type[DefaultTypedLoggerValue]], bool]] = None,
461
+ #
462
+ # value_wrapper: ta.Optional[TypedLoggerValueWrapperFn] = None,
463
+ # ) -> None:
464
+ # self._add_default_keys = add_default_keys
465
+ # self._default_key_filter = default_key_filter
466
+ #
467
+ # self._add_default_values = add_default_values
468
+ # self._default_value_filter = default_value_filter
469
+ #
470
+ # self._value_wrapper = value_wrapper
471
+ #
472
+ # self._lst: ta.List[TypedLoggerBindingItem] = []
473
+
474
+
451
475
  def as_typed_logger_bindings(
452
476
  *objs: CanTypedLoggerBinding,
453
477
 
omlish/logs/utils.py CHANGED
@@ -1,14 +1,17 @@
1
+ # ruff: noqa: UP006 UP007 UP045
2
+ # @omlish-lite
1
3
  import functools
2
4
  import logging
5
+ import time
6
+ import typing as ta
3
7
 
4
-
5
- ##
8
+ from .protocols import LoggerLike
6
9
 
7
10
 
8
- _log = logging.getLogger(__name__)
11
+ ##
9
12
 
10
13
 
11
- def error_logging(log=_log): # noqa
14
+ def error_logging(log): # noqa
12
15
  def outer(fn):
13
16
  @functools.wraps(fn)
14
17
  def inner(*args, **kwargs):
@@ -21,3 +24,56 @@ def error_logging(log=_log): # noqa
21
24
  return inner
22
25
 
23
26
  return outer
27
+
28
+
29
+ ##
30
+
31
+
32
+ class LogTimingContext:
33
+ DEFAULT_LOG: ta.ClassVar[ta.Optional[LoggerLike]] = None
34
+
35
+ class _NOT_SPECIFIED: # noqa
36
+ def __new__(cls, *args, **kwargs): # noqa
37
+ raise TypeError
38
+
39
+ def __init__(
40
+ self,
41
+ description: str,
42
+ *,
43
+ log: ta.Union[LoggerLike, ta.Type[_NOT_SPECIFIED], None] = _NOT_SPECIFIED, # noqa
44
+ level: int = logging.DEBUG,
45
+ ) -> None:
46
+ super().__init__()
47
+
48
+ self._description = description
49
+ if log is self._NOT_SPECIFIED:
50
+ log = self.DEFAULT_LOG # noqa
51
+ self._log: ta.Optional[LoggerLike] = log # type: ignore
52
+ self._level = level
53
+
54
+ def set_description(self, description: str) -> 'LogTimingContext':
55
+ self._description = description
56
+ return self
57
+
58
+ _begin_time: float
59
+ _end_time: float
60
+
61
+ def __enter__(self) -> 'LogTimingContext':
62
+ self._begin_time = time.time()
63
+
64
+ if self._log is not None:
65
+ self._log.log(self._level, f'Begin : {self._description}') # noqa
66
+
67
+ return self
68
+
69
+ def __exit__(self, exc_type, exc_val, exc_tb):
70
+ self._end_time = time.time()
71
+
72
+ if self._log is not None:
73
+ self._log.log(
74
+ self._level,
75
+ f'End : {self._description} - {self._end_time - self._begin_time:0.2f} s elapsed',
76
+ )
77
+
78
+
79
+ log_timing_context = LogTimingContext