############################################################################# # # $Id: util.py,v 2.41 2005/02/12 23:45:29 irmen Exp $ # Pyro Utilities # # This is part of "Pyro" - Python Remote Objects # which is (c) Irmen de Jong - irmen@users.sourceforge.net # ############################################################################# import os, sys import time, random import Pyro # bring in Pyro.config from Pyro.util2 import * # bring in 'missing' util functions # bogus lock class, for systems that don't have threads. class BogusLock: def acquire(self): pass def release(self): pass def getLockObject(): if supports_multithreading(): from threading import Lock return Lock() else: return BogusLock() # bogus event class, for systems that don't have threads class BogusEvent: def __init__(self): self.flag=0 def isSet(self): return self.flag==1 def set(self): self.flag=1 def clear(self): self.flag=0 def wait(self,timeout=None): raise RuntimeError("cannot wait in non-threaded environment") def getEventObject(): if supports_multithreading(): from threading import Event return Event() else: return BogusEvent() # Logging stuff. # Select the logging implementation to use! if Pyro.config.PYRO_STDLOGGING: # new-style logging using logging module, python 2.3+ import logging, logging.config cfgfile=Pyro.config.PYRO_STDLOGGING_CFGFILE if not os.path.isabs(cfgfile): Pyro.config.PYRO_STDLOGGING_CFGFILE=os.path.join(Pyro.config.PYRO_STORAGE, cfgfile) cfgfile=Pyro.config.PYRO_STDLOGGING_CFGFILE externalConfig=0 try: open(cfgfile).close() logging.config.fileConfig(cfgfile) externalConfig=1 except IOError,x: # Config file couldn't be read! Use builtin config. # First make the logfiles absolute paths: if not os.path.isabs(Pyro.config.PYRO_LOGFILE): Pyro.config.PYRO_LOGFILE=os.path.join(Pyro.config.PYRO_STORAGE, Pyro.config.PYRO_LOGFILE) if not os.path.isabs(Pyro.config.PYRO_USER_LOGFILE): Pyro.config.PYRO_USER_LOGFILE=os.path.join(Pyro.config.PYRO_STORAGE, Pyro.config.PYRO_USER_LOGFILE) class LoggerBase: if externalConfig: def __init__(self): self.logger=logging.getLogger(self._getLoggerName()) else: def __init__(self): self.logger=logging.getLogger("Pyro."+str(id(self))) # each time a different logger ... self.setLevel(self._getPyroLevel()) handler=logging.FileHandler(self._logfile()) handler.setFormatter(logging.Formatter("%(asctime)s [%(process)d:%(thread)d] ** %(levelname)s ** %(message)s")) self.logger.addHandler(handler) def setLevel(self, pyroLevel): if pyroLevel>=3: self.logger.setLevel(logging.DEBUG) elif pyroLevel>=2: self.logger.setLevel(logging.WARN) elif pyroLevel>=1: self.logger.setLevel(logging.ERROR) else: self.logger.setLevel(999) def msg(self,source,*args): self.setLevel(self._getPyroLevel()) if not args: (args, source) = ([source], "N/A") self.logger.info("%s ** %s", source, reduce(lambda x,y: str(x)+' '+str(y),args)) def warn(self,source,*args): self.setLevel(self._getPyroLevel()) if not args: (args, source) = ([source], "N/A") self.logger.warn("%s ** %s", source, reduce(lambda x,y: str(x)+' '+str(y),args)) def error(self,source,*args): self.setLevel(self._getPyroLevel()) if not args: (args, source) = ([source], "N/A") self.logger.error("%s ** %s", source, reduce(lambda x,y: str(x)+' '+str(y),args)) def raw(self,ztr): self.logger.log(999,ztr.rstrip()) def _logfile(self): raise NotImplementedError,'must override' def _getlevel(self): raise NotImplementedError,'must override' class SystemLogger(LoggerBase): def _getLoggerName(self): return "Pyro.system" def _getPyroLevel(self): return Pyro.config.PYRO_TRACELEVEL def _logfile(self): return Pyro.config.PYRO_LOGFILE class UserLogger(LoggerBase): def _getLoggerName(self): return "Pyro.user" def _getPyroLevel(self): return Pyro.config.PYRO_USER_TRACELEVEL def _logfile(self): return Pyro.config.PYRO_USER_LOGFILE else: # classic Pyro logging. class LoggerBase: # Logger base class. Subclasses must override _logfile and _checkTraceLevel. def __init__(self): self.lock=getLockObject() def msg(self,source,*args): if self._checkTraceLevel(3): self._trace('NOTE',source, args) def warn(self,source,*args): if self._checkTraceLevel(2): self._trace('WARN',source, args) def error(self,source,*args): if self._checkTraceLevel(1): self._trace('ERR!',source, args) def raw(self,str): self.lock.acquire() try: f=open(self._logfile(),'a') f.write(str) f.close() finally: self.lock.release() def _trace(self,typ,source, arglist): self.lock.acquire() try: if not arglist: (arglist, source) = ([source], "N/A") try: tf=open(self._logfile(),'a') try: pid=os.getpid() pidinfo=" ["+str(os.getpid()) except: pidinfo=" [" # XXX jython has no getpid() if supports_multithreading(): pidinfo+=":"+threading.currentThread().getName() pidinfo+="] " tf.write(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time()))+ pidinfo+'** '+typ+' ** '+str(source)+' ** '+reduce(lambda x,y: str(x)+' '+str(y),arglist)+'\n') tf.close() except Exception,x: pass finally: self.lock.release() def _logfile(self): raise NotImplementedError,'must override' def _checkTraceLevel(self,level): raise NotImplementedError,'must override' class SystemLogger(LoggerBase): def _checkTraceLevel(self, level): return Pyro.config.PYRO_TRACELEVEL >= level def _logfile(self): filename=Pyro.config.PYRO_LOGFILE if not os.path.isabs(filename): Pyro.config.PYRO_LOGFILE=os.path.join(Pyro.config.PYRO_STORAGE, filename) return Pyro.config.PYRO_LOGFILE class UserLogger(LoggerBase): def _checkTraceLevel(self, level): return Pyro.config.PYRO_USER_TRACELEVEL >= level def _logfile(self): filename=Pyro.config.PYRO_USER_LOGFILE if not os.path.isabs(filename): Pyro.config.PYRO_USER_LOGFILE=os.path.join(Pyro.config.PYRO_STORAGE, filename) return Pyro.config.PYRO_USER_LOGFILE # The logger object 'Log'. Log = SystemLogger() # Caching directory lister, outputs (filelist,dirlist) tuple # Based upon dircache.py, but implemented in a callable object # that has a thread-safe cache. class DirLister: def __init__(self): self.lock=getLockObject() self.__listdir_cache = {} def __call__(self,path): self.lock.acquire() try: try: cached_mtime, files, directories = self.__listdir_cache[path] del self.__listdir_cache[path] except KeyError: cached_mtime, files, directories = -1, [], [] finally: self.lock.release() mtime = os.stat(path)[8] if mtime <> cached_mtime: files=[] directories=[] for e in os.listdir(path): if os.path.isdir(os.path.join(path,e)): directories.append(e) else: files.append(e) self.lock.acquire() try: self.__listdir_cache[path] = mtime, files, directories return files,directories finally: self.lock.release() listdir = DirLister() # callable object # Fairly simple argument options parser. Like getopt(3). class ArgParser: def __init__(self): pass def parse(self, args, optionlist): # optionlist is a string such as "ab:c" which means # we search for 3 options (-a, -b, -c) of which -b has an argument. self.options={} # public, the option->value dictionary self.args=[] # public, the rest of the arguments self.ignored=[] # public, ignored options optionlist+=' ' # add sentinel if type(args)==type(''): args=args.split() while args: arg=args[0] del args[0] if arg[0]=='-': if len(arg)>=2: # arg is an option. Check our list idx = optionlist.find(arg[1]) if idx>=0: if optionlist[idx+1]==':': # option requires argument. if len(arg)>=3: # argument is appended. Use this. self.options[arg[1]]=arg[2:] continue # fetch argument from next string if len(args)>=1: self.options[arg[1]]=args[0] del args[0] continue else: # missing arg, substitute None self.options[arg[1]]=None else: # option requires no argument, use None self.options[arg[1]]=None else: # didn't find this option, skip it self.ignored.append(arg[1]) else: # arg is a single '-'. Stop parsing. for a in args: self.args.append(a) args=None else: # arg is no option, add it to the residu list and continue self.args.append(arg) def hasOpt(self, option): return self.options.has_key(option) def getOpt(self, option, default=Exception()): try: return self.options[option] except KeyError: if not isinstance(default,Exception): return default raise KeyError('no such option') def printIgnored(self): if self.ignored: print 'Ignored options:', for o in self.ignored: print '-'+o, print _getGUID_counter=0 # extra safeguard against double numbers _getGUID_lock=getLockObject() if os.name=='java': def getGUID(): # Jython uses java's own ID routine used by RMI import java.rmi.dgc return java.rmi.dgc.VMID().toString().replace(':','-') else: import socket, binascii def getGUID(): # Generate readable GUID string. # The GUID is constructed as follows: hexlified string of # AAAAAAAA-AAAABBBB-BBBBBBBB-BBCCCCCC (a 128-bit number in hex) # where A=network address, B=timestamp, C=random. # The 128 bit number is returned as a string of 16 8-bits characters. # For A: should use the machine's MAC ethernet address, but there is no # portable way to get it... use the IP address + 2 bytes process id. import Pyro.protocol ip=Pyro.protocol.getIPAddress() if ip: networkAddrStr=binascii.hexlify(socket.inet_aton(ip))+"%04x" % os.getpid() else: # can't get IP address... use another value, like our Python id() and PID Log.warn('getGUID','Can\'t get IP address') try: ip=os.getpid() except: ip=0 ip += id(getGUID) networkAddrStr = "%08lx%04x" % (ip, os.getpid()) _getGUID_lock.acquire() # cannot generate multiple GUIDs at once global _getGUID_counter t1=time.time()*100 +_getGUID_counter _getGUID_counter+=1 _getGUID_lock.release() t2=int((t1*time.clock())%sys.maxint) & 0xffffff t1=int(t1%sys.maxint) timestamp = (long(t1) << 24) | t2 r2=(random.randint(0,sys.maxint/2)>>4) & 0xffff r3=(random.randint(0,sys.maxint/2)>>5) & 0xff return networkAddrStr+'%014x%06x' % (timestamp, (r2<<8)|r3 ) def genguid_scripthelper(argv): p=ArgParser() p.parse(argv,'') if p.args or p.ignored: print 'Usage: genguid (no arguments)' print 'This tool generates Pyro UIDs.' raise SystemExit print getGUID() # Get the configured pickling module. # Currently supported: cPickle, pickle, gnosis.xml.pickle (@paranoia -1), # and PyXML 0.8+'s xml.marshal.generic. def getPickle(): if Pyro.config.PYRO_XML_PICKLE: # user requires xml pickle. Fails if that is not available! return getXMLPickle() else: try: import cPickle return cPickle except ImportError: # Fall back on pickle if cPickle isn't available import pickle return pickle _xmlpickle={} # the following wrapper is necessary because PyXML's pickler # cannot take a 'binary' third argument... class PyXMLPickler: def __init__(self): import xml.marshal.generic self.pickle=xml.marshal.generic def dumps(self, obj, bin=None): return self.pickle.dumps(obj) def dump(self, obj, file, bin=None): return self.pickle.dumps(obj,file) def loads(self, str): return self.pickle.loads(str) def load(self, file): return self.pickle.load(file) def getXMLPickle(impl=None): # load & config the required xml pickle. # Currently supported: Gnosis Utils' gnosis.xml.pickle, # and PyXML 0.8+'s xml.marshal.generic global _xmlpickle if not impl: impl=Pyro.config.PYRO_XML_PICKLE impl=impl.lower() if impl=='any': # first try if PyXML is available, otherwise, choose Gnosis. impl='pyxml' Pyro.config.PYRO_XML_PICKLE=impl try: import xml.marshal.generic except ImportError: impl='gnosis' Pyro.config.PYRO_XML_PICKLE=impl if impl in _xmlpickle: return _xmlpickle[impl] try: if impl=='pyxml': # PyXML init _xmlpickle['pyxml']=PyXMLPickler() return _xmlpickle['pyxml'] elif impl=='gnosis': # Gnosis init import gnosis.xml.pickle _xmlpickle['gnosis']=gnosis.xml.pickle gnosis.xml.pickle.setParanoia(0) # default paranoia level is too strict for Pyro gnosis.xml.pickle.setParser('SAX') # use fastest parser (cEXPAT?) return gnosis.xml.pickle else: raise ImportError('unsupported xml pickle implementation requested: '+impl) except ImportError: Log.error('server was asked to use xml pickling but implementation ('+impl+') is not available') raise NotImplementedError('server was asked to use xml pickling but implementation ('+impl+') is not available') # Pyro traceback printing def getPyroTraceback(exc_obj): import constants try: exc_type, exc_value, exc_trb=sys.exc_info() remote_tb=getattr(exc_obj,constants.TRACEBACK_ATTRIBUTE,None) local_tb=formatTraceback(exc_type, exc_value, exc_trb) if remote_tb: return [ '---- remote Pyro traceback ----\n' ] + \ remote_tb + [ '---- local traceback ----\n' ] + local_tb else: # hmm. no remote tb info, return just the local tb. return local_tb finally: # clean up cycle to traceback del exc_type, exc_value, exc_trb def formatTraceback(ex_type, ex_value, tb): import traceback if Pyro.config.PYRO_DETAILED_TRACEBACK: import linecache get_line_number = traceback.tb_lineno res = ['-'*50+ "\n", " <%s> RAISED : %s\n" % (str(ex_type), str(ex_value)), " Extended Stacktrace follows (most recent call last)\n", '-'*50+'\n' ] try: # Do some manipulation shit of stack if tb != None: frame_stack = [] line_number_stack = [] #tb = sys.exc_info()[2] while 1: line_num = get_line_number(tb) line_number_stack.append(line_num) if not tb.tb_next: break tb = tb.tb_next f = tb.tb_frame for x in line_number_stack: frame_stack.append(f) f = f.f_back frame_stack.reverse() lines = iter(line_number_stack) seen_crap = 0 for frame in frame_stack: # Get items flocals = frame.f_locals.items()[:] line_num = lines.next() filename = frame.f_code.co_filename name = None for key, value, in flocals: if key == "self": name = "%s::%s" % (value.__class__.__name__, frame.f_code.co_name) if name == None: name = frame.f_code.co_name res.append('File "%s", line (%s), in %s\n' % (filename, line_num, name)) res.append("Source code:\n") code_line = linecache.getline(filename, line_num) if code_line: res.append(' %s\n' % code_line.strip()) if not seen_crap: seen_crap = 1 continue res.append("Local values:\n") flocals.sort() for key, value, in flocals: if key in frame.f_code.co_names: local_res=" %20s = " % key try: local_res += repr(value) except: try: local_res += str(value) except: local_res += "" res.append(local_res+"\n") res.append('-'*50 + '\n') res.append(" <%s> RAISED : %s\n" % (str(ex_type), str(ex_value))) res.append('-'*50+'\n') return res except: return ['-'*50+"\nError building extended traceback!!! :\n", ''.join(traceback.format_exception(* sys.exc_info() ) ) + '-'*50 + '\n', 'Original Exception follows:\n', ''.join(traceback.format_exception(ex_type, ex_value, tb)) ] else: # default traceback format. return traceback.format_exception(ex_type, ex_value, tb)