summaryrefslogtreecommitdiffstats
path: root/python/knot_resolver/manager/logging.py
blob: c9b446533f0c679004d66a807f43fac044b4bc3e (plain)
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
97
98
99
100
101
102
103
104
105
106
import logging
import logging.handlers
import os
import sys
from typing import Optional

from knot_resolver.datamodel.config_schema import KresConfig
from knot_resolver.datamodel.logging_schema import LogTargetEnum
from knot_resolver.manager.config_store import ConfigStore, only_on_real_changes_update

from .constants import LOGGING_LEVEL_STARTUP

logger = logging.getLogger(__name__)


def get_log_format(config: KresConfig) -> str:
    """
    Based on an environment variable $KRES_SUPRESS_LOG_PREFIX, returns the appropriate format string for logger.
    """

    if os.environ.get("KRES_SUPRESS_LOG_PREFIX") == "true":
        # In this case, we are running under supervisord and it's adding prefixes to our output
        return "[%(levelname)s] %(name)s: %(message)s"
    else:
        # In this case, we are running standalone during inicialization and we need to add a prefix to each line
        # by ourselves to make it consistent
        assert config.logging.target != "syslog"
        stream = ""
        if config.logging.target == "stderr":
            stream = " (stderr)"

        pid = os.getpid()
        return f"%(asctime)s manager[{pid}]{stream}: [%(levelname)s] %(name)s: %(message)s"


async def _set_log_level(config: KresConfig) -> None:
    levels_map = {
        "crit": "CRITICAL",
        "err": "ERROR",
        "warning": "WARNING",
        "notice": "WARNING",
        "info": "INFO",
        "debug": "DEBUG",
    }

    # when logging group is set to make us log with DEBUG
    if config.logging.groups and "manager" in config.logging.groups:
        target = "DEBUG"
    # otherwise, follow the standard log level
    else:
        target = levels_map[config.logging.level]

    # expect exactly one existing log handler on the root
    logger.warning(f"Changing logging level to '{target}'")
    logging.getLogger().setLevel(target)


async def _set_logging_handler(config: KresConfig) -> None:
    target: Optional[LogTargetEnum] = config.logging.target

    if target is None:
        target = "stdout"

    handler: logging.Handler
    if target == "syslog":
        handler = logging.handlers.SysLogHandler(address="/dev/log")
        handler.setFormatter(logging.Formatter("%(name)s: %(message)s"))
    elif target == "stdout":
        handler = logging.StreamHandler(sys.stdout)
        handler.setFormatter(logging.Formatter(get_log_format(config)))
    elif target == "stderr":
        handler = logging.StreamHandler(sys.stderr)
        handler.setFormatter(logging.Formatter(get_log_format(config)))
    else:
        raise RuntimeError(f"Unexpected value '{target}' for log target in the config")

    root = logging.getLogger()

    # if we had a MemoryHandler before, we should give it the new handler where we can flush it
    if isinstance(root.handlers[0], logging.handlers.MemoryHandler):
        root.handlers[0].setTarget(handler)

    # stop the old handler
    root.handlers[0].flush()
    root.handlers[0].close()
    root.removeHandler(root.handlers[0])

    # configure the new handler
    root.addHandler(handler)


@only_on_real_changes_update(lambda config: config.logging)
async def _configure_logger(config: KresConfig) -> None:
    await _set_logging_handler(config)
    await _set_log_level(config)


async def logger_init(config_store: ConfigStore) -> None:
    await config_store.register_on_change_callback(_configure_logger)


def logger_startup() -> None:
    logging.getLogger().setLevel(LOGGING_LEVEL_STARTUP)
    err_handler = logging.StreamHandler(sys.stderr)
    err_handler.setFormatter(logging.Formatter(logging.BASIC_FORMAT))
    logging.getLogger().addHandler(logging.handlers.MemoryHandler(10_000, logging.ERROR, err_handler))