File: 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 (117 lines) | stat: -rw-r--r-- 4,006 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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
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__)

NOTICE_LEVEL = (logging.WARNING + logging.INFO) // 2
NOTICE_NAME = "NOTICE"

_config_to_level = {
    "crit": logging.CRITICAL,
    "err": logging.ERROR,
    "warning": logging.WARNING,
    "notice": NOTICE_LEVEL,
    "info": logging.INFO,
    "debug": logging.DEBUG,
}

_level_to_name = {
    logging.CRITICAL: "CRITICAL",
    logging.ERROR: "ERROR",
    logging.WARNING: "WARNING",
    NOTICE_LEVEL: NOTICE_NAME,
    logging.INFO: "INFO",
    logging.DEBUG: "DEBUG",
}


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"
    # 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:
    # when logging group is set to make us log with DEBUG
    if config.logging.groups and "manager" in config.logging.groups:
        target = logging.DEBUG
    # otherwise, follow the standard log level
    else:
        target = _config_to_level[config.logging.level]

    # expect exactly one existing log handler on the root
    logger.warning(f"Changing logging level to '{_level_to_name[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, force: bool = False) -> 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))