ominfra 0.0.0.dev156__py3-none-any.whl → 0.0.0.dev158__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.
@@ -142,9 +142,9 @@ InjectorBindingOrBindings = ta.Union['InjectorBinding', 'InjectorBindings']
142
142
  ConfigMapping = ta.Mapping[str, ta.Any]
143
143
 
144
144
  # ../../omlish/http/handlers.py
145
- HttpHandler = ta.Callable[['HttpHandlerRequest'], 'HttpHandlerResponse']
145
+ HttpHandler = ta.Callable[['HttpHandlerRequest'], 'HttpHandlerResponse'] # ta.TypeAlias
146
146
 
147
- # ../../omlish/http/coroserver.py
147
+ # ../../omlish/http/coro/server.py
148
148
  CoroHttpServerFactory = ta.Callable[[SocketAddress], 'CoroHttpServer']
149
149
 
150
150
 
@@ -1697,8 +1697,8 @@ class _CachedNullary(_AbstractCachedNullary):
1697
1697
  return self._value
1698
1698
 
1699
1699
 
1700
- def cached_nullary(fn): # ta.Callable[..., T]) -> ta.Callable[..., T]:
1701
- return _CachedNullary(fn)
1700
+ def cached_nullary(fn: CallableT) -> CallableT:
1701
+ return _CachedNullary(fn) # type: ignore
1702
1702
 
1703
1703
 
1704
1704
  def static_init(fn: CallableT) -> CallableT:
