FEATURE AVAILABILITY STARTING FROM RELEASE 2.8.2

Logging with the JS7 Logger

JS7 - GraalVM Python Jobs are based on Oracle® GraalVM and can use the JS7 Logger like this:

Example for implementation of JS7 Job with Python
class JS7Job(js7.Job):
    def processOrder(self, js7Step):
        logger = js7Step.getLogger()

        logger.info("logging some information")
        logger.warn(".. logging some warning")
        logger.error(".. logging some error")
        logger.debug(".. logging some debug output")

        # do some stuff

Explanation:

  • The js7Step.getLogger() object is provided that offers related methods for logging.
  • By default output of info(), warn() and debug() methods is written to the stdout channel, output by error() is written to stderr.
    • The argument log_level can be used for a job to specify the log level:
      • log_level = info : default, no debug output enabled.
      • log_level = debug : includes debug output
    • For details see  JS7 - JITL Common Variables.
  • For details see JS7 - Job API.

Logging with the Python Logger

Basically the Python print(), logging.info(), logging.warning() etc. methods cannot be used as they directly address the stdout channel. As a result use of the methods writes output to the JS7 Agent's stdout channel and to its ./logs/watchdog.log file.

It is an option to override the above logging methods and to patch them to js7Step.getLogger() methods like this:

Example: Python Logger override (Script include)
import builtins, logging

def _convert(msg, *args, **kwargs) -> str:
    # Ensures that 'msg' is always a string
    msg = str(msg)
    
    # Removes 'extra' from 'kwargs' and passes the value to the variable 'extra'
    extra = kwargs.pop("extra", None)
    
    # Checks if 'args' is not empty and '%' is in 'msg'
    if args and ("%" in msg):
        msg = msg % args # If true, apply the modulo operator to replace '%s, %d, ..'
    if extra:
        # If 'extra' is not empty, the values are prepended to 'msg'
        extra_string = " ".join(str(v) for _, v in extra.items())
        msg = f"{extra_string}  {msg}"

    return msg

def _print(*objects, sep=" ", end="\n", file=None, flush=False):
    msg = " ".join(map(str, objects))
    if file is sys.stderr:
        js7Step.getLogger().error(msg)
    else:
        js7Step.getLogger().info(msg)

def _info(msg, *args, **kwargs):
    js7Step.getLogger().info(_convert(msg, *args, **kwargs))

def _warning(msg, *args, **kwargs):
    js7Step.getLogger().warn(_convert(msg, *args, **kwargs))

def _error(msg, *args, **kwargs):
    js7Step.getLogger().error(_convert(msg, *args, **kwargs))

def _debug(msg, *args, **kwargs):
    js7Step.getLogger().debug(_convert(msg, *args, **kwargs))

def _exception(msg, *args, **kwargs):
    js7Step.getLogger().error(_convert(msg, *args, **kwargs))

def _log(level: int, msg, *args, **kwargs):
    match level:
        case 0:
            _info(msg, *args, **kwargs)
        case 10:
            _debug(msg, *args, **kwargs)
        case 20:
            _info(msg, *args, **kwargs)
        case 30:
            _warning(msg, *args, **kwargs)
        case 40:
            _error(msg, *args, **kwargs)
        case 50:
            _error(msg, *args, **kwargs)
        case _:
            _error(f"unknown log level: {level}")

# This patches the methods print and logging.
# (i) Every job starts in a new context, therefore this only overrides the methods within the current job
builtins.print    = _print
logging.log       = _log
logging.info      = _info
logging.warning   = _warning
logging.error     = _error
logging.critical  = _error
logging.exception = _exception
logging.debug     = _debug


(info) To make the override globally available, users can add it to a JS7 - Script Include.


In the Configuration->Inventory view the override is configured like this:


The Script Include can be referenced from a job using the syntax: ##!include <name-of-script-include>

As a result, jobs can use the Python Print and Logger functions as usual:

Example for implementation of JS7 Job with Python
class JS7Job(js7.Job):
    def processOrder(self, js7Step):
        ##!include Python-Logging
        print("printing some information")
        logging.info("hello %s", "world")
        logging.warning("processing delay: %d seconds", 5)
        logging.error("logging some error: %s", "ERROR")
        logging.debug("debug output: %s", {"key": "value"})

Resources

Downloads