I am using clipspy v1.0.0 and Python 3.10 on Ubuntu 22.0.4 LTS.
I am trying to get commands from CLIPS rules (e.g. print t 'WARN: Listen up, maggots...'
) to print to the console by calling my registered function, which will then parse the message and recognise that it is a warning, so use the logging module to write a warning level message to the log file.
This is what I have so far:
(defrule welcome
(name "Tyler Durden")
=>
(printout t "INFO: Gentlemen, welcome to Fight Club. The first rule of Fight Club is: you do not talk about Fight Club!" crlf))
import logging
import logging.handlers
import re
import clips
log_format = '%(asctime)s - %(levelname)s - %(message)s'
logging.basicConfig(level=logging.INFO, format=log_format)
logger = logging.getLogger('CLIPS')
log_level = logging.INFO
log_filename = 'expert'
handler = logging.handlers.TimedRotatingFileHandler(f"{log_filename}.log", when="midnight", interval=1)
handler.setLevel(log_level)
formatter = logging.Formatter(log_format)
handler.setFormatter(formatter)
# add a suffix which you want
handler.suffix = "%Y%m%d"
#need to change the extMatch variable to match the suffix for it
handler.extMatch = re.compile(r"^\d{8}$")
# finally add handler to logger
logger.addHandler(handler)
def my_print(msg):
# Placeholder code, not parsing message or using logger for now ...
print(f"CLIPS: {msg}")
try:
env = clips.Environment()
router = clips.LoggingRouter()
env.add_router(router)
env.define_function(my_print)
env.load("example1.clp")
env.assert_string('(name "Tyler Durden")')
#env.reset()
env.run()
# while True:
# env.run()
except clips.CLIPSError as err:
print(f"CLIPS error: {err}")
except KeyboardInterrupt:
print("Stopping...")
finally:
env.clear()
me@yourbox$ python example.py
2023-02-21 23:58:20,860 - INFO - INFO: Gentlemen, welcome to Fight Club. The first rule of Fight Club is: you do not talk about Fight Club!
A log file is created, but nothing is written to it. Also, it seems stdout is being simply routed to Pythons stdout, instead of calling my function.
How do I fix the code above, so that when a (print t)
statement is encountered in a CLIPS program, it, it simultaneously prints to console, and writes to log using the correct (i.e. specified) log level.
The clips.LoggingRouter
uses the root logger API such as logging.info()
. So, do like the following example.
import logging
import logging.handlers
import clips
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers = [
logging.StreamHandler(),
logging.handlers.TimedRotatingFileHandler('expert.log'),
])
env = clips.Environment()
env.add_router(clips.LoggingRouter())
...
env.run()
If you want to specify different log levels for handlers, do like the following example.
...
handler1 = logging.StreamHandler()
handler1.setLevel(logging.INFO)
handler2 = logging.handlers.TimedRotatingFileHandler('expert.log')
handler2.setLevel(logging.ERROR)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers = [handler1, handler2])
If you want to log a message with the log level determined by the prefix of the message, define a subclass of the clips.LoggingRouter
instead of hacking the logging
module, like the following example.
...
class LoggingRouterWithLevelByPrefix(clips.LoggingRouter):
def write(self, name, msg):
if msg.startswith('WARN:'):
logging.warn(msg[5:])
elif msg.startswith('ERROR:'):
logging.error(msg[6:])
else:
logging.info(msg)
...
env.add_router(LoggingRouterWithLevelByPrefix())
...
But keep in mind that Clips defines logical names such as stdout
, stderr
, stdwrn
, which clips.LoggingRouter
uses to determine the log level. So you can use them like the following example.
(printout t "This will be the INFO level." crlf)
(printout stdwrn "This will be the WARNING level." crlf)
(printout stderr "This will be the ERROR level." crlf)