logpyros.py 12.3 KB
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)=="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)