omlish 0.0.0.dev118__py3-none-any.whl → 0.0.0.dev119__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.
omlish/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev118'
2
- __revision__ = '3d4642bb79ba8bc7d7c6a1009237095185ac3c86'
1
+ __version__ = '0.0.0.dev119'
2
+ __revision__ = 'abf03cc06fe1610ddf750a1392aa9c9de00acfd3'
3
3
 
4
4
 
5
5
  #
@@ -0,0 +1,163 @@
1
+ # ruff: noqa: UP007 UP012
2
+ import ctypes as ct
3
+ import logging
4
+ import sys
5
+ import syslog
6
+ import threading
7
+ import typing as ta
8
+
9
+ from .cached import cached_nullary
10
+
11
+
12
+ ##
13
+
14
+
15
+ class sd_iovec(ct.Structure): # noqa
16
+ pass
17
+
18
+
19
+ sd_iovec._fields_ = [
20
+ ('iov_base', ct.c_void_p), # Pointer to data.
21
+ ('iov_len', ct.c_size_t), # Length of data.
22
+ ]
23
+
24
+
25
+ ##
26
+
27
+
28
+ @cached_nullary
29
+ def sd_libsystemd() -> ta.Any:
30
+ lib = ct.CDLL('libsystemd.so.0')
31
+
32
+ lib.sd_journal_sendv = lib['sd_journal_sendv'] # type: ignore
33
+ lib.sd_journal_sendv.restype = ct.c_int
34
+ lib.sd_journal_sendv.argtypes = [ct.POINTER(sd_iovec), ct.c_int]
35
+
36
+ return lib
37
+
38
+
39
+ @cached_nullary
40
+ def sd_try_libsystemd() -> ta.Optional[ta.Any]:
41
+ try:
42
+ return sd_libsystemd()
43
+ except OSError: # noqa
44
+ return None
45
+
46
+
47
+ ##
48
+
49
+
50
+ def sd_journald_send(**fields: str) -> int:
51
+ lib = sd_libsystemd()
52
+
53
+ msgs = [
54
+ f'{k.upper()}={v}\0'.encode('utf-8')
55
+ for k, v in fields.items()
56
+ ]
57
+
58
+ vec = (sd_iovec * len(msgs))()
59
+ cl = (ct.c_char_p * len(msgs))() # noqa
60
+ for i in range(len(msgs)):
61
+ vec[i].iov_base = ct.cast(ct.c_char_p(msgs[i]), ct.c_void_p)
62
+ vec[i].iov_len = len(msgs[i]) - 1
63
+
64
+ return lib.sd_journal_sendv(vec, len(msgs))
65
+
66
+
67
+ ##
68
+
69
+
70
+ SD_LOG_LEVEL_MAP: ta.Mapping[int, int] = {
71
+ logging.FATAL: syslog.LOG_EMERG, # system is unusable
72
+ # LOG_ALERT ? # action must be taken immediately
73
+ logging.CRITICAL: syslog.LOG_CRIT,
74
+ logging.ERROR: syslog.LOG_ERR,
75
+ logging.WARNING: syslog.LOG_WARNING,
76
+ # LOG_NOTICE ? # normal but significant condition
77
+ logging.INFO: syslog.LOG_INFO,
78
+ logging.DEBUG: syslog.LOG_DEBUG,
79
+ }
80
+
81
+
82
+ class JournaldLogHandler(logging.Handler):
83
+ """
84
+ TODO:
85
+ - fallback handler for when this barfs
86
+ """
87
+
88
+ def __init__(
89
+ self,
90
+ *,
91
+ use_formatter_output: bool = False,
92
+ ) -> None:
93
+ super().__init__()
94
+
95
+ sd_libsystemd()
96
+
97
+ self._use_formatter_output = use_formatter_output
98
+
99
+ #
100
+
101
+ EXTRA_RECORD_ATTRS_BY_JOURNALD_FIELD: ta.ClassVar[ta.Mapping[str, str]] = {
102
+ 'name': 'name',
103
+ 'module': 'module',
104
+ 'exception': 'exc_text',
105
+ 'thread_name': 'threadName',
106
+ 'task_name': 'taskName',
107
+ }
108
+
109
+ def make_fields(self, record: logging.LogRecord) -> ta.Mapping[str, str]:
110
+ formatter_message = self.format(record)
111
+ if self._use_formatter_output:
112
+ message = formatter_message
113
+ else:
114
+ message = record.message
115
+
116
+ fields: dict[str, str] = {
117
+ 'message': message,
118
+ 'priority': str(SD_LOG_LEVEL_MAP[record.levelno]),
119
+ 'tid': str(threading.get_ident()),
120
+ }
121
+
122
+ if (pathname := record.pathname) is not None:
123
+ fields['code_file'] = pathname
124
+ if (lineno := record.lineno) is not None:
125
+ fields['code_lineno'] = str(lineno)
126
+ if (func_name := record.funcName) is not None:
127
+ fields['code_func'] = func_name
128
+
129
+ for f, a in self.EXTRA_RECORD_ATTRS_BY_JOURNALD_FIELD.items():
130
+ if (v := getattr(record, a, None)) is not None:
131
+ fields[f] = str(v)
132
+
133
+ return fields
134
+
135
+ #
136
+
137
+ def emit(self, record: logging.LogRecord) -> None:
138
+ try:
139
+ fields = self.make_fields(record)
140
+
141
+ if rc := sd_journald_send(**fields):
142
+ raise RuntimeError(f'{self.__class__.__name__}.emit failed: {rc=}') # noqa
143
+
144
+ except RecursionError: # See issue 36272
145
+ raise
146
+
147
+ except Exception: # noqa
148
+ self.handleError(record)
149
+
150
+
151
+ def journald_log_handler_factory(
152
+ *,
153
+ no_tty_check: bool = False,
154
+ no_fallback: bool = False,
155
+ ) -> logging.Handler:
156
+ if (
157
+ sys.platform == 'linux' and
158
+ (no_tty_check or not sys.stderr.isatty()) and
159
+ (no_fallback or sd_try_libsystemd() is not None)
160
+ ):
161
+ return JournaldLogHandler()
162
+
163
+ return logging.StreamHandler()
omlish/lite/logs.py CHANGED
@@ -91,7 +91,7 @@ class StandardLogFormatter(logging.Formatter):
91
91
  if datefmt:
