""" log.py: Error logging module for Python. $Revision: 1.7 $ $Date: 2002/06/22 04:08:54 $ Exports: Log ( fileName=None, prefix=None ): [ if (fileName is a string or None) and (prefix is a string or None) -> if Log() has never been instantiated -> return a new, singleton instance of Log(), logging to fileName if given, using prefix (prefix) if given, defaulting to DEFAULT_PREFIX else -> return the singleton instance ] .addLogFile ( fileName ): [ if fileName is a string -> if fileName names a file that can be opened for writing -> self := self with fileName added to its list of log files else -> self := self with an error logged because we can't open (fileName) raise IOError ] .setPrefix ( prefix ): [ if prefix is a string -> self := self with its prefix changed to (prefix) ] .msgKind ( kind, text, ... ): [ if self has logged no messages of type (kind) -> self := self with a count of 1 message of type (kind) else -> self := self with one more message of type (kind) In any case -> +:= (self's prefix) + (kind) + ": " + (concatenation of all (text) arguments) + "\n" (self's log files) +:= ] .error ( text, ... ): [ if self has logged no messages of type "Error" -> self := self with a count of 1 message of type "Error" else -> self := self with one more message of type "Error" In any case -> +:= (self's prefix) + ("Error") + ": " + (concatenation of all (text) arguments) + "\n" (self's log files) +:= ] .warning ( text, ... ): [ if self has logged no messages of type WARNING_KIND -> self := self with a count of 1 message of that kind else -> self := self with one more message of that kind In any case -> +:= (self's prefix) + WARNING_KIND + ": " + (concatenation of all (text) arguments) + "\n" (self's log files) +:= ] .count ( kind="Error" ): [ if self has logged no messages of type (kind) -> return 0 else -> return the number of messages self has logged of type (kind) ] .fatal ( text, ... ): [ +:= (self's prefix) + ("Fatal error: ") + (concatenation of all (text) arguments) + "\n" (self's log files) +:= stop execution ] .message ( text, ... ): [ +:= (self's prefix) + (concatenation of all (text) arguments) + "\n" (self's log files) +:= ] .write ( text, ... ): [ +:= (concatenation of all (text) arguments) + "\n" (self's log files) +:= ] .revision [ the RCS revision string ] .date [ the RCS date string ] Features: * All messages are written to sys.stderr * All messages are also written to any number of additional log files that callers want written * Messages are classified according to a ``message kind'' code (e.g., errors, warnings), and callers can ask the object how many messages of a given kind have been logged * A characteristic prefix, defaulting to "*** ", is prefixed to each output line. This module is an example of the Singleton pattern, as described in: Gamma, Erich; Richard Helm; Ralph Johnson; John Vlissides. Design patterns: elements of reusable object-oriented software. Addison-Wesley, 1994, ISBN 0-201-63361-2. In practice, this means that all instantiations are the identical object. The singleton object is class _Log; use the Log() function as the actual constructor. Written by John W. Shipman (john@nmt.edu), New Mexico Tech Computer Center, Socorro, NM 87801. """ import sys import string #================================================================ # Manifest constants #---------------------------------------------------------------- ERROR_KIND = "Error" WARNING_KIND = "Warning" # - - - L o g - - - def Log ( fileName=None, prefix=None ): """ [ if (fileName is a string or None) and (prefix) is a string or None) -> return the singleton _Log object, logging to fileName (unless fileName is None), and with prefix (prefix) (unless prefix is None, in which case the prefix is _Log.DEFAULT_PREFIX) ] """ #-- 1 -- if not _Log.instance: _Log.instance = _Log ( ) #-- 2 -- if fileName: _Log.instance.addLogFile ( fileName ) #-- 3 -- if prefix: _Log.instance.setPrefix ( prefix ) #-- 4 -- return _Log.instance # - - - c l a s s _ L o g - - - class _Log: """ Singleton log object. Do not directly instantiate _Log(); use the Log() constructor function above. State: .instance: [ a member which is None if self has never been instantiated, else self ] .kindMap: [ a dictionary such that .kindMap[k] == the count of all messages of type (k) logged so far ] .logList: [ all fileNames passed to constructors, as a list of writeable file objects ] .prefix: [ the last (prefix) argument passed to a constructor, defaulting to DEFAULT_PREFIX ] """ DEFAULT_PREFIX = "*** " DEFAULT_KIND = ERROR_KIND instance = None revision = "$Revision: 1.7 $" date = "$Date: 2002/06/22 04:08:54 $" # - - - _ _ i n i t _ _ - - - def __init__ ( self, fileName=None, prefix=None ): """ Constructor for the Log object. """ self.kindMap = {} self.logList = [] self.prefix = self.DEFAULT_PREFIX # - - - s e t P r e f i x - - - def setPrefix ( self, prefix ): """ [ if prefix is a string -> self.prefix := prefix ] """ self.prefix = prefix # - - - a d d L o g F i l e - - - def addLogFile ( self, fileName ): """ [ if fileName is a string -> if fileName names a file that can be opened for writing -> self.logList +:= (fileName), opened for writing else -> raise IOError ] """ f = open ( fileName, "w" ) self.logList.append ( f ) # - - - m s g K i n d - - - def msgKind ( self, kind, *text ): """ [ if self.kindMap has no key (kind) -> self.kindMap[kind] := 1 else -> self.kindMap[kind] +:= 1 In any case -> +:= (self's prefix) + (kind) + ": " + (concatenation of all (text) arguments) + "\n" (self's log files) +:= ] """ #-- 1 -- # [ if self.kindMap has no key (kind) -> # self.kindMap[kind] := 1 # else -> # self.kindMap[kind] +:= 1 ] if not self.kindMap.has_key ( kind ): self.kindMap[kind] = 1 else: self.kindMap[kind] = self.kindMap[kind] + 1 #-- 2 -- # [ +:= (self's prefix) + (kind) + ": " + # (concatenation of all (text) arguments) + # "\n" # (self's log files) +:= ] output = kind + ": " + string.join ( text, "" ) self.message ( output ) # - - - e r r o r - - - def error ( self, *text ): """ [ if self.kindMap has key (DEFAULT_KIND) -> self.kindMap[DEFAULT_KIND] = self.kindMap[DEFAULT_KIND] + 1 else -> self.kindMap[DEFAULT_KIND] = 1 In any case -> +:= (self's prefix) + ("Error") + ": " + (concatenation of all (text) arguments) + "\n" (self's log files) +:= ] """ allText = string.join ( text, "" ) self.msgKind ( self.DEFAULT_KIND, allText ) # - - - w a r n i n g - - - def warning ( self, *text ): """ [ if self.kindMap has key (DEFAULT_KIND) -> self.kindMap[DEFAULT_KIND] = self.kindMap[DEFAULT_KIND] + 1 else -> self.kindMap[DEFAULT_KIND] = 1 In any case -> +:= (self's prefix) + ("Error") + ": " + (concatenation of all (text) arguments) + "\n" (self's log files) +:= ] """ allText = string.join ( text, "" ) self.msgKind ( WARNING_KIND, allText ) # - - - c o u n t - - - def count ( self, kind=DEFAULT_KIND ): """ Returns number of messages of type (kind) """ if self.kindMap.has_key ( kind ): return self.kindMap[kind] else: return 0 # - - - f a t a l - - - def fatal ( self, *text ): """ Write a message and stop execution """ allText = string.join ( text, "" ) self.msgKind ( "Fatal error", allText ) raise SystemExit # - - - m e s s a g e - - - def message ( self, *text ): """ Write a message with the standard prefix """ output = self.prefix + string.join ( text, "" ) self.write ( output ) # - - - w r i t e - - - def write ( self, *text ): """ Write the given text to and all log files """ output = string.join ( text, "" ) + "\n" sys.stderr.write ( output ) for f in self.logList: f.write ( output ) # - - - r e v i s i o n - - - def revision ( self ): return "$Revision: 1.7 $" # - - - d a t e - - - def date ( self ): return "$Date: 2002/06/22 04:08:54 $"