@@ -2202,6 +2202,13 @@ json_dump_compact: ta.Callable[..., bytes] = functools.partial(json.dump, **JSON
2202
2202
  json_dumps_compact: ta.Callable[..., str] = functools.partial(json.dumps, **JSON_COMPACT_KWARGS)
2203
2203
 
2204
2204
 
2205
+ ########################################
2206
+ # ../../../omlish/lite/logs.py
2207
+
2208
+
2209
+ log = logging.getLogger(__name__)
2210
+
2211
+
2205
2212
  ########################################
2206
2213
  # ../../../omlish/lite/maybes.py
2207
2214
 
@@ -2486,6 +2493,116 @@ class Func3(ta.Generic[A0, A1, A2, T]):
2486
2493
  return self.fn(a0, a1, a2)
2487
2494
 
2488
2495
 
2496
+ ########################################
2497
+ # ../../../omlish/logs/filters.py
2498
+
2499
+
2500
+ class TidLogFilter(logging.Filter):
2501
+ def filter(self, record):
2502
+ record.tid = threading.get_native_id()
2503
+ return True
2504
+
2505
+
2506
+ ########################################
2507
+ # ../../../omlish/logs/proxy.py
2508
+
2509
+
2510
+ class ProxyLogFilterer(logging.Filterer):
2511
+ def __init__(self, underlying: logging.Filterer) -> None: # noqa
2512
+ self._underlying = underlying
2513
+
2514
+ @property
2515
+ def underlying(self) -> logging.Filterer:
2516
+ return self._underlying
2517
+
2518
+ @property
2519
+ def filters(self):
2520
+ return self._underlying.filters
2521
+
2522
+ @filters.setter
2523
+ def filters(self, filters):
2524
+ self._underlying.filters = filters
2525
+
2526
+ def addFilter(self, filter): # noqa
2527
+ self._underlying.addFilter(filter)
2528
+
2529
+ def removeFilter(self, filter): # noqa
2530
+ self._underlying.removeFilter(filter)
2531
+
2532
+ def filter(self, record):
2533
+ return self._underlying.filter(record)
2534
+
2535
+
2536
+ class ProxyLogHandler(ProxyLogFilterer, logging.Handler):
2537
+ def __init__(self, underlying: logging.Handler) -> None: # noqa
2538
+ ProxyLogFilterer.__init__(self, underlying)
2539
+
2540
+ _underlying: logging.Handler
2541
+
2542
+ @property
2543
+ def underlying(self) -> logging.Handler:
2544
+ return self._underlying
2545
+
2546
+ def get_name(self):
2547
+ return self._underlying.get_name()
2548
+
2549
+ def set_name(self, name):
2550
+ self._underlying.set_name(name)
2551
+
2552
+ @property
2553
+ def name(self):
2554
+ return self._underlying.name
2555
+
2556
+ @property
2557
+ def level(self):
2558
+ return self._underlying.level
2559
+
2560
+ @level.setter
2561
+ def level(self, level):
2562
+ self._underlying.level = level
2563
+
2564
+ @property
2565
+ def formatter(self):
2566
+ return self._underlying.formatter
2567
+
2568
+ @formatter.setter
2569
+ def formatter(self, formatter):
2570
+ self._underlying.formatter = formatter
2571
+
2572
+ def createLock(self):
2573
+ self._underlying.createLock()
2574
+
2575
+ def acquire(self):
2576
+ self._underlying.acquire()
2577
+
2578
+ def release(self):
2579
+ self._underlying.release()
2580
+
2581
+ def setLevel(self, level):
2582
+ self._underlying.setLevel(level)
2583
+
2584
+ def format(self, record):
2585
+ return self._underlying.format(record)
2586
+
2587
+ def emit(self, record):
2588
+ self._underlying.emit(record)
2589
+
2590
+ def handle(self, record):
2591
+ return self._underlying.handle(record)
2592
+
2593
+ def setFormatter(self, fmt):
2594
+ self._underlying.setFormatter(fmt)
2595
+
2596
+ def flush(self):
2597
+ self._underlying.flush()
2598
+
2599
+ def close(self):
2600
+ self._underlying.close()
2601
+
2602
+ def handleError(self, record):
2603
+ self._underlying.handleError(record)
2604
+
2605
+
2489
2606
  ########################################
2490
2607
  # ../events.py
2491
2608
 
@@ -2858,6 +2975,44 @@ def decode_wait_status(sts: int) -> ta.Tuple[Rc, str]:
2858
2975
  return Rc(-1), msg
2859
2976
 
2860
2977
 
2978
+ ##
2979
+
2980
+
2981
+ class WaitedPid(ta.NamedTuple):
2982
+ pid: Pid
2983
+ sts: Rc
2984
+
2985
+
2986
+ def waitpid(
2987
+ *,
2988
+ log: ta.Optional[logging.Logger] = None,
2989
+ ) -> ta.Optional[WaitedPid]:
2990
+ # Need pthread_sigmask here to avoid concurrent sigchld, but Python doesn't offer in Python < 3.4. There is
2991
+ # still a race condition here; we can get a sigchld while we're sitting in the waitpid call. However, AFAICT, if
2992
+ # waitpid is interrupted by SIGCHLD, as long as we call waitpid again (which happens every so often during the
2993
+ # normal course in the mainloop), we'll eventually reap the child that we tried to reap during the interrupted
2994
+ # call. At least on Linux, this appears to be true, or at least stopping 50 processes at once never left zombies
2995
+ # lying around.
2996
+ try:
2997
+ pid, sts = os.waitpid(-1, os.WNOHANG)
2998
+
2999
+ except OSError as exc:
3000
+ code = exc.args[0]
3001
+
3002
+ if code not in (errno.ECHILD, errno.EINTR):
3003
+ if log is not None:
3004
+ log.critical('waitpid error %r; a process may not be cleaned up properly', code)
3005
+
3006
+ if code == errno.EINTR:
3007
+ if log is not None:
3008
+ log.debug('EINTR during reap')
3009
+
3010
+ return None
3011
+
3012
+ else:
3013
+ return WaitedPid(pid, sts) # type: ignore
3014
+
3015
+
2861
3016
  ########################################
2862
3017
  # ../utils/users.py
2863
3018
 
@@ -4635,333 +4790,63 @@ class Injection:
4635
4790
  tag: ta.Any = None,
4636
4791
  array: ta.Optional[bool] = None, # noqa
4637
4792
 
4638
- to_fn: ta.Any = None,
4639
- to_ctor: ta.Any = None,
4640
- to_const: ta.Any = None,
4641
- to_key: ta.Any = None,
4642
-
4643
- singleton: bool = False,
4644
-
4645
- eager: bool = False,
4646
- ) -> InjectorBindingOrBindings:
4647
- return InjectorBinder.bind(
4648
- obj,
4649
-
4650
- key=key,
4651
- tag=tag,
4652
- array=array,
4653
-
4654
- to_fn=to_fn,
4655
- to_ctor=to_ctor,
4656
- to_const=to_const,
4657
- to_key=to_key,
4658
-
4659
- singleton=singleton,
4660
-
4661
- eager=eager,
4662
- )
4663
-
4664
- # helpers
4665
-
4666
- @classmethod
4667
- def bind_factory(
4668
- cls,
4669
- fn: ta.Callable[..., T],
4670
- cls_: U,
4671
- ann: ta.Any = None,
4672
- ) -> InjectorBindingOrBindings:
4673
- return cls.bind(make_injector_factory(fn, cls_, ann))
4674
-
4675
- @classmethod
4676
- def bind_array(
4677
- cls,
4678
- obj: ta.Any = None,
4679
- *,
4680
- tag: ta.Any = None,
4681
- ) -> InjectorBindingOrBindings:
4682
- return bind_injector_array(obj, tag=tag)
4683
-
4684
- @classmethod
4685
- def bind_array_type(
4686
- cls,
4687
- ele: ta.Union[InjectorKey, InjectorKeyCls],
4688
- cls_: U,
4689
- ann: ta.Any = None,
4690
- ) -> InjectorBindingOrBindings:
4691
- return cls.bind(make_injector_array_type(ele, cls_, ann))
4692
-
4693
-
4694
- inj = Injection
4695
-
4696
-
4697
- ########################################
4698
- # ../../../omlish/lite/logs.py
4699
- """
4700
- TODO:
4701
- - translate json keys
4702
- - debug
4703
- """
4704
-
4705
-
4706
- log = logging.getLogger(__name__)
4707
-
4708
-
4709
- ##
4710
-
4711
-
4712
- class TidLogFilter(logging.Filter):
4713
-
4714
- def filter(self, record):
4715
- record.tid = threading.get_native_id()
4716
- return True
4717
-
4718
-
4719
- ##
4720
-
4721
-
4722
- class JsonLogFormatter(logging.Formatter):
4723
-
4724
- KEYS: ta.Mapping[str, bool] = {
4725
- 'name': False,
4726
- 'msg': False,
4727
- 'args': False,
4728
- 'levelname': False,
4729
- 'levelno': False,
4730
- 'pathname': False,
4731
- 'filename': False,
4732
- 'module': False,
4733
- 'exc_info': True,
4734
- 'exc_text': True,
4735
- 'stack_info': True,
4736
- 'lineno': False,
4737
- 'funcName': False,
4738
- 'created': False,
4739
- 'msecs': False,
4740
- 'relativeCreated': False,
4741
- 'thread': False,
4742
- 'threadName': False,
4743
- 'processName': False,
4744
- 'process': False,
4745
- }
4746
-
4747
- def format(self, record: logging.LogRecord) -> str:
4748
- dct = {
4749
- k: v
4750
- for k, o in self.KEYS.items()
4751
- for v in [getattr(record, k)]
4752
- if not (o and v is None)
4753
- }
4754
- return json_dumps_compact(dct)
4755
-
4756
-
4757
- ##
4758
-
4759
-
4760
- STANDARD_LOG_FORMAT_PARTS = [
4761
- ('asctime', '%(asctime)-15s'),
4762
- ('process', 'pid=%(process)-6s'),
4763
- ('thread', 'tid=%(thread)x'),
4764
- ('levelname', '%(levelname)s'),
4765
- ('name', '%(name)s'),
4766
- ('separator', '::'),
4767
- ('message', '%(message)s'),
4768
- ]
4769
-
4770
-
4771
- class StandardLogFormatter(logging.Formatter):
4772
-
4773
- @staticmethod
4774
- def build_log_format(parts: ta.Iterable[ta.Tuple[str, str]]) -> str:
4775
- return ' '.join(v for k, v in parts)
4776
-
4777
- converter = datetime.datetime.fromtimestamp # type: ignore
4778
-
4779
- def formatTime(self, record, datefmt=None):
4780
- ct = self.converter(record.created) # type: ignore
4781
- if datefmt:
4782
- return ct.strftime(datefmt) # noqa
4783
- else:
4784
- t = ct.strftime('%Y-%m-%d %H:%M:%S')
4785
- return '%s.%03d' % (t, record.msecs) # noqa
4786
-
4787
-
4788
- ##
4789
-
4790
-
4791
- class ProxyLogFilterer(logging.Filterer):
4792
- def __init__(self, underlying: logging.Filterer) -> None: # noqa
4793
- self._underlying = underlying
4794
-
4795
- @property
4796
- def underlying(self) -> logging.Filterer:
4797
- return self._underlying
4798
-
4799
- @property
4800
- def filters(self):
4801
- return self._underlying.filters
4802
-
4803
- @filters.setter
4804
- def filters(self, filters):
4805
- self._underlying.filters = filters
4806
-
4807
- def addFilter(self, filter): # noqa
4808
- self._underlying.addFilter(filter)
4809
-
4810
- def removeFilter(self, filter): # noqa
4811
- self._underlying.removeFilter(filter)
4812
-
4813
- def filter(self, record):
4814
- return self._underlying.filter(record)
4815
-
4816
-
4817
- class ProxyLogHandler(ProxyLogFilterer, logging.Handler):
4818
- def __init__(self, underlying: logging.Handler) -> None: # noqa
4819
- ProxyLogFilterer.__init__(self, underlying)
4820
-
4821
- _underlying: logging.Handler
4822
-
4823
- @property
4824
- def underlying(self) -> logging.Handler:
4825
- return self._underlying
4826
-
4827
- def get_name(self):
4828
- return self._underlying.get_name()
4829
-
4830
- def set_name(self, name):
4831
- self._underlying.set_name(name)
4832
-
4833
- @property
4834
- def name(self):
4835
- return self._underlying.name
4836
-
4837
- @property
4838
- def level(self):
4839
- return self._underlying.level
4840
-
4841
- @level.setter
4842
- def level(self, level):
4843
- self._underlying.level = level
4844
-
4845
- @property
4846
- def formatter(self):
4847
- return self._underlying.formatter
4848
-
4849
- @formatter.setter
4850
- def formatter(self, formatter):
4851
- self._underlying.formatter = formatter
4852
-
4853
- def createLock(self):
4854
- self._underlying.createLock()
4855
-
4856
- def acquire(self):
4857
- self._underlying.acquire()
4858
-
4859
- def release(self):
4860
- self._underlying.release()
4861
-
4862
- def setLevel(self, level):
4863
- self._underlying.setLevel(level)
4864
-
4865
- def format(self, record):
4866
- return self._underlying.format(record)
4867
-
4868
- def emit(self, record):
4869
- self._underlying.emit(record)
4870
-
4871
- def handle(self, record):
4872
- return self._underlying.handle(record)
4873
-
4874
- def setFormatter(self, fmt):
4875
- self._underlying.setFormatter(fmt)
4876
-
4877
- def flush(self):
4878
- self._underlying.flush()
4879
-
4880
- def close(self):
4881
- self._underlying.close()
4882
-
4883
- def handleError(self, record):
4884
- self._underlying.handleError(record)
4885
-
4886
-
4887
- ##
4888
-
4889
-
4890
- class StandardLogHandler(ProxyLogHandler):
4891
- pass
4892
-
4893
-
4894
- ##
4895
-
4896
-
4897
- @contextlib.contextmanager
4898
- def _locking_logging_module_lock() -> ta.Iterator[None]:
4899
- if hasattr(logging, '_acquireLock'):
4900
- logging._acquireLock() # noqa
4901
- try:
4902
- yield
4903
- finally:
4904
- logging._releaseLock() # type: ignore # noqa
4905
-
4906
- elif hasattr(logging, '_lock'):
4907
- # https://github.com/python/cpython/commit/74723e11109a320e628898817ab449b3dad9ee96
4908
- with logging._lock: # noqa
4909
- yield
4910
-
4911
- else:
4912
- raise Exception("Can't find lock in logging module")
4913
-
4914
-
4915
- def configure_standard_logging(
4916
- level: ta.Union[int, str] = logging.INFO,
4917
- *,
4918
- json: bool = False,
4919
- target: ta.Optional[logging.Logger] = None,
4920
- force: bool = False,
4921
- handler_factory: ta.Optional[ta.Callable[[], logging.Handler]] = None,
4922
- ) -> ta.Optional[StandardLogHandler]:
4923
- with _locking_logging_module_lock():
4924
- if target is None:
4925
- target = logging.root
4926
-
4927
- #
4928
-
4929
- if not force:
4930
- if any(isinstance(h, StandardLogHandler) for h in list(target.handlers)):
4931
- return None
4932
-
4933
- #
4793
+ to_fn: ta.Any = None,
4794
+ to_ctor: ta.Any = None,
4795
+ to_const: ta.Any = None,
4796
+ to_key: ta.Any = None,
4934
4797
 
