Plain is headed towards 1.0! Subscribe for development updates →

Logging

Default logging settings and key-value logger.

In Python, logging can be a surprisingly complex topic.

So Plain aims for easy-to-use defaults that "just work".

app_logger

The default app_logger doesn't do much!

But it is paired with the default settings to actually show the logs like you would expect, without any additional configuration.

from plain.logs import app_logger


def example_function():
    app_logger.info("Hey!")

app_logger.kv

 1import logging
 2
 3app_logger = logging.getLogger("app")
 4
 5
 6class KVLogger:
 7    def __init__(self, logger):
 8        self.logger = logger
 9        self.context = {}  # A dict that will be output in every log message
10
11    def log(self, level, message, **kwargs):
12        msg_kwargs = {
13            **kwargs,
14            **self.context,  # Put these last so they're at the end of the line
15        }
16        self.logger.log(level, f"{message} {self._format_kwargs(msg_kwargs)}")
17
18    def _format_kwargs(self, kwargs):
19        outputs = []
20
21        for k, v in kwargs.items():
22            self._validate_key(k)
23            formatted_value = self._format_value(v)
24            outputs.append(f"{k}={formatted_value}")
25
26        return " ".join(outputs)
27
28    def _validate_key(self, key):
29        if " " in key:
30            raise ValueError("Keys cannot have spaces")
31
32        if "=" in key:
33            raise ValueError("Keys cannot have equals signs")
34
35        if '"' in key or "'" in key:
36            raise ValueError("Keys cannot have quotes")
37
38    def _format_value(self, value):
39        if isinstance(value, str):
40            s = value
41        else:
42            s = str(value)
43
44        if '"' in s:
45            # Escape quotes and surround it
46            s = s.replace('"', '\\"')
47            s = f'"{s}"'
48        elif s == "":
49            # Quote empty strings instead of printing nothing
50            s = '""'
51        elif any(char in s for char in [" ", "/", "'", ":", "=", "."]):
52            # Surround these with quotes for parsers
53            s = f'"{s}"'
54
55        return s
56
57    def info(self, message, **kwargs):
58        self.log(logging.INFO, message, **kwargs)
59
60    def debug(self, message, **kwargs):
61        self.log(logging.DEBUG, message, **kwargs)
62
63    def warning(self, message, **kwargs):
64        self.log(logging.WARNING, message, **kwargs)
65
66    def error(self, message, **kwargs):
67        self.log(logging.ERROR, message, **kwargs)
68
69    def critical(self, message, **kwargs):
70        self.log(logging.CRITICAL, message, **kwargs)
71
72
73# Make this accessible from the app_logger
74app_logger.kv = KVLogger(app_logger)