d83750e757264cdd8c4f436e64e7d005046ca0d7
[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 import os
34 import inotify.adapters
35 import threading
36
37
38 class Level(IntEnum):
39     """Severity levels of the log messages."""
40     DEBUG = 10
41     INFO = 20
42     WARNING = 30
43     ERROR = 40
44
45
46 LEVEL_STRINGS = {Level.DEBUG: "DEBUG",
47                  Level.INFO: "INFO",
48                  Level.WARNING: "WARNING",
49                  Level.ERROR: "ERROR"}
50
51
52 Value = TypeVar('Value', str, int)
53
54
55 class Logger():
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.
59
60     Keyword arguments:
61     name -- name of the component. The name will appear as part of the log
62             entries.
63     """
64     def __init__(self, name: str = sys.argv[0], level: Level = Level.ERROR):
65         """Initialize a Logger instance.
66
67             Keyword arguments:
68             name -- name of the component. The name will appear as part of the
69                     log entries.
70         """
71         self.procname = name
72         self.current_level = level
73         self.mdc = {}
74         
75
76     # Pass configmap_monitor = True to monitor configmap to change logs dynamically using configmap
77     def mdclog_format_init(self,configmap_monitor=False):
78
79         self.mdc = {"SYSTEM_NAME":"","HOST_NAME":"","SERVICE_NAME":"","CONTAINER_NAME":"","POD_NAME":""}
80         self.get_env_params_values()
81         try:
82             self.filename = os.environ['CONFIG_MAP_NAME']
83             self.dirname = str(self.filename[:self.filename.rindex('/')])
84             if configmap_monitor == True:
85                 self.register_log_change_notify()
86             
87         except Exception as e:
88             print("Unable to Add Watch on ConfigMap File",e)
89
90
91     def _output_log(self, log: str):
92         """Output the log, currently to stdout."""
93         print(log)
94
95     def log(self, level: Level, message: str):
96         """Log a message.
97
98         Logs the message with the given severity if it is equal or higher than
99         the current logging level.
100
101         Keyword arguments:
102         level -- severity of the log message
103         message -- log message
104         """
105         if level >= self.current_level:
106             log_entry = {}
107             log_entry["ts"] = int(round(time.time() * 1000))
108             log_entry["crit"] = LEVEL_STRINGS[level]
109             log_entry["id"] = self.procname
110             log_entry["mdc"] = self.mdc
111             log_entry["msg"] = message
112             self._output_log(json.dumps(log_entry))
113
114     def error(self, message: str):
115         """Log an error message. Equals to log(ERROR, msg)."""
116         self.log(Level.ERROR, message)
117
118     def warning(self, message: str):
119         """Log a warning message. Equals to log(WARNING, msg)."""
120         self.log(Level.WARNING, message)
121
122     def info(self, message: str):
123         """Log an info message. Equals to log(INFO, msg)."""
124         self.log(Level.INFO, message)
125
126     def debug(self, message: str):
127         """Log a debug message. Equals to log(DEBUG, msg)."""
128         self.log(Level.DEBUG, message)
129
130     def set_level(self, level: Level):
131         """Set current logging level.
132
133         Keyword arguments:
134         level -- logging level. Log messages with lower severity will be
135                  filtered.
136         """
137         try:
138             self.current_level = Level(level)
139         except ValueError:
140             pass
141
142     def get_level(self) -> Level:
143         """Return the current logging level."""
144         return self.current_level
145
146     def add_mdc(self, key: str, value: Value):
147         """Add a logger specific MDC.
148
149         If an MDC with the given key exists, it is replaced with the new one.
150         An MDC can be removed with remove_mdc() or clean_mdc().
151
152         Keyword arguments:
153         key -- MDC key
154         value -- MDC value
155         """
156         self.mdc[key] = value
157         
158
159     def get_env_params_values(self):
160
161         try:
162             self.mdc['SYSTEM_NAME'] = os.environ['SYSTEM_NAME']
163         except:
164             self.mdc['SYSTEM_NAME'] = ""
165
166         try:
167             self.mdc['HOST_NAME'] = os.environ['HOST_NAME']
168         except:
169             self.mdc['HOST_NAME'] = ""
170
171         try:
172             self.mdc['SERVICE_NAME'] = os.environ['SERVICE_NAME']
173         except:
174             self.mdc['SERVICE_NAME'] = ""
175     
176         try:
177             self.mdc['CONTAINER_NAME'] = os.environ['CONTAINER_NAME']
178         except:
179             self.mdc['CONTAINER_NAME'] = ""
180
181         try:
182             self.mdc['POD_NAME'] = os.environ['POD_NAME']
183         except:
184             self.mdc['POD_NAME'] = ""
185
186
187     
188     def update_mdc_log_level_severity(self,level):
189         severity_level = Level.DEBUG
190
191         if(level == ""):
192             print("Invalid Log Level defined in ConfigMap")
193         elif((level.upper() == "ERROR") or (level.upper() == "ERR" )):
194             severity_level = Level.ERROR
195         elif((level.upper() == "WARNING") or (level.upper() == "WARN")):
196             severity_level = Level.WARNING
197         elif(level.upper() == "INFO"):
198             severity_level = Level.INFO
199         elif(level.upper() == "DEBUG"):
200             severity_level = Level.DEBUG
201         
202         self.set_level(severity_level)
203       
204         
205         
206
207
208     def parse_file(self):
209         src = open(self.filename,'r')
210         level = ""
211         for line in src:
212             if 'log-level:' in line:
213                 level_tmp = str(line.split(':')[-1]).strip()
214                 level = level_tmp
215                 break
216         src.close()
217         self.update_mdc_log_level_severity(level)
218         
219
220
221
222     def monitor_loglevel_change_handler(self):
223         i = inotify.adapters.Inotify()
224         i.add_watch(self.dirname)
225         for event in i.event_gen():
226             if (event is not None) and ('IN_MODIFY' in str(event[1]) or 'IN_DELETE' in str(event[1])):
227                 self.parse_file()
228
229
230     def register_log_change_notify(self):
231         t1 = threading.Thread(target=self.monitor_loglevel_change_handler)
232         t1.daemon = True
233         try:
234             t1.start() 
235         except (KeyboardInterrupt, SystemExit):
236             cleanup_stop_thread()
237             sys.exit()
238         
239
240
241
242     def get_mdc(self, key: str) -> Value:
243         """Return logger's MDC value with the given key or None."""
244         try:
245             return self.mdc[key]
246         except KeyError:
247             return None
248
249     def remove_mdc(self, key: str):
250         """Remove logger's MDC with the given key."""
251         try:
252             del self.mdc[key]
253         except KeyError:
254             pass
255
256     def clean_mdc(self):
257         """Remove all MDCs of the logger instance."""
258         self.mdc = {}