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

Reply via email to