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