92
92
  return ct.strftime(datefmt) # noqa
93
93
  else:
94
- t = ct.strftime("%Y-%m-%d %H:%M:%S") # noqa
94
+ t = ct.strftime('%Y-%m-%d %H:%M:%S')
95
95
  return '%s.%03d' % (t, record.msecs)
96
96
 
97
97
 
@@ -228,6 +228,7 @@ def configure_standard_logging(
228
228
  json: bool = False,
229
229
  target: ta.Optional[logging.Logger] = None,
230
230
  force: bool = False,
231
+ handler_factory: ta.Optional[ta.Callable[[], logging.Handler]] = None,
231
232
  ) -> ta.Optional[StandardLogHandler]:
232
233
  with _locking_logging_module_lock():
233
234
  if target is None:
@@ -241,7 +242,10 @@ def configure_standard_logging(
241
242
 
242
243
  #
243
244
 
244
- handler = logging.StreamHandler()
245
+ if handler_factory is not None:
246
+ handler = handler_factory()
247
+ else:
248
+ handler = logging.StreamHandler()
245
249
 
246
250
  #
247
251
 
omlish/logs/_abc.py CHANGED
@@ -16,27 +16,80 @@ ExceptionInfo: ta.TypeAlias = tuple[type[BaseException], BaseException, types.Tr
16
16
 
17
17
 
18
18
  class LogRecord:
19
+ """https://docs.python.org/3/library/logging.html#logrecord-attributes"""
20
+
21
+ # Name of the logger used to log the call.
19
22
  name: str
23
+
24
+ # Human-readable time when the LogRecord was created. By default this is of the form '2003-07-08 16:49:45,896' (the
25
+ # numbers after the comma are millisecond portion of the time).
26
+ asctime: str
27
+
28
+ # The logged message, computed as msg % args. This is set when Formatter.format() is invoked.
29
+ message: str
30
+
31
+ # The format string passed in the original logging call. Merged with args to produce message, or an arbitrary object
32
+ # (see Using arbitrary objects as messages).
20
33
  msg: str
34
+
35
+ # The tuple of arguments merged into msg to produce message, or a dict whose values are used for the merge (when
36
+ # there is only one argument, and it is a dictionary).
21
37
  args: tuple
38
+
39
+ # Text logging level for the message ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL').
22
40
  levelname: str
41
+
42
+ # # Numeric logging level for the message (DEBUG, INFO, WARNING, ERROR, CRITICAL).
23
43
  levelno: Level
44
+
45
+ # Full pathname of the source file where the logging call was issued (if available).
24
46
  pathname: str
47
+
48
+ # Filename portion of pathname.
25
49
  filename: str
50
+
51
+ # Module (name portion of filename).
26
52
  module: str
53
+
54
+ # Exception tuple (à la sys.exc_info) or, if no exception has occurred, None.
27
55
  exc_info: ExceptionInfo | None
56
+
28
57
  exc_text: str | None
58
+
59
+ # Stack frame information (where available) from the bottom of the stack in the current thread, up to and including
60
+ # the stack frame of the logging call which resulted in the creation of this record.
29
61
  stack_info: str | None
62
+
63
+ # Source line number where the logging call was issued (if available).
30
64
  lineno: int
65
+
66
+ # Name of function containing the logging call.
31
67
  funcName: str
68
+
69
+ # Time when the LogRecord was created (as returned by time.time_ns() / 1e9).
32
70
  created: float
71
+
72
+ # Millisecond portion of the time when the LogRecord was created.
33
73
  msecs: float
74
+
75
+ # Time in milliseconds when the LogRecord was created, relative to the time the logging module was loaded.
34
76
  relativeCreated: float
77
+
78
+ # Thread ID (if available).
35
79
  thread: int
80
+
81
+ # Thread name (if available).
36
82
  threadName: str
83
+
84
+ # Process name (if available).
37
85
  processName: str
86
+
87
+ # Process ID (if available).
38
88
  process: int
39
89
 
90
+ # asyncio.Task name (if available).
91
+ taskName: str
92
+
40
93
 
41
94
  ##
42
95
 
omlish/logs/handlers.py CHANGED
@@ -6,5 +6,5 @@ class ListHandler(logging.Handler):
6
6
  super().__init__()
7
7
  self.records: list[logging.LogRecord] = []
8
8
 
9
- def emit(self, record):
9
+ def emit(self, record: logging.LogRecord) -> None:
10
10
  self.records.append(record)
@@ -16,9 +16,9 @@ class PydevdPlugin:
16
16
  # if (dbg := opd.get_global_debugger()) is not None:
17
17
  # dbg.set_unit_tests_debugging_mode()
18
18
 
19
- def pytest_exception_interact(self, node, call, report):
20
- if opd.get_setup() is not None:
21
- if not node.session.config.option.no_pydevd:
22
- opd.debug_unhandled_exception(call.excinfo._excinfo) # noqa
23
-
24
- return report
19
+ # def pytest_exception_interact(self, node, call, report):
20
+ # if opd.get_setup() is not None:
21
+ # if not node.session.config.option.no_pydevd:
22
+ # opd.debug_unhandled_exception(call.excinfo._excinfo) # noqa
23
+ #
24
+ # return report
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: omlish
3
- Version: 0.0.0.dev118
3
+ Version: 0.0.0.dev119
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -1,5 +1,5 @@
1
1
  omlish/.manifests.json,sha256=CxGnj-UiRPlZgmgWoovDWrOnqpSEmBy_kqA7cdfSA3w,1431
2
- omlish/__about__.py,sha256=MinOfy1NZwNjncbIZv15_JVwYRxDPosv0eEZPZCDSpM,3352
2
+ omlish/__about__.py,sha256=cKBa887pzxYkOLw1HFwEiT4g4dP8AxuJQrGNkTmiju8,3352
3
3
  omlish/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  omlish/argparse.py,sha256=cqKGAqcxuxv_s62z0gq29L9KAvg_3-_rFvXKjVpRJjo,8126
5
5
  omlish/c3.py,sha256=4vogWgwPb8TbNS2KkZxpoWbwjj7MuHG2lQG-hdtkvjI,8062
@@ -303,8 +303,9 @@ omlish/lite/check.py,sha256=ouJme9tkzWXKymm_xZDK4mngdYSkxDrok6CSegvf-1w,1015
303
303
  omlish/lite/contextmanagers.py,sha256=_n6a9xhn06BD8H6A_SDtcipMrSBpzBqcxI0Ob2juomM,1226
304
304
  omlish/lite/docker.py,sha256=3IVZZtIm7-UdB2SwArmN_MosTva1_KifyYp3YWjODbE,337
305
305
  omlish/lite/io.py,sha256=lcpI1cS_Kn90tvYMg8ZWkSlYloS4RFqXCk-rKyclhdg,3148
306
+ omlish/lite/journald.py,sha256=3nfahFbTrdrfp9txhtto6JYAyrus2kcAFtviqdm3qAo,3949
306
307
  omlish/lite/json.py,sha256=7-02Ny4fq-6YAu5ynvqoijhuYXWpLmfCI19GUeZnb1c,740
307
- omlish/lite/logs.py,sha256=vkFkSX0Izb2P-NNMqqNLSec0BzeLOtHoQWgdXwQuDPU,6007
308
+ omlish/lite/logs.py,sha256=tw7mbQslkyo9LopfgQnj0tYiqJ9TDNiw7D07aF7Dm2g,6176
308
309
  omlish/lite/marshal.py,sha256=SyYMsJ-TaGO9FX7LykBB0WtqsqetX9eLBotPiz3M_xg,9478
309
310
  omlish/lite/pidfile.py,sha256=PRSDOAXmNkNwxh-Vwif0Nrs8RAmWroiNhLKIbdjwzBc,1723
310
311
  omlish/lite/reflect.py,sha256=9QYJwdINraq1JNMEgvoqeSlVvRRgOXpxAkpgX8EgRXc,1307
@@ -313,10 +314,10 @@ omlish/lite/secrets.py,sha256=3Mz3V2jf__XU9qNHcH56sBSw95L3U2UPL24bjvobG0c,816
313
314
  omlish/lite/strings.py,sha256=QURcE4-1pKVW8eT_5VCJpXaHDWR2dW2pYOChTJnZDiQ,1504
314
315
  omlish/lite/subprocesses.py,sha256=_YwUpvfaC2pV5TMC9-Ivuw1Ao-YxteD3a1NQwGERft4,3380
315
316
  omlish/logs/__init__.py,sha256=FbOyAW-lGH8gyBlSVArwljdYAU6RnwZLI5LwAfuNnrk,438
316
- omlish/logs/_abc.py,sha256=UgrCUQVUi_PvT3p1CEkb3P74CFrFcZq2AFby3GEUv9M,5974
317
+ omlish/logs/_abc.py,sha256=rWySJcr1vatu-AR1EYtODRhi-TjFaixqUzXeWg1c0GA,8006
317
318
  omlish/logs/configs.py,sha256=EE0jlNaXJbGnM7V-y4xS5VwyTBSTzFzc0BYaVjg0JmA,1283
318
319
  omlish/logs/formatters.py,sha256=q79nMnR2mRIStPyGrydQHpYTXgC5HHptt8lH3W2Wwbs,671
319
- omlish/logs/handlers.py,sha256=nyuFgmO05By_Xwq7es58ClzS51-F53lJL7gD0x5IqAg,228
320
+ omlish/logs/handlers.py,sha256=UpzUf3kWBBzWOnrtljoZsLjISw3Ix-ePz3Nsmp6lRgE,255
320
321
  omlish/logs/noisy.py,sha256=Ubc-eTH6ZbGYsLfUUi69JAotwuUwzb-SJBeGo_0dIZI,348
321
322
  omlish/logs/utils.py,sha256=MgGovbP0zUrZ3FGD3qYNQWn-l0jy0Y0bStcQvv5BOmQ,391
322
323
  omlish/marshal/__init__.py,sha256=iVA7n31L08Bdub6HKPvYOXVvDhk2CMA6rPeKDL_u1to,2298
@@ -457,7 +458,7 @@ omlish/testing/pytest/plugins/asyncs.py,sha256=SV6oKCy50CGkzLGYX-CT4MfWNqsrH8ONE
457
458
  omlish/testing/pytest/plugins/depskip.py,sha256=xithY-OMtjwhv8mcRNkv-WI_PSQtHldQ8H1s60MIXkk,2673
458
459
  omlish/testing/pytest/plugins/logging.py,sha256=1zs6Xe54wiaSjabCviaFXwKkoN97CKm3mA5mEoUeJGs,380
459
460
  omlish/testing/pytest/plugins/managermarks.py,sha256=AP3ty-QB-8O5DkulwUOudBlUOvXMHhBfNyY-0yCmejk,1520
460
- omlish/testing/pytest/plugins/pydevd.py,sha256=1RCkuqD6Zeq-GcfS9vj33_9xZ41NtVx1yu7aKWi9FuA,827
461
+ omlish/testing/pytest/plugins/pydevd.py,sha256=AXtN83M39ZKJ4VH3MJEhvPnAmYzD5u1r8ehz-0om50Q,842
461
462
  omlish/testing/pytest/plugins/repeat.py,sha256=flSQzE9GFOWksVKz-mUGnpxJpv3yRqn1G4K8pW7JHs0,498
462
463
  omlish/testing/pytest/plugins/skips.py,sha256=EoZDg1uWccgbAegmzqI85c7RliycD1e2J4Y7vfDRhwM,1041
463
464
  omlish/testing/pytest/plugins/spacing.py,sha256=JQQhi9q3c523Ro1a_K_9RGAb7HotiO74N8bYX2VESFE,707
@@ -470,9 +471,9 @@ omlish/text/glyphsplit.py,sha256=Ug-dPRO7x-OrNNr8g1y6DotSZ2KH0S-VcOmUobwa4B0,329
470
471
  omlish/text/indent.py,sha256=6Jj6TFY9unaPa4xPzrnZemJ-fHsV53IamP93XGjSUHs,1274
471
472
  omlish/text/parts.py,sha256=7vPF1aTZdvLVYJ4EwBZVzRSy8XB3YqPd7JwEnNGGAOo,6495
472
473
  omlish/text/random.py,sha256=jNWpqiaKjKyTdMXC-pWAsSC10AAP-cmRRPVhm59ZWLk,194
473
- omlish-0.0.0.dev118.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
474
- omlish-0.0.0.dev118.dist-info/METADATA,sha256=f3JHsixv9JqvQsSIUjV4f-1FvN8dkjwvxk9r_XgHVDk,4000
475
- omlish-0.0.0.dev118.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
476
- omlish-0.0.0.dev118.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
477
- omlish-0.0.0.dev118.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
478
- omlish-0.0.0.dev118.dist-info/RECORD,,
474
+ omlish-0.0.0.dev119.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
475
+ omlish-0.0.0.dev119.dist-info/METADATA,sha256=sMg30UlhW1GLNw8MkOhVqMaaiObB2IXCn22KIqaMpWU,4000
476
+ omlish-0.0.0.dev119.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
477
+ omlish-0.0.0.dev119.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
478
+ omlish-0.0.0.dev119.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
479
+ omlish-0.0.0.dev119.dist-info/RECORD,,