summary refs log tree commit diff
path: root/Python logging/index.md
blob: 867dc20c2d1e72281709bcb4384cbe625f047217 (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
107
108
109
110
111
112
113
114
---
created: 2024-03-17
description: Python logging with ✨pretty colors🌸
---

## Logging > print
Logging is useful, because all python code can use the same standard, allowing you to centrally control:

- Which messages you wish to see
    - Every debug message, for local development
    - Disable debug messages, by changing 1 line e.g. for end user deployment
- How to format them
    - Do you wish to see which module and line of code this message originated from
- Where they should end up
    - Printed to [STDOUT](https://en.wikipedia.org/wiki/Standard_streams) (terminal)
    - A log file
    - A log server
    - Maybe a notification should be triggered on CRITICAL errors

> A python [*"module"*](https://docs.python.org/3/tutorial/modules.html) is simply just the name for a .py file. 
> e.g. the file `database_handler.py` is a module called `database_handler`

```py
import logging


class ColorFormatter(logging.Formatter):
    grey = "\x1b[90;20m"
    cyan = "\x1b[96;20m"
    yellow = "\x1b[33;20m"
    red = "\x1b[31;20m"
    bold_red = "\x1b[31;1m"
    reset = "\x1b[0m"
    format = "%(asctime)s,%(msecs)03d %(levelname)-8s [%(filename)s:%(lineno)d] %(message)s"

    FORMATS = {
        logging.DEBUG: grey + format + reset,
        logging.INFO: cyan + format + reset,
        logging.WARNING: yellow + format + reset,
        logging.ERROR: red + format + reset,
        logging.CRITICAL: bold_red + format + reset
    }

    def format(self, record):
        log_fmt = self.FORMATS.get(record.levelno)
        formatter = logging.Formatter(log_fmt)
        return formatter.format(record)


logger = logging.getLogger(__name__)  # Instantiate a logger to be used in this module

# Display every message Change this to INFO to see INFO and above (filter out DEBUG)
# See: https://docs.python.org/3/howto/logging.html#logging-levels
logger.root.setLevel(logging.DEBUG)

stream_handler = logging.StreamHandler()  # This catches and handles log messages on the root handler
stream_handler.setFormatter(ColorFormatter())
logger.root.addHandler(stream_handler)

logger.debug("debug message")
logger.info("info message")
logger.warning("warning message")
logger.error("error message")
logger.critical("critical message")
```

> Code snippet is modified, but based on:
> [stackoverflow.com - How can I color Python logging output?](https://stackoverflow.com/questions/384076/how-can-i-color-python-logging-output#answer-56944256).
> Used and credited as per [stackoverflow.com - CC BY-SA 4.0 content license](https://stackoverflow.com/help/licensing)

<pre>
$ python /tmp/1.py
<span style="color:grey">2024-03-17 14:09:06,411,411 DEBUG    [1.py:37] debug message</span>
<span style="color:var(--main-color)">2024-03-17 14:09:06,411,411 INFO     [1.py:38] info message</span>
<span style="color:yellow">2024-03-17 14:09:06,411,411 WARNING  [1.py:39] warning message</span>
<span style="color:red">2024-03-17 14:09:06,411,411 ERROR    [1.py:40] error message</span>
<span style="font-weight: bold; color:red">2024-03-17 14:09:06,411,411 CRITICAL [1.py:41] critical message</span>
</pre>


In subsequent modules, do the following:
```py
import logging

logger = logging.getLogger(__name__) # Set the logger name, to the name of the module

logger.debug("debug message")
logger.info("info message")
logger.warning("warning message")
logger.error("error message")
logger.critical("critical message")
```

> Wish to change the colors?
> Color codes can be found here: [wikipedia.org - ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit)

Okay, but why do we need to do it this way?

Because logging is hieratical, these 2 loggers will be siblings under the root logger (see following tree structure).
Therefore we set a handler on the root logger which ensures all logging will hit our formatting code.

<pre>
$ root
├── <span class="devicons devicons-python"></span> main
└── <span class="devicons devicons-python"></span> module_a
</pre>

You can set a breakpoint and inspect `logger.parent` to see this structure:
```pycon
>>> logger.parent
<RootLogger root (DEBUG)>
```

Read more: [docs.python.org - Logging HOWTO](https://docs.python.org/3/howto/logging.html)