1 # Copyright (c) 2019 AT&T Intellectual Property.
2 # Copyright (c) 2018-2019 Nokia.
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
8 # http://www.apache.org/licenses/LICENSE-2.0
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.
16 # This source code is part of the near-RT RIC (RAN Intelligent Controller)
17 # platform project (RICP).
20 """Structured logging library with Mapped Diagnostic Context
22 Outputs the log entries to standard out in structured format, json currently.
23 Severity based filtering.
24 Supports Mapped Diagnostic Context (MDC).
26 Set MDC pairs are automatically added to log entries by the library.
28 from typing import TypeVar
29 from enum import IntEnum
34 import inotify.adapters
39 """Severity levels of the log messages."""
46 LEVEL_STRINGS = {Level.DEBUG: "DEBUG",
48 Level.WARNING: "WARNING",
52 Value = TypeVar('Value', str, int)
56 """Initialize the mdclogging module.
57 Calling of the function is optional. If not called, the process name
58 (sys.argv[0]) is used by default.
61 name -- name of the component. The name will appear as part of the log
64 def __init__(self, name: str = sys.argv[0], level: Level = Level.ERROR):
65 """Initialize a Logger instance.
68 name -- name of the component. The name will appear as part of the
72 self.current_level = level
75 # Pass configmap_monitor = True to monitor configmap to change logs dynamically using configmap
77 def mdclog_format_init(self, configmap_monitor=False):
79 self.mdc = {"PID": "", "SYSTEM_NAME": "", "HOST_NAME": "", "SERVICE_NAME": "", "CONTAINER_NAME": "", "POD_NAME": ""}
80 self.get_env_params_values()
82 self.filename = os.environ['CONFIG_MAP_NAME']
83 self.dirname = str(self.filename[:self.filename.rindex('/')])
87 self.register_log_change_notify()
89 except Exception as e:
90 print("Unable to Add Watch on ConfigMap File", e)
92 def _output_log(self, log: str):
94 """Output the log, currently to stdout."""
97 def log(self, level: Level, message: str):
100 Logs the message with the given severity if it is equal or higher than
101 the current logging level.
104 level -- severity of the log message
105 message -- log message
107 if level >= self.current_level:
109 log_entry["ts"] = int(round(time.time() * 1000))
110 log_entry["crit"] = LEVEL_STRINGS[level]
111 log_entry["id"] = self.procname
112 log_entry["mdc"] = self.mdc
113 log_entry["msg"] = message
114 self._output_log(json.dumps(log_entry))
116 def error(self, message: str):
117 """Log an error message. Equals to log(ERROR, msg)."""
118 self.log(Level.ERROR, message)
120 def warning(self, message: str):
121 """Log a warning message. Equals to log(WARNING, msg)."""
122 self.log(Level.WARNING, message)
124 def info(self, message: str):
125 """Log an info message. Equals to log(INFO, msg)."""
126 self.log(Level.INFO, message)
128 def debug(self, message: str):
129 """Log a debug message. Equals to log(DEBUG, msg)."""
130 self.log(Level.DEBUG, message)
132 def set_level(self, level: Level):
133 """Set current logging level.
136 level -- logging level. Log messages with lower severity will be
140 self.current_level = Level(level)
144 def get_level(self) -> Level:
145 """Return the current logging level."""
146 return self.current_level
148 def add_mdc(self, key: str, value: Value):
149 """Add a logger specific MDC.
151 If an MDC with the given key exists, it is replaced with the new one.
152 An MDC can be removed with remove_mdc() or clean_mdc().
158 self.mdc[key] = value
160 def get_env_params_values(self):
163 self.mdc['SYSTEM_NAME'] = os.environ['SYSTEM_NAME']
165 self.mdc['SYSTEM_NAME'] = ""
168 self.mdc['HOST_NAME'] = os.environ['HOST_NAME']
170 self.mdc['HOST_NAME'] = ""
173 self.mdc['SERVICE_NAME'] = os.environ['SERVICE_NAME']
175 self.mdc['SERVICE_NAME'] = ""
178 self.mdc['CONTAINER_NAME'] = os.environ['CONTAINER_NAME']
180 self.mdc['CONTAINER_NAME'] = ""
183 self.mdc['POD_NAME'] = os.environ['POD_NAME']
185 self.mdc['POD_NAME'] = ""
187 self.mdc['PID'] = os.getpid()
191 def update_mdc_log_level_severity(self, level):
193 severity_level = Level.ERROR
196 print("Invalid Log Level defined in ConfigMap")
197 elif((level.upper() == "ERROR") or (level.upper() == "ERR")):
198 severity_level = Level.ERROR
199 elif((level.upper() == "WARNING") or (level.upper() == "WARN")):
200 severity_level = Level.WARNING
201 elif(level.upper() == "INFO"):
202 severity_level = Level.INFO
203 elif(level.upper() == "DEBUG"):
204 severity_level = Level.DEBUG
206 self.set_level(severity_level)
208 def parse_file(self):
209 src = open(self.filename, 'r')
212 if 'log-level:' in line:
213 level_tmp = str(line.split(':')[-1]).strip()
217 self.update_mdc_log_level_severity(level)
219 def monitor_loglevel_change_handler(self):
220 i = inotify.adapters.Inotify()
221 i.add_watch(self.dirname)
222 for event in i.event_gen():
223 if (event is not None) and ('IN_MODIFY' in str(event[1]) or 'IN_DELETE' in str(event[1])):
226 def register_log_change_notify(self):
227 t1 = threading.Thread(target=self.monitor_loglevel_change_handler)
231 except (KeyboardInterrupt, SystemExit):
232 # TODO: add cleanup handler
233 # cleanup_stop_thread()
236 def get_mdc(self, key: str) -> Value:
237 """Return logger's MDC value with the given key or None."""
243 def remove_mdc(self, key: str):
244 """Remove logger's MDC with the given key."""
251 """Remove all MDCs of the logger instance."""