ominfra 0.0.0.dev156__py3-none-any.whl → 0.0.0.dev158__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -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