File: patch_logger.py

package info (click to toggle)
knot-resolver 6.0.17-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 16,376 kB
  • sloc: javascript: 42,732; ansic: 40,311; python: 12,580; cpp: 2,121; sh: 1,988; xml: 193; makefile: 181
file content (96 lines) | stat: -rw-r--r-- 3,411 bytes parent folder | download
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