4935
- if handler_factory is not None:
4936
- handler = handler_factory()
4937
- else:
4938
- handler = logging.StreamHandler()
4798
+ singleton: bool = False,
4939
4799
 
4940
- #
4800
+ eager: bool = False,
4801
+ ) -> InjectorBindingOrBindings:
4802
+ return InjectorBinder.bind(
4803
+ obj,
4941
4804
 
4942
- formatter: logging.Formatter
4943
- if json:
4944
- formatter = JsonLogFormatter()
4945
- else:
4946
- formatter = StandardLogFormatter(StandardLogFormatter.build_log_format(STANDARD_LOG_FORMAT_PARTS))
4947
- handler.setFormatter(formatter)
4805
+ key=key,
4806
+ tag=tag,
4807
+ array=array,
4948
4808
 
4949
- #
4809
+ to_fn=to_fn,
4810
+ to_ctor=to_ctor,
4811
+ to_const=to_const,
4812
+ to_key=to_key,
4950
4813
 
4951
- handler.addFilter(TidLogFilter())
4814
+ singleton=singleton,
4952
4815
 
4953
- #
4816
+ eager=eager,
4817
+ )
4954
4818
 
4955
- target.addHandler(handler)
4819
+ # helpers
4956
4820
 
4957
- #
4821
+ @classmethod
4822
+ def bind_factory(
4823
+ cls,
4824
+ fn: ta.Callable[..., T],
4825
+ cls_: U,
4826
+ ann: ta.Any = None,
4827
+ ) -> InjectorBindingOrBindings:
4828
+ return cls.bind(make_injector_factory(fn, cls_, ann))
4958
4829
 
