Logger

Global logger for fedbiomed

Written above origin Logger class provided by python.

Following features were added from to the original module:

  • provides a logger instance of FedLogger, which is also a singleton, so it can be used "as is"
  • provides a dedicated file handler
  • provides a JSON/gRPC handler (this permit to send error messages from a node to a researcher)
  • works on python scripts / ipython / notebook
  • manages a dictionary of handlers. Default keys are 'CONSOLE', 'GRPC', 'FILE', but any key is allowed (only one handler by key)
  • allow changing log level globally, or on a specific handler (using its key)
  • log levels can be provided as string instead of logging.* levels (no need to import logging in caller's code) just as in the initial python logger

A typical usage is:

from fedbiomed.common.logger import logger

logger.info("information message")

All methods of the original python logger are provided. To name a few:

  • logger.debug()
  • logger.info()
  • logger.warning()
  • logger.error()
  • logger.critical()

Contrary to other Fed-BioMed classes, the API of FedLogger is compliant with the coding conventions used for logger (lowerCameCase)

Dependency issue

Please pay attention to not create dependency loop then importing other fedbiomed package

Attributes

DEFAULT_FORMAT module-attribute

DEFAULT_FORMAT = '%(asctime)s %(name)s %(levelname)s - %(message)s'

DEFAULT_LOG_FILE module-attribute

DEFAULT_LOG_FILE = 'mylog.log'

DEFAULT_LOG_LEVEL module-attribute

DEFAULT_LOG_LEVEL = logging.WARNING

logger module-attribute

logger = FedLogger()

Classes

FedLogger

FedLogger(level=DEFAULT_LOG_LEVEL)

Base class for the logger.

It uses python logging module by composition (only log() method is overwritten)

All methods from the logging module can be accessed through the _logger member of the class if necessary (instead of overloading all the methods) (ex: logger._logger.getEffectiveLevel() )

Should not be imported

An initial console logger is installed (so the logger has at minimum one handler)

Parameters:

Name Type Description Default
level str

initial loglevel. This loglevel will be the default for all handlers, if called without the default level

DEFAULT_LOG_LEVEL
Source code in fedbiomed/common/logger.py
def __init__(self, level: str = DEFAULT_LOG_LEVEL):
    """Constructor of base class

    An initial console logger is installed (so the logger has at minimum one handler)

    Args:
        level: initial loglevel. This loglevel will be the default for all handlers, if called
            without the default level


    """

    # internal tables
    # transform string to logging.level
    self._nameToLevel = {
        "DEBUG": logging.DEBUG,
        "INFO": logging.INFO,
        "WARNING": logging.WARNING,
        "ERROR": logging.ERROR,
        "CRITICAL": logging.CRITICAL,
    }

    # transform logging.level to string
    self._levelToName = {
        logging.DEBUG: "DEBUG",
        logging.INFO: "INFO",
        logging.WARNING: "WARNING",
        logging.ERROR: "ERROR",
        logging.CRITICAL: "CRITICAL"
    }

    # name this logger
    self._logger = logging.getLogger("fedbiomed")

    # Do not propagate (avoids log duplication when third party libraries uses logging module)
    self._logger.propagate = False

    self._default_level = DEFAULT_LOG_LEVEL  # MANDATORY ! KEEP THIS PLEASE !!!
    self._default_level = self._internal_level_translator(level)

    self._logger.setLevel(self._default_level)

    # init the handlers list and add a console handler on startup
    self._handlers = {}
    self.add_console_handler()

    pass

Functions

add_console_handler
add_console_handler(format=DEFAULT_FORMAT, level=DEFAULT_LOG_LEVEL)

Adds a console handler

Parameters:

Name Type Description Default
format str

the format string of the logger

DEFAULT_FORMAT
level Any

initial level of the logger for this handler (optional) if not given, the default level is set

DEFAULT_LOG_LEVEL
Source code in fedbiomed/common/logger.py
def add_console_handler(self,
                        format: str = DEFAULT_FORMAT,
                        level: Any = DEFAULT_LOG_LEVEL):

    """Adds a console handler

    Args:
        format: the format string of the logger
        level: initial level of the logger for this handler (optional) if not given, the default level is set
    """
    if is_ipython():
        handler = _IpythonConsoleHandler()
    else:
        handler = logging.StreamHandler()

    handler.setLevel(self._internal_level_translator(level))

    formatter = logging.Formatter(format)
    handler.setFormatter(formatter)
    self._internal_add_handler("CONSOLE", handler)

    pass
add_file_handler
add_file_handler(filename=DEFAULT_LOG_FILE, format=DEFAULT_FORMAT, level=DEFAULT_LOG_LEVEL)

Adds a file handler

Parameters:

Name Type Description Default
filename str

File to log to

DEFAULT_LOG_FILE
format str

Log format

DEFAULT_FORMAT
level any

Initial level of the logger

DEFAULT_LOG_LEVEL
Source code in fedbiomed/common/logger.py
def add_file_handler(
        self,
        filename: str = DEFAULT_LOG_FILE,
        format: str = DEFAULT_FORMAT,
        level: any = DEFAULT_LOG_LEVEL):
    """Adds a file handler

    Args:
        filename: File to log to
        format: Log format
        level: Initial level of the logger
    """

    handler = logging.FileHandler(filename=filename, mode='a')
    handler.setLevel(self._internal_level_translator(level))

    formatter = logging.Formatter(format)
    handler.setFormatter(formatter)

    self._internal_add_handler("FILE", handler)
add_grpc_handler
add_grpc_handler(on_log=None, node_id=None, level=logging.INFO)

Adds a gRPC handler, to publish error message on a topic

Parameters:

Name Type Description Default
on_log Callable

Provided by higher level GRPC implementation

None
node_id str

id of the caller (necessary for msg formatting to the researcher)

None
level Any

level of this handler (non-mandatory) level must be lower than ERROR to ensure that the research get all ERROR/CRITICAL messages

INFO
Source code in fedbiomed/common/logger.py
def add_grpc_handler(self,
                     on_log: Callable = None,
                     node_id: str = None,
                     level: Any = logging.INFO
                     ):

    """Adds a gRPC handler, to publish error message on a topic

    Args:
        on_log: Provided by higher level GRPC implementation
        node_id: id of the caller (necessary for msg formatting to the researcher)
        level: level of this handler (non-mandatory) level must be lower than ERROR to ensure that the
            research get all ERROR/CRITICAL messages
    """

    handler = _GrpcHandler(
        on_log=on_log,
        node_id=node_id,
    )

    # may be not necessary ?
    handler.setLevel(self._internal_level_translator(level))
    formatter = _GrpcFormatter(node_id)

    handler.setFormatter(formatter)
    self._internal_add_handler("GRPC", handler)

    # as a side effect this will set the minimal level to ERROR
    self.setLevel(level, "GRPC")

    pass
critical
critical(msg, *args, broadcast=False, researcher_id=None, **kwargs)

Same as info message

Source code in fedbiomed/common/logger.py
def critical(self, msg, *args, broadcast=False, researcher_id=None, **kwargs):
    """Same as info message"""
    self._logger.critical(msg, *args, **kwargs,
                          extra={"researcher_id": researcher_id, 'broadcast': broadcast})
debug
debug(msg, *args, broadcast=False, researcher_id=None, **kwargs)

Same as info message

Source code in fedbiomed/common/logger.py
def debug(self, msg, *args, broadcast=False, researcher_id=None, **kwargs):
    """Same as info message"""
    self._logger.debug(msg, *args, **kwargs,
                       extra={"researcher_id": researcher_id, 'broadcast': broadcast})
error
error(msg, *args, broadcast=False, researcher_id=None, **kwargs)

Same as info message

Source code in fedbiomed/common/logger.py
def error(self, msg, *args, broadcast=False, researcher_id=None, **kwargs):
    """Same as info message"""
    self._logger.error(msg, *args, **kwargs,
                       extra={"researcher_id": researcher_id, 'broadcast': broadcast})
info
info(msg, *args, broadcast=False, researcher_id=None, **kwargs)

Extends arguments of info message.

Valid only GrpcHandler is existing

Parameters:

Name Type Description Default
msg

Message to log

required
broadcast

Broadcast message to all available researchers

False
researcher_id

ID of the researcher that the message will be sent. If broadcast True researcher id will be ignored

None
Source code in fedbiomed/common/logger.py
def info(self, msg, *args, broadcast=False, researcher_id=None, **kwargs):
    """Extends arguments of info message.

    Valid only GrpcHandler is existing

    Args:
        msg: Message to log
        broadcast: Broadcast message to all available researchers
        researcher_id: ID of the researcher that the message will be sent.
            If broadcast True researcher id will be ignored
    """
    self._logger.info(msg, *args, **kwargs,
                      extra={"researcher_id": researcher_id, 'broadcast': broadcast})
log
log(level, msg)

Overrides the logging.log() method to allow the use of string instead of a logging.* level

Source code in fedbiomed/common/logger.py
def log(self, level: Any, msg: str):
    """Overrides the logging.log() method to allow the use of string instead of a logging.* level """

    level = logger._internal_level_translator(level)
    self._logger.log(
        level,
        msg
    )
setLevel
setLevel(level, htype=None)

Overrides the setLevel method, to deal with level given as a string and to change le level of one or all known handlers

This also change the default level for all future handlers.

Remark

Level should not be lower than CRITICAL (meaning CRITICAL errors are always displayed)

Example:

setLevel( logging.DEBUG, 'FILE')

Parameters:

Name Type Description Default
level

level to modify, can be a string or a logging.* level (mandatory)

required
htype

if provided (non-mandatory), change the level of the given handler. if not provided (or None), change the level of all known handlers

None
Source code in fedbiomed/common/logger.py
def setLevel(self, level: Any, htype: Any = None):
    """Overrides the setLevel method, to deal with level given as a string and to change le level of
    one or all known handlers

    This also change the default level for all future handlers.

    !!! info "Remark"

        Level should not be lower than CRITICAL (meaning CRITICAL errors are always displayed)

        Example:
        ```python
        setLevel( logging.DEBUG, 'FILE')
        ```

    Args:
        level : level to modify, can be a string or a logging.* level (mandatory)
        htype : if provided (non-mandatory), change the level of the given handler. if not  provided (or None),
            change the level of all known handlers
    """

    level = self._internal_level_translator(level)

    if htype is None:
        # store this level (for future handler adding)
        self._logger.setLevel(level)

        for h in self._handlers:
            self._handlers[h].setLevel(level)
        return

    if htype in self._handlers:
        self._handlers[htype].setLevel(level)
        return

    # htype provided but no handler for this type exists
    self._logger.warning(htype + " handler not initialized yet")
warning
warning(msg, *args, broadcast=False, researcher_id=None, **kwargs)

Same as info message

Source code in fedbiomed/common/logger.py
def warning(self, msg, *args, broadcast=False, researcher_id=None, **kwargs):
    """Same as info message"""
    self._logger.warning(msg, *args, **kwargs,
                         extra={"researcher_id": researcher_id, 'broadcast': broadcast})

Functions