blob: d588a1d2a9fb36982dacb1b1a1347bec1fb009f5 [file] [log] [blame]
quilesja049b7c2019-10-28 18:08:00 +01001##
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
quilesja049b7c2019-10-28 18:08:00 +010024import asyncio
quilesja049b7c2019-10-28 18:08:00 +010025import datetime
beierlmf52cb7c2020-04-21 16:36:35 -040026import inspect
27import logging
28import threading # only for logging purposes (not for using threads)
29import time
quilesja049b7c2019-10-28 18:08:00 +010030
31
32class Loggable:
beierlmf52cb7c2020-04-21 16:36:35 -040033 def __init__(self, log, log_to_console: bool = False, prefix: str = ""):
quilesja049b7c2019-10-28 18:08:00 +010034
beierlmf52cb7c2020-04-21 16:36:35 -040035 self._last_log_time = None # used for time increment in logging
quilesja049b7c2019-10-28 18:08:00 +010036 self._log_to_console = log_to_console
37 self._prefix = prefix
38 if log is not None:
39 self.log = log
40 else:
41 self.log = logging.getLogger(__name__)
42
43 def debug(self, msg: str):
beierlmf52cb7c2020-04-21 16:36:35 -040044 self._log_msg(log_level="DEBUG", msg=msg)
quilesja049b7c2019-10-28 18:08:00 +010045
46 def info(self, msg: str):
beierlmf52cb7c2020-04-21 16:36:35 -040047 self._log_msg(log_level="INFO", msg=msg)
quilesja049b7c2019-10-28 18:08:00 +010048
49 def warning(self, msg: str):
beierlmf52cb7c2020-04-21 16:36:35 -040050 self._log_msg(log_level="WARNING", msg=msg)
quilesja049b7c2019-10-28 18:08:00 +010051
52 def error(self, msg: str):
beierlmf52cb7c2020-04-21 16:36:35 -040053 self._log_msg(log_level="ERROR", msg=msg)
quilesja049b7c2019-10-28 18:08:00 +010054
55 def critical(self, msg: str):
beierlmf52cb7c2020-04-21 16:36:35 -040056 self._log_msg(log_level="CRITICAL", msg=msg)
quilesja049b7c2019-10-28 18:08:00 +010057
beierlmf52cb7c2020-04-21 16:36:35 -040058 ####################################################################################
quilesja049b7c2019-10-28 18:08:00 +010059
60 def _log_msg(self, log_level: str, msg: str):
61 """Generic log method"""
62 msg = self._format_log(
63 log_level=log_level,
64 msg=msg,
65 obj=self,
66 level=3,
67 include_path=False,
68 include_thread=False,
beierlmf52cb7c2020-04-21 16:36:35 -040069 include_coroutine=True,
quilesja049b7c2019-10-28 18:08:00 +010070 )
71 if self._log_to_console:
72 print(msg)
73 else:
74 if self.log is not None:
beierlmf52cb7c2020-04-21 16:36:35 -040075 if log_level == "DEBUG":
quilesja049b7c2019-10-28 18:08:00 +010076 self.log.debug(msg)
beierlmf52cb7c2020-04-21 16:36:35 -040077 elif log_level == "INFO":
quilesja049b7c2019-10-28 18:08:00 +010078 self.log.info(msg)
beierlmf52cb7c2020-04-21 16:36:35 -040079 elif log_level == "WARNING":
quilesja049b7c2019-10-28 18:08:00 +010080 self.log.warning(msg)
beierlmf52cb7c2020-04-21 16:36:35 -040081 elif log_level == "ERROR":
quilesja049b7c2019-10-28 18:08:00 +010082 self.log.error(msg)
beierlmf52cb7c2020-04-21 16:36:35 -040083 elif log_level == "CRITICAL":
quilesja049b7c2019-10-28 18:08:00 +010084 self.log.critical(msg)
85
86 def _format_log(
beierlmf52cb7c2020-04-21 16:36:35 -040087 self,
88 log_level: str,
89 msg: str = "",
90 obj: object = None,
91 level: int = None,
92 include_path: bool = False,
93 include_thread: bool = False,
94 include_coroutine: bool = True,
quilesja049b7c2019-10-28 18:08:00 +010095 ) -> str:
96
97 # time increment from last log
98 now = time.perf_counter()
99 if self._last_log_time is None:
beierlmf52cb7c2020-04-21 16:36:35 -0400100 time_str = " (+0.000)"
quilesja049b7c2019-10-28 18:08:00 +0100101 else:
102 diff = round(now - self._last_log_time, 3)
beierlmf52cb7c2020-04-21 16:36:35 -0400103 time_str = " (+{})".format(diff)
quilesja049b7c2019-10-28 18:08:00 +0100104 self._last_log_time = now
105
106 if level is None:
107 level = 1
108
109 # stack info
110 fi = inspect.stack()[level]
111 filename = fi.filename
112 func = fi.function
113 lineno = fi.lineno
114 # filename without path
115 if not include_path:
beierlmf52cb7c2020-04-21 16:36:35 -0400116 i = filename.rfind("/")
quilesja049b7c2019-10-28 18:08:00 +0100117 if i > 0:
beierlmf52cb7c2020-04-21 16:36:35 -0400118 filename = filename[i + 1 :]
quilesja049b7c2019-10-28 18:08:00 +0100119
120 # datetime
beierlmf52cb7c2020-04-21 16:36:35 -0400121 dt = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
quilesja049b7c2019-10-28 18:08:00 +0100122 dt = dt + time_str
quilesjac4e0de2019-11-27 16:12:02 +0000123 # dt = time_str # logger already shows datetime
quilesja049b7c2019-10-28 18:08:00 +0100124
125 # current thread
126 if include_thread:
beierlmf52cb7c2020-04-21 16:36:35 -0400127 thread_name = "th:{}".format(threading.current_thread().getName())
quilesja049b7c2019-10-28 18:08:00 +0100128 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400129 thread_name = ""
quilesja049b7c2019-10-28 18:08:00 +0100130
131 # current coroutine
132
beierlmf52cb7c2020-04-21 16:36:35 -0400133 coroutine_id = ""
quilesja049b7c2019-10-28 18:08:00 +0100134 if include_coroutine:
135 try:
136 if asyncio.Task.current_task() is not None:
beierlmf52cb7c2020-04-21 16:36:35 -0400137
quilesja049b7c2019-10-28 18:08:00 +0100138 def print_cor_name(c):
139 import inspect
beierlmf52cb7c2020-04-21 16:36:35 -0400140
quilesja049b7c2019-10-28 18:08:00 +0100141 try:
142 for m in inspect.getmembers(c):
beierlmf52cb7c2020-04-21 16:36:35 -0400143 if m[0] == "__name__":
quilesja049b7c2019-10-28 18:08:00 +0100144 return m[1]
145 except Exception:
146 pass
beierlmf52cb7c2020-04-21 16:36:35 -0400147
quilesja049b7c2019-10-28 18:08:00 +0100148 coro = asyncio.Task.current_task()._coro
beierlmf52cb7c2020-04-21 16:36:35 -0400149 coroutine_id = "coro-{} {}()".format(
150 hex(id(coro))[2:], print_cor_name(coro)
151 )
quilesja049b7c2019-10-28 18:08:00 +0100152 except Exception:
beierlmf52cb7c2020-04-21 16:36:35 -0400153 coroutine_id = ""
quilesja049b7c2019-10-28 18:08:00 +0100154
155 # classname
156 if obj is not None:
157 obj_type = obj.__class__.__name__ # type: str
beierlmf52cb7c2020-04-21 16:36:35 -0400158 log_msg = "{} {} {} {} {}::{}.{}():{}\n{}".format(
159 self._prefix,
160 dt,
161 thread_name,
162 coroutine_id,
163 filename,
164 obj_type,
165 func,
166 lineno,
167 str(msg),
168 )
quilesja049b7c2019-10-28 18:08:00 +0100169 else:
beierlmf52cb7c2020-04-21 16:36:35 -0400170 log_msg = "{} {} {} {} {}::{}():{}\n{}".format(
171 self._prefix,
172 dt,
173 thread_name,
174 coroutine_id,
175 filename,
176 func,
177 lineno,
178 str(msg),
179 )
quilesja049b7c2019-10-28 18:08:00 +0100180
181 return log_msg