57ab7b787b20854458027839b3e2deb8612d5c87
[com/pylog.git] / mdclogpy / mdclogpy / Logger.py
1 # Copyright (c) 2019 AT&T Intellectual Property.
2 # Copyright (c) 2018-2019 Nokia.
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16 """Structured logging library with Mapped Diagnostic Context
17
18 Outputs the log entries to standard out in structured format, json currently.
19 Severity based filtering.
20 Supports Mapped Diagnostic Context (MDC).
21
22 Set MDC pairs are automatically added to log entries by the library.
23 """
24 from typing import TypeVar
25 from enum import IntEnum
26 import sys
27 import json
28 import time
29
30
31 class Level(IntEnum):
32     """Severity levels of the log messages."""
33     DEBUG = 10
34     INFO = 20
35     WARNING = 30
36     ERROR = 40
37
38
39 LEVEL_STRINGS = {Level.DEBUG: "DEBUG",
40                  Level.INFO: "INFO",
41                  Level.WARNING: "WARNING",
42                  Level.ERROR: "ERROR"}
43
44
45 Value = TypeVar('Value', str, int)
46
47
48 class Logger():
49     """Initialize the mdclogging module.
50     Calling of the function is optional. If not called, the process name
51     (sys.argv[0]) is used by default.
52
53     Keyword arguments:
54     name -- name of the component. The name will appear as part of the log
55             entries.
56     """
57     def __init__(self, name: str = sys.argv[0], level: Level = Level.DEBUG):
58         """Initialize a Logger instance.
59
60             Keyword arguments:
61             name -- name of the component. The name will appear as part of the
62                     log entries.
63         """
64         self.procname = name
65         self.current_level = level
66         self.mdc = {}
67
68     def _output_log(self, log: str):
69         """Output the log, currently to stdout."""
70         print(log)
71
72     def log(self, level: Level, message: str):
73         """Log a message.
74
75         Logs the message with the given severity if it is equal or higher than
76         the current logging level.
77
78         Keyword arguments:
79         level -- severity of the log message
80         message -- log message
81         """
82         if level >= self.current_level:
83             log_entry = {}
84             log_entry["ts"] = int(round(time.time() * 1000))
85             log_entry["crit"] = LEVEL_STRINGS[level]
86             log_entry["id"] = self.procname
87             log_entry["mdc"] = self.mdc
88             log_entry["msg"] = message
89             self._output_log(json.dumps(log_entry))
90
91     def error(self, message: str):
92         """Log an error message. Equals to log(ERROR, msg)."""
93         self.log(Level.ERROR, message)
94
95     def warning(self, message: str):
96         """Log a warning message. Equals to log(WARNING, msg)."""
97         self.log(Level.WARNING, message)
98
99     def info(self, message: str):
100         """Log an info message. Equals to log(INFO, msg)."""
101         self.log(Level.INFO, message)
102
103     def debug(self, message: str):
104         """Log a debug message. Equals to log(DEBUG, msg)."""
105         self.log(Level.DEBUG, message)
106
107     def set_level(self, level: Level):
108         """Set current logging level.
109
110         Keyword arguments:
111         level -- logging level. Log messages with lower severity will be
112                  filtered.
113         """
114         try:
115             self.current_level = Level(level)
116         except ValueError:
117             pass
118
119     def get_level(self) -> Level:
120         """Return the current logging level."""
121         return self.current_level
122
123     def add_mdc(self, key: str, value: Value):
124         """Add a logger specific MDC.
125
126         If an MDC with the given key exists, it is replaced with the new one.
127         An MDC can be removed with remove_mdc() or clean_mdc().
128
129         Keyword arguments:
130         key -- MDC key
131         value -- MDC value
132         """
133         self.mdc[key] = value
134
135     def get_mdc(self, key: str) -> Value:
136         """Return logger's MDC value with the given key or None."""
137         try:
138             return self.mdc[key]
139         except KeyError:
140             return None
141
142     def remove_mdc(self, key: str):
143         """Remove logger's MDC with the given key."""
144         try:
145             del self.mdc[key]
146         except KeyError:
147             pass
148
149     def clean_mdc(self):
150         """Remove all MDCs of the logger instance."""
151         self.mdc = {}