Fix bug 1002
[osm/N2VC.git] / n2vc / loggable.py
1 ##
2 # Copyright 2019 Telefonica Investigacion y Desarrollo, S.A.U.
3 # This file is part of OSM
4 # All Rights Reserved.
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 # http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
15 # implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
18 #
19 # For those usages not covered by the Apache License, Version 2.0 please
20 # contact with: nfvlabs@tid.es
21 ##
22
23
24 import logging
25 import asyncio
26 import time
27 import inspect
28 import datetime
29 import threading # only for logging purposes (not for using threads)
30
31
32 class Loggable:
33
34 def __init__(
35 self,
36 log,
37 log_to_console: bool = False,
38 prefix: str = ''
39 ):
40
41 self._last_log_time = None # used for time increment in logging
42 self._log_to_console = log_to_console
43 self._prefix = prefix
44 if log is not None:
45 self.log = log
46 else:
47 self.log = logging.getLogger(__name__)
48
49 def debug(self, msg: str):
50 self._log_msg(log_level='DEBUG', msg=msg)
51
52 def info(self, msg: str):
53 self._log_msg(log_level='INFO', msg=msg)
54
55 def warning(self, msg: str):
56 self._log_msg(log_level='WARNING', msg=msg)
57
58 def error(self, msg: str):
59 self._log_msg(log_level='ERROR', msg=msg)
60
61 def critical(self, msg: str):
62 self._log_msg(log_level='CRITICAL', msg=msg)
63
64 ##################################################################################################
65
66 def _log_msg(self, log_level: str, msg: str):
67 """Generic log method"""
68 msg = self._format_log(
69 log_level=log_level,
70 msg=msg,
71 obj=self,
72 level=3,
73 include_path=False,
74 include_thread=False,
75 include_coroutine=True
76 )
77 if self._log_to_console:
78 print(msg)
79 else:
80 if self.log is not None:
81 if log_level == 'DEBUG':
82 self.log.debug(msg)
83 elif log_level == 'INFO':
84 self.log.info(msg)
85 elif log_level == 'WARNING':
86 self.log.warning(msg)
87 elif log_level == 'ERROR':
88 self.log.error(msg)
89 elif log_level == 'CRITICAL':
90 self.log.critical(msg)
91
92 def _format_log(
93 self,
94 log_level: str,
95 msg: str = '',
96 obj: object = None,
97 level: int = None,
98 include_path: bool = False,
99 include_thread: bool = False,
100 include_coroutine: bool = True
101 ) -> str:
102
103 # time increment from last log
104 now = time.perf_counter()
105 if self._last_log_time is None:
106 time_str = ' (+0.000)'
107 else:
108 diff = round(now - self._last_log_time, 3)
109 time_str = ' (+{})'.format(diff)
110 self._last_log_time = now
111
112 if level is None:
113 level = 1
114
115 # stack info
116 fi = inspect.stack()[level]
117 filename = fi.filename
118 func = fi.function
119 lineno = fi.lineno
120 # filename without path
121 if not include_path:
122 i = filename.rfind('/')
123 if i > 0:
124 filename = filename[i+1:]
125
126 # datetime
127 dt = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')
128 dt = dt + time_str
129 # dt = time_str # logger already shows datetime
130
131 # current thread
132 if include_thread:
133 thread_name = 'th:{}'.format(threading.current_thread().getName())
134 else:
135 thread_name = ''
136
137 # current coroutine
138
139 coroutine_id = ''
140 if include_coroutine:
141 try:
142 if asyncio.Task.current_task() is not None:
143 def print_cor_name(c):
144 import inspect
145 try:
146 for m in inspect.getmembers(c):
147 if m[0] == '__name__':
148 return m[1]
149 except Exception:
150 pass
151 coro = asyncio.Task.current_task()._coro
152 coroutine_id = 'coro-{} {}()'.format(hex(id(coro))[2:], print_cor_name(coro))
153 except Exception:
154 coroutine_id = ''
155
156 # classname
157 if obj is not None:
158 obj_type = obj.__class__.__name__ # type: str
159 log_msg = \
160 '{} {} {} {} {}::{}.{}():{}\n{}'\
161 .format(self._prefix, dt, thread_name, coroutine_id, filename, obj_type, func, lineno, str(msg))
162 else:
163 log_msg = \
164 '{} {} {} {} {}::{}():{}\n{}'\
165 .format(self._prefix, dt, thread_name, coroutine_id, filename, func, lineno, str(msg))
166
167 return log_msg