1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
|
# type: ignore
# pylint: disable=protected-access
import os
import sys
import traceback
from typing import Any, Literal
from supervisor.dispatchers import POutputDispatcher
from supervisor.loggers import LevelsByName, StreamHandler, SyslogHandler
from supervisor.supervisord import Supervisor
FORWARD_LOG_LEVEL = LevelsByName.CRIT # to make sure it's always printed
def empty_function(*args, **kwargs):
pass
FORWARD_MSG_FORMAT: str = "%(name)s[%(pid)d]%(stream)s: %(data)s"
def p_output_dispatcher_log(self: POutputDispatcher, data: bytearray):
if data:
# parse the input
if not isinstance(data, bytes):
text = data
else:
try:
text = data.decode("utf-8")
except UnicodeDecodeError:
text = "Undecodable: %r" % data
# print line by line prepending correct prefix to match the style
config = self.process.config
config.options.logger.handlers = forward_handlers
for line in text.splitlines():
stream = ""
if self.channel == "stderr":
stream = " (stderr)"
config.options.logger.log(
FORWARD_LOG_LEVEL, FORWARD_MSG_FORMAT, name=config.name, stream=stream, data=line, pid=self.process.pid
)
config.options.logger.handlers = supervisord_handlers
def _create_handler(fmt, level, target: Literal["stdout", "stderr", "syslog"]) -> StreamHandler:
if target == "syslog":
handler = SyslogHandler()
else:
handler = StreamHandler(sys.stdout if target == "stdout" else sys.stderr)
handler.setFormat(fmt)
handler.setLevel(level)
return handler
supervisord_handlers = []
forward_handlers = []
def inject(supervisord: Supervisor, **config: Any) -> Any: # pylint: disable=useless-return
try:
# reconfigure log handlers
supervisord.options.logger.info("reconfiguring log handlers")
supervisord_handlers.append(
_create_handler(
f"%(asctime)s supervisor[{os.getpid()}]: [%(levelname)s] %(message)s\n",
supervisord.options.loglevel,
config["target"],
)
)
forward_handlers.append(
_create_handler("%(asctime)s %(message)s\n", supervisord.options.loglevel, config["target"])
)
supervisord.options.logger.handlers = supervisord_handlers
# replace output handler for subprocesses
POutputDispatcher._log = p_output_dispatcher_log # noqa: SLF001
# we forward stdio in all cases, even when logging to syslog. This should prevent the unforturtunate
# case of swallowing an error message leaving the users confused. To make the forwarded lines obvious
# we just prepend a explanatory string at the beginning of all messages
if config["target"] == "syslog":
global FORWARD_MSG_FORMAT
FORWARD_MSG_FORMAT = "captured stdio output from " + FORWARD_MSG_FORMAT
# this method is called by supervisord when loading the plugin,
# it should return XML-RPC object, which we don't care about
# That's why why are returning just None
return None
# if we fail to load the module, print some explanation
# should not happen when run by endusers
except BaseException:
traceback.print_exc()
raise
|