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)