diff --git a/HOWTO_TEST.txt b/HOWTO_TEST.txt index 336de13..f787eb2 100644 --- a/HOWTO_TEST.txt +++ b/HOWTO_TEST.txt @@ -13,8 +13,10 @@ $ ./pyros.py testall (B) Test with agents: -- (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): +- (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): $ ./pyros.py -ts start agentDeviceGemini,agentDeviceSBIG,agentMultiRequester + (add "-d" option for debug mode) - (2) Test against REAL Gemini telescope $ ./pyros.py -t start agentDeviceGemini,agentMultiRequester diff --git a/src/core/pyros_django/agent/Agent.py b/src/core/pyros_django/agent/Agent.py index f23958f..9874bc3 100755 --- a/src/core/pyros_django/agent/Agent.py +++ b/src/core/pyros_django/agent/Agent.py @@ -116,7 +116,8 @@ from config.configpyros import ConfigPyros #from devices.TelescopeRemoteControlDefault import TelescopeRemoteControlDefault #from utils.JDManipulator import * -from agent.logpyros import LogPyros +##from agent.logpyros import LogPyros +from src.logpyros import LogPyros from device_controller.abstract_component.device_controller import DCCNotFoundException, UnimplementedGenericCmdException, UnknownNativeCmdException @@ -398,12 +399,17 @@ class Agent: #def __init__(self, name:str="Agent", config_filename:str=None, RUN_IN_THREAD=True): def __init__(self, config_filename:str=None, RUN_IN_THREAD=True, DEBUG_MODE=False): #self.name = name - printd("*** ENVIRONMENT VARIABLE PYROS_DEBUG is:", os.environ.get('PYROS_DEBUG'), '***') self.name = self.__class__.__name__ - self.DEBUG_MODE = DEBUG_MODE + printd("*** ENVIRONMENT VARIABLE PYROS_DEBUG is:", os.environ.get('PYROS_DEBUG'), '***') + ##self.DEBUG_MODE = DEBUG_MODE + self.DEBUG_MODE = os.environ.get('PYROS_DEBUG', '0')=='1' self._log = LogPyros(self.name, AgentLogs) - self._log.debug_level = DEBUG_MODE - self.printd("LOG DEBUG LEVEL IS:", self._log.debug_level) + ##self._log.debug_level = DEBUG_MODE + # Default LOG level is INFO + log_level = LogPyros.LOG_LEVEL_INFO # INFO + self._log.set_global_log_level(LogPyros.LOG_LEVEL_DEBUG) if self.DEBUG_MODE else self._log.set_global_log_level(log_level) + ##self.printd("LOG LEVEL IS:", self._log.debug_level) + self.print("LOG LEVEL IS:", self._log.get_global_log_level()) # New way with PathLib my_parent_abs_dir = Path(__file__).resolve().parent @@ -983,7 +989,10 @@ class Agent: self.printd("------------------------------------------") # --- update the log parameters - self._log.path_data = self._path_data + ##self._log.path_data = self._path_data + ##print("new self._log.path_data is", self._log.path_data) + self._log.set_global_path_data(self._path_data) + print("new self._log.global_path_data is", self._log.get_global_path_data()) self._log.home = home diff --git a/src/core/pyros_django/agent/logpyros.py b/src/core/pyros_django/agent/logpyros.py deleted file mode 100644 index e3b19ac..0000000 --- a/src/core/pyros_django/agent/logpyros.py +++ /dev/null @@ -1,369 +0,0 @@ -import os -import math -import tempfile -from io import StringIO -import sys - -# --- update the path of Python in case to test this file alone -py_path = os.sys.path -py_pwd = os.path.normpath(os.getcwd() + "/../../../..") -if (py_pwd not in py_path): - (os.sys.path).append(py_pwd) - -# --- import PyROS celestial mechanics from the pyros root directory -import src.core.celme as celme - -class LogPyros: - """ - Manage PyROS logs in display, file and database. - - First, create an instance: - log = LogPyros("test",None) - - Second, use the print methods to log: - log.print("Something to log") - """ - - # === Level of log - LOG_LEVEL_INFO = 0 - LOG_LEVEL_WARNING = -1 - LOG_LEVEL_ERROR = -2 - LOG_LEVEL_DEBUG = 1 - LOG_LEVEL_DEBUG2 = 2 - - # === Constants - NO_ERROR = 0 - - ERR_ALIAS_NAME_NOT_DEFINED = 10 - - ERR_FILE_NOT_EXISTS = 101 - ERR_PATH_NOT_EXISTS = 102 - ERR_EMPTY_FILENAME = 103 - ERR_EMPTY_PATHNAME = 104 - - # === Private variables - _last_errno = NO_ERROR - - _path_data = '' - _agent_alias = '' - _path_data_log = '' - _path_wwwpyros = '' - - _date = None - _home = None - _noon_hour = 12 - _last_lines = list() - _nbmax_last_lines = 30 - -# ===================================================================== -# ===================================================================== -# Private methods -# ===================================================================== -# ===================================================================== - - def _mkdir_path_log(self): - # --- - if self._agent_alias == "": - self._last_errno = self.ERR_EMPTY_FILENAME - else: - self._path_www_log = self._path_wwwpyros + "/logs/" + self._agent_alias - os.mkdir(self._path_www_log) - # --- - if self._path_data == "": - self._last_errno = self.ERR_EMPTY_PATHNAME - else: - self._path_data_log = self._path_data + "/logs/" + self._agent_alias - os.mkdir(self._path_data_log) - - def _jd2datedigit(self, jd): - """ Convert a julian day into a string of YYYYMMDD - """ - datetmp = self._date.date(jd); - datedigit = (datetmp.digits(0))[0:8] - return datedigit - - def _date2night(self, date): - # night is the truncated part of the date of the previous noon - self._date.date(date) - jd = self._date.jd() - jd0 = math.floor(jd) - self._noon_hour/24 - d = celme.Date(jd0) - djd = jd-jd0 - # print(f"jd0 = {d.iso()} djd={djd}") - if djd>=1: - jd0 += 1 - djd = jd-jd0 - if djd>=1: - jd0 += 1 - d = celme.Date(jd0) - night = d.digits(0)[0:8] - return night - - def _night2dates(self, night): - """ - jd_noon0 = previous noon - jd_midnight = midnight instant - jd_noon1 = next noon - """ - d = celme.Date(night) - d_noon0 = d + self._noon_hour/24 - jd_noon0 = d_noon0.jd() - jd_midnight = jd_noon0 + 0.5 - jd_noon1 = jd_noon0 + 1.0 - return jd_noon0, jd_midnight, jd_noon1 - - def _convert_args2str(self, *args, **kwargs): - """ - return a string as the result of a print method - """ - bak = sys.stdout # on sauvegarde l'ancien stdout - result = StringIO() - sys.stdout = result - print (*args, **kwargs,end='') - sys.stdout = bak # on restore stdout - return result.getvalue() - -# ===================================================================== -# ===================================================================== -# Private methods getter/setter -# ===================================================================== -# ===================================================================== - - def _set_debug_level(self, level:str): - if type(level).__name__=="bool": - if level==True: - level = 1 - else: - level = 0 - self._debug_level = level - - def _get_debug_level(self): - return self._debug_level - - def _set_agent_alias(self, agent_alias:str): - if agent_alias=="": - self._last_errno = self.ERR_ALIAS_NAME_NOT_DEFINED - raise Exception(f"Agent alias must be not be empty string.") - self._agent_alias = agent_alias - return self.NO_ERROR - - def _get_agent_alias(self): - return self._agent_alias - - def _set_path_data(self, path_data:str): - if not os.path.exists(path_data): - self._last_errno = self.ERR_PATH_NOT_EXISTS - raise Exception(f"Path '{path_data}' for data does not exists. Create it first manually.") - self._path_data = path_data - return self.NO_ERROR - - def _get_path_data(self): - return self._path_data - - def _set_path_wwwpyros(self, path_wwwpyros:str): - if not os.path.exists(path_wwwpyros): - self._path_wwwpyros = self.ERR_PATH_NOT_EXISTS - raise Exception(f"Path '{path_wwwpyros}' for wwwpyros does not exists. Create it first manually.") - self._path_wwwpyros = path_wwwpyros - return self.NO_ERROR - - def _get_path_wwwpyros(self): - return self._path_wwwpyros - - def _set_home(self, home:str): - self._home.home(home) - longitude = self._home.longitude - self._noon_hour = (180 - longitude)/15.0 - # - place noon_hour in the range [0 24[ - if self._noon_hour < 0: - self._noon_hour += 24 - return self.NO_ERROR - - def _get_home(self): - return self._home.gps - - def _set_logtable(self, logtable): - self._logtable = logtable - return self.NO_ERROR - - def _get_logtable(self): - return self._logtable - - def _set_nbmax_last_lines(self, nbmax_last_lines): - self._nbmax_last_lines = nbmax_last_lines - return self.NO_ERROR - - def _get_nbmax_last_lines(self): - return self._nbmax_last_lines - -# ===================================================================== -# ===================================================================== -# Property methods -# ===================================================================== -# ===================================================================== - - agent_alias = property(_get_agent_alias, _set_agent_alias) - - path_data = property(_get_path_data, _set_path_data) - - path_wwwpyros = property(_get_path_wwwpyros, _set_path_wwwpyros) - - home = property(_get_home, _set_home) - - logtable = property(_get_logtable, _set_logtable) - -# ===================================================================== -# ===================================================================== -# Public Property methods -# ===================================================================== -# ===================================================================== - - debug_level = property(_get_debug_level, _set_debug_level) - - nbmax_last_lines = property(_get_nbmax_last_lines, _set_nbmax_last_lines) - -# ===================================================================== -# ===================================================================== -# Methods for users -# ===================================================================== -# ===================================================================== - - def print(self, *args, **kwargs): - """ - This is the method to print in the console display and in a log file. - In the console display, the message is presented as: - (Agent_name) message - In the log file the message is presented as: - Date-ISO message - """ - msg = self._convert_args2str(*args, **kwargs) - # --- prepare the super_msg - if msg=="": - super_msg = msg - else: - super_msg = f"({self._agent_alias}) {msg}" - # --- classical print - print(super_msg) - # --- no more if the agent name is not defined - if self._agent_alias=="": - return - # --- - self.file(msg) - - def printd(self, *args, **kwargs): - """ - Same as print method but only if debug level is > threashold - """ - if self._debug_level > 0: - self.print(*args, **kwargs) - - def file(self, *args, **kwargs): - """ - This is the method to print in a log file. - In the log file the message is presented as: - Date-ISO message - The last file is also apended with the message - """ - msg = self._convert_args2str(*args, **kwargs) - # --- no more if the agent name is not defined - if self._agent_alias=="": - return - # --- check the path to write the log file - path = os.path.normpath(self._path_data + os.sep + "logs" + os.sep + self._agent_alias) - if not os.path.exists(path): - try: - os.makedirs(path) - except: - p = f"Cannot create path {path}" - raise Exception(p) - # --- compute the current night digital date - night = self._date2night("now") - # --- build the log file name - file = os.path.normpath(path + os.sep + self._agent_alias + '_' + night + '.log') - # --- prepare the super_msg - self._date.date("NOW") - super_msg = f"{self._date.iso()} {msg}" - # --- write super_msg in the log file - with open(file,'a') as fic: - fic.write(super_msg+"\n") - # --- last lines - self._last_lines.append(super_msg) - n = len(self._last_lines) - if n>self.nbmax_last_lines: - self._last_lines = self._last_lines[n-self.nbmax_last_lines:] - # --- build the log file name - file = os.path.normpath(path + os.sep + self._agent_alias + '_' + 'last' + '.log') - # --- write super_msg in the log file - with open(file,'w') as fic: - for line in self._last_lines: - fic.write(line+"\n") - - - def db(self, *args, **kwargs): - """ - This is the method to print in the agent_log table of the database. - """ - if self.logtable==None: - return - msg = self._convert_args2str(*args, **kwargs) - self.logtable.objects.create(name=self._agent_alias, message=msg) - -# ===================================================================== -# ===================================================================== -# Special methods -# ===================================================================== -# ===================================================================== - - def __init__(self, agent_alias:str, logtable=None, home:str="GPS 0 E 43 150", path_data:str="", path_wwwpyros:str="" ): - self._last_errno = self.NO_ERROR - # --- - if agent_alias != "": - self.agent_alias = agent_alias - else: - self.agent_alias = "Unknown_agent" - # --- - if path_data != "": - self.path_data = path_data - else: - self.path_data = tempfile.gettempdir() - # --- - if path_wwwpyros != "": - self.path_wwwpyros = path_wwwpyros - self._date = celme.Date() - self._home = celme.Home(home) - self._noon_hour = 12 ; # local hour corresponding to the date change - self.logtable = None - self.nbmax_last_lines = 30 - -# ===================================================================== -# ===================================================================== -# Test if main -# ===================================================================== -# ===================================================================== - -if __name__ == "__main__": - - # === Instance parameters - - # --- agent_alias = string added at the begining of each line of display log - # --- agent_alias = is the name of log files - # --- agent_alias = is the field name of database logs - agent_alias = "test" - # --- agent_alias = directory where are written log files - path_data = "c:/srv/work/pyros" - # --- home = define the hour of noon when log files are archived - home = "GPS 2 E 43 148" - - # === Instanciation - - # --- The second parameter is the Django object for database - log = LogPyros(agent_alias,None,home,path_data) - - # === Use of logs. Parameters are the same as a Python print - - a = 2 - b = 'tutu' - log.print(f"a={a} b={b}") - - log.print(a,b) - diff --git a/src/core/pyros_django/agent/logpyros_orig.py b/src/core/pyros_django/agent/logpyros_orig.py new file mode 100644 index 0000000..61f80ea --- /dev/null +++ b/src/core/pyros_django/agent/logpyros_orig.py @@ -0,0 +1,369 @@ +import os +import math +import tempfile +from io import StringIO +import sys + +# --- update the path of Python in case to test this file alone +py_path = os.sys.path +py_pwd = os.path.normpath(os.getcwd() + "/../../../..") +if (py_pwd not in py_path): + (os.sys.path).append(py_pwd) + +# --- import PyROS celestial mechanics from the pyros root directory +import src.core.celme as celme + +class LogPyrosOriginal: + """ + Manage PyROS logs in display, file and database. + + First, create an instance: + log = LogPyros("test",None) + + Second, use the print methods to log: + log.print("Something to log") + """ + + # === Level of log + LOG_LEVEL_INFO = 0 + LOG_LEVEL_WARNING = -1 + LOG_LEVEL_ERROR = -2 + LOG_LEVEL_DEBUG = 1 + LOG_LEVEL_DEBUG2 = 2 + + # === Constants + NO_ERROR = 0 + + ERR_ALIAS_NAME_NOT_DEFINED = 10 + + ERR_FILE_NOT_EXISTS = 101 + ERR_PATH_NOT_EXISTS = 102 + ERR_EMPTY_FILENAME = 103 + ERR_EMPTY_PATHNAME = 104 + + # === Private variables + _last_errno = NO_ERROR + + _path_data = '' + _agent_alias = '' + _path_data_log = '' + _path_wwwpyros = '' + + _date = None + _home = None + _noon_hour = 12 + _last_lines = list() + _nbmax_last_lines = 30 + +# ===================================================================== +# ===================================================================== +# Private methods +# ===================================================================== +# ===================================================================== + + def _mkdir_path_log(self): + # --- + if self._agent_alias == "": + self._last_errno = self.ERR_EMPTY_FILENAME + else: + self._path_www_log = self._path_wwwpyros + "/logs/" + self._agent_alias + os.mkdir(self._path_www_log) + # --- + if self._path_data == "": + self._last_errno = self.ERR_EMPTY_PATHNAME + else: + self._path_data_log = self._path_data + "/logs/" + self._agent_alias + os.mkdir(self._path_data_log) + + def _jd2datedigit(self, jd): + """ Convert a julian day into a string of YYYYMMDD + """ + datetmp = self._date.date(jd); + datedigit = (datetmp.digits(0))[0:8] + return datedigit + + def _date2night(self, date): + # night is the truncated part of the date of the previous noon + self._date.date(date) + jd = self._date.jd() + jd0 = math.floor(jd) - self._noon_hour/24 + d = celme.Date(jd0) + djd = jd-jd0 + # print(f"jd0 = {d.iso()} djd={djd}") + if djd>=1: + jd0 += 1 + djd = jd-jd0 + if djd>=1: + jd0 += 1 + d = celme.Date(jd0) + night = d.digits(0)[0:8] + return night + + def _night2dates(self, night): + """ + jd_noon0 = previous noon + jd_midnight = midnight instant + jd_noon1 = next noon + """ + d = celme.Date(night) + d_noon0 = d + self._noon_hour/24 + jd_noon0 = d_noon0.jd() + jd_midnight = jd_noon0 + 0.5 + jd_noon1 = jd_noon0 + 1.0 + return jd_noon0, jd_midnight, jd_noon1 + + def _convert_args2str(self, *args, **kwargs): + """ + return a string as the result of a print method + """ + bak = sys.stdout # on sauvegarde l'ancien stdout + result = StringIO() + sys.stdout = result + print (*args, **kwargs,end='') + sys.stdout = bak # on restore stdout + return result.getvalue() + +# ===================================================================== +# ===================================================================== +# Private methods getter/setter +# ===================================================================== +# ===================================================================== + + def _set_debug_level(self, level:str): + if type(level).__name__=="bool": + if level==True: + level = 1 + else: + level = 0 + self._debug_level = level + + def _get_debug_level(self): + return self._debug_level + + def _set_agent_alias(self, agent_alias:str): + if agent_alias=="": + self._last_errno = self.ERR_ALIAS_NAME_NOT_DEFINED + raise Exception(f"Agent alias must be not be empty string.") + self._agent_alias = agent_alias + return self.NO_ERROR + + def _get_agent_alias(self): + return self._agent_alias + + def _set_path_data(self, path_data:str): + if not os.path.exists(path_data): + self._last_errno = self.ERR_PATH_NOT_EXISTS + raise Exception(f"Path '{path_data}' for data does not exists. Create it first manually.") + self._path_data = path_data + return self.NO_ERROR + + def _get_path_data(self): + return self._path_data + + def _set_path_wwwpyros(self, path_wwwpyros:str): + if not os.path.exists(path_wwwpyros): + self._path_wwwpyros = self.ERR_PATH_NOT_EXISTS + raise Exception(f"Path '{path_wwwpyros}' for wwwpyros does not exists. Create it first manually.") + self._path_wwwpyros = path_wwwpyros + return self.NO_ERROR + + def _get_path_wwwpyros(self): + return self._path_wwwpyros + + def _set_home(self, home:str): + self._home.home(home) + longitude = self._home.longitude + self._noon_hour = (180 - longitude)/15.0 + # - place noon_hour in the range [0 24[ + if self._noon_hour < 0: + self._noon_hour += 24 + return self.NO_ERROR + + def _get_home(self): + return self._home.gps + + def _set_logtable(self, logtable): + self._logtable = logtable + return self.NO_ERROR + + def _get_logtable(self): + return self._logtable + + def _set_nbmax_last_lines(self, nbmax_last_lines): + self._nbmax_last_lines = nbmax_last_lines + return self.NO_ERROR + + def _get_nbmax_last_lines(self): + return self._nbmax_last_lines + +# ===================================================================== +# ===================================================================== +# Property methods +# ===================================================================== +# ===================================================================== + + agent_alias = property(_get_agent_alias, _set_agent_alias) + + path_data = property(_get_path_data, _set_path_data) + + path_wwwpyros = property(_get_path_wwwpyros, _set_path_wwwpyros) + + home = property(_get_home, _set_home) + + logtable = property(_get_logtable, _set_logtable) + +# ===================================================================== +# ===================================================================== +# Public Property methods +# ===================================================================== +# ===================================================================== + + debug_level = property(_get_debug_level, _set_debug_level) + + nbmax_last_lines = property(_get_nbmax_last_lines, _set_nbmax_last_lines) + +# ===================================================================== +# ===================================================================== +# Methods for users +# ===================================================================== +# ===================================================================== + + def print(self, *args, **kwargs): + """ + This is the method to print in the console display and in a log file. + In the console display, the message is presented as: + (Agent_name) message + In the log file the message is presented as: + Date-ISO message + """ + msg = self._convert_args2str(*args, **kwargs) + # --- prepare the super_msg + if msg=="": + super_msg = msg + else: + super_msg = f"({self._agent_alias}) {msg}" + # --- classical print + print(super_msg) + # --- no more if the agent name is not defined + if self._agent_alias=="": + return + # --- + self.file(msg) + + def printd(self, *args, **kwargs): + """ + Same as print method but only if debug level is > threashold + """ + if self._debug_level > 0: + self.print(*args, **kwargs) + + def file(self, *args, **kwargs): + """ + This is the method to print in a log file. + In the log file the message is presented as: + Date-ISO message + The last file is also apended with the message + """ + msg = self._convert_args2str(*args, **kwargs) + # --- no more if the agent name is not defined + if self._agent_alias=="": + return + # --- check the path to write the log file + path = os.path.normpath(self._path_data + os.sep + "logs" + os.sep + self._agent_alias) + if not os.path.exists(path): + try: + os.makedirs(path) + except: + p = f"Cannot create path {path}" + raise Exception(p) + # --- compute the current night digital date + night = self._date2night("now") + # --- build the log file name + file = os.path.normpath(path + os.sep + self._agent_alias + '_' + night + '.log') + # --- prepare the super_msg + self._date.date("NOW") + super_msg = f"{self._date.iso()} {msg}" + # --- write super_msg in the log file + with open(file,'a') as fic: + fic.write(super_msg+"\n") + # --- last lines + self._last_lines.append(super_msg) + n = len(self._last_lines) + if n>self.nbmax_last_lines: + self._last_lines = self._last_lines[n-self.nbmax_last_lines:] + # --- build the log file name + file = os.path.normpath(path + os.sep + self._agent_alias + '_' + 'last' + '.log') + # --- write super_msg in the log file + with open(file,'w') as fic: + for line in self._last_lines: + fic.write(line+"\n") + + + def db(self, *args, **kwargs): + """ + This is the method to print in the agent_log table of the database. + """ + if self.logtable==None: + return + msg = self._convert_args2str(*args, **kwargs) + self.logtable.objects.create(name=self._agent_alias, message=msg) + +# ===================================================================== +# ===================================================================== +# Special methods +# ===================================================================== +# ===================================================================== + + def __init__(self, agent_alias:str, logtable=None, home:str="GPS 0 E 43 150", path_data:str="", path_wwwpyros:str="" ): + self._last_errno = self.NO_ERROR + # --- + if agent_alias != "": + self.agent_alias = agent_alias + else: + self.agent_alias = "Unknown_agent" + # --- + if path_data != "": + self.path_data = path_data + else: + self.path_data = tempfile.gettempdir() + # --- + if path_wwwpyros != "": + self.path_wwwpyros = path_wwwpyros + self._date = celme.Date() + self._home = celme.Home(home) + self._noon_hour = 12 ; # local hour corresponding to the date change + self.logtable = None + self.nbmax_last_lines = 30 + +# ===================================================================== +# ===================================================================== +# Test if main +# ===================================================================== +# ===================================================================== + +if __name__ == "__main__": + + # === Instance parameters + + # --- agent_alias = string added at the begining of each line of display log + # --- agent_alias = is the name of log files + # --- agent_alias = is the field name of database logs + agent_alias = "test" + # --- agent_alias = directory where are written log files + path_data = "c:/srv/work/pyros" + # --- home = define the hour of noon when log files are archived + home = "GPS 2 E 43 148" + + # === Instanciation + + # --- The second parameter is the Django object for database + log = LogPyros(agent_alias,None,home,path_data) + + # === Use of logs. Parameters are the same as a Python print + + a = 2 + b = 'tutu' + log.print(f"a={a} b={b}") + + log.print(a,b) + diff --git a/src/core/pyros_django/common/models.py b/src/core/pyros_django/common/models.py index 307a873..107da54 100644 --- a/src/core/pyros_django/common/models.py +++ b/src/core/pyros_django/common/models.py @@ -296,6 +296,7 @@ class AgentLogs(models.Model): def __str__(self): return (f"{self.created} {self.name} {self.message}") + class AgentSurvey(models.Model): """ | id | name | created | updated | validity_duration (default=1mn) | mode (active/idle) | status (launch/init/loop/exit/...) | diff --git a/src/device_controller/abstract_component/device_controller.py b/src/device_controller/abstract_component/device_controller.py index 47c2af6..6ee6037 100755 --- a/src/device_controller/abstract_component/device_controller.py +++ b/src/device_controller/abstract_component/device_controller.py @@ -28,6 +28,7 @@ import time sys.path.append("../../../..") #import src.core.pyros_django.utils.celme as celme import src.core.celme as celme +from src.logpyros import LogPyros #sys.path.append('../..') #from src.client.socket_client_abstract import UnknownNativeCmdException, SocketClientAbstract @@ -51,8 +52,14 @@ GET_ONLY=False TIMEOUT_SEND = 10 TIMEOUT_RECEIVE = 10 - -def printd(*args, **kwargs): +''' +logger = LogPyros(__name__) +def log(self, *args, **kwargs): logger.print(*args, **kwargs) +# DEBUG print +def printd(self, *args, **kwargs): logger.printd(*args, **kwargs) +def tprintd(self, *args, **kwargs): printd('(THREAD):', *args, *kwargs) +''' +def printd(*args, **kwargs): if os.environ.get('PYROS_DEBUG', '0')=='1': print(*args, **kwargs) @@ -451,8 +458,11 @@ class DeviceController(): #self.DEBUG_MODE = DEBUG self.DEBUG_MODE = os.environ.get('PYROS_DEBUG', '0') == '1' - set_logger(self.DEBUG_MODE) - log_d("Logger configured") + ##set_logger(self.DEBUG_MODE) + ##log_d("Logger configured") + self._log = LogPyros(self.__class__.__name__) + self.print("coucou") + # Set host (IP) and port #printd("IN DeviceController") @@ -506,6 +516,8 @@ class DeviceController(): + + # Set my list of dc components def set_dc_components(self, dc_components:list): self._my_dc_components = dc_components @@ -521,10 +533,12 @@ class DeviceController(): self._my_channel.set_logger(DEBUG) ''' + def print(self, *args, **kwargs): self._log.print(*args, **kwargs) + # DEBUG print def printd(self, *args, **kwargs): - #self._log.printd(*args, **kwargs) - if self.DEBUG_MODE: printd(*args, **kwargs) + self._log.printd(*args, **kwargs) + #if self.DEBUG_MODE: printd(*args, **kwargs) def tprintd(self, *args, **kwargs): self.printd('(THREAD):', *args, *kwargs) diff --git a/src/logpyros.py b/src/logpyros.py new file mode 100644 index 0000000..66856a6 --- /dev/null +++ b/src/logpyros.py @@ -0,0 +1,459 @@ +import os +import math +import tempfile +from io import StringIO +import sys + +# --- update the path of Python in case to test this file alone +py_path = os.sys.path +py_pwd = os.path.normpath(os.getcwd() + "/../../../..") +if (py_pwd not in py_path): + (os.sys.path).append(py_pwd) + +# --- import PyROS celestial mechanics from the pyros root directory +import src.core.celme as celme + + +''' +logger = None + +def log_d(msg:str, *args, **kwargs): logger.debug(msg, *args, **kwargs) +def log_i(msg:str, *args, **kwargs): logger.info(msg, *args, **kwargs) +def log_w(msg:str, *args, **kwargs): logger.warning(msg, *args, **kwargs) +def log_e(msg:str, *args, **kwargs): logger.error(msg, *args, **kwargs) +def log_c(msg:str, *args, **kwargs): logger.critical(msg, *args, **kwargs) +''' + +class LogPyros: + """ + Manage PyROS logs in display, file and database. + + First, create an instance: + log = LogPyros("test",None) + + Second, use the print methods to log: + log.print("Something to log") + """ + + # CLASS attributes + _GLOBAL_PATH_DATA = '' + + # === Level of log + ''' + LOG_LEVEL_INFO = 0 + LOG_LEVEL_WARNING = -1 + LOG_LEVEL_ERROR = -2 + LOG_LEVEL_DEBUG = 1 + LOG_LEVEL_DEBUG2 = 2 + ''' + # Python standard logging module values: NOTSET=0, DEBUG=10, INFO=20, WARN=30, ERROR=40, CRITICAL=50 + LOG_LEVEL_NOTSET = 0 + LOG_LEVEL_DEBUG = 10 + LOG_LEVEL_DEBUG2 = 15 + LOG_LEVEL_INFO = 20 + LOG_LEVEL_WARNING = 30 + LOG_LEVEL_ERROR = 40 + LOG_LEVEL_CRITICAL = 50 + + # Default LOG level is NOTSET (no filter) + _GLOBAL_LOG_LEVEL = LOG_LEVEL_NOTSET + + # === Constants + NO_ERROR = 0 + + ERR_ALIAS_NAME_NOT_DEFINED = 10 + + ERR_FILE_NOT_EXISTS = 101 + ERR_PATH_NOT_EXISTS = 102 + ERR_EMPTY_FILENAME = 103 + ERR_EMPTY_PATHNAME = 104 + + # === Private variables + _last_errno = NO_ERROR + + ##_path_data = '' + _caller_alias = '' + _path_data_log = '' + _path_wwwpyros = '' + + _date = None + _home = None + _noon_hour = 12 + #_last_lines = {'chrono':[], 'agent':[]} + _last_lines = _last_lines_agent = [] + _nbmax_last_lines = 30 + +# ===================================================================== +# ===================================================================== +# Private methods +# ===================================================================== +# ===================================================================== + + ''' + def _mkdir_path_log(self): + # --- + if self._caller_alias == "": + self._last_errno = self.ERR_EMPTY_FILENAME + else: + self._path_www_log = self._path_wwwpyros + "/logs/" + self._caller_alias + os.mkdir(self._path_www_log) + # --- + if self._path_data == "": + self._last_errno = self.ERR_EMPTY_PATHNAME + else: + self._path_data_log = self._path_data + "/logs/" + self._caller_alias + os.mkdir(self._path_data_log) + ''' + + def _jd2datedigit(self, jd): + """ Convert a julian day into a string of YYYYMMDD + """ + datetmp = self._date.date(jd); + datedigit = (datetmp.digits(0))[0:8] + return datedigit + + def _date2night(self, date): + # night is the truncated part of the date of the previous noon + self._date.date(date) + jd = self._date.jd() + jd0 = math.floor(jd) - self._noon_hour/24 + d = celme.Date(jd0) + djd = jd-jd0 + # print(f"jd0 = {d.iso()} djd={djd}") + if djd>=1: + jd0 += 1 + djd = jd-jd0 + if djd>=1: + jd0 += 1 + d = celme.Date(jd0) + night = d.digits(0)[0:8] + return night + + def _night2dates(self, night): + """ + jd_noon0 = previous noon + jd_midnight = midnight instant + jd_noon1 = next noon + """ + d = celme.Date(night) + d_noon0 = d + self._noon_hour/24 + jd_noon0 = d_noon0.jd() + jd_midnight = jd_noon0 + 0.5 + jd_noon1 = jd_noon0 + 1.0 + return jd_noon0, jd_midnight, jd_noon1 + + def _convert_args2str(self, *args, **kwargs): + """ + return a string as the result of a print method + """ + bak = sys.stdout # on sauvegarde l'ancien stdout + result = StringIO() + sys.stdout = result + print (*args, **kwargs,end='') + sys.stdout = bak # on restore stdout + return result.getvalue() + +# ===================================================================== +# ===================================================================== +# Private methods getter/setter +# ===================================================================== +# ===================================================================== + + + def set_global_log_level(self, log_level:int): + LogPyros._GLOBAL_LOG_LEVEL = log_level + def get_global_log_level(self): + return LogPyros._GLOBAL_LOG_LEVEL + + ''' + def _set_debug_level(self, level:str): + if type(level).__name__=="bool": + if level==True: + level = 1 + else: + level = 0 + self._debug_level = level + def _get_debug_level(self): + return self._debug_level + ''' + + def _set_caller_alias(self, caller_alias:str): + if caller_alias=="": + self._last_errno = self.ERR_ALIAS_NAME_NOT_DEFINED + raise Exception(f"Agent alias must not be empty string.") + self._caller_alias = caller_alias + return self.NO_ERROR + + def _get_caller_alias(self): + return self._caller_alias + + def _set_path_data(self, path_data:str): + if not os.path.exists(path_data): + self._last_errno = self.ERR_PATH_NOT_EXISTS + raise Exception(f"Path '{path_data}' for data does not exists. Create it first manually.") + self._path_data = path_data + return self.NO_ERROR + def _get_path_data(self): + return self._path_data + + def set_global_path_data(self, path_data:str): + if not os.path.exists(path_data): + self._last_errno = self.ERR_PATH_NOT_EXISTS + raise Exception(f"Path '{path_data}' for data does not exists. Create it first manually.") + LogPyros._GLOBAL_PATH_DATA = path_data + return self.NO_ERROR + def get_global_path_data(self): + return LogPyros._GLOBAL_PATH_DATA + def get_full_GLOBAL_PATH_DATA(self): + return LogPyros._GLOBAL_PATH_DATA + os.sep + "logs" + + def _set_path_wwwpyros(self, path_wwwpyros:str): + if not os.path.exists(path_wwwpyros): + self._path_wwwpyros = self.ERR_PATH_NOT_EXISTS + raise Exception(f"Path '{path_wwwpyros}' for wwwpyros does not exists. Create it first manually.") + self._path_wwwpyros = path_wwwpyros + return self.NO_ERROR + + def _get_path_wwwpyros(self): + return self._path_wwwpyros + + def _set_home(self, home:str): + self._home.home(home) + longitude = self._home.longitude + self._noon_hour = (180 - longitude)/15.0 + # - place noon_hour in the range [0 24[ + if self._noon_hour < 0: + self._noon_hour += 24 + return self.NO_ERROR + + def _get_home(self): + return self._home.gps + + def _set_logtable(self, logtable): + self._logtable = logtable + return self.NO_ERROR + + def _get_logtable(self): + return self._logtable + + def _set_nbmax_last_lines(self, nbmax_last_lines): + self._nbmax_last_lines = nbmax_last_lines + return self.NO_ERROR + + def _get_nbmax_last_lines(self): + return self._nbmax_last_lines + +# ===================================================================== +# ===================================================================== +# Property methods +# ===================================================================== +# ===================================================================== + + caller_alias = property(_get_caller_alias, _set_caller_alias) + + ##path_data = property(_get_path_data, _set_path_data) + + path_wwwpyros = property(_get_path_wwwpyros, _set_path_wwwpyros) + + home = property(_get_home, _set_home) + + logtable = property(_get_logtable, _set_logtable) + +# ===================================================================== +# ===================================================================== +# Public Property methods +# ===================================================================== +# ===================================================================== + + ##debug_level = property(_get_debug_level, _set_debug_level) + + nbmax_last_lines = property(_get_nbmax_last_lines, _set_nbmax_last_lines) + +# ===================================================================== +# ===================================================================== +# Methods for users +# ===================================================================== +# ===================================================================== + + def log_if_level_lower_than(self, log_level, *args, **kwargs): + """ + This is the method to print in the console display and in a log file. + In the console display, the message is presented as: + (Agent_name) message + In the log file the message is presented as: + Date-ISO message + """ + # FILTER : log only if global log level is <= log_level + if self.get_global_log_level() > log_level: return + + msg = self._convert_args2str(*args, **kwargs) + # --- prepare the super_msg + formatted_msg = f"({self._caller_alias}) {msg}" if msg else "" + # --- classical print + print(formatted_msg) + # --- no more if the agent name is not defined + if not self._caller_alias: return + # --- + # LOG into a file if Agent or if level is enough + ##self.file(msg) + ##if self._caller_alias.startswith('Agent') or log_level >= self.LOG_LEVEL_WARNING: + if self._caller_alias.startswith('Agent') or log_level >= self.LOG_LEVEL_INFO: + self.file(msg) + + ##if self._debug_level > 0: + def printd(self, *args, **kwargs): self.log_d(*args, **kwargs) + def log_d(self, *args, **kwargs): self.log_if_level_lower_than(self.LOG_LEVEL_DEBUG, *args, **kwargs) + def log_i(self, *args, **kwargs): self.log_if_level_lower_than(self.LOG_LEVEL_INFO, *args, **kwargs) + def log_w(self, *args, **kwargs): self.log_if_level_lower_than(self.LOG_LEVEL_WARNING, *args, **kwargs) + def log_e(self, *args, **kwargs): self.log_if_level_lower_than(self.LOG_LEVEL_ERROR, *args, **kwargs) + def log_c(self, *args, **kwargs): self.log_if_level_lower_than(self.LOG_LEVEL_CRITICAL, *args, **kwargs) + + # DEFAULT LOG methods that log at level INFO + def log(self, *args, **kwargs): self.log_i(*args, **kwargs) + def print(self, *args, **kwargs): self.log(*args, **kwargs) + + + + def log_msg_to_file(self, log_msg, path, file_name, night): + # 1) Create path if not exists + if not os.path.exists(path): + try: + os.makedirs(path) + except: + p = f"Cannot create log path {path}" + raise Exception(p) + # 2) APPEND msg to FULL DAY file + file_prefix = os.path.normpath(path + os.sep + file_name + '_') + with open(file_prefix+night+'.log', 'a') as fic: + fic.write(log_msg+"\n") + # 3) UPDATE (REWRITE) LAST LINES file (truncated to nbmax_last_lines) + #filetype = "chrono" if file_name=="pyros2" else "agent" + #ll = self._last_lines[filetype] + ll = self._last_lines if file_name=="pyros2" else self._last_lines_agent + ll.append(log_msg) + n = len(ll) + #print("n is", n, "self.nbmax_last_lines is", self.nbmax_last_lines) + if n > self.nbmax_last_lines: + ll = ll[n-self.nbmax_last_lines:] + #print("ll is", len(ll)) + #print("self._last_lines is", len(self._last_lines)) + ''' + if file_name == "pyros2": + self._last_lines.append(log_msg) + n = len(self._last_lines) + if n > self.nbmax_last_lines: + self._last_lines = self._last_lines[n-self.nbmax_last_lines:] + else: + self._last_lines[-1] = log_msg + ''' + with open(file_prefix+'last'+'.log','w') as fic: + for line in ll: + fic.write(line+"\n") + if file_name=="pyros2": + self._last_lines = ll + else: + self._last_lines_agent = ll + #self._last_lines[filetype] = ll + + + def file(self, *args, **kwargs): + """ + This is the method to print in a log file. + In the log file the message is presented as: + Date-ISO message + The last file is also appended with the message + """ + msg = self._convert_args2str(*args, **kwargs) + # --- no more if the agent name is not defined + if self._caller_alias=="": return + + # --- 1) Compute the current night digital date + night = self._date2night("now") + self._date.date("NOW") + + # --- 2) LOG message to the CHRONO daily unique log file name (pyros.log) + path = os.path.normpath(self.get_full_GLOBAL_PATH_DATA()) + #msg_prefix = f"{self._date.iso()} {self._caller_alias} {msg}" + msg_prefix = f"{self._date.iso()} ({self._caller_alias}) " + #file_prefix = os.path.normpath(path + os.sep + "pyros2" + '_') + file_name = "pyros2" + self.log_msg_to_file(msg_prefix + msg, path, file_name, night) + + # --- 3) (only) If AGENT, DO THE SAME but to the AGENT daily file (in its own folder) + if self._caller_alias.startswith("Agent"): + path += os.sep + self._caller_alias + msg_prefix = f"{self._date.iso()} " + file_name = self._caller_alias + self.log_msg_to_file(msg_prefix + msg, path, file_name, night) + + + + def db(self, *args, **kwargs): + """ + This is the method to print in the agent_log table of the database. + """ + if self.logtable==None: return + msg = self._convert_args2str(*args, **kwargs) + self.logtable.objects.create(name=self._caller_alias, message=msg) + +# ===================================================================== +# ===================================================================== +# Special methods +# ===================================================================== +# ===================================================================== + + def __init__(self, caller_name:str, logtable=None, home:str="GPS 0 E 43 150", path_data:str="", path_wwwpyros:str="" ): + self._last_errno = self.NO_ERROR + # --- + self.caller_alias = caller_name if caller_name else "Unknown_agent" + #print("CALLER IS set to ", self.caller_alias) + # --- + if path_data: + ##self.path_data = path_data + self.set_global_path_data(path_data) + elif not self.get_global_path_data(): + ##self.path_data = tempfile.gettempdir() + ##print("log self.path_data is", self.path_data) + self.set_global_path_data(tempfile.gettempdir()) + print("log self.global_path_data is", self.get_global_path_data()) + # --- + if path_wwwpyros != "": + self.path_wwwpyros = path_wwwpyros + self._date = celme.Date() + self._home = celme.Home(home) + self._noon_hour = 12 ; # local hour corresponding to the date change + self.logtable = None + self.nbmax_last_lines = 30 + +# ===================================================================== +# ===================================================================== +# Test if main +# ===================================================================== +# ===================================================================== + +if __name__ == "__main__": + + # === Instance parameters + + # --- caller_alias = string added at the begining of each line of display log + # --- caller_alias = is the name of log files + # --- caller_alias = is the field name of database logs + caller_alias = "test" + # --- caller_alias = directory where are written log files + path_data = "c:/srv/work/pyros" + # --- home = define the hour of noon when log files are archived + home = "GPS 2 E 43 148" + + # === Instanciation + + # --- The second parameter is the Django object for database + log = LogPyros(caller_alias,None,home,path_data) + + # === Use of logs. Parameters are the same as a Python print + + a = 2 + b = 'tutu' + log.print(f"a={a} b={b}") + + log.print(a,b) + diff --git a/src/pyros_future_logger.py b/src/pyros_future_logger.py new file mode 100755 index 0000000..af5496d --- /dev/null +++ b/src/pyros_future_logger.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 + +"""LOGGER + +""" + + +# Standard library imports +import logging +# https://docs.python.org/3/library/logging.handlers.html#module-logging.handlers +from logging.handlers import SMTPHandler, TimedRotatingFileHandler +import sys + +# Third party imports +# None + +# Local application imports +# None + + +logger = None + + +def get_console_handler(): + # Console should print ALL messages (starting from lower level DEBUG) + c_handler = logging.StreamHandler(sys.stdout) + c_handler.setLevel(logging.DEBUG) + c_format = logging.Formatter('%(name)s - %(levelname)s - %(message)s') + c_handler.setFormatter(c_format) + return c_handler + +def get_file_handler(): + # Log File should contain ONLY messages starting from level WARNING (ERROR ?) + #f_handler = logging.FileHandler('file.log') + f_handler = TimedRotatingFileHandler("file.log", when='midnight') + f_handler.setLevel(logging.WARNING) + # Create formatter and add it to handler + #f_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + f_format = logging.Formatter('%(asctime)s - %(levelname)s - %(filename)s : %(lineno)s - %(message)s') + f_handler.setFormatter(f_format) + return f_handler + + +def get_db_handler(): + # DB should contain ONLY messages starting from level ERROR + # TODO: + pass + +def get_email_handler(): + # Email should be sent ONLY for messages starting from level CRITICAL + # http://sametmax.com/envoi-dun-email-par-logging-en-cas-de-plantage-dun-script-python-ou-comment-faire-bouffer-uxe9-a-smtphandler/ + # TODO: a ameliorer avec article ci-dessus pour UNICODE + m_handler = SMTPHandler( + # Host et port + ('SMTP.GMAIL.COM', 587), + # From + "MOI@GMAIL.COM", + # To (liste) + ["QUELQU.UN@QUELQUE.PART"], + # Sujet du message + "Erreur critique dans module MMM", + # pour l'authentification + credentials = ("MONEMAIL@GMAIL.COM", "MONSUPERPASSWORD"), + secure = () + ) + m_handler.setLevel(logging.WARNING) + m_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + m_handler.setFormatter(m_format) + return m_handler + + +def get_logger(logger_name): + global logger + + # Basic configuration + ''' + #logging.basicConfig(level=logging.DEBUG) + logging.basicConfig(level=logging.DEBUG, filename='client.log', filemode='a', format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') + logging.debug("Client instanciated") + ''' + + # Advanced configuration + # https://docs.python.org/3/library/logging.html#logging.getLogger + #logger = logging.getLogger(__name__) + logger = logging.getLogger(logger_name) + + # If not set, default level is NOTSET (all messages are logged) + # The defined levels, in order of increasing severity : DEBUG, INFO, WARNING, ERROR, CRITICAL + logger.setLevel(logging.DEBUG) # better to have too much log than not enough + + # Create and add handlers to the logger + logger.addHandler(get_console_handler()) + logger.addHandler(get_file_handler()) + # TODO: + ##logger.addHandler(db_handler) + ##logger.addHandler(m_handler) + + # with this pattern, it's rarely necessary to propagate the error up to parent + ##logger.propagate = False + return logger + + + +# Shortcuts for logger: +#def log_d(msg:str): logger.debug(msg) +def log_d(msg:str, *args, **kwargs): logger.debug(msg, *args, **kwargs) +def log_i(msg:str, *args, **kwargs): logger.info(msg, *args, **kwargs) +def log_w(msg:str, *args, **kwargs): logger.warning(msg, *args, **kwargs) +def log_e(msg:str, *args, **kwargs): logger.error(msg, *args, **kwargs) +def log_c(msg:str, *args, **kwargs): logger.critical(msg, *args, **kwargs) + + + + +if __name__ == "__main__": + my_logger = get_logger("my_module_name") + my_logger.debug("a debug message") -- libgit2 0.21.2