class_docstr: A simple class that receives messages on a SUB socket, as published
  by subclasses of `zmq.log.handlers.PUBHandler`, and logs them itself.
  
  This can subscribe to multiple topics, but defaults to all topics.
class_docstr_tok: ["A","simple","class","that","receives","messages","on","a","SUB","socket",",","as","published","by","subclasses","of","`","zmq.log.handlers.PUBHandler","`",",","and","logs","them","itself",".","This","can","subscribe","to","multiple","topics",",","but","defaults","to","all","topics","."]
docstr: receive and parse a message, then log it.
docstr_tok: ["receive","and","parse","a","message",",","then","log","it","."]
code: def log_message(self, raw):
      """receive and parse a message, then log it."""
      if len(raw) != 2 or "." not in raw[0]:
          self.log.error("Invalid log message: %s" % raw)
          return
      else:
          topic, msg = raw
          # don't newline, since log messages always newline:
          topic, level_name = topic.rsplit(".", 1)
          level, topic = self._extract_level(topic)
          if msg[-1] == "\n":
              msg = msg[:-1]
          self.log.log(level, f"[{topic}] {msg}")
  
file_path: ipyparallel/ipyparallel/apps/logwatcher.py
imports: import logging
  import zmq
  from jupyter_client.localinterfaces import localhost
  from tornado import ioloop
  from traitlets import Instance, List, Unicode
  from traitlets.config.configurable import LoggingConfigurable
  from zmq.eventloop import zmqstream
label_desc: Use image node_id 9 for calling the LogWatcher obj's underlying member method code with example usage: obj.log_message(raw) without return types