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:
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()anddebug()methods is written to the stdout channel, output byerror()is written to stderr.- The argument
log_levelcan 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.
- The argument
- 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:
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
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:
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
PrintandLoggeroverride implementations from the Script Include (upload .json): Python-Logging.includescript.json- Workflow (upload .json): pdPythonLogging.workflow.json
