Hi William, On Thu, Apr 23, 2009 at 17:10 -0400, William McVey wrote: > On Wed, 2009-04-22 at 10:54 -0400, William McVey wrote: > > I'm using py.execnet with the SshGateway and I'd like to fire off > > logging messages that get sent over the ssh connection. I was wondering > > if someone has already coded up a logging handler for this.
I think it is a fine usecase. > After a bit of frustration, I have remote logging over an execnet > channel working, with no changes to the py.execnet code itself to > support this. I had to resort to a nasty trick in order to get the > channel to send and receive the logging.LogRecord objects. I think this > could be made a lot cleaner if py.execnet were tweaked to allow defining > a dictionary of namespaces to use when deserializing objects coming over > a channel. If there is any interest in this by the py lib maintainers, > I'll go ahead and fork the code using bitbucket, add this capability in > (probably as a new method on the channel object) and issue a pull > notification. > Anyway, attached is a simple demo script that shows the ExecNetHandler > logging handler in action. I'd love to hear feedback on this. you could put the "remote_code" into a module and pass the imported module to remote_exec - this allows to keep syntax coloring and also allows to more easily unittest it. Maybe that would alleviate some of the frustration? Also you could maybe just send a dictionary of instance values and call a Logrecord object with it as **kwargs. As you say yourself, your code works on the presumtion that py.execnet uses repr/eval for sending and receiving of objects. That may change soon and would break your code. A more general solution for sending non-marshallable objects is to have some pickling layer on top of current channels. For use with distributed testing, i've implemented a "PickleChannel", see here: http://bitbucket.org/hpk42/py-trunk/src/c90f1bf8bac4/py/test/dist/mypickle.py with unit and functional tests here: http://bitbucket.org/hpk42/py-trunk/src/c90f1bf8bac4/py/test/dist/testing/test_mypickle.py This code also uses an additional concept "ImmutablePickle" which restricts object pickling in the following way: If Process A sends an object OBJ to process B and process B sends it back (either directly or as an attribute of another object) then A will get a reference to OBJ and not a fresh copy. Usually pickle would create a copy in process A. I've found this preserving of object identity useful for distributing testing tasks. Obviously one could also use plain pickle, maybe by introducing a pickling option to remote_exec() that could have different values like e.g. "ipickle", "pickle", "repr" etc. If you are up to coding this in a fork i'd be happy to review it. As a first step, having "pickle" and a default that reflects the current situation would be fine and should help your use case. I'd probably keep the layering as it probably keeps code changes to a minimum. cheers, holger > -- William > import py, time, logging > > remote_code = """ > import logging > > class FakeLogRecord(object): > "Fakes a LogRecord's repr for passing over an execnet channel" > def __init__(self, log): > self.log = log > > def __repr__(self): > l = self.log > # Serialization of objects going over a channel happens by calling > # repr() on the object. Deserialization happens with an > # eval(mesg, {}). With a minimal namespace, I need to handle the > # importing of the module myself as an expression encoded in the repr > # of the object. Currently, I ignore specified traceback objects > # attached to a LogRecord via exc_info=True. > return "__import__('logging').LogRecord(%r, %r, %r, %r, %r, %r, ())" > % ( > l.name, l.levelno, l.pathname, l.lineno, l.msg, l.args) > > > class ExecNetHandler(logging.Handler): > "Send logging messages over an py.execnet connection" > > def __init__(self, channel): > logging.Handler.__init__(self) > self.channel = channel > > def emit(self, record): > obj = FakeLogRecord(record) > self.channel.send(obj) > > # A testrun > log = logging.getLogger("remote") > log.setLevel(logging.DEBUG) > log.addHandler(ExecNetHandler(channel)) > > log.debug("Starting up") > channel.send("Look ma, I'm home") > log.info("I'm running on the remote side") > try: > raise RuntimeError("just an exception") > except: > log.error("Somebody set up us the bomb", exc_info=True) > log.debug("Closing down") > """ > > logging.basicConfig(format="%(name)s: %(levelname)s: %(message)s") > remote_log = logging.getLogger("remote") > handler = logging.StreamHandler() > handler.setLevel(logging.INFO) > remote_log.addHandler(handler) > > > gw = py.execnet.PopenGateway() > channel = gw.remote_exec(remote_code) > > time.sleep(2) > for mesg in channel: > if isinstance(mesg, logging.LogRecord): > remote_log.handle(mesg) > continue > print "From the remote side:", `mesg` > > _______________________________________________ > py-dev mailing list > py-dev@codespeak.net > http://codespeak.net/mailman/listinfo/py-dev -- Metaprogramming, Python, Testing: http://tetamap.wordpress.com Python, PyPy, pytest contracting: http://merlinux.eu _______________________________________________ py-dev mailing list py-dev@codespeak.net http://codespeak.net/mailman/listinfo/py-dev