# -*- coding: utf-8 -*- """ Logger class for IPython's logging facilities. $Id: Logger.py 430 2004-11-30 08:52:05Z fperez $ """ #***************************************************************************** # Copyright (C) 2001 Janko Hauser and # Copyright (C) 2001-2004 Fernando Perez # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. #***************************************************************************** #**************************************************************************** # Modules and globals from IPython import Release __author__ = '%s <%s>\n%s <%s>' % \ ( Release.authors['Janko'] + Release.authors['Fernando'] ) __license__ = Release.license # Python standard modules import os,sys,glob # Homebrewed from IPython.genutils import * #**************************************************************************** # FIXME: The logger class shouldn't be a mixin, it throws too many things into # the InteractiveShell namespace. Rather make it a standalone tool, and create # a Logger instance in InteractiveShell that uses it. Doing this will require # tracking down a *lot* of nasty uses of the Logger attributes in # InteractiveShell, but will clean up things quite a bit. class Logger: """A Logfile Mixin class with different policies for file creation""" # FIXME: once this isn't a mixin, log_ns should just be 'namespace', since the # names won't collide anymore. def __init__(self,log_ns): self._i00,self._i,self._ii,self._iii = '','','','' self.do_full_cache = 0 # FIXME. There's also a do_full.. in OutputCache self.log_ns = log_ns # defaults self.LOGMODE = 'backup' self.defname = 'logfile' def create_log(self,header='',fname='',defname='.Logger.log'): """Generate a new log-file with a default header""" if fname: self.LOG = fname if self.LOG: self.logfname = self.LOG else: self.logfname = defname if self.LOGMODE == 'over': if os.path.isfile(self.logfname): os.remove(self.logfname) self.logfile = open(self.logfname,'w') if self.LOGMODE == 'backup': if os.path.isfile(self.logfname): backup_logname = self.logfname+'~' # Manually remove any old backup, since os.rename may fail # under Windows. if os.path.isfile(backup_logname): os.remove(backup_logname) os.rename(self.logfname,backup_logname) self.logfile = open(self.logfname,'w') elif self.LOGMODE == 'global': self.logfname = os.path.join(self.home_dir, self.defname) self.logfile = open(self.logfname, 'a') self.LOG = self.logfname elif self.LOGMODE == 'rotate': if os.path.isfile(self.logfname): if os.path.isfile(self.logfname+'.001~'): old = glob.glob(self.logfname+'.*~') old.sort() old.reverse() for f in old: root, ext = os.path.splitext(f) num = int(ext[1:-1])+1 os.rename(f, root+'.'+`num`.zfill(3)+'~') os.rename(self.logfname, self.logfname+'.001~') self.logfile = open(self.logfname,'w') elif self.LOGMODE == 'append': self.logfile = open(self.logfname,'a') if self.LOGMODE != 'append': self.logfile.write(header) self.logfile.flush() def logstart(self, header='',parameter_s = ''): if not hasattr(self, 'LOG'): logfname = self.LOG or parameter_s or './'+self.defname self.create_log(header,logfname) elif parameter_s and hasattr(self,'logfname') and \ parameter_s != self.logfname: self.close_log() self.create_log(header,parameter_s) self._dolog = 1 def switch_log(self,val): """Switch logging on/off. val should be ONLY 0 or 1.""" if not val in [0,1]: raise ValueError, \ 'Call switch_log ONLY with 0 or 1 as argument, not with:',val label = {0:'OFF',1:'ON'} try: _ = self.logfile except AttributeError: print """ Logging hasn't been started yet (use %logstart for that). %logon/%logoff are for temporarily starting and stopping logging for a logfile which already exists. But you must first start the logging process with %logstart (optionally giving a logfile name).""" else: if self._dolog == val: print 'Logging is already',label[val] else: print 'Switching logging',label[val] self._dolog = 1 - self._dolog def logstate(self): """Print a status message about the logger.""" try: logfile = self.logfname except: print 'Logging has not been activated.' else: state = self._dolog and 'active' or 'temporarily suspended' print """ File:\t%s Mode:\t%s State:\t%s """ % (logfile,self.LOGMODE,state) def log(self, line,continuation=None): """Write the line to a log and create input cache variables _i*.""" # update the auto _i tables #print '***logging line',line # dbg #print '***cache_count', self.outputcache.prompt_count # dbg input_hist = self.log_ns['_ih'] if not continuation and line: self._iii = self._ii self._ii = self._i self._i = self._i00 # put back the final \n of every input line self._i00 = line+'\n' #print 'Logging input:<%s>' % line # dbg input_hist.append(self._i00) # hackish access to top-level namespace to create _i1,_i2... dynamically to_main = {'_i':self._i,'_ii':self._ii,'_iii':self._iii} if self.do_full_cache: in_num = self.outputcache.prompt_count # add blank lines if the input cache fell out of sync. This can happen # for embedded instances which get killed via C-D and then get resumed. while in_num >= len(input_hist): input_hist.append('\n') new_i = '_i%s' % in_num if continuation: self._i00 = '%s%s\n' % (self.log_ns[new_i],line) input_hist[in_num] = self._i00 to_main[new_i] = self._i00 self.log_ns.update(to_main) if self._dolog and line: self.logfile.write(line+'\n') self.logfile.flush() def close_log(self): if hasattr(self, 'logfile'): self.logfile.close() self.logfname = ''