StandOut 3.0.0 Alpha

0 views
Skip to first unread message

fuzz...@voidspace.org.uk

unread,
Jun 18, 2006, 1:30:31 PM6/18/06
to pytho...@googlegroups.com

StandOut 3.0.0 Alpha

emoticon:dove I managed to go an entire week without a Python related entry. Of course, despite this lots of things have been going on. I have a stack of potential blog entries metaphorically at my side, some of which may never see the light of day. But first the most important thing.

There is now a new version of StandOut in subversion :

StandOut 3.0.0 Alpha

There are also unit tests to go with it.

StandOut is a simple class that redirects sys.stdout and sys.stderr. This serves two purposes. By passing in a file name when you instantiate StandOut it can log everything printed (and all errors) to a file. You can also assign messages a priority, and tell StandOut what priority of messages to actually display, making implementing different "verbosity levels" very easy.

When I first wrote StandOut I thought I would use the verbosity features, but I didn't actually use them until recent changes in rest2web. I was amazed that it worked, and that it was easy to use. Unfortunately along the way, StandOut had grown lots of cruft and complicated ways of using it that nobody needed.

This version is a complete rewrite. It is much better written, with a simpler and straightforward API. It isn't compatible with the previous version, so programs currently using StandOut will need minor modifications to use the new version.

Enough prattle, here's how to use it.

By default StandOut diverts both stdout and stderr. If you pass in a filename (optional) then that will be used to log to. Output on stderr will be prefixed with "[err] " [1].

import os
import sys

from standout3 import StandOut
filename = os.path.join(os.path.expanduser('~'), 'logfile.txt')

standout = StandOut(filename)

print 'hello' # replace with real code ;-)
sys.stderr('This is an error')

standout.close()

The log file will then contain :

hello
[err] This is an error

The standout.close() call restores sys.stdout and sys.stderr to their normal state and closes the logfile. If an unhandled exception occurs during your code, execution will terminate. If you want to guarantee that close is still called, you can put it inside a try:... finally: block. Unfortunately the exception will then be raised (by the finally clause) after close has been called. You can use the following trick to get round this :

import os
import sys
import traceback

from standout3 import StandOut
filename = os.path.join(os.path.expanduser('~'), 'logfile.txt')

standout = StandOut(filename)

try:
    raise TypeError("Some Error")
finally:
    traceback.print_last()
    standout.close()

You will see that the error still appears in the logfile in the usual way. On the other hand, if you just let your program terminate (without trapping errors like this), then the open log file will be garbage collected and closed for you. Smile

By default all messages have a priority of five. There are four possible output methods :

  1. The stdout stream (outStream)
  2. Logging output to the file (outLogfile)
  3. The stderr stream (errStream)
  4. Logging errors to the file (errLogfile)

You can configure their threshold independently, but there are easier ways as well. If a message has a equal or higher priority than the threshold for an output method, it will be passed on. If the priority is lower than the threshold, it will be dropped. Easy hey. Razz

The default threshold for the output stream and file is five. The default threshold for the error stream and file is zero (everything logged).

import os
import sys

from standout3 import StandOut
filename = os.path.join(os.path.expanduser('~'), 'logfile.txt')

standout = StandOut(filename, priority=4)

print 'Priority is ', standout.priority
print 'Output message with a priority of four'
print >> sys.stderr, 'Error message with a priority of four'

standout.priority = 6
print 'Output message with a priority of six'
print >> sys.stderr, 'Error message with a priority of six'

standout.close()

Unfortunately the message telling you that the priority is four won't be displayed because the threshold is too low.

Logged to the file will be :

[err] Error message with a priority of four
Output message with a priority of six
[err] Error message with a priority of six

There is also another way of setting the priority of individual messages, which you may prefer. This is what I used to implement varying levels of verbosity in rest2web.

import os
import sys

from standout3 import StandOut
filename = os.path.join(os.path.expanduser('~'), 'logfile.txt')

standout = StandOut(filename, errThreshold=5)

sys.stdout.write('Message with a priority of six', 6)
sys.stderr.write('Error with a priority of six', 6)

sys.stdout.write('Message with a priority of four', 4)
sys.stderr.write('Error with a priority of four', 4)

standout.close()

Because we explicitly set the threshold for errors to five, only output and errors with a priority of five (or greater) will be passed on. The above code results in the following output :

Message with a priority of six
[err] Error with a priority of six

The following properties (which can also be passed in as keyword arguments) are used to set different thresholds for the different output methods :

  • standout.threshold - get or set the threshold for all of them
  • standout.outThreshold - get or set the threshold for the output stream and output logfile
  • standout.errThreshold - get or set the threshold for the error stream and error logfile
  • standout.outStreamThreshold - get or set the threshold for the output stream
  • standout.outLogfileThreshold - get or set the threshold for the output logfile
  • standout.errStreamThreshold - get or set the threshold for the error stream
  • standout.errLogfileThreshold - get or set the threshold for the error logfile

If the output methods have different thresholds, then standout.threshold (etc) will return -1.

There is currently no way of logging to a separate file for errors, other than instantiating StandOut twice :

import os
import sys

from standout3 import StandOut
filename = os.path.join(os.path.expanduser('~'), 'logfile.txt')
errorFile = os.path.join(os.path.expanduser('~'), 'errorLog.txt')

standout1 = StandOut(filename, stdErr=False)
standout2 = StandOut(errorFile, stdOut=False)

... # code

standout1.close()
standout2.close()

I developed this using the test driven development methodolgy we use at work. This guides the development process and results in better code and a better API. Someday I may do a blog entry about it. Laughing

The result is an 8k module with 38k of tests. I'm sure the tests could be better factored, but I think the code is pretty good and well tested.

There are still a few questions before I do a final release :

  1. Should I build in a way to log to a separate error file with a single instance of StandOut ?
  2. If you can specify a separate error file, should errPrefix default to '' ?
  3. Should you be able to pass in open files as well as filenames ?

I personally think that using two instances for separate logfiles is fine. This kind of answers questions one and two. I don't see the need for passing around open files, but it would make testing easier.

Anyway, feedback appreciated.

[1]The final module will be called standout rather than standout3. The name is temporary to avoid clashes with the current standout module. You can change the error prefix by passing in a string with the errPrefix keyword. It defaults to "[err] ".

Posted by Fuzzyman on 2006-06-18 18:07:18.
Categories: Python, Projects
Visit the Voidspace Techie Blog to read this entry and more.

Reply all
Reply to author
Forward
0 new messages