Commit 92a28ec7a8c66daed382a1bcfd9a9c29e5d4b178
1 parent
1cf4a67f
Exists in
dev
LOGGER UNIQUE POUR TOUT LE PROJET
J'ai déplacé (dans src/) et adapté logpyros pour ça (reste maintenant à nettoyer un peu...)
Showing
7 changed files
with
616 additions
and
14 deletions
Show diff stats
HOWTO_TEST.txt
... | ... | @@ -13,8 +13,10 @@ |
13 | 13 | $ ./pyros.py testall |
14 | 14 | |
15 | 15 | (B) Test with agents: |
16 | -- (1) FULL Test with agentMultiRequester (AgentMajordome like) sending commands to several device agents at once : the Gemini telescope device agent (or simulator), and the SBIG device agent (or simulator): | |
16 | +- (1) FULL Test with agentMultiRequester (AgentMajordome like) sending commands to several device agents at once : | |
17 | + the Gemini telescope device agent (or simulator), and the SBIG device agent (or simulator): | |
17 | 18 | $ ./pyros.py -ts start agentDeviceGemini,agentDeviceSBIG,agentMultiRequester |
19 | + (add "-d" option for debug mode) | |
18 | 20 | - (2) Test against REAL Gemini telescope |
19 | 21 | $ ./pyros.py -t start agentDeviceGemini,agentMultiRequester |
20 | 22 | ... | ... |
src/core/pyros_django/agent/Agent.py
... | ... | @@ -116,7 +116,8 @@ from config.configpyros import ConfigPyros |
116 | 116 | #from devices.TelescopeRemoteControlDefault import TelescopeRemoteControlDefault |
117 | 117 | #from utils.JDManipulator import * |
118 | 118 | |
119 | -from agent.logpyros import LogPyros | |
119 | +##from agent.logpyros import LogPyros | |
120 | +from src.logpyros import LogPyros | |
120 | 121 | |
121 | 122 | from device_controller.abstract_component.device_controller import DCCNotFoundException, UnimplementedGenericCmdException, UnknownNativeCmdException |
122 | 123 | |
... | ... | @@ -398,12 +399,17 @@ class Agent: |
398 | 399 | #def __init__(self, name:str="Agent", config_filename:str=None, RUN_IN_THREAD=True): |
399 | 400 | def __init__(self, config_filename:str=None, RUN_IN_THREAD=True, DEBUG_MODE=False): |
400 | 401 | #self.name = name |
401 | - printd("*** ENVIRONMENT VARIABLE PYROS_DEBUG is:", os.environ.get('PYROS_DEBUG'), '***') | |
402 | 402 | self.name = self.__class__.__name__ |
403 | - self.DEBUG_MODE = DEBUG_MODE | |
403 | + printd("*** ENVIRONMENT VARIABLE PYROS_DEBUG is:", os.environ.get('PYROS_DEBUG'), '***') | |
404 | + ##self.DEBUG_MODE = DEBUG_MODE | |
405 | + self.DEBUG_MODE = os.environ.get('PYROS_DEBUG', '0')=='1' | |
404 | 406 | self._log = LogPyros(self.name, AgentLogs) |
405 | - self._log.debug_level = DEBUG_MODE | |
406 | - self.printd("LOG DEBUG LEVEL IS:", self._log.debug_level) | |
407 | + ##self._log.debug_level = DEBUG_MODE | |
408 | + # Default LOG level is INFO | |
409 | + log_level = LogPyros.LOG_LEVEL_INFO # INFO | |
410 | + self._log.set_global_log_level(LogPyros.LOG_LEVEL_DEBUG) if self.DEBUG_MODE else self._log.set_global_log_level(log_level) | |
411 | + ##self.printd("LOG LEVEL IS:", self._log.debug_level) | |
412 | + self.print("LOG LEVEL IS:", self._log.get_global_log_level()) | |
407 | 413 | |
408 | 414 | # New way with PathLib |
409 | 415 | my_parent_abs_dir = Path(__file__).resolve().parent |
... | ... | @@ -983,7 +989,10 @@ class Agent: |
983 | 989 | self.printd("------------------------------------------") |
984 | 990 | |
985 | 991 | # --- update the log parameters |
986 | - self._log.path_data = self._path_data | |
992 | + ##self._log.path_data = self._path_data | |
993 | + ##print("new self._log.path_data is", self._log.path_data) | |
994 | + self._log.set_global_path_data(self._path_data) | |
995 | + print("new self._log.global_path_data is", self._log.get_global_path_data()) | |
987 | 996 | self._log.home = home |
988 | 997 | |
989 | 998 | ... | ... |
src/core/pyros_django/agent/logpyros.py renamed to src/core/pyros_django/agent/logpyros_orig.py
src/core/pyros_django/common/models.py
... | ... | @@ -296,6 +296,7 @@ class AgentLogs(models.Model): |
296 | 296 | def __str__(self): |
297 | 297 | return (f"{self.created} {self.name} {self.message}") |
298 | 298 | |
299 | + | |
299 | 300 | class AgentSurvey(models.Model): |
300 | 301 | """ |
301 | 302 | | id | name | created | updated | validity_duration (default=1mn) | mode (active/idle) | status (launch/init/loop/exit/...) | | ... | ... |
src/device_controller/abstract_component/device_controller.py
... | ... | @@ -28,6 +28,7 @@ import time |
28 | 28 | sys.path.append("../../../..") |
29 | 29 | #import src.core.pyros_django.utils.celme as celme |
30 | 30 | import src.core.celme as celme |
31 | +from src.logpyros import LogPyros | |
31 | 32 | |
32 | 33 | #sys.path.append('../..') |
33 | 34 | #from src.client.socket_client_abstract import UnknownNativeCmdException, SocketClientAbstract |
... | ... | @@ -51,8 +52,14 @@ GET_ONLY=False |
51 | 52 | TIMEOUT_SEND = 10 |
52 | 53 | TIMEOUT_RECEIVE = 10 |
53 | 54 | |
54 | - | |
55 | -def printd(*args, **kwargs): | |
55 | +''' | |
56 | +logger = LogPyros(__name__) | |
57 | +def log(self, *args, **kwargs): logger.print(*args, **kwargs) | |
58 | +# DEBUG print | |
59 | +def printd(self, *args, **kwargs): logger.printd(*args, **kwargs) | |
60 | +def tprintd(self, *args, **kwargs): printd('(THREAD):', *args, *kwargs) | |
61 | +''' | |
62 | +def printd(*args, **kwargs): | |
56 | 63 | if os.environ.get('PYROS_DEBUG', '0')=='1': print(*args, **kwargs) |
57 | 64 | |
58 | 65 | |
... | ... | @@ -451,8 +458,11 @@ class DeviceController(): |
451 | 458 | |
452 | 459 | #self.DEBUG_MODE = DEBUG |
453 | 460 | self.DEBUG_MODE = os.environ.get('PYROS_DEBUG', '0') == '1' |
454 | - set_logger(self.DEBUG_MODE) | |
455 | - log_d("Logger configured") | |
461 | + ##set_logger(self.DEBUG_MODE) | |
462 | + ##log_d("Logger configured") | |
463 | + self._log = LogPyros(self.__class__.__name__) | |
464 | + self.print("coucou") | |
465 | + | |
456 | 466 | |
457 | 467 | # Set host (IP) and port |
458 | 468 | #printd("IN DeviceController") |
... | ... | @@ -506,6 +516,8 @@ class DeviceController(): |
506 | 516 | |
507 | 517 | |
508 | 518 | |
519 | + | |
520 | + | |
509 | 521 | # Set my list of dc components |
510 | 522 | def set_dc_components(self, dc_components:list): self._my_dc_components = dc_components |
511 | 523 | |
... | ... | @@ -521,10 +533,12 @@ class DeviceController(): |
521 | 533 | self._my_channel.set_logger(DEBUG) |
522 | 534 | ''' |
523 | 535 | |
536 | + def print(self, *args, **kwargs): self._log.print(*args, **kwargs) | |
537 | + | |
524 | 538 | # DEBUG print |
525 | 539 | def printd(self, *args, **kwargs): |
526 | - #self._log.printd(*args, **kwargs) | |
527 | - if self.DEBUG_MODE: printd(*args, **kwargs) | |
540 | + self._log.printd(*args, **kwargs) | |
541 | + #if self.DEBUG_MODE: printd(*args, **kwargs) | |
528 | 542 | |
529 | 543 | def tprintd(self, *args, **kwargs): |
530 | 544 | self.printd('(THREAD):', *args, *kwargs) | ... | ... |
... | ... | @@ -0,0 +1,459 @@ |
1 | +import os | |
2 | +import math | |
3 | +import tempfile | |
4 | +from io import StringIO | |
5 | +import sys | |
6 | + | |
7 | +# --- update the path of Python in case to test this file alone | |
8 | +py_path = os.sys.path | |
9 | +py_pwd = os.path.normpath(os.getcwd() + "/../../../..") | |
10 | +if (py_pwd not in py_path): | |
11 | + (os.sys.path).append(py_pwd) | |
12 | + | |
13 | +# --- import PyROS celestial mechanics from the pyros root directory | |
14 | +import src.core.celme as celme | |
15 | + | |
16 | + | |
17 | +''' | |
18 | +logger = None | |
19 | + | |
20 | +def log_d(msg:str, *args, **kwargs): logger.debug(msg, *args, **kwargs) | |
21 | +def log_i(msg:str, *args, **kwargs): logger.info(msg, *args, **kwargs) | |
22 | +def log_w(msg:str, *args, **kwargs): logger.warning(msg, *args, **kwargs) | |
23 | +def log_e(msg:str, *args, **kwargs): logger.error(msg, *args, **kwargs) | |
24 | +def log_c(msg:str, *args, **kwargs): logger.critical(msg, *args, **kwargs) | |
25 | +''' | |
26 | + | |
27 | +class LogPyros: | |
28 | + """ | |
29 | + Manage PyROS logs in display, file and database. | |
30 | + | |
31 | + First, create an instance: | |
32 | + log = LogPyros("test",None) | |
33 | + | |
34 | + Second, use the print methods to log: | |
35 | + log.print("Something to log") | |
36 | + """ | |
37 | + | |
38 | + # CLASS attributes | |
39 | + _GLOBAL_PATH_DATA = '' | |
40 | + | |
41 | + # === Level of log | |
42 | + ''' | |
43 | + LOG_LEVEL_INFO = 0 | |
44 | + LOG_LEVEL_WARNING = -1 | |
45 | + LOG_LEVEL_ERROR = -2 | |
46 | + LOG_LEVEL_DEBUG = 1 | |
47 | + LOG_LEVEL_DEBUG2 = 2 | |
48 | + ''' | |
49 | + # Python standard logging module values: NOTSET=0, DEBUG=10, INFO=20, WARN=30, ERROR=40, CRITICAL=50 | |
50 | + LOG_LEVEL_NOTSET = 0 | |
51 | + LOG_LEVEL_DEBUG = 10 | |
52 | + LOG_LEVEL_DEBUG2 = 15 | |
53 | + LOG_LEVEL_INFO = 20 | |
54 | + LOG_LEVEL_WARNING = 30 | |
55 | + LOG_LEVEL_ERROR = 40 | |
56 | + LOG_LEVEL_CRITICAL = 50 | |
57 | + | |
58 | + # Default LOG level is NOTSET (no filter) | |
59 | + _GLOBAL_LOG_LEVEL = LOG_LEVEL_NOTSET | |
60 | + | |
61 | + # === Constants | |
62 | + NO_ERROR = 0 | |
63 | + | |
64 | + ERR_ALIAS_NAME_NOT_DEFINED = 10 | |
65 | + | |
66 | + ERR_FILE_NOT_EXISTS = 101 | |
67 | + ERR_PATH_NOT_EXISTS = 102 | |
68 | + ERR_EMPTY_FILENAME = 103 | |
69 | + ERR_EMPTY_PATHNAME = 104 | |
70 | + | |
71 | + # === Private variables | |
72 | + _last_errno = NO_ERROR | |
73 | + | |
74 | + ##_path_data = '' | |
75 | + _caller_alias = '' | |
76 | + _path_data_log = '' | |
77 | + _path_wwwpyros = '' | |
78 | + | |
79 | + _date = None | |
80 | + _home = None | |
81 | + _noon_hour = 12 | |
82 | + #_last_lines = {'chrono':[], 'agent':[]} | |
83 | + _last_lines = _last_lines_agent = [] | |
84 | + _nbmax_last_lines = 30 | |
85 | + | |
86 | +# ===================================================================== | |
87 | +# ===================================================================== | |
88 | +# Private methods | |
89 | +# ===================================================================== | |
90 | +# ===================================================================== | |
91 | + | |
92 | + ''' | |
93 | + def _mkdir_path_log(self): | |
94 | + # --- | |
95 | + if self._caller_alias == "": | |
96 | + self._last_errno = self.ERR_EMPTY_FILENAME | |
97 | + else: | |
98 | + self._path_www_log = self._path_wwwpyros + "/logs/" + self._caller_alias | |
99 | + os.mkdir(self._path_www_log) | |
100 | + # --- | |
101 | + if self._path_data == "": | |
102 | + self._last_errno = self.ERR_EMPTY_PATHNAME | |
103 | + else: | |
104 | + self._path_data_log = self._path_data + "/logs/" + self._caller_alias | |
105 | + os.mkdir(self._path_data_log) | |
106 | + ''' | |
107 | + | |
108 | + def _jd2datedigit(self, jd): | |
109 | + """ Convert a julian day into a string of YYYYMMDD | |
110 | + """ | |
111 | + datetmp = self._date.date(jd); | |
112 | + datedigit = (datetmp.digits(0))[0:8] | |
113 | + return datedigit | |
114 | + | |
115 | + def _date2night(self, date): | |
116 | + # night is the truncated part of the date of the previous noon | |
117 | + self._date.date(date) | |
118 | + jd = self._date.jd() | |
119 | + jd0 = math.floor(jd) - self._noon_hour/24 | |
120 | + d = celme.Date(jd0) | |
121 | + djd = jd-jd0 | |
122 | + # print(f"jd0 = {d.iso()} djd={djd}") | |
123 | + if djd>=1: | |
124 | + jd0 += 1 | |
125 | + djd = jd-jd0 | |
126 | + if djd>=1: | |
127 | + jd0 += 1 | |
128 | + d = celme.Date(jd0) | |
129 | + night = d.digits(0)[0:8] | |
130 | + return night | |
131 | + | |
132 | + def _night2dates(self, night): | |
133 | + """ | |
134 | + jd_noon0 = previous noon | |
135 | + jd_midnight = midnight instant | |
136 | + jd_noon1 = next noon | |
137 | + """ | |
138 | + d = celme.Date(night) | |
139 | + d_noon0 = d + self._noon_hour/24 | |
140 | + jd_noon0 = d_noon0.jd() | |
141 | + jd_midnight = jd_noon0 + 0.5 | |
142 | + jd_noon1 = jd_noon0 + 1.0 | |
143 | + return jd_noon0, jd_midnight, jd_noon1 | |
144 | + | |
145 | + def _convert_args2str(self, *args, **kwargs): | |
146 | + """ | |
147 | + return a string as the result of a print method | |
148 | + """ | |
149 | + bak = sys.stdout # on sauvegarde l'ancien stdout | |
150 | + result = StringIO() | |
151 | + sys.stdout = result | |
152 | + print (*args, **kwargs,end='') | |
153 | + sys.stdout = bak # on restore stdout | |
154 | + return result.getvalue() | |
155 | + | |
156 | +# ===================================================================== | |
157 | +# ===================================================================== | |
158 | +# Private methods getter/setter | |
159 | +# ===================================================================== | |
160 | +# ===================================================================== | |
161 | + | |
162 | + | |
163 | + def set_global_log_level(self, log_level:int): | |
164 | + LogPyros._GLOBAL_LOG_LEVEL = log_level | |
165 | + def get_global_log_level(self): | |
166 | + return LogPyros._GLOBAL_LOG_LEVEL | |
167 | + | |
168 | + ''' | |
169 | + def _set_debug_level(self, level:str): | |
170 | + if type(level).__name__=="bool": | |
171 | + if level==True: | |
172 | + level = 1 | |
173 | + else: | |
174 | + level = 0 | |
175 | + self._debug_level = level | |
176 | + def _get_debug_level(self): | |
177 | + return self._debug_level | |
178 | + ''' | |
179 | + | |
180 | + def _set_caller_alias(self, caller_alias:str): | |
181 | + if caller_alias=="": | |
182 | + self._last_errno = self.ERR_ALIAS_NAME_NOT_DEFINED | |
183 | + raise Exception(f"Agent alias must not be empty string.") | |
184 | + self._caller_alias = caller_alias | |
185 | + return self.NO_ERROR | |
186 | + | |
187 | + def _get_caller_alias(self): | |
188 | + return self._caller_alias | |
189 | + | |
190 | + def _set_path_data(self, path_data:str): | |
191 | + if not os.path.exists(path_data): | |
192 | + self._last_errno = self.ERR_PATH_NOT_EXISTS | |
193 | + raise Exception(f"Path '{path_data}' for data does not exists. Create it first manually.") | |
194 | + self._path_data = path_data | |
195 | + return self.NO_ERROR | |
196 | + def _get_path_data(self): | |
197 | + return self._path_data | |
198 | + | |
199 | + def set_global_path_data(self, path_data:str): | |
200 | + if not os.path.exists(path_data): | |
201 | + self._last_errno = self.ERR_PATH_NOT_EXISTS | |
202 | + raise Exception(f"Path '{path_data}' for data does not exists. Create it first manually.") | |
203 | + LogPyros._GLOBAL_PATH_DATA = path_data | |
204 | + return self.NO_ERROR | |
205 | + def get_global_path_data(self): | |
206 | + return LogPyros._GLOBAL_PATH_DATA | |
207 | + def get_full_GLOBAL_PATH_DATA(self): | |
208 | + return LogPyros._GLOBAL_PATH_DATA + os.sep + "logs" | |
209 | + | |
210 | + def _set_path_wwwpyros(self, path_wwwpyros:str): | |
211 | + if not os.path.exists(path_wwwpyros): | |
212 | + self._path_wwwpyros = self.ERR_PATH_NOT_EXISTS | |
213 | + raise Exception(f"Path '{path_wwwpyros}' for wwwpyros does not exists. Create it first manually.") | |
214 | + self._path_wwwpyros = path_wwwpyros | |
215 | + return self.NO_ERROR | |
216 | + | |
217 | + def _get_path_wwwpyros(self): | |
218 | + return self._path_wwwpyros | |
219 | + | |
220 | + def _set_home(self, home:str): | |
221 | + self._home.home(home) | |
222 | + longitude = self._home.longitude | |
223 | + self._noon_hour = (180 - longitude)/15.0 | |
224 | + # - place noon_hour in the range [0 24[ | |
225 | + if self._noon_hour < 0: | |
226 | + self._noon_hour += 24 | |
227 | + return self.NO_ERROR | |
228 | + | |
229 | + def _get_home(self): | |
230 | + return self._home.gps | |
231 | + | |
232 | + def _set_logtable(self, logtable): | |
233 | + self._logtable = logtable | |
234 | + return self.NO_ERROR | |
235 | + | |
236 | + def _get_logtable(self): | |
237 | + return self._logtable | |
238 | + | |
239 | + def _set_nbmax_last_lines(self, nbmax_last_lines): | |
240 | + self._nbmax_last_lines = nbmax_last_lines | |
241 | + return self.NO_ERROR | |
242 | + | |
243 | + def _get_nbmax_last_lines(self): | |
244 | + return self._nbmax_last_lines | |
245 | + | |
246 | +# ===================================================================== | |
247 | +# ===================================================================== | |
248 | +# Property methods | |
249 | +# ===================================================================== | |
250 | +# ===================================================================== | |
251 | + | |
252 | + caller_alias = property(_get_caller_alias, _set_caller_alias) | |
253 | + | |
254 | + ##path_data = property(_get_path_data, _set_path_data) | |
255 | + | |
256 | + path_wwwpyros = property(_get_path_wwwpyros, _set_path_wwwpyros) | |
257 | + | |
258 | + home = property(_get_home, _set_home) | |
259 | + | |
260 | + logtable = property(_get_logtable, _set_logtable) | |
261 | + | |
262 | +# ===================================================================== | |
263 | +# ===================================================================== | |
264 | +# Public Property methods | |
265 | +# ===================================================================== | |
266 | +# ===================================================================== | |
267 | + | |
268 | + ##debug_level = property(_get_debug_level, _set_debug_level) | |
269 | + | |
270 | + nbmax_last_lines = property(_get_nbmax_last_lines, _set_nbmax_last_lines) | |
271 | + | |
272 | +# ===================================================================== | |
273 | +# ===================================================================== | |
274 | +# Methods for users | |
275 | +# ===================================================================== | |
276 | +# ===================================================================== | |
277 | + | |
278 | + def log_if_level_lower_than(self, log_level, *args, **kwargs): | |
279 | + """ | |
280 | + This is the method to print in the console display and in a log file. | |
281 | + In the console display, the message is presented as: | |
282 | + (Agent_name) message | |
283 | + In the log file the message is presented as: | |
284 | + Date-ISO message | |
285 | + """ | |
286 | + # FILTER : log only if global log level is <= log_level | |
287 | + if self.get_global_log_level() > log_level: return | |
288 | + | |
289 | + msg = self._convert_args2str(*args, **kwargs) | |
290 | + # --- prepare the super_msg | |
291 | + formatted_msg = f"({self._caller_alias}) {msg}" if msg else "" | |
292 | + # --- classical print | |
293 | + print(formatted_msg) | |
294 | + # --- no more if the agent name is not defined | |
295 | + if not self._caller_alias: return | |
296 | + # --- | |
297 | + # LOG into a file if Agent or if level is enough | |
298 | + ##self.file(msg) | |
299 | + ##if self._caller_alias.startswith('Agent') or log_level >= self.LOG_LEVEL_WARNING: | |
300 | + if self._caller_alias.startswith('Agent') or log_level >= self.LOG_LEVEL_INFO: | |
301 | + self.file(msg) | |
302 | + | |
303 | + ##if self._debug_level > 0: | |
304 | + def printd(self, *args, **kwargs): self.log_d(*args, **kwargs) | |
305 | + def log_d(self, *args, **kwargs): self.log_if_level_lower_than(self.LOG_LEVEL_DEBUG, *args, **kwargs) | |
306 | + def log_i(self, *args, **kwargs): self.log_if_level_lower_than(self.LOG_LEVEL_INFO, *args, **kwargs) | |
307 | + def log_w(self, *args, **kwargs): self.log_if_level_lower_than(self.LOG_LEVEL_WARNING, *args, **kwargs) | |
308 | + def log_e(self, *args, **kwargs): self.log_if_level_lower_than(self.LOG_LEVEL_ERROR, *args, **kwargs) | |
309 | + def log_c(self, *args, **kwargs): self.log_if_level_lower_than(self.LOG_LEVEL_CRITICAL, *args, **kwargs) | |
310 | + | |
311 | + # DEFAULT LOG methods that log at level INFO | |
312 | + def log(self, *args, **kwargs): self.log_i(*args, **kwargs) | |
313 | + def print(self, *args, **kwargs): self.log(*args, **kwargs) | |
314 | + | |
315 | + | |
316 | + | |
317 | + def log_msg_to_file(self, log_msg, path, file_name, night): | |
318 | + # 1) Create path if not exists | |
319 | + if not os.path.exists(path): | |
320 | + try: | |
321 | + os.makedirs(path) | |
322 | + except: | |
323 | + p = f"Cannot create log path {path}" | |
324 | + raise Exception(p) | |
325 | + # 2) APPEND msg to FULL DAY file | |
326 | + file_prefix = os.path.normpath(path + os.sep + file_name + '_') | |
327 | + with open(file_prefix+night+'.log', 'a') as fic: | |
328 | + fic.write(log_msg+"\n") | |
329 | + # 3) UPDATE (REWRITE) LAST LINES file (truncated to nbmax_last_lines) | |
330 | + #filetype = "chrono" if file_name=="pyros2" else "agent" | |
331 | + #ll = self._last_lines[filetype] | |
332 | + ll = self._last_lines if file_name=="pyros2" else self._last_lines_agent | |
333 | + ll.append(log_msg) | |
334 | + n = len(ll) | |
335 | + #print("n is", n, "self.nbmax_last_lines is", self.nbmax_last_lines) | |
336 | + if n > self.nbmax_last_lines: | |
337 | + ll = ll[n-self.nbmax_last_lines:] | |
338 | + #print("ll is", len(ll)) | |
339 | + #print("self._last_lines is", len(self._last_lines)) | |
340 | + ''' | |
341 | + if file_name == "pyros2": | |
342 | + self._last_lines.append(log_msg) | |
343 | + n = len(self._last_lines) | |
344 | + if n > self.nbmax_last_lines: | |
345 | + self._last_lines = self._last_lines[n-self.nbmax_last_lines:] | |
346 | + else: | |
347 | + self._last_lines[-1] = log_msg | |
348 | + ''' | |
349 | + with open(file_prefix+'last'+'.log','w') as fic: | |
350 | + for line in ll: | |
351 | + fic.write(line+"\n") | |
352 | + if file_name=="pyros2": | |
353 | + self._last_lines = ll | |
354 | + else: | |
355 | + self._last_lines_agent = ll | |
356 | + #self._last_lines[filetype] = ll | |
357 | + | |
358 | + | |
359 | + def file(self, *args, **kwargs): | |
360 | + """ | |
361 | + This is the method to print in a log file. | |
362 | + In the log file the message is presented as: | |
363 | + Date-ISO message | |
364 | + The last file is also appended with the message | |
365 | + """ | |
366 | + msg = self._convert_args2str(*args, **kwargs) | |
367 | + # --- no more if the agent name is not defined | |
368 | + if self._caller_alias=="": return | |
369 | + | |
370 | + # --- 1) Compute the current night digital date | |
371 | + night = self._date2night("now") | |
372 | + self._date.date("NOW") | |
373 | + | |
374 | + # --- 2) LOG message to the CHRONO daily unique log file name (pyros.log) | |
375 | + path = os.path.normpath(self.get_full_GLOBAL_PATH_DATA()) | |
376 | + #msg_prefix = f"{self._date.iso()} {self._caller_alias} {msg}" | |
377 | + msg_prefix = f"{self._date.iso()} ({self._caller_alias}) " | |
378 | + #file_prefix = os.path.normpath(path + os.sep + "pyros2" + '_') | |
379 | + file_name = "pyros2" | |
380 | + self.log_msg_to_file(msg_prefix + msg, path, file_name, night) | |
381 | + | |
382 | + # --- 3) (only) If AGENT, DO THE SAME but to the AGENT daily file (in its own folder) | |
383 | + if self._caller_alias.startswith("Agent"): | |
384 | + path += os.sep + self._caller_alias | |
385 | + msg_prefix = f"{self._date.iso()} " | |
386 | + file_name = self._caller_alias | |
387 | + self.log_msg_to_file(msg_prefix + msg, path, file_name, night) | |
388 | + | |
389 | + | |
390 | + | |
391 | + def db(self, *args, **kwargs): | |
392 | + """ | |
393 | + This is the method to print in the agent_log table of the database. | |
394 | + """ | |
395 | + if self.logtable==None: return | |
396 | + msg = self._convert_args2str(*args, **kwargs) | |
397 | + self.logtable.objects.create(name=self._caller_alias, message=msg) | |
398 | + | |
399 | +# ===================================================================== | |
400 | +# ===================================================================== | |
401 | +# Special methods | |
402 | +# ===================================================================== | |
403 | +# ===================================================================== | |
404 | + | |
405 | + def __init__(self, caller_name:str, logtable=None, home:str="GPS 0 E 43 150", path_data:str="", path_wwwpyros:str="" ): | |
406 | + self._last_errno = self.NO_ERROR | |
407 | + # --- | |
408 | + self.caller_alias = caller_name if caller_name else "Unknown_agent" | |
409 | + #print("CALLER IS set to ", self.caller_alias) | |
410 | + # --- | |
411 | + if path_data: | |
412 | + ##self.path_data = path_data | |
413 | + self.set_global_path_data(path_data) | |
414 | + elif not self.get_global_path_data(): | |
415 | + ##self.path_data = tempfile.gettempdir() | |
416 | + ##print("log self.path_data is", self.path_data) | |
417 | + self.set_global_path_data(tempfile.gettempdir()) | |
418 | + print("log self.global_path_data is", self.get_global_path_data()) | |
419 | + # --- | |
420 | + if path_wwwpyros != "": | |
421 | + self.path_wwwpyros = path_wwwpyros | |
422 | + self._date = celme.Date() | |
423 | + self._home = celme.Home(home) | |
424 | + self._noon_hour = 12 ; # local hour corresponding to the date change | |
425 | + self.logtable = None | |
426 | + self.nbmax_last_lines = 30 | |
427 | + | |
428 | +# ===================================================================== | |
429 | +# ===================================================================== | |
430 | +# Test if main | |
431 | +# ===================================================================== | |
432 | +# ===================================================================== | |
433 | + | |
434 | +if __name__ == "__main__": | |
435 | + | |
436 | + # === Instance parameters | |
437 | + | |
438 | + # --- caller_alias = string added at the begining of each line of display log | |
439 | + # --- caller_alias = is the name of log files | |
440 | + # --- caller_alias = is the field name of database logs | |
441 | + caller_alias = "test" | |
442 | + # --- caller_alias = directory where are written log files | |
443 | + path_data = "c:/srv/work/pyros" | |
444 | + # --- home = define the hour of noon when log files are archived | |
445 | + home = "GPS 2 E 43 148" | |
446 | + | |
447 | + # === Instanciation | |
448 | + | |
449 | + # --- The second parameter is the Django object for database | |
450 | + log = LogPyros(caller_alias,None,home,path_data) | |
451 | + | |
452 | + # === Use of logs. Parameters are the same as a Python print | |
453 | + | |
454 | + a = 2 | |
455 | + b = 'tutu' | |
456 | + log.print(f"a={a} b={b}") | |
457 | + | |
458 | + log.print(a,b) | |
459 | + | ... | ... |
... | ... | @@ -0,0 +1,117 @@ |
1 | +#!/usr/bin/env python3 | |
2 | + | |
3 | +"""LOGGER | |
4 | + | |
5 | +""" | |
6 | + | |
7 | + | |
8 | +# Standard library imports | |
9 | +import logging | |
10 | +# https://docs.python.org/3/library/logging.handlers.html#module-logging.handlers | |
11 | +from logging.handlers import SMTPHandler, TimedRotatingFileHandler | |
12 | +import sys | |
13 | + | |
14 | +# Third party imports | |
15 | +# None | |
16 | + | |
17 | +# Local application imports | |
18 | +# None | |
19 | + | |
20 | + | |
21 | +logger = None | |
22 | + | |
23 | + | |
24 | +def get_console_handler(): | |
25 | + # Console should print ALL messages (starting from lower level DEBUG) | |
26 | + c_handler = logging.StreamHandler(sys.stdout) | |
27 | + c_handler.setLevel(logging.DEBUG) | |
28 | + c_format = logging.Formatter('%(name)s - %(levelname)s - %(message)s') | |
29 | + c_handler.setFormatter(c_format) | |
30 | + return c_handler | |
31 | + | |
32 | +def get_file_handler(): | |
33 | + # Log File should contain ONLY messages starting from level WARNING (ERROR ?) | |
34 | + #f_handler = logging.FileHandler('file.log') | |
35 | + f_handler = TimedRotatingFileHandler("file.log", when='midnight') | |
36 | + f_handler.setLevel(logging.WARNING) | |
37 | + # Create formatter and add it to handler | |
38 | + #f_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') | |
39 | + f_format = logging.Formatter('%(asctime)s - %(levelname)s - %(filename)s : %(lineno)s - %(message)s') | |
40 | + f_handler.setFormatter(f_format) | |
41 | + return f_handler | |
42 | + | |
43 | + | |
44 | +def get_db_handler(): | |
45 | + # DB should contain ONLY messages starting from level ERROR | |
46 | + # TODO: | |
47 | + pass | |
48 | + | |
49 | +def get_email_handler(): | |
50 | + # Email should be sent ONLY for messages starting from level CRITICAL | |
51 | + # http://sametmax.com/envoi-dun-email-par-logging-en-cas-de-plantage-dun-script-python-ou-comment-faire-bouffer-uxe9-a-smtphandler/ | |
52 | + # TODO: a ameliorer avec article ci-dessus pour UNICODE | |
53 | + m_handler = SMTPHandler( | |
54 | + # Host et port | |
55 | + ('SMTP.GMAIL.COM', 587), | |
56 | + # From | |
57 | + "MOI@GMAIL.COM", | |
58 | + # To (liste) | |
59 | + ["QUELQU.UN@QUELQUE.PART"], | |
60 | + # Sujet du message | |
61 | + "Erreur critique dans module MMM", | |
62 | + # pour l'authentification | |
63 | + credentials = ("MONEMAIL@GMAIL.COM", "MONSUPERPASSWORD"), | |
64 | + secure = () | |
65 | + ) | |
66 | + m_handler.setLevel(logging.WARNING) | |
67 | + m_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') | |
68 | + m_handler.setFormatter(m_format) | |
69 | + return m_handler | |
70 | + | |
71 | + | |
72 | +def get_logger(logger_name): | |
73 | + global logger | |
74 | + | |
75 | + # Basic configuration | |
76 | + ''' | |
77 | + #logging.basicConfig(level=logging.DEBUG) | |
78 | + logging.basicConfig(level=logging.DEBUG, filename='client.log', filemode='a', format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') | |
79 | + logging.debug("Client instanciated") | |
80 | + ''' | |
81 | + | |
82 | + # Advanced configuration | |
83 | + # https://docs.python.org/3/library/logging.html#logging.getLogger | |
84 | + #logger = logging.getLogger(__name__) | |
85 | + logger = logging.getLogger(logger_name) | |
86 | + | |
87 | + # If not set, default level is NOTSET (all messages are logged) | |
88 | + # The defined levels, in order of increasing severity : DEBUG, INFO, WARNING, ERROR, CRITICAL | |
89 | + logger.setLevel(logging.DEBUG) # better to have too much log than not enough | |
90 | + | |
91 | + # Create and add handlers to the logger | |
92 | + logger.addHandler(get_console_handler()) | |
93 | + logger.addHandler(get_file_handler()) | |
94 | + # TODO: | |
95 | + ##logger.addHandler(db_handler) | |
96 | + ##logger.addHandler(m_handler) | |
97 | + | |
98 | + # with this pattern, it's rarely necessary to propagate the error up to parent | |
99 | + ##logger.propagate = False | |
100 | + return logger | |
101 | + | |
102 | + | |
103 | + | |
104 | +# Shortcuts for logger: | |
105 | +#def log_d(msg:str): logger.debug(msg) | |
106 | +def log_d(msg:str, *args, **kwargs): logger.debug(msg, *args, **kwargs) | |
107 | +def log_i(msg:str, *args, **kwargs): logger.info(msg, *args, **kwargs) | |
108 | +def log_w(msg:str, *args, **kwargs): logger.warning(msg, *args, **kwargs) | |
109 | +def log_e(msg:str, *args, **kwargs): logger.error(msg, *args, **kwargs) | |
110 | +def log_c(msg:str, *args, **kwargs): logger.critical(msg, *args, **kwargs) | |
111 | + | |
112 | + | |
113 | + | |
114 | + | |
115 | +if __name__ == "__main__": | |
116 | + my_logger = get_logger("my_module_name") | |
117 | + my_logger.debug("a debug message") | ... | ... |