43c48e4a1d7cfdc7442fda3f6309a9169d2a6a5b
[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 # This source code is part of the near-RT RIC (RAN Intelligent Controller)
17 # platform project (RICP).
18 #
19
20 """Structured logging library with Mapped Diagnostic Context
21
22 Outputs the log entries to standard out in structured format, json currently.
23 Severity based filtering.
24 Supports Mapped Diagnostic Context (MDC).
25
26 Set MDC pairs are automatically added to log entries by the library.
27 """
28 from typing import TypeVar
29 from enum import IntEnum
30 import sys
31 import json
32 import time
33
34
35 class Level(IntEnum):
36     """Severity levels of the log messages."""
37     DEBUG = 10
38     INFO = 20
39     WARNING = 30
40     ERROR = 40
41
42
43 LEVEL_STRINGS = {Level.DEBUG: "DEBUG",
44                  Level.INFO: "INFO",
45                  Level.WARNING: "WARNING",
46                  Level.ERROR: "ERROR"}
47
48
49 Value = TypeVar('Value', str, int)
50
51
52 class Logger():
53     """Initialize the mdclogging module.
54     Calling of the function is optional. If not called, the process name
55     (sys.argv[0]) is used by default.
56
57     Keyword arguments:
58     name -- name of the component. The name will appear as part of the log
59             entries.
60     """
61     def __init__(self, name: str = sys.argv[0], level: Level = Level.DEBUG):
62         """Initialize a Logger instance.
63
64             Keyword arguments:
65             name -- name of the component. The name will appear as part of the
66                     log entries.
67         """
68         self.procname = name
69         self.current_level = level
70         self.mdc = {}
71
72     def _output_log(self, log: str):
73         """Output the log, currently to stdout."""
74         print(log)
75
76     def log(self, level: Level, message: str):
77         """Log a message.
78
79         Logs the message with the given severity if it is equal or higher than
80         the current logging level.
81
82         Keyword arguments:
83         level -- severity of the log message
84         message -- log message
85         """
86         if level >= self.current_level:
87             log_entry = {}
88             log_entry["ts"] = int(round(time.time() * 1000))
89             log_entry["crit"] = LEVEL_STRINGS[level]
90             log_entry["id"] = self.procname
91             log_entry["mdc"] = self.mdc
92             log_entry["msg"] = message
93             self._output_log(json.dumps(log_entry))
94
95     def error(self, message: str):
96         """Log an error message. Equals to log(ERROR, msg)."""
97         self.log(Level.ERROR, message)
98
99     def warning(self, message: str):
100         """Log a warning message. Equals to log(WARNING, msg)."""
101         self.log(Level.WARNING, message)
102
103     def info(self, message: str):
104         """Log an info message. Equals to log(INFO, msg)."""
105         self.log(Level.INFO, message)
106
107     def debug(self, message: str):
108         """Log a debug message. Equals to log(DEBUG, msg)."""
109         self.log(Level.DEBUG, message)
110
111     def set_level(self, level: Level):
112         """Set current logging level.
113
114         Keyword arguments:
115         level -- logging level. Log messages with lower severity will be
116                  filtered.
117         """
118         try:
119             self.current_level = Level(level)
120         except ValueError:
121             pass
122
123     def get_level(self) -> Level:
124         """Return the current logging level."""
125         return self.current_level
126
127     def add_mdc(self, key: str, value: Value):
128         """Add a logger specific MDC.
129
130         If an MDC with the given key exists, it is replaced with the new one.
131         An MDC can be removed with remove_mdc() or clean_mdc().
132
133         Keyword arguments:
134         key -- MDC key
135         value -- MDC value
136         """
137         self.mdc[key] = value
138
139     def get_mdc(self, key: str) -> Value:
140         """Return logger's MDC value with the given key or None."""
141         try:
142             return self.mdc[key]
143         except KeyError:
144             return None
145
146     def remove_mdc(self, key: str):
147         """Remove logger's MDC with the given key."""
148         try:
149             del self.mdc[key]
150         except KeyError:
151             pass
152
153     def clean_mdc(self):
154         """Remove all MDCs of the logger instance."""
155         self.mdc = {}