Module Gnumed.pycommon.gmLog2

GNUmed logging framework setup.

All error logging, user notification and otherwise unhandled exception handling should go through classes or functions of this module.

Theory of operation:

This module tailors the standard logging framework to the needs of GNUmed.

By importing gmLog2 into your code you'll get the root logger send to a unicode file with messages in a format useful for debugging. The filename is either taken from the command line (–log-file=…) or derived from the name of the main application.

The log file will be found in one of the following standard locations:

1) given on the command line as "–log-file=LOGFILE" 2) ~/./.log 3) /dir/of/binary/.log (mainly for DOS/Windows)

where is derived from the name of the main application.

If you want to specify just a directory for the log file you must end the –log-file definition with a slash.

By importing "logging" and getting a logger your modules never need to worry about the real message destination or whether at any given time there's a valid logger available.

Your MAIN module simply imports gmLog2 and all other modules will merrily and automagically start logging away.

Ad hoc call stack logging recipe:

    call_stack = inspect.stack()
    call_stack.reverse()
    for idx in range(1, len(call_stack)):
            caller = call_stack[idx]
            _log.debug('%s[%s] @ [%s] in [%s]', ' '* idx, caller[3], caller[2], caller[1])
    del call_stack

Functions

def add_word2hide(word: str)
Expand source code
def add_word2hide(word:str):
        """Add a string to the list of strings to be scrubbed from logging output.

        Useful for hiding credentials etc.
        """
        if word is None:
                return

        if word.strip() == '':
                return

        if word not in __words2hide:
                __words2hide.append(str(word))

Add a string to the list of strings to be scrubbed from logging output.

Useful for hiding credentials etc.

def flush()
Expand source code
def flush():
        """Log a <synced> line and flush handlers."""
        logger = logging.getLogger('gm.logging')
        logger.critical('-------- synced log file -------------------------------')
        root_logger = logging.getLogger()
        for handler in root_logger.handlers:
                handler.flush()

Log a line and flush handlers.

def log_instance_state(instance)
Expand source code
def log_instance_state(instance):
        """Log the state of a class instance."""
        logger = logging.getLogger('gm.logging')
        logger.debug('state of %s', instance)
        for attr in [ a for a in dir(instance) if not a.startswith('__') ]:
                try:
                        val = getattr(instance, attr)
                except AttributeError:
                        val = '<cannot access>'
                logger.debug('  %s: %s', attr, val)

Log the state of a class instance.

def log_multiline(level: int = 10,
message: str = None,
line_prefix: str = None,
text: str | list[str] = None)
Expand source code
def log_multiline(level:int=logging.DEBUG, message:str=None, line_prefix:str=None, text:str|list[str]=None):
        """Log multi-line text in a standard format.

        Args:
                level: a log level
                message: an arbitrary message to add in
                line_prefix: a string to prefix lines with
                text: the multi-line text to log
        """
        if not text:
                return

        if line_prefix:
                line_template = '%s: %%s' % line_prefix
        else:
                line_template = '  > %s'
        lines2log = []
        if message:
                lines2log.append(message)
        if isinstance(text, list):
                lines2log.extend([ line_template % line for line in text ])
        else:
                lines2log.extend([ line_template % line for line in text.split('\n') ])
        logger = logging.getLogger('gm.logging')
        logger.log(level, '\n'.join(lines2log))

Log multi-line text in a standard format.

Args

level
a log level
message
an arbitrary message to add in
line_prefix
a string to prefix lines with
text
the multi-line text to log
def log_stack_trace(message: str = None, t=None, v=None, tb=None)
Expand source code
def log_stack_trace(message:str=None, t=None, v=None, tb=None):
        """Log exception details and stack trace.

        (t,v,tb) are what sys.exc_info() returns.

        If any of (t,v,tb) is None it is attempted to be
        retrieved from sys.exc_info().

        Args:
                message: arbitrary message to add in
                t: an exception type
                v: an exception value
                tb: a traceback object
        """

        logger = logging.getLogger('gm.logging')

        if t is None:
                t = sys.exc_info()[0]
        if v is None:
                v = sys.exc_info()[1]
        if tb is None:
                tb = sys.exc_info()[2]
        if tb is None:
                logger.debug('sys.exc_info() did not return a traceback object, trying sys.last_traceback')
                try:
                        tb = sys.last_traceback
                except AttributeError:
                        logger.debug('no stack to trace (no exception information available)')
                        return

        # log exception details
        logger.debug('exception: %s', v)
        logger.debug('type: %s', t)
        logger.debug('list of attributes:')
        for attr in [ a for a in dir(v) if not a.startswith('__') ]:
                try:
                        val = getattr(v, attr)
                except AttributeError:
                        val = '<cannot access>'
                logger.debug('  %s: %s', attr, val)

        # make sure we don't leave behind a binding
        # to the traceback as warned against in
        # sys.exc_info() documentation
        try:
                # recurse back to root caller
                while 1:
                        if not tb.tb_next:
                                break
                        tb = tb.tb_next
                # put the frames on a stack
                stack_of_frames = []
                frame = tb.tb_frame
                while frame:
                        stack_of_frames.append(frame)
                        frame = frame.f_back
        finally:
                del tb
        stack_of_frames.reverse()

        if message is not None:
                logger.debug(message)
        logger.debug('stack trace follows:')
        logger.debug('(locals by frame, outmost frame first)')
        for frame in stack_of_frames:
                logger.debug (
                        '--- frame [%s]: #%s, %s -------------------',
                        frame.f_code.co_name,
                        frame.f_lineno,
                        frame.f_code.co_filename
                )
                for varname, value in frame.f_locals.items():
                        if varname == '__doc__':
                                continue
                        logger.debug('%20s = %s', varname, value)

Log exception details and stack trace.

(t,v,tb) are what sys.exc_info() returns.

If any of (t,v,tb) is None it is attempted to be retrieved from sys.exc_info().

Args

message
arbitrary message to add in
t
an exception type
v
an exception value
tb
a traceback object
def log_step(level: int = 10, message: str = None, restart: bool = False)
Expand source code
def log_step(level:int=logging.DEBUG, message:str=None, restart:bool=False):
        if restart:
                global __current_log_step
                __current_log_step = 1
        logger = logging.getLogger('gm.logging')
        if message:
                logger.log(level, '%s - %s', __current_log_step, message)
        else:
                logger.log(level, '%s', __current_log_step)
        __current_log_step += 1
def print_logfile_name()
Expand source code
def print_logfile_name():
        global _logfile_name_printed
        if _logfile_name_printed:
                return

        print('Log file:', _logfile_name)
        _logfile_name_printed = True