Version 13 of Errx

Updated 2002-09-04 22:19:50

errx

The idea of errx came to me as I was enerved by debugging puts. It resembles a bit of the BSD errx/warnx pair, although it combines both functionalities into one function. Furthermore it offers something like a debuggers bt , i.e. backtrace.

It is not yet finished, but I use it daily already, although the more fancy things (like the bt) are not yet fully finished or even nonexistant.

Martin Weber [L1 ]


Make it simple, paste it here - code on a wiki page, with the ensuing discussions, is more attractive and pedagogical than a URL to download a tar.gz bundle... (RS ;-)


In fact I had exactly that in mind - errx itself is lean, but I am using it in a bundle with some other stuff - bgerror, and some other utility stuff which I've packed into ::Error ... gonna present errx here and offer the rest for download :)


And here we go. This is the factored out errx which I already use. I have cut out about anything which is not yet finished.


 # When this is sourced, set errchann and debugging if not set
 # to sane values.
 if {![info exists ::errchann]} { set ::errchann stderr }
 if {![info exists ::debugging]} { set ::debugging 0 }

 proc errx { txt {flags {info debug}} } {
    global argv0 debugging errchann errorInfo lastargs

    if {![string length $errchann]} { return }

    if {![info exists debugging]} { set debugging 0 }

    # if !debugging immediate return when flags contain debug
    if {([set dbgpos [lsearch $flags debug]] != -1) && !$debugging} { return } else { set flags [lreplace $flags $dbgpos $dbgpos] }

    upvar _DEBUG _debug
    if {![info exists _debug]} { set _debug 0 }
    if {([set dbgpos [lsearch $flags DEBUG]] != -1) && !$_debug} { return } else { set flags [lreplace $flags $dbgpos $dbgpos] }

    if {[info level]==1}  { ;# caller is global!
        set caller "*global*"
        set verbose ""
    } else {
        set caller [lindex [info level -1] 0]
        set verbose "$argv0:\[<$caller>\] Called as: [info level -1]"
    }
    if {[set flp [lsearch $flags time]]!= -1} {
        set intro "$argv0@([clock format [clock seconds]]):\[<$caller>\]"
        set flags [lreplace $flags $flp $flp]
    } else {
        set intro "$argv0:\[<$caller>\]"
    }

    switch -- $flags {
        sparse      {   puts $errchann "$txt" }
        ""          -
        debug       { ;# this shouldn't happen, but can if two debug flags are given. 
                        puts $errchann "$intro:<DEBUG> $txt"
                    }
        info        {   puts $errchann "$intro:<INFO> $txt" }
        warning     {   puts $errchann "$intro:<WARNING> $txt"}
        error       {   puts $errchann "$intro:<ERROR!> $txt\n$verbose" ; exit 1 }
        critical    {   puts $errchann "$intro: ** CRITICAL ERROR ! **"
                        if {[info exists errorInfo]} { puts $errchann " -- $errorInfo" }
                        if {[info exists errorCode]} { puts $errchann " -- $errorCode" }
                        for {set lv [expr [info level] -1 ]} { $lv } { incr lv -1 } {
                            puts $errchann "================================================================================"
                            puts $errchann " Level $lv, [info level $lv]."
                            puts $errchann " Local variables:"
                            uplevel #$lv { foreach v [info locals] { upvar #[info level] $v _v; puts -nonewline $::errchann "$v=$_v\t" } }
                            puts $errchann "\n"
                        }
                        exit 1
                    }
        default     { errx "Invalid flags given to errx ($flags)!" error } 
    }
    return  
 }

Its strong points are mainly that you can switch messages on and off, and even use for non debugging, but warning or informational purposes.

If the variable _DEBUG is set in the calling environment, and set to 1 (or true), then the DEBUG token works. If the global variable debugging is set, then the debug token works. (works in the sense like "the debug modifier works", see the first and third example)

