Logging with the JS7 Logger

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

FEATURE AVAILABILITY STARTING FROM RELEASE 2.8.2

Example for implementation of JS7Job 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 Console 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 map them to js7Step.getLogger() methods like this:

Example for implementation of Python Logger override
import builtins, logging

def logging_override(js7Step): 
	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, we prepend the values to 'msg'
        	extra_string = " ".join(str(v) for _, v in extra.items())
        	msg = f"{extra_string}  {msg}"

    	return 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 _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}")
    
	# Here we patch the methods for 'print' and 'logging'
	# (i) Every Job starts in a new context, therefore this only overrides the methods within the current Job
	builtins.print    = js7Step.getLogger().info
	logging.log       = _log
	logging.info      = _info
	logging.warning   = _warning
	logging.error     = _error
	logging.critical  = _error
	logging.exception = _error
	logging.debug     = _debug   


class JS7Job(js7.Job):
    def processOrder(self, js7Step):
        logging_override(js7Step)
        logging.warning("some %s", "warning") # Now you can use 'logging' as usual


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


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 Console Logger as usually:

Example for implementation of JS7Job 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