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 |
1 |
import asyncio |
25 |
1 |
import datetime |
26 |
1 |
import inspect |
27 |
1 |
import logging |
28 |
1 |
import threading # only for logging purposes (not for using threads) |
29 |
1 |
import time |
30 |
|
|
31 |
|
|
32 |
1 |
class Loggable: |
33 |
1 |
def __init__(self, log, log_to_console: bool = False, prefix: str = ""): |
34 |
|
|
35 |
1 |
self._last_log_time = None # used for time increment in logging |
36 |
1 |
self._log_to_console = log_to_console |
37 |
1 |
self._prefix = prefix |
38 |
1 |
if log is not None: |
39 |
1 |
self.log = log |
40 |
|
else: |
41 |
1 |
self.log = logging.getLogger(__name__) |
42 |
|
|
43 |
1 |
def debug(self, msg: str): |
44 |
0 |
self._log_msg(log_level="DEBUG", msg=msg) |
45 |
|
|
46 |
1 |
def info(self, msg: str): |
47 |
0 |
self._log_msg(log_level="INFO", msg=msg) |
48 |
|
|
49 |
1 |
def warning(self, msg: str): |
50 |
0 |
self._log_msg(log_level="WARNING", msg=msg) |
51 |
|
|
52 |
1 |
def error(self, msg: str): |
53 |
0 |
self._log_msg(log_level="ERROR", msg=msg) |
54 |
|
|
55 |
1 |
def critical(self, msg: str): |
56 |
0 |
self._log_msg(log_level="CRITICAL", msg=msg) |
57 |
|
|
58 |
|
#################################################################################### |
59 |
|
|
60 |
1 |
def _log_msg(self, log_level: str, msg: str): |
61 |
|
"""Generic log method""" |
62 |
0 |
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, |
69 |
|
include_coroutine=True, |
70 |
|
) |
71 |
0 |
if self._log_to_console: |
72 |
0 |
print(msg) |
73 |
|
else: |
74 |
0 |
if self.log is not None: |
75 |
0 |
if log_level == "DEBUG": |
76 |
0 |
self.log.debug(msg) |
77 |
0 |
elif log_level == "INFO": |
78 |
0 |
self.log.info(msg) |
79 |
0 |
elif log_level == "WARNING": |
80 |
0 |
self.log.warning(msg) |
81 |
0 |
elif log_level == "ERROR": |
82 |
0 |
self.log.error(msg) |
83 |
0 |
elif log_level == "CRITICAL": |
84 |
0 |
self.log.critical(msg) |
85 |
|
|
86 |
1 |
def _format_log( |
87 |
|
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, |
95 |
|
) -> str: |
96 |
|
|
97 |
|
# time increment from last log |
98 |
0 |
now = time.perf_counter() |
99 |
0 |
if self._last_log_time is None: |
100 |
0 |
time_str = " (+0.000)" |
101 |
|
else: |
102 |
0 |
diff = round(now - self._last_log_time, 3) |
103 |
0 |
time_str = " (+{})".format(diff) |
104 |
0 |
self._last_log_time = now |
105 |
|
|
106 |
0 |
if level is None: |
107 |
0 |
level = 1 |
108 |
|
|
109 |
|
# stack info |
110 |
0 |
fi = inspect.stack()[level] |
111 |
0 |
filename = fi.filename |
112 |
0 |
func = fi.function |
113 |
0 |
lineno = fi.lineno |
114 |
|
# filename without path |
115 |
0 |
if not include_path: |
116 |
0 |
i = filename.rfind("/") |
117 |
0 |
if i > 0: |
118 |
0 |
filename = filename[i + 1:] |
119 |
|
|
120 |
|
# datetime |
121 |
0 |
dt = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f") |
122 |
0 |
dt = dt + time_str |
123 |
|
# dt = time_str # logger already shows datetime |
124 |
|
|
125 |
|
# current thread |
126 |
0 |
if include_thread: |
127 |
0 |
thread_name = "th:{}".format(threading.current_thread().getName()) |
128 |
|
else: |
129 |
0 |
thread_name = "" |
130 |
|
|
131 |
|
# current coroutine |
132 |
|
|
133 |
0 |
coroutine_id = "" |
134 |
0 |
if include_coroutine: |
135 |
0 |
try: |
136 |
0 |
if asyncio.Task.current_task() is not None: |
137 |
|
|
138 |
0 |
def print_cor_name(c): |
139 |
0 |
import inspect |
140 |
|
|
141 |
0 |
try: |
142 |
0 |
for m in inspect.getmembers(c): |
143 |
0 |
if m[0] == "__name__": |
144 |
0 |
return m[1] |
145 |
0 |
except Exception: |
146 |
0 |
pass |
147 |
|
|
148 |
0 |
coro = asyncio.Task.current_task()._coro |
149 |
0 |
coroutine_id = "coro-{} {}()".format( |
150 |
|
hex(id(coro))[2:], print_cor_name(coro) |
151 |
|
) |
152 |
0 |
except Exception: |
153 |
0 |
coroutine_id = "" |
154 |
|
|
155 |
|
# classname |
156 |
0 |
if obj is not None: |
157 |
0 |
obj_type = obj.__class__.__name__ # type: str |
158 |
0 |
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 |
|
) |
169 |
|
else: |
170 |
0 |
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 |
|
) |
180 |
|
|
181 |
0 |
return log_msg |