Welcome, guest | Sign In | My Account | Store | Cart

This recipe allows a user to place debug messages, error messages and standard messages throughout a program. The function name and line number will be added to each debug and error message before it is printed out. In addition, each of these messages can be passed to multiple handler objects that can direct the output to log files, e-mails, stdout, etc.

Python, 140 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#messaging.py
#this is a module used for messaging.  It allows multiple classes
#to handle various types of messages.  It should work on all python
#versions >= 1.5.2

import sys, string, exceptions

#this flag determines whether debug output is sent to debug handlers themselves
debug = 1

def setDebugging(debugging):
    global debug
    debug = debugging

class MessagingException(exceptions.Exception):
    """an exception class for any errors that may occur in 
    a messaging function"""
    def __init__(self, args=None):
        self.args = args

class FakeException(exceptions.Exception):
    """an exception that is thrown and then caught
    to get a reference to the current execution frame"""
    pass        
        
        
class MessageHandler:
    """All message handlers should inherit this class.  Each method will be 
    passed a string when the executing program passes calls a messaging function"""
    def handleStdMsg(self, msg):
        """do something with a standard message from the program"""
        pass
    def handleErrMsg(self, msg):
        """do something with an error message.  This will already include the
        class, method, and line of the call"""
        pass
    def handleDbgMsg(self, msg):
        """do something with a debug message.  This will already include the
        class, method, and line of the call"""
        pass

class defaultMessageHandler(MessageHandler):
    """This is a default message handler.  It simply spits all strings to
    standard out"""
    def handleStdMsg(self, msg):
        sys.stdout.write(msg + "\n")
    def handleErrMsg(self, msg):
        sys.stderr.write(msg + "\n")
    def handleDbgMsg(self, msg):
        sys.stdout.write(msg + "\n")

#this keeps track of the handlers
_messageHandlers = []

#call this with the handler to register it for receiving messages
def registerMessageHandler(handler):
    """we're not going to check for inheritance, but we should check to make
    sure that it has the correct methods"""
    for methodName in ["handleStdMsg", "handleErrMsg", "handleDbgMsg"]:
        try:
            getattr(handler, methodName)
        except:            
            raise MessagingException, "The class " + handler.__class__.__name__ + " is missing a " + methodName + " method"
    _messageHandlers.append(handler)
    
    
def getCallString(level):
    #this gets us the frame of the caller and will work
    #in python versions 1.5.2 and greater (there are better
    #ways starting in 2.1
    try:
        raise FakeException("this is fake")
    except Exception, e:
        #get the current execution frame
        f = sys.exc_info()[2].tb_frame
    #go back as many call-frames as was specified
    while level >= 0:        
        f = f.f_back
        level = level-1
    #if there is a self variable in the caller's local namespace then
    #we'll make the assumption that the caller is a class method
    obj = f.f_locals.get("self", None)
    functionName = f.f_code.co_name
    if obj:
        callStr = obj.__class__.__name__+"::"+f.f_code.co_name+" (line "+str(f.f_lineno)+")"
    else:
        callStr = f.f_code.co_name+" (line "+str(f.f_lineno)+")"        
    return callStr        
    
#send this message to all handlers of std messages
def stdMsg(*args):
    stdStr = string.join(map(str, args), " ")
    for handler in _messageHandlers:
        handler.handleStdMsg(stdStr)

#send this message to all handlers of error messages
def errMsg(*args):
    errStr = "Error in "+getCallString(1)+" : "+string.join(map(str, args), " ")
    for handler in _messageHandlers:
        handler.handleErrMsg(errStr)

#send this message to all handlers of debug messages
def dbgMsg(*args):
    if not debug:
        return
    errStr = getCallString(1)+" : "+string.join(map(str, args), " ")
    for handler in _messageHandlers:
        handler.handleDbgMsg(errStr)


registerMessageHandler(defaultMessageHandler())
#end of messaging.py

#test.py
#here is a simple use case for the above module
from messaging import stdMsg, dbgMsg, errMsg, setDebugging

setDebugging(0)

dbgMsg("this won't be printed")
stdMsg("but this will")

setDebugging(1)

def foo():
    dbgMsg("this is a debug message in", "foo")
    
class bar:
    def baz(self):
        errMsg("this is an error message in bar")

foo()
b = bar()
b.baz()
#end of test.py

output is :
but this will
foo (line 12) : this is a debug message in foo
Error in bar::baz (line 16) : this is an error message in bar
 

I use debug statements regularly in my code and often include the name of the function in the debug message. This is tedious and error prone if I copy and paste debug statements between different functions. In addition, I have often desired the ability to easily turn off debug statements without needing to use if not debug: print "stuff" statements everywhere.

This code automatically inserts the function name, class name, and line number of the debug statement or error statement when it is printed out. This makes it easy to jump straight to that line in an editor. Each of the messaging methods: stdMsg, errMsg, and dbgMsg can be used just like a print statement in that you can separate an arbitrary number of arguments with commas and each one will be converted to a string. e.g. dbgMsg("the value of X is", x)

The other nice thing about this type of system is that you can create multiple "handler" objects by subclassing MessageHandler. Each registered handler gets sent each string when a program calls a Msg function so it's possible that one Msg call could send text to a log file, a gui window, and standard out.

Created by Christian Bird on Wed, 14 Aug 2002 (PSF)
Python recipes (4591)
Christian Bird's recipes (1)

Required Modules

  • (none specified)

Other Information and Tasks