4959
- if level is not None:
4960
- target.setLevel(level)
4830
+ @classmethod
4831
+ def bind_array(
4832
+ cls,
4833
+ obj: ta.Any = None,
4834
+ *,
4835
+ tag: ta.Any = None,
4836
+ ) -> InjectorBindingOrBindings:
4837
+ return bind_injector_array(obj, tag=tag)
4961
4838
 
4962
- #
4839
+ @classmethod
4840
+ def bind_array_type(
4841
+ cls,
4842
+ ele: ta.Union[InjectorKey, InjectorKeyCls],
4843
+ cls_: U,
4844
+ ann: ta.Any = None,
4845
+ ) -> InjectorBindingOrBindings:
4846
+ return cls.bind(make_injector_array_type(ele, cls_, ann))
4963
4847
 
4964
- return StandardLogHandler(handler)
4848
+
4849
+ inj = Injection
4965
4850
 
4966
4851
 
4967
4852
  ########################################
@@ -4971,6 +4856,7 @@ TODO:
4971
4856
  - pickle stdlib objs? have to pin to 3.8 pickle protocol, will be cross-version
4972
4857
  - namedtuple
4973
4858
  - literals
4859
+ - newtypes?
4974
4860
  """
4975
4861
 
4976
4862
 
@@ -5425,6 +5311,60 @@ def check_runtime_version() -> None:
5425
5311
  raise OSError(f'Requires python {REQUIRED_PYTHON_VERSION}, got {sys.version_info} from {sys.executable}') # noqa
5426
5312
 
5427
5313
 
5314
+ ########################################
5315
+ # ../../../omlish/logs/json.py
5316
+ """
5317
+ TODO:
5318
+ - translate json keys
5319
+ """
5320
+
5321
+
5322
+ class JsonLogFormatter(logging.Formatter):
5323
+ KEYS: ta.Mapping[str, bool] = {
5324
+ 'name': False,
5325
+ 'msg': False,
5326
+ 'args': False,
5327
+ 'levelname': False,
5328
+ 'levelno': False,
5329
+ 'pathname': False,
5330
+ 'filename': False,
5331
+ 'module': False,
5332
+ 'exc_info': True,
5333
+ 'exc_text': True,
5334
+ 'stack_info': True,
5335
+ 'lineno': False,
5336
+ 'funcName': False,
5337
+ 'created': False,
5338
+ 'msecs': False,
5339
+ 'relativeCreated': False,
5340
+ 'thread': False,
5341
+ 'threadName': False,
5342
+ 'processName': False,
5343
+ 'process': False,
5344
+ }
5345
+
5346
+ def __init__(
5347
+ self,
5348
+ *args: ta.Any,
5349
+ json_dumps: ta.Optional[ta.Callable[[ta.Any], str]] = None,
5350
+ **kwargs: ta.Any,
5351
+ ) -> None:
5352
+ super().__init__(*args, **kwargs)
5353
+
5354
+ if json_dumps is None:
5355
+ json_dumps = json_dumps_compact
5356
+ self._json_dumps = json_dumps
5357
+
5358
+ def format(self, record: logging.LogRecord) -> str:
5359
+ dct = {
5360
+ k: v
5361
+ for k, o in self.KEYS.items()
5362
+ for v in [getattr(record, k)]
5363
+ if not (o and v is None)
5364
+ }
5365
+ return self._json_dumps(dct)
5366
+
5367
+
5428
5368
  ########################################
5429
5369
  # ../../../omlish/os/journald.py
5430
5370
 
@@ -5813,6 +5753,126 @@ class UnsupportedMethodHttpHandlerError(Exception):
5813
5753
  pass
5814
5754
 
5815
5755
 
5756
+ ########################################
5757
+ # ../../../omlish/logs/standard.py
5758
+ """
5759
+ TODO:
5760
+ - structured
5761
+ - prefixed
5762
+ - debug
5763
+ """
5764
+
5765
+
5766
+ ##
5767
+
5768
+
5769
+ STANDARD_LOG_FORMAT_PARTS = [
5770
+ ('asctime', '%(asctime)-15s'),
5771
+ ('process', 'pid=%(process)-6s'),
5772
+ ('thread', 'tid=%(thread)x'),
5773
+ ('levelname', '%(levelname)s'),
5774
+ ('name', '%(name)s'),
5775
+ ('separator', '::'),
5776
+ ('message', '%(message)s'),
5777
+ ]
5778
+
5779
+
5780
+ class StandardLogFormatter(logging.Formatter):
5781
+ @staticmethod
5782
+ def build_log_format(parts: ta.Iterable[ta.Tuple[str, str]]) -> str:
5783
+ return ' '.join(v for k, v in parts)
5784
+
5785
+ converter = datetime.datetime.fromtimestamp # type: ignore
5786
+
5787
+ def formatTime(self, record, datefmt=None):
5788
+ ct = self.converter(record.created) # type: ignore
5789
+ if datefmt:
5790
+ return ct.strftime(datefmt) # noqa
5791
+ else:
5792
+ t = ct.strftime('%Y-%m-%d %H:%M:%S')
5793
+ return '%s.%03d' % (t, record.msecs) # noqa
5794
+
5795
+
5796
+ ##
5797
+
5798
+
5799
+ class StandardLogHandler(ProxyLogHandler):
5800
+ pass
5801
+
5802
+
5803
+ ##
5804
+
5805
+
5806
+ @contextlib.contextmanager
5807
+ def _locking_logging_module_lock() -> ta.Iterator[None]:
5808
+ if hasattr(logging, '_acquireLock'):
5809
+ logging._acquireLock() # noqa
5810
+ try:
5811
+ yield
5812
+ finally:
5813
+ logging._releaseLock() # type: ignore # noqa
5814
+
5815
+ elif hasattr(logging, '_lock'):
5816
+ # https://github.com/python/cpython/commit/74723e11109a320e628898817ab449b3dad9ee96
5817
+ with logging._lock: # noqa
5818
+ yield
5819
+
5820
+ else:
5821
+ raise Exception("Can't find lock in logging module")
5822
+
5823
+
5824
+ def configure_standard_logging(
5825
+ level: ta.Union[int, str] = logging.INFO,
5826
+ *,
5827
+ json: bool = False,
5828
+ target: ta.Optional[logging.Logger] = None,
5829
+ force: bool = False,
5830
+ handler_factory: ta.Optional[ta.Callable[[], logging.Handler]] = None,
5831
+ ) -> ta.Optional[StandardLogHandler]:
5832
+ with _locking_logging_module_lock():
5833
+ if target is None:
5834
+ target = logging.root
5835
+
5836
+ #
5837
+
5838
+ if not force:
5839
+ if any(isinstance(h, StandardLogHandler) for h in list(target.handlers)):
5840
+ return None
5841
+
5842
+ #
5843
+
5844
+ if handler_factory is not None:
5845
+ handler = handler_factory()
5846
+ else:
5847
+ handler = logging.StreamHandler()
5848
+
5849
+ #
5850
+
5851
+ formatter: logging.Formatter
5852
+ if json:
5853
+ formatter = JsonLogFormatter()
5854
+ else:
5855
+ formatter = StandardLogFormatter(StandardLogFormatter.build_log_format(STANDARD_LOG_FORMAT_PARTS))
5856
+ handler.setFormatter(formatter)
5857
+
5858
+ #
5859
+
5860
+ handler.addFilter(TidLogFilter())
5861
+
5862
+ #
5863
+
5864
+ target.addHandler(handler)
5865
+
5866
+ #
5867
+
5868
+ if level is not None:
5869
+ target.setLevel(level)
5870
+
5871
+ #
5872
+
5873
+ return StandardLogHandler(handler)
5874
+
5875
+
5816
5876
  ########################################
5817
5877
  # ../configs.py
5818
5878
 
@@ -6049,6 +6109,8 @@ class ServerConfig:
6049
6109
 
6050
6110
  groups: ta.Optional[ta.Sequence[ProcessGroupConfig]] = None
6051
6111
 
6112
+ group_config_dirs: ta.Optional[ta.Sequence[str]] = None
6113
+
6052
6114
  @classmethod
6053
6115
  def new(
6054
6116
  cls,
@@ -6104,7 +6166,7 @@ def parse_logging_level(value: ta.Union[str, int]) -> int:
6104
6166
 
6105
6167
 
6106
6168
  ########################################
6107
- # ../../../omlish/http/coroserver.py
6169
+ # ../../../omlish/http/coro/server.py
6108
6170
  # PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
6109
6171
  # --------------------------------------------
6110
6172
  #
@@ -6844,7 +6906,7 @@ class ProcessGroup(
6844
6906
 
6845
6907
 
6846
6908
  ########################################
6847
- # ../../../omlish/io/fdio/corohttp.py
6909
+ # ../../../omlish/http/coro/fdio.py
6848
6910
 
6849
6911
 
6850
6912
  class CoroHttpServerConnectionFdioHandler(SocketFdioHandler):
@@ -9054,7 +9116,7 @@ class Supervisor:
9054
9116
  if depth >= 100:
9055
9117
  return
9056
9118
 
9057
- wp = waitpid()
9119
+ wp = waitpid(log=log)
9058
9120
 
9059
9121
  if wp is None or not wp.pid:
9060
9122
  return
@@ -9095,34 +9157,6 @@ class Supervisor:
9095
9157
  self._event_callbacks.notify(event(this_tick, self))
9096
9158
 
9097
9159
 
9098
- ##
9099
-
9100
-
9101
- class WaitedPid(ta.NamedTuple):
9102
- pid: Pid
9103
- sts: Rc
9104
-
9105
-
9106
- def waitpid() -> ta.Optional[WaitedPid]:
9107
- # Need pthread_sigmask here to avoid concurrent sigchld, but Python doesn't offer in Python < 3.4. There is
9108
- # still a race condition here; we can get a sigchld while we're sitting in the waitpid call. However, AFAICT, if
9109
- # waitpid is interrupted by SIGCHLD, as long as we call waitpid again (which happens every so often during the
9110
- # normal course in the mainloop), we'll eventually reap the child that we tried to reap during the interrupted
9111
- # call. At least on Linux, this appears to be true, or at least stopping 50 processes at once never left zombies
9112
- # lying around.
9113
- try:
9114
- pid, sts = os.waitpid(-1, os.WNOHANG)
9115
- except OSError as exc:
9116
- code = exc.args[0]
9117
- if code not in (errno.ECHILD, errno.EINTR):
9118
- log.critical('waitpid error %r; a process may not be cleaned up properly', code)
9119
- if code == errno.EINTR:
9120
- log.debug('EINTR during reap')
9121
- return None
9122
- else:
9123
- return WaitedPid(pid, sts) # type: ignore
9124
-
9125
-
9126
9160
  ########################################
9127
9161
  # ../inject.py
9128
9162