JWock82 / Pynite

A 3D structural engineering finite element library for Python.
MIT License
460 stars 93 forks source link

Printing On/Off #91

Closed 4rimbach closed 3 years ago

4rimbach commented 3 years ago

In analysis functions (FEModel3D.Analyze() or .Analyze_PDelta()) add option to allow printing to be turned off

''' def Analyze(self, check_statics=False, max_iter=30, sparse=True, printing=True): ''' Performs first-order static analysis.

    Iterations are performed if tension-only members or
    compression-only members are present.
    Parameters
    ----------
    check_statics : bool, optional
        When set to True, causes a statics check to be performed
    max_iter : number, optional
        The maximum number of iterations to try to get convergence
        for tension/compression-only analysis.
    sparse : bool, optional
        Indicates whether the sparse matrix solver should be used. A matrix can be considered
        sparse or dense depening on how many zero terms there are. Structural stiffness
        matrices often contain many zero terms. The sparse solver can offer faster solutions
        for such matrices. Using the sparse solver on dense matrices may lead to slower
        solution times.

printing: bool, optional description ''' if printing == True: print('+-----------+') print('| Analyzing |') print('+-----------+') etc

SoundsSerious commented 3 years ago

This seems like a good option, using the python logging library is ideal for this, typically you might elevate logging with DEBUG mode.

To manage the syntax of this, and to provide context on what is logging, i embed this mixin into a class, and then call self.info('...') or self.error(e,'error message:'). This is helpful for identifying the source of problems since context is automatic.

The context is in brackets and message after that like: [beam: base_support]: load applied at 50% lenght fx: 0 fy: 10 fz: 0.

From: https://github.com/SoundsSerious/ottermaticslib/blob/master/ottermatics/logging.py

    '''Class to include easy formatting in subclasses'''

    _log = None

    log_on = True
    #gelf = None

    log_fmt = "[%(name)-24s]%(message)s"
    log_silo = False

    @property
    def logger(self):
        global LOG_LEVEL
        if self._log is None:
            self._log = logging.getLogger('otterlog_' +self.identity)
            self._log.setLevel(level = LOG_LEVEL)

            #Apply Filter Info
            self._log.addFilter(self)

            #Eliminate Outside Logging Interaction
            if self.log_silo:
                self._log.handlers = []
                self._log.propagate = False
                self.installSTDLogger()
                #self.installGELFLogger()

        return self._log

    # def installGELFLogger(self):
    #     '''Installs GELF Logger'''
    #     gelf = graypy.GELFUDPHandler(host=GELF_HOST,port=12203, extra_fields=True)
    #     self._log.addHandler(gelf)

    def resetLog(self):
        self._log = None

    def installSTDLogger(self):
        '''We only want std logging to start'''
        sh = logging.StreamHandler(sys.stdout)
        peerlog = logging.Formatter(self.log_fmt)
        sh.setFormatter(peerlog)
        self._log.addHandler( sh )            

    def add_fields(self, record):
        '''Overwrite this to modify logging fields'''
        pass

    def filter(self, record):
        '''This acts as the interface for `logging.Filter`
        Don't overwrite this, use `add_fields` instead.'''
        record.name = self.identity.lower()
        self.add_fields(record)
        return True

    def msg(self,*args):
        '''Writes to log... this should be for raw data or something... least priorty'''
        if self.log_on:
            self.logger.log(0,self.extract_message(args))

    def debug(self,*args):
        '''Writes at a low level to the log file... usually this should
        be detailed messages about what exactly is going on'''
        if self.log_on:
            self.logger.debug( self.extract_message(args))

    def info(self,*args):
        '''Writes to log but with info category, these are important typically
        and inform about progress of process in general'''
        if self.log_on:
            self.logger.info( self.extract_message(args))

    def warning(self,*args):
        '''Writes to log as a warning'''
        self.logger.warning(self.extract_message(args))

    def error(self,error,msg=''):
        '''Writes to log as a error'''
        fmt = '{msg!r}|{err!r}'
        tb = ''.join(traceback.format_exception(etype=type(error), value=error, tb=error.__traceback__))
        self.logger.exception( fmt.format(msg=msg,err=tb))

    def critical(self,*args):
        '''A routine to communicate to the root of the server network that there is an issue'''
        global SLACK_WEBHOOK_NOTIFICATION
        msg = self.extract_message(args)
        self.logger.critical(msg)

        self.slack_notification(self.identity.title(),msg)        

    def slack_notification(self, category, message, stage=HOSTNAME):
        global SLACK_WEBHOOK_NOTIFICATION
        if SLACK_WEBHOOK_NOTIFICATION is None and 'SLACK_WEBHOOK_NOTIFICATION' in os.environ:
            self.info('getting slack webhook')
            SLACK_WEBHOOK_NOTIFICATION = os.environ['SLACK_WEBHOOK_NOTIFICATION']
        headers = {'Content-type': 'application/json'}
        data = {'text':"{category} on {stage}:\n```{message}```".format(\
                            category=category.upper(),\
                            stage=stage,\
                            message=message)}
        self.info(f'Slack Notification : {SLACK_WEBHOOK_NOTIFICATION}:{category},{message}')
        slack_note = requests.post(SLACK_WEBHOOK_NOTIFICATION,data= json.dumps(data).encode('ascii'),headers=headers) 

    def extract_message(self,args):
        for arg in args:
            if type(arg) is str:
                return arg
        return ''

    @property
    def identity(self):
        return type(self).__name__
JWock82 commented 3 years ago

This feature is now available in version 0.0.39. By default the log is turned off. Use the log argument in the Analyze method to turn console logging on or off: FEModel3D.Analyze(log=True) or FEModel3D.Analyze_PDelta(log=True).