3

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:

CLIPS rule file (example.clp)

(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))

Python program (example.py)

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()

Bash

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.

Homunculus Reticulli
  • 65,167
  • 81
  • 216
  • 341

1 Answers1

2

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)
relent95
  • 3,703
  • 1
  • 14
  • 17
  • +1 Close. I have two gripes **1**. All messages are being logged at the info level (which defeats a major benefit of log files). I want to be able to able to specify the log level of the message in my CLIPS `print t` statement. One way I thought of doing that was to prefix my message with the debug level (e.g. `'WARN: Blah blah'`), and then have a function parse that message and issue the appropriate log write command. **2**. It is not clear how I can set up my handler the way I was doing before. – Homunculus Reticulli Feb 22 '23 at 03:24
  • Regarding the second gripe, I think I can simply setup the handler up before passing it ... it's almost 3:30 AM, so I'm not thinking clearly, I'll review after I've had some sleep. – Homunculus Reticulli Feb 22 '23 at 03:25
  • I added other examples. Check them. – relent95 Feb 22 '23 at 08:40
  • Deriving from `clips.LoggingRouter` was how I was intending to proceed last night, after looking at the clipspy source. However, the solution you proposed (specifying the router name with the ``print` function, seems more simple, and more elegant. The only problem is that `(printout stderr "This will be the ERROR level." crlf)` neither prints to console, or writes to file. I will check to see if that router's priority is being overriden or something more malevolent is going on. – Homunculus Reticulli Feb 22 '23 at 11:27
  • It was fixed in the master branch for [this issue](https://github.com/noxdafox/clipspy/issues/48). Try the master branch. – relent95 Feb 23 '23 at 01:25
  • I'm installing from master branch (as you suggested - to fix the **INFO** log level issue), using `pip install git+https://github.com/noxdafox/clipspy.git`, and the build fails with error message: `fatal error: clips.h: No such file or directory`. Do I need to have the CLIPS sources locally (if yes, which version are you binding to? I'm guessing 6.4.0 from the repo tags). I am installing on Linux (Ubuntu 22.04 LTS) – Homunculus Reticulli Mar 10 '23 at 23:00
  • See [this](https://github.com/noxdafox/clipspy#building-from-sources) and [this](https://clipspy.readthedocs.io/en/latest/#building-from-sources). Run commands in a proper python environment(which provides ```python``` as python3 in a PATH such as python-is-python3 package or Anaconda). – relent95 Mar 11 '23 at 02:45
  • thanks, but instructions weren't too useful - I eventually had to hack around, to figure out that I had to build wheels locally first, before doing a `pip` local install using the wheel. – Homunculus Reticulli Mar 11 '23 at 23:46
  • You don't need to build a wheel. You only need to run ```make``` and ```sudo make install```. Building a wheel is needed if you want to distribute the package. For that see [this](https://github.com/pypa/cibuildwheel). But it's not an easy task. – relent95 Mar 12 '23 at 01:20
  • Clipspy doesn't use a build system such as cibuildwheel or multibuild. See [this](https://clipspy.readthedocs.io/en/latest/#manylinux-wheels). – relent95 Mar 12 '23 at 01:32