In general you'd use it like follows:

 # if {$_DEBUG} in calling environment, output <INFO> msg
 errx "local-var-I-want-to-know is $bla" {info DEBUG}

 # output a <WARNING> unconditionally
 errx "I just deleted my harddisk!" warning

 # if {$::debugging}, output a <debug> msg
 errx "Hmm, environment doesn't seem sane..." debug

 # Output a error along with stack backtrace, inspection
 # of local variables up the stack and a timestamp,
 # exit the application.
 errx "YIKES! THE ALIENS ARE COMING!" {critical time}

 # Kill the application with your error message
 errx "hmm, something went wildly wrong." error

What I like about it is that it displays the caller and the script for which it works, normally with 'puts' you do all the same all the time:

 puts stderr "this-proc, that-var is $bla @ [clock format [clock seconds]"

Furthermore, when you switch your code to use a global routine like that, can easily say something like:

 set ::debugging 1
 set ::errchann [open debug.log {WRONLY CREAT TRUNC}]

and collect all information from it at a central place...


Once I finished errx to the like I want it, I'll put up the tar.gz I mentioned earlier. Included in this Error module is a facility for bgerror handling (installing/reinstalling of handlers etc.), setting of debug - levels (offers control of verbosity on a global level, too), Switching of errchann with reinstalling the old one, a predefined bgerror for use with event-driven tcl applications, and concise documentation.

Things errx is going to learn still:

  • norecord, record

Record error messages in an internal stack for use if errchann is unset

  • replay

Get the messages from the stack

  • rdr

"redirect", Report grandparent-caller, not parent-environment (For use with replay)

  • ml or multiline

Better handling for multiline messages probably automatic

  • stack

Just display a stack trace

  • vars

Display callers environment (info locals)

  • filtered stack trace with help of errorInfo

Consider the following:

 % proc x1 {} {
    puts bla
    puts blah
    x2
    puts blabla
 }
 % proc x2 {} {
    puts banzai
    error ayaken
    puts dead
 }
 % catch x1 zack

 % puts $zack
 ayaken

At this place, in errorInfo a nice stacktrace is in. It contains line numbers, too, which we can re-transform into code by the following:

 % lindex [split [info body <proc>] "\n"] [expr <lineno in errInf> - 1]

Or come up with a bit of environment in the calling function easily with

 % lrange [split [info body <proc>] "\n"] [expr <lineno in errInf> -3] [expr <lineno in errInf> +2]

I like the idea, gonna put it into the Error module I'll end up with...

- these are to be combined, too ... if you have further suggestions, well, this is a wiki :) Go ahead.

If you wonder what for all that junk, the ultimate goal is to create something you can distribute with your application and which hides debug messages from the user. If he has a serious problem, he can send a problem report which included all kinds of debug messages collected internally, along with stack traces, local variables etc. etc.


More to come, -Martin

4/9/2002: Nice! The one point I'd like to raise, is that the arg expansion of Tcl makes it unattractive to leave lots of errx calls in (just like various flavors of "assert"). Example:

        errx "time needed for 1000 calls is [time {mycall arg} 1000]" debug

That statement is going to take time even when debugging is turned off.

Slightly less convenient, but a way to solve this, is to use uplevel inside errx so calls can be rewritten as:

        errx {time needed for 1000 calls is [time {mycall arg} 1000]} debug

It ain't perfect, but at least this way one can leave all the calls in and have them take relatively little time (even less if errx gets redefined to "prox errx args {}" when pressed for time). Would this approach be an idea worth considering for errx? -jcw


05/09/2002 CET :)

Yes, you got a point there. In fact, I have not used errx's arguments up to now to carry "expensive" operations like the one you mentioned, although I do think your point is valid. I tried to toy around with something like

 interp alias {} errx {} \;\#

to turn it off when there is no debugging, but that didn't work. The solution you suggest is quite worth adding, and I will do it later - und update this page once I've changed errx. -Martin