Commit 92a28ec7a8c66daed382a1bcfd9a9c29e5d4b178

Authored by Etienne Pallier
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...)
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
... ... @@ -13,7 +13,7 @@ if (py_pwd not in py_path):
13 13 # --- import PyROS celestial mechanics from the pyros root directory
14 14 import src.core.celme as celme
15 15  
16   -class LogPyros:
  16 +class LogPyrosOriginal:
17 17 """
18 18 Manage PyROS logs in display, file and database.
19 19  
... ...
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)
... ...
src/logpyros.py 0 → 100644
... ... @@ -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 +
... ...
src/pyros_future_logger.py 0 → 100755
... ... @@ -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")
... ...