blob: 40efa2417659b6917d0db0176d62d048ae2b07e0 [file] [log] [blame]
##
# Copyright 2019 Telefonica Investigacion y Desarrollo, S.A.U.
# This file is part of OSM
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# For those usages not covered by the Apache License, Version 2.0 please
# contact with: nfvlabs@tid.es
##
import logging
import asyncio
import time
import inspect
import datetime
import threading # only for logging purposes (not for using threads)
class Loggable:
def __init__(
self,
log,
log_to_console: bool = False,
prefix: str = ''
):
self._last_log_time = None # used for time increment in logging
self._log_to_console = log_to_console
self._prefix = prefix
if log is not None:
self.log = log
else:
self.log = logging.getLogger(__name__)
def debug(self, msg: str):
self._log_msg(log_level='DEBUG', msg=msg)
def info(self, msg: str):
self._log_msg(log_level='INFO', msg=msg)
def warning(self, msg: str):
self._log_msg(log_level='WARNING', msg=msg)
def error(self, msg: str):
self._log_msg(log_level='ERROR', msg=msg)
def critical(self, msg: str):
self._log_msg(log_level='CRITICAL', msg=msg)
##################################################################################################
def _log_msg(self, log_level: str, msg: str):
"""Generic log method"""
msg = self._format_log(
log_level=log_level,
msg=msg,
obj=self,
level=3,
include_path=False,
include_thread=False,
include_coroutine=True
)
if self._log_to_console:
print(msg)
else:
if self.log is not None:
if log_level == 'DEBUG':
self.log.debug(msg)
elif log_level == 'INFO':
self.log.info(msg)
elif log_level == 'WARNING':
self.log.warning(msg)
elif log_level == 'ERROR':
self.log.error(msg)
elif log_level == 'CRITICAL':
self.log.critical(msg)
def _format_log(
self,
log_level: str,
msg: str = '',
obj: object = None,
level: int = None,
include_path: bool = False,
include_thread: bool = False,
include_coroutine: bool = True
) -> str:
# time increment from last log
now = time.perf_counter()
if self._last_log_time is None:
time_str = ' (+0.000)'
else:
diff = round(now - self._last_log_time, 3)
time_str = ' (+{})'.format(diff)
self._last_log_time = now
if level is None:
level = 1
# stack info
fi = inspect.stack()[level]
filename = fi.filename
func = fi.function
lineno = fi.lineno
# filename without path
if not include_path:
i = filename.rfind('/')
if i > 0:
filename = filename[i+1:]
# datetime
dt = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')
dt = dt + time_str
dt = time_str # logger already shows datetime
# current thread
if include_thread:
thread_name = 'th:{}'.format(threading.current_thread().getName())
else:
thread_name = ''
# current coroutine
coroutine_id = ''
if include_coroutine:
try:
if asyncio.Task.current_task() is not None:
def print_cor_name(c):
import inspect
try:
for m in inspect.getmembers(c):
if m[0] == '__name__':
return m[1]
except Exception:
pass
coro = asyncio.Task.current_task()._coro
coroutine_id = 'coro-{} {}()'.format(hex(id(coro))[2:], print_cor_name(coro))
except Exception:
coroutine_id = ''
# classname
if obj is not None:
obj_type = obj.__class__.__name__ # type: str
log_msg = \
'{} {} {} {} {}::{}.{}():{}\n{}'\
.format(self._prefix, dt, thread_name, coroutine_id, filename, obj_type, func, lineno, str(msg))
else:
log_msg = \
'{} {} {} {} {}::{}():{}\n{}'\
.format(self._prefix, dt, thread_name, coroutine_id, filename, func, lineno, str(msg))
return log_msg