logger.tcl

Go to the documentation of this file.
00001 /*  logger.tcl --*/
00002 /* */
00003 /*    Tcl implementation of a general logging facility.*/
00004 /* */
00005 /*  Copyright (c) 2003      by David N. Welton <davidw@dedasys.com>*/
00006 /*  Copyright (c) 2004-2007 by Michael Schlenker <mic42@users.sourceforge.net>*/
00007 /*  Copyright (c) 2006      by Andreas Kupries <andreas_kupries@users.sourceforge.net>*/
00008 /* */
00009 /*  See the file license.terms.*/
00010 
00011 /*  The logger package provides an 'object oriented' log facility that*/
00012 /*  lets you have trees of services, that inherit from one another.*/
00013 /*  This is accomplished through the use of Tcl namespaces.*/
00014 
00015 
00016 package require Tcl 8.2
00017 package provide logger 0.8
00018 
00019 namespace ::logger {
00020     namespace tree {}
00021     namespace export init enable disable services servicecmd import
00022 
00023     /*  The active services.*/
00024     variable services {}
00025 
00026     /*  The log 'levels'.*/
00027     variable levels [list debug info notice warn error critical alert emergency]
00028     
00029     /*  The default global log level used for new logging services*/
00030     variable enabled "debug"
00031 
00032     /*  Tcl return codes (in numeric order)*/
00033     variable RETURN_CODES   [list "ok" "error" "return" "break" "continue"]
00034 }
00035 
00036 /*  ::logger::_nsExists --*/
00037 /* */
00038 /*    Workaround for missing namespace exists in Tcl 8.2 and 8.3.*/
00039 /* */
00040 
00041 if {[package vcompare [package provide Tcl] 8.4] < 0} {
00042     ret  ::logger::_nsExists (type ns) {
00043         expr {![catch {namespace parent $ns}]}
00044     }
00045 } else {
00046     ret  ::logger::_nsExists (type ns) {
00047         namespace exists $ns
00048     }
00049 }
00050 
00051 /*  ::logger::_cmdPrefixExists --*/
00052 /* */
00053 /*  Utility function to check if a given callback prefix exists,*/
00054 /*  this should catch all oddities in prefix names, including spaces, */
00055 /*  glob patterns, non normalized namespaces etc.*/
00056 /* */
00057 /*  Arguments:*/
00058 /*    prefix - The command prefix to check*/
00059 /*    */
00060 /*  Results:*/
00061 /*    1 or 0 for yes or no*/
00062 /* */
00063 ret  ::logger::_cmdPrefixExists (type prefix) {
00064     set cmd [lindex $prefix 0]
00065     set full [namespace eval :: namespace which [list $cmd]]
00066     if {[string equal $full ""]} {return 0} else {return 1}
00067     # normalize namespaces
00068     set ns [namespace qualifiers $cmd]
00069     set cmd ${ns}::[namespace tail $cmd]
00070     set matches [::info commands ${ns}::*]
00071     if {[lsearch -exact $matches $cmd] != -1} {return 1}
00072     return 0
00073 }
00074 
00075 /*  ::logger::walk --*/
00076 /* */
00077 /*    Walk namespaces, starting in 'start', and evaluate 'code' in*/
00078 /*    them.*/
00079 /* */
00080 /*  Arguments:*/
00081 /*    start - namespace to start in.*/
00082 /*    code - code to execute in namespaces walked.*/
00083 /* */
00084 /*  Side Effects:*/
00085 /*    Side effects of code executed.*/
00086 /* */
00087 /*  Results:*/
00088 /*    None.*/
00089 
00090 ret  ::logger::walk ( type start , type code ) {
00091     set children [namespace children $start]
00092     foreach c $children {
00093     logger::walk $c $code
00094     namespace eval $c $code
00095     }
00096 }
00097 
00098 ret  ::logger::init (type service) {
00099     variable levels
00100     variable services
00101     variable enabled
00102         
00103     # We create a 'tree' namespace to house all the services, so
00104     # they are in a 'safe' namespace sandbox, and won't overwrite
00105     # any commands.
00106     namespace eval tree::${service} {
00107         variable service
00108         variable levels
00109         variable oldname
00110         variable enabled
00111     }
00112 
00113     lappend services $service
00114 
00115     set [namespace current]::tree::${service}::service $service
00116     set [namespace current]::tree::${service}::levels $levels
00117     set [namespace current]::tree::${service}::oldname $service
00118     set [namespace current]::tree::${service}::enabled $enabled
00119     
00120     namespace eval tree::${service} {
00121     # Callback to use when the service in question is shut down.
00122     variable delcallback [namespace current]::no-op
00123 
00124     # Callback when the loglevel is changed
00125     variable levelchangecallback [namespace current]::no-op
00126     
00127     # State variable to decide when to call levelcallback
00128     variable inSetLevel 0
00129     
00130     # The currently configured levelcommands
00131     variable lvlcmds 
00132     array set lvlcmds {}
00133 
00134     # List of procedures registered via the trace command
00135     variable traceList ""
00136 
00137     # Flag indicating whether or not tracing is currently enabled
00138     variable tracingEnabled 0
00139 
00140     # We use this to disable a service completely.  In Tcl 8.4
00141     # or greater, by using this, disabled log calls are a
00142     # no-op!
00143 
00144     proc no-op args {}
00145 
00146 
00147     proc stdoutcmd {level text} {
00148         variable service
00149         puts "\[[clock format [clock seconds]]\] \[$service\] \[$level\] \'$text\'"
00150     }
00151 
00152     proc stderrcmd {level text} {
00153         variable service
00154         puts stderr "\[[clock format [clock seconds]]\] \[$service\] \[$level\] \'$text\'"
00155     }
00156 
00157 
00158     # setlevel --
00159     #
00160     #   This command differs from enable and disable in that
00161     #   it disables all the levels below that selected, and
00162     #   then enables all levels above it, which enable/disable
00163     #   do not do.
00164     #
00165     # Arguments:
00166     #   lv - the level, as defined in $levels.
00167     #
00168     # Side Effects:
00169     #   Runs disable for the level, and then enable, in order
00170     #   to ensure that all levels are set correctly.
00171     #
00172     # Results:
00173     #   None.
00174 
00175 
00176     proc setlevel {lv} {
00177         variable inSetLevel 1
00178         set oldlvl [currentloglevel]
00179         
00180         # do not allow enable and disable to do recursion
00181         if {[catch {
00182             disable $lv 0
00183             set newlvl [enable $lv 0]
00184         } msg] == 1} {
00185             return -code error -errorcode $::errorCode $msg
00186         }
00187         # do the recursion here
00188         logger::walk [namespace current] [list setlevel $lv]
00189         
00190         set inSetLevel 0
00191         lvlchangewrapper $oldlvl $newlvl
00192         return
00193     }
00194 
00195     # enable --
00196     #
00197     #   Enable a particular 'level', and above, for the
00198     #   service, and its 'children'.
00199     #
00200     # Arguments:
00201     #   lv - the level, as defined in $levels.
00202     #
00203     # Side Effects:
00204     #   Enables logging for the particular level, and all
00205     #   above it (those more important).  It also walks
00206     #   through all services that are 'children' and enables
00207     #   them at the same level or above.
00208     #
00209     # Results:
00210     #   None.
00211 
00212     proc enable {lv {recursion 1}} {
00213         variable levels
00214         set lvnum [lsearch -exact $levels $lv]
00215         if { $lvnum == -1 } {
00216         return -code error "Invalid level '$lv' - levels are $levels"
00217         }
00218 
00219         variable enabled
00220         set newlevel $enabled
00221         set elnum [lsearch -exact $levels $enabled]
00222         if {($elnum == -1) || ($elnum > $lvnum)} {
00223             set newlevel $lv
00224         }
00225                 
00226         variable service
00227         while { $lvnum <  [llength $levels] } {
00228         interp alias {} [namespace current]::[lindex $levels $lvnum] \
00229             {} [namespace current]::[lindex $levels $lvnum]cmd
00230         incr lvnum
00231         }
00232         
00233         if {$recursion} {
00234             logger::walk [namespace current] [list enable $lv]
00235         }
00236         lvlchangewrapper $enabled $newlevel
00237         set enabled $newlevel
00238     }
00239 
00240     # disable --
00241     #
00242     #   Disable a particular 'level', and below, for the
00243     #   service, and its 'children'.
00244     #
00245     # Arguments:
00246     #   lv - the level, as defined in $levels.
00247     #
00248     # Side Effects:
00249     #   Disables logging for the particular level, and all
00250     #   below it (those less important).  It also walks
00251     #   through all services that are 'children' and disables
00252     #   them at the same level or below.
00253     #
00254     # Results:
00255     #   None.
00256 
00257     proc disable {lv {recursion 1}} {
00258         variable levels
00259         set lvnum [lsearch -exact $levels $lv]
00260         if { $lvnum == -1 } {
00261         return -code error "Invalid level '$lv' - levels are $levels"
00262         }
00263 
00264         variable enabled
00265         set newlevel $enabled
00266         set elnum [lsearch -exact $levels $enabled]
00267         if {($elnum > -1) && ($elnum <= $lvnum)} {
00268             if {$lvnum+1 >= [llength $levels]} {
00269                 set newlevel "none"
00270             } else {
00271                 set newlevel [lindex $levels [expr {$lvnum+1}]]
00272             }
00273         }
00274         
00275         while { $lvnum >= 0 } {
00276         
00277         interp alias {} [namespace current]::[lindex $levels $lvnum] {} \
00278             [namespace current]::no-op
00279         incr lvnum -1
00280         }
00281         if {$recursion} {
00282             logger::walk [namespace current] [list disable $lv]
00283         }
00284         lvlchangewrapper $enabled $newlevel
00285         set enabled $newlevel
00286     }
00287 
00288     # currentloglevel --
00289     #
00290     #   Get the currently enabled log level for this service.
00291     #
00292     # Arguments:
00293     #   none
00294     #
00295     # Side Effects:
00296     #   none
00297     #
00298     # Results:
00299     #   current log level
00300     #
00301 
00302     proc currentloglevel {} {
00303         variable enabled
00304         return $enabled
00305     }
00306 
00307     # lvlchangeproc --
00308     #
00309     #   Set or introspect a callback for when the logger instance 
00310     #   changes its loglevel.
00311     #
00312     # Arguments:
00313     #   cmd - the Tcl command to call, it is called with two parameters, old and new log level.
00314     #   or none for introspection
00315     #
00316     # Side Effects:
00317     #   None.
00318     #
00319     # Results:
00320     #   If no arguments are given return the current callback cmd.
00321 
00322     proc lvlchangeproc {args} {
00323         variable levelchangecallback
00324         
00325         switch -exact -- [llength [::info level 0]] {
00326                 1   {return $levelchangecallback}
00327                 2   {
00328                      if {[::logger::_cmdPrefixExists [lindex $args 0]]} {
00329                         set levelchangecallback [lindex $args 0]
00330                      } else {
00331                         return -code error "Invalid cmd '[lindex $args 0]' - does not exist"
00332                      }    
00333                     }
00334                 default {
00335                     return -code error "Wrong # of arguments. Usage: \${log}::lvlchangeproc ?cmd?"
00336                 }
00337         }
00338     }
00339 
00340     proc lvlchangewrapper {old new} {
00341         variable inSetLevel
00342         
00343         # we are called after disable and enable are finished 
00344         if {$inSetLevel} {return}
00345         
00346         # no action if level does not change
00347         if {[string equal $old $new]} {return}
00348         
00349         variable levelchangecallback
00350         # no action if levelchangecallback isn't a valid command
00351         if {[::logger::_cmdPrefixExists $levelchangecallback]} {
00352         catch {
00353             uplevel \#0 [linsert $levelchangecallback end $old $new]
00354         }
00355         }
00356     }
00357     
00358     # logproc --
00359     #
00360     #   Command used to create a procedure that is executed to
00361     #   perform the logging.  This could write to disk, out to
00362     #   the network, or something else.
00363     #   If two arguments are given, use an existing command.
00364     #   If three arguments are given, create a proc.
00365     #
00366     # Arguments:
00367     #   lv - the level to log, which must be one of $levels.
00368     #   args - either zero, one or two arguments.
00369     #          if zero this returns the current command registered 
00370     #          if one, this is a cmd name that is called for this level
00371     #          if two, these are an argument and proc body
00372     #
00373     # Side Effects:
00374     #   Creates a logging command to take care of the details
00375     #   of logging an event.
00376     #
00377     # Results:
00378     #   If called with zero length args, returns the name of the currently
00379     #   configured logging procedure.
00380     #   
00381     #
00382 
00383     proc logproc {lv args} {
00384         variable levels
00385         variable lvlcmds
00386         
00387         set lvnum [lsearch -exact $levels $lv]
00388         if { ($lvnum == -1) && ($lv != "trace") } {
00389         return -code error "Invalid level '$lv' - levels are $levels"
00390         }
00391         switch -exact -- [llength $args] {
00392         0  {
00393             return $lvlcmds($lv)
00394            }
00395         1  {
00396             set cmd [lindex $args 0]
00397             if {[string equal "[namespace current]::${lv}cmd" $cmd]} {return} 
00398             if {[llength [::info commands $cmd]]} {
00399                 proc ${lv}cmd {args} "uplevel 1 \[list $cmd \[lindex \$args end\]\]"
00400             } else {
00401                 return -code error "Invalid cmd '$cmd' - does not exist"
00402             }
00403             set lvlcmds($lv) $cmd
00404         }
00405         2  {
00406             foreach {arg body} $args {break}
00407             proc ${lv}cmd {args} "_setservicename \$args; 
00408                                       set val \[${lv}customcmd \[lindex \$args end\]\] ; 
00409                                       _restoreservice; set val"
00410             proc ${lv}customcmd $arg $body
00411             set lvlcmds($lv) [namespace current]::${lv}customcmd
00412         }
00413         default {
00414             return -code error "Usage: \${log}::logproc level ?cmd?\nor \${log}::logproc level argname body"
00415         }
00416         }
00417     }
00418 
00419 
00420     # delproc --
00421     #
00422     #   Set or introspect a callback for when the logger instance 
00423     #   is deleted.
00424     #
00425     # Arguments:
00426     #   cmd - the Tcl command to call.
00427     #   or none for introspection
00428     #
00429     # Side Effects:
00430     #   None.
00431     #
00432     # Results:
00433     #   If no arguments are given return the current callback cmd.
00434 
00435     proc delproc {args} {
00436         variable delcallback
00437         
00438         switch -exact -- [llength [::info level 0]] {
00439                 1   {return $delcallback}
00440                 2   { if {[::logger::_cmdPrefixExists [lindex $args 0]]} {
00441                             set delcallback [lindex $args 0]
00442                       } else {
00443                         return -code error "Invalid cmd '[lindex $args 0]' - does not exist"                      
00444                       }
00445                     }
00446                 default {
00447                     return -code error "Wrong # of arguments. Usage: \${log}::delproc ?cmd?"
00448                 }
00449         }
00450     }
00451 
00452 
00453     # delete --
00454     #
00455     #   Delete the namespace and its children.
00456 
00457     proc delete {} {
00458         variable delcallback
00459         variable service
00460         
00461         logger::walk [namespace current] delete
00462         if {[::logger::_cmdPrefixExists $delcallback]} {
00463              uplevel \#0 [lrange $delcallback 0 end]  
00464         } 
00465         # clean up the global services list
00466         set idx [lsearch -exact [logger::services] $service]
00467         if {$idx !=-1} {
00468             set ::logger::services [lreplace [logger::services] $idx $idx]
00469         }
00470         
00471         namespace delete [namespace current]
00472         
00473     }
00474 
00475     # services --
00476     #
00477     #   Return all child services 
00478     
00479     proc services {} {
00480         variable service
00481         
00482         set children [list]
00483         foreach srv [logger::services] {
00484             if {[string match "${service}::*" $srv]} {
00485                 lappend children $srv
00486             }
00487         }
00488         return $children
00489     }
00490 
00491     # servicename --
00492     #
00493     #   Return the name of the service
00494     
00495     proc servicename {} {
00496         variable service
00497         return $service
00498     }
00499     
00500     proc _setservicename {arg} {
00501         variable service
00502         variable oldname
00503         if {[llength $arg] <= 1} {
00504             return
00505         } else {
00506             set oldname $service
00507             set service [lindex $arg end-1]
00508         }
00509     }
00510         
00511     proc _restoreservice {} {
00512         variable service
00513         variable oldname
00514         set service $oldname
00515         return
00516     }
00517     
00518     proc trace { action args } {
00519         variable service
00520 
00521         # Allow other boolean values (true, false, yes, no, 0, 1) to be used
00522         # as synonymns for "on" and "off".
00523 
00524         if {[string is boolean $action]} {
00525             set xaction [expr {($action && 1) ? "on" : "off"}]
00526         } else {
00527             set xaction $action
00528         }
00529 
00530         # Check for required arguments for actions/subcommands and dispatch
00531         # to the appropriate procedure.
00532 
00533         switch -- $xaction {
00534             "status" {
00535                 return [uplevel 1 [list logger::_trace_status $service $args]]
00536             }
00537             "on" {
00538                 if {[llength $args]} {
00539                     return -code error "wrong # args: should be \"trace on\""
00540                 }
00541                 return [logger::_trace_on $service]
00542             }
00543             "off" {
00544                 if {[llength $args]} {
00545                     return -code error "wrong # args: should be \"trace off\""
00546                 }
00547                 return [logger::_trace_off $service]
00548             }
00549             "add" {
00550                 if {![llength $args]} {
00551                     return -code error \
00552                         "wrong # args: should be \"trace add ?-ns? <proc> ...\""
00553                 }
00554                 return [uplevel 1 [list ::logger::_trace_add $service $args]]
00555             }
00556             "remove" {
00557                 if {![llength $args]} {
00558                     return -code error \
00559                         "wrong # args: should be \"trace remove ?-ns? <proc> ...\""
00560                 }
00561                 return [uplevel 1 [list ::logger::_trace_remove $service $args]]
00562             }
00563 
00564             default {
00565             return -code error \
00566                     "Invalid action \"$action\": must be status, add, remove,\
00567                     on, or off"
00568             }
00569         }
00570     }
00571 
00572     # Walk the parent service namespaces to see first, if they
00573     # exist, and if any are enabled, and then, as a
00574     # consequence, enable this one
00575     # too.
00576 
00577     enable $enabled
00578     variable parent [namespace parent]
00579     while {[string compare $parent "::logger::tree"]} {
00580         # If the 'enabled' variable doesn't exist, create the
00581         # whole thing.
00582         if { ! [::info exists ${parent}::enabled] } {
00583         
00584         logger::init [string range $parent 16 end]
00585         }
00586         set enabled [set ${parent}::enabled]
00587         enable $enabled
00588         set parent [namespace parent $parent]
00589     }
00590     }
00591 
00592     # Now create the commands for different levels.
00593 
00594     namespace eval tree::${service} {
00595     set parent [namespace parent]
00596 
00597     # We 'inherit' the commands from the parents.  This
00598     # means that, if you want to share the same methods with
00599     # children, they should be instantiated after the parent's
00600     # methods have been defined.
00601     if {[string compare $parent "::logger::tree"]} {
00602         foreach lvl [::logger::levels] {
00603             # OPTIMIZE: do not allow multiple aliases in the hierarchy
00604             #           they can always be replaced by more efficient
00605             #           direct aliases to the target procs.
00606             interp alias {} [namespace current]::${lvl}cmd {} ${parent}::${lvl}cmd $service
00607         }
00608         # inherit the starting loglevel of the parent service
00609         setlevel [${parent}::currentloglevel]
00610 
00611     } else {
00612         foreach lvl [concat [::logger::levels] "trace"] {
00613             proc ${lvl}cmd {args} "_setservicename \$args ; 
00614                                    set val \[stdoutcmd $lvl \[lindex \$args end\]\] ; 
00615                                    _restoreservice; set val"
00616             set lvlcmds($lvl) [namespace current]::${lvl}cmd
00617         }
00618     }
00619     }
00620     
00621     
00622     return ::logger::tree::${service}
00623 }
00624 
00625 /*  ::logger::services --*/
00626 /* */
00627 /*    Returns a list of all active services.*/
00628 /* */
00629 /*  Arguments:*/
00630 /*    None.*/
00631 /* */
00632 /*  Side Effects:*/
00633 /*    None.*/
00634 /* */
00635 /*  Results:*/
00636 /*    List of active services.*/
00637 
00638 ret  ::logger::services () {
00639     variable services
00640     return $services
00641 }
00642 
00643 /*  ::logger::enable --*/
00644 /* */
00645 /*    Global enable for a certain level.  NOTE - this implementation*/
00646 /*    isn't terribly effective at the moment, because it might hit*/
00647 /*    children before their parents, who will then walk down the*/
00648 /*    tree attempting to disable the children again.*/
00649 /* */
00650 /*  Arguments:*/
00651 /*    lv - level above which to enable logging.*/
00652 /* */
00653 /*  Side Effects:*/
00654 /*    Enables logging in a given level, and all higher levels.*/
00655 /* */
00656 /*  Results:*/
00657 /*    None.*/
00658 
00659 ret  ::logger::enable (type lv) {
00660     variable services
00661     if {[catch {
00662         foreach sv $services {
00663         ::logger::tree::${sv}::enable $lv
00664         }
00665     } msg] == 1} {
00666         return -code error -errorcode $::errorCode $msg
00667     }
00668 }
00669 
00670 ret  ::logger::disable (type lv) {
00671     variable services
00672     if {[catch {
00673         foreach sv $services {
00674         ::logger::tree::${sv}::disable $lv
00675         }
00676     } msg] == 1} {
00677         return -code error -errorcode $::errorCode $msg
00678     }
00679 }
00680 
00681 ret  ::logger::setlevel (type lv) {
00682     variable services
00683     variable enabled
00684     variable levels
00685     if {[lsearch -exact $levels $lv] == -1} {
00686         return -code error "Invalid level '$lv' - levels are $levels"
00687     } 
00688     set enabled $lv    
00689     if {[catch {
00690         foreach sv $services {
00691         ::logger::tree::${sv}::setlevel $lv
00692         }
00693     } msg] == 1} {
00694         return -code error -errorcode $::errorCode $msg
00695     }
00696 }
00697 
00698 /*  ::logger::levels --*/
00699 /* */
00700 /*    Introspect the available log levels.  Provided so a caller does*/
00701 /*    not need to know implementation details or code the list*/
00702 /*    himself.*/
00703 /* */
00704 /*  Arguments:*/
00705 /*    None.*/
00706 /* */
00707 /*  Side Effects:*/
00708 /*    None.*/
00709 /* */
00710 /*  Results:*/
00711 /*    levels - The list of valid log levels accepted by enable and disable*/
00712 
00713 ret  ::logger::levels () {
00714     variable levels
00715     return $levels
00716 }
00717 
00718 /*  ::logger::servicecmd --*/
00719 /* */
00720 /*    Get the command token for a given service name.*/
00721 /* */
00722 /*  Arguments:*/
00723 /*    service - name of the service.*/
00724 /* */
00725 /*  Side Effects:*/
00726 /*    none*/
00727 /* */
00728 /*  Results:*/
00729 /*    log - namespace token for this service*/
00730 
00731 ret  ::logger::servicecmd (type service) {
00732     variable services
00733     if {[lsearch -exact $services $service] == -1} {
00734         return -code error "Service \"$service\" does not exist."
00735     }
00736     return "::logger::tree::${service}"
00737 }
00738 
00739 /*  ::logger::import --*/
00740 /* */
00741 /*    Import the logging commands.*/
00742 /* */
00743 /*  Arguments:*/
00744 /*    service - name of the service.*/
00745 /* */
00746 /*  Side Effects:*/
00747 /*    creates aliases in the target namespace*/
00748 /* */
00749 /*  Results:*/
00750 /*    none*/
00751 
00752 ret  ::logger::import (type args) {
00753     variable services
00754     
00755     if {[llength $args] == 0 || [llength $args] > 7} {
00756     return -code error "Wrong # of arguments: \"logger::import ?-all?\
00757                         ?-force?\
00758                         ?-prefix prefix? ?-namespace namespace? service\""
00759     }
00760     
00761     # process options
00762     #
00763     set import_all 0
00764     set force 0
00765     set prefix ""
00766     set ns [uplevel 1 namespace current]
00767     while {[llength $args] > 1} {
00768         set opt [lindex $args 0]
00769         set args [lrange $args 1 end]
00770         switch  -exact -- $opt {
00771             -all    { set import_all 1}
00772             -prefix { set prefix [lindex $args 0]
00773                       set args [lrange $args 1 end]        
00774                     }
00775             -namespace {
00776                       set ns [lindex $args 0]
00777                       set args [lrange $args 1 end]
00778             }
00779             -force {
00780                      set force 1
00781             }
00782             default {
00783                 return -code error "Unknown argument: \"$opt\" :\nUsage:\
00784                 \"logger::import ?-all? ?-force?\
00785                         ?-prefix prefix? ?-namespace namespace? service\""
00786             }
00787         }
00788     }
00789     
00790     #
00791     # build the list of commands to import
00792     #
00793     
00794     set cmds [logger::levels]
00795     lappend cmds "trace"
00796     if {$import_all} {
00797         lappend cmds setlevel enable disable logproc delproc services 
00798         lappend cmds servicename currentloglevel delete
00799     }
00800     
00801     #
00802     # check the service argument
00803     #
00804     
00805     set service [lindex $args 0]
00806     if {[lsearch -exact $services $service] == -1} {
00807             return -code error "Service \"$service\" does not exist."
00808     }
00809 
00810     #
00811     # setup the namespace for the import
00812     #
00813 
00814     set sourcens [logger::servicecmd $service]     
00815     set localns  [uplevel 1 namespace current]
00816     
00817     if {[string match ::* $ns]} {
00818         set importns $ns
00819     } else {
00820         set importns ${localns}::$ns
00821     }    
00822 
00823     # fake namespace exists for Tcl 8.2 - 8.3
00824     if {![_nsExists $importns]} {
00825         namespace eval $importns {}
00826     } 
00827 
00828     
00829     #
00830     # prepare the import
00831     #
00832     
00833     set imports ""
00834     foreach cmd $cmds {
00835         set cmdname ${importns}::${prefix}$cmd
00836         set collision [llength [info commands $cmdname]]
00837         if {$collision && !$force} {
00838             return -code error "can't import command \"$cmdname\": already exists"
00839         }
00840         lappend imports ${importns}::${prefix}$cmd ${sourcens}::${cmd}
00841     }
00842     
00843     #
00844     # and execute the aliasing after checking all is well
00845     #
00846     
00847     foreach {target source} $imports {
00848         proc $target {args} "uplevel 1 \[linsert \$args 0 $source \]"
00849     }
00850 }
00851 
00852 /*  ::logger::initNamespace --*/
00853 /* */
00854 /*    Creates a logger for the specified namespace and makes the log*/
00855 /*    commands available to said namespace as well. Allows the initial*/
00856 /*    setting of a default log level.*/
00857 /* */
00858 /*  Arguments:*/
00859 /*    ns    - Namespace to initialize, is also the service name, modulo a ::-prefix*/
00860 /*    level - Initial log level, optional, defaults to 'warn'.*/
00861 /* */
00862 /*  Side Effects:*/
00863 /*    creates aliases in the target namespace*/
00864 /* */
00865 /*  Results:*/
00866 /*    none*/
00867 
00868 ret  ::logger::initNamespace (type ns , optional level =warn) {
00869     set service [string trimleft $ns :]
00870     namespace eval $ns [list ::logger::init $service]
00871     namespace eval $ns [list ::logger::import -force -all -namespace log $service]
00872     namespace eval $ns [list log::setlevel $level]
00873     return
00874 }
00875 
00876 /*  This procedure handles the "logger::trace status" command.  Given no*/
00877 /*  arguments, returns a list of all procedures that have been registered*/
00878 /*  via "logger::trace add".  Given one or more procedure names, it will*/
00879 /*  return 1 if all were registered, or 0 if any were not.*/
00880 
00881 ret  ::logger::_trace_status ( type service , type procList ) {
00882     upvar #0 ::logger::tree::${service}::traceList traceList
00883 
00884     # If no procedure names were given, just return the registered list
00885 
00886     if {![llength $procList]} {
00887         return $traceList
00888     }
00889 
00890     # Get caller's namespace for qualifying unqualified procedure names
00891 
00892     set caller_ns [uplevel 1 namespace current]
00893     set caller_ns [string trimright $caller_ns ":"]
00894 
00895     # Search for any specified proc names that are *not* registered
00896 
00897     foreach procName $procList {
00898         # Make sure the procedure namespace is qualified
00899 
00900         if {![string match "::*" $procName]} {
00901             set procName ${caller_ns}::$procName
00902         }
00903 
00904         # Check if the procedure has been registered for tracing
00905 
00906         if {[lsearch -exact $traceList $procName] == -1} {
00907         return 0
00908         }
00909     }
00910 
00911     return 1
00912 }
00913 
00914 /*  This procedure handles the "logger::trace on" command.  If tracing*/
00915 /*  is turned off, it will enable Tcl trace handlers for all of the procedures*/
00916 /*  registered via "logger::trace add".  Does nothing if tracing is already*/
00917 /*  turned on.*/
00918 
00919 ret  ::logger::_trace_on ( type service ) {
00920     set tcl_version [package provide Tcl]
00921 
00922     if {[package vcompare $tcl_version "8.4"] < 0} {
00923         return -code error \
00924             "execution tracing is not available in Tcl $tcl_version"
00925     }
00926 
00927     namespace eval ::logger::tree::${service} {
00928         if {!$tracingEnabled} {
00929             set tracingEnabled 1
00930             ::logger::_enable_traces $service $traceList
00931         }
00932     }
00933 
00934     return 1
00935 }
00936 
00937 /*  This procedure handles the "logger::trace off" command.  If tracing*/
00938 /*  is turned on, it will disable Tcl trace handlers for all of the procedures*/
00939 /*  registered via "logger::trace add", leaving them in the list so they*/
00940 /*  tracing on all of them can be enabled again with "logger::trace on".*/
00941 /*  Does nothing if tracing is already turned off.*/
00942 
00943 ret  ::logger::_trace_off ( type service ) {
00944     namespace eval ::logger::tree::${service} {
00945         if {$tracingEnabled} {
00946             ::logger::_disable_traces $service $traceList
00947             set tracingEnabled 0
00948         }
00949     }
00950 
00951     return 1
00952 }
00953 
00954 /*  This procedure is used by the logger::trace add and remove commands to*/
00955 /*  process the arguments in a common fashion.  If the -ns switch is given*/
00956 /*  first, this procedure will return a list of all existing procedures in*/
00957 /*  all of the namespaces given in remaining arguments.  Otherwise, each*/
00958 /*  argument is taken to be either a pattern for a glob-style search of*/
00959 /*  procedure names or, failing that, a namespace, in which case this*/
00960 /*  procedure returns a list of all the procedures matching the given*/
00961 /*  pattern (or all in the named namespace, if no procedures match).*/
00962 
00963 ret  ::logger::_trace_get_proclist ( type inputList ) {
00964     set procList ""
00965 
00966     if {[string equal [lindex $inputList 0] "-ns"]} {
00967     # Verify that at least one target namespace was supplied
00968 
00969     set inputList [lrange $inputList 1 end]
00970     if {![llength $inputList]} {
00971         return -code error "Must specify at least one namespace target"
00972     }
00973 
00974     # Rebuild the argument list to contain namespace procedures
00975 
00976     foreach namespace $inputList {
00977             # Don't allow tracing of the logger (or child) namespaces
00978 
00979         if {![string match "::logger::*" $namespace]} {
00980         set nsProcList  [::info procs ${namespace}::*]
00981                 set procList    [concat $procList $nsProcList]
00982             }
00983     }
00984     } else {
00985         # Search for procs or namespaces matching each of the specified
00986         # patterns.
00987 
00988         foreach pattern $inputList {
00989         set matches [uplevel 1 ::info proc $pattern]
00990 
00991         if {![llength $matches]} {
00992             if {[uplevel 1 namespace exists $pattern]} {
00993             set matches [::info procs ${pattern}::*]
00994             }
00995 
00996                 # Matched procs will be qualified due to above pattern
00997 
00998                 set procList [concat $procList $matches]
00999             } elseif {[string match "::*" $pattern]} {
01000                 # Patterns were pre-qualified - add them directly
01001 
01002                 set procList [concat $procList $matches]
01003             } else {
01004                 # Qualify each proc with the namespace it was in
01005 
01006                 set ns [uplevel 1 namespace current]
01007                 if {$ns == "::"} {
01008                     set ns ""
01009                 }
01010                 foreach proc $matches {
01011                     lappend procList ${ns}::$proc
01012                 }
01013             }
01014         }
01015     }
01016 
01017     return $procList
01018 }
01019 
01020 /*  This procedure handles the "logger::trace add" command.  If the tracing*/
01021 /*  feature is enabled, it will enable the Tcl entry and leave trace handlers*/
01022 /*  for each procedure specified that isn't already being traced.  Each*/
01023 /*  procedure is added to the list of procedures that the logger trace feature*/
01024 /*  should log when tracing is enabled.*/
01025 
01026 ret  ::logger::_trace_add ( type service , type procList ) {
01027     upvar #0 ::logger::tree::${service}::traceList traceList
01028 
01029     # Handle -ns switch and glob search patterns for procedure names
01030 
01031     set procList [uplevel 1 [list logger::_trace_get_proclist $procList]]
01032 
01033     # Enable tracing for each procedure that has not previously been
01034     # specified via logger::trace add.  If tracing is off, this will just
01035     # store the name of the procedure for later when tracing is turned on.
01036 
01037     foreach procName $procList {
01038         if {[lsearch -exact $traceList $procName] == -1} {
01039             lappend traceList $procName
01040             ::logger::_enable_traces $service [list $procName]
01041         }
01042     }
01043 }
01044 
01045 /*  This procedure handles the "logger::trace remove" command.  If the tracing*/
01046 /*  feature is enabled, it will remove the Tcl entry and leave trace handlers*/
01047 /*  for each procedure specified.  Each procedure is removed from the list*/
01048 /*  of procedures that the logger trace feature should log when tracing is*/
01049 /*  enabled.*/
01050 
01051 ret  ::logger::_trace_remove ( type service , type procList ) {
01052     upvar #0 ::logger::tree::${service}::traceList traceList
01053 
01054     # Handle -ns switch and glob search patterns for procedure names
01055 
01056     set procList [uplevel 1 [list logger::_trace_get_proclist $procList]]
01057 
01058     # Disable tracing for each proc that previously had been specified
01059     # via logger::trace add.  If tracing is off, this will just
01060     # remove the name of the procedure from the trace list so that it
01061     # will be excluded when tracing is turned on.
01062 
01063     foreach procName $procList {
01064         set index [lsearch -exact $traceList $procName]
01065         if {$index != -1} {
01066             set traceList [lreplace $traceList $index $index]
01067             ::logger::_disable_traces $service [list $procName]
01068         }
01069     }
01070 }
01071 
01072 /*  This procedure enables Tcl trace handlers for all procedures specified.*/
01073 /*  It is used both to enable Tcl's tracing for a single procedure when*/
01074 /*  removed via "logger::trace add", as well as to enable all traces*/
01075 /*  via "logger::trace on".*/
01076 
01077 ret  ::logger::_enable_traces ( type service , type procList ) {
01078     upvar #0 ::logger::tree::${service}::tracingEnabled tracingEnabled
01079 
01080     if {$tracingEnabled} {
01081         foreach procName $procList {
01082             ::trace add execution $procName enter \
01083                 [list ::logger::_trace_enter $service]
01084             ::trace add execution $procName leave \
01085                 [list ::logger::_trace_leave $service]
01086         }
01087     }
01088 }
01089 
01090 /*  This procedure disables Tcl trace handlers for all procedures specified.*/
01091 /*  It is used both to disable Tcl's tracing for a single procedure when*/
01092 /*  removed via "logger::trace remove", as well as to disable all traces*/
01093 /*  via "logger::trace off".*/
01094 
01095 ret  ::logger::_disable_traces ( type service , type procList ) {
01096     upvar #0 ::logger::tree::${service}::tracingEnabled tracingEnabled
01097 
01098     if {$tracingEnabled} {
01099         foreach procName $procList {
01100             ::trace remove execution $procName enter \
01101                 [list ::logger::_trace_enter $service]
01102             ::trace remove execution $procName leave \
01103                 [list ::logger::_trace_leave $service]
01104         }
01105     }
01106 }
01107 
01108 /* */
01109 /*  Trace Handlers*/
01110 /* */
01111 
01112 /*  This procedure is invoked upon entry into a procedure being traced*/
01113 /*  via "logger::trace add" when tracing is enabled via "logger::trace on"*/
01114 /*  to log information about how the procedure was called.*/
01115 
01116 ret  ::logger::_trace_enter ( type service , type cmd , type op ) {
01117     # Parse the command
01118     set procName [uplevel 1 namespace origin [lindex $cmd 0]]
01119     set args     [lrange $cmd 1 end]
01120 
01121     # Display the message prefix
01122     set callerLvl [expr {[::info level] - 1}]
01123     set calledLvl [::info level]
01124 
01125     lappend message "proc" $procName
01126     lappend message "level" $calledLvl
01127     lappend message "script" [uplevel ::info script]
01128 
01129     # Display the caller information
01130     set caller ""
01131     if {$callerLvl >= 1} {
01132     # Display the name of the caller proc w/prepended namespace
01133     catch {
01134         set callerProcName [lindex [::info level $callerLvl] 0]
01135         set caller [uplevel 2 namespace origin $callerProcName]
01136     }
01137     }
01138 
01139     lappend message "caller" $caller
01140 
01141     # Display the argument names and values
01142     set argSpec [uplevel 1 ::info args $procName]
01143     set argList ""
01144     if {[llength $argSpec]} {
01145     foreach argName $argSpec {
01146             lappend argList $argName
01147 
01148         if {$argName == "args"} {
01149                 lappend argList $args
01150                 break
01151         } else {
01152             lappend argList [lindex $args 0]
01153             set args [lrange $args 1 end]
01154             }
01155     }
01156     }
01157 
01158     lappend message "procargs" $argList
01159     set message [list $op $message]
01160 
01161     ::logger::tree::${service}::tracecmd $message
01162 }
01163 
01164 /*  This procedure is invoked upon leaving into a procedure being traced*/
01165 /*  via "logger::trace add" when tracing is enabled via "logger::trace on"*/
01166 /*  to log information about the result of the procedure call.*/
01167 
01168 ret  ::logger::_trace_leave ( type service , type cmd , type status , type rc , type op ) {
01169     variable RETURN_CODES
01170 
01171     # Parse the command
01172     set procName [uplevel 1 namespace origin [lindex $cmd 0]]
01173 
01174     # Gather the caller information
01175     set callerLvl [expr {[::info level] - 1}]
01176     set calledLvl [::info level]
01177 
01178     lappend message "proc" $procName "level" $calledLvl
01179     lappend message "script" [uplevel ::info script]
01180 
01181     # Get the name of the proc being returned to w/prepended namespace
01182     set caller ""
01183     catch {
01184         set callerProcName [lindex [::info level $callerLvl] 0]
01185         set caller [uplevel 2 namespace origin $callerProcName]
01186     }
01187 
01188     lappend message "caller" $caller
01189 
01190     # Convert the return code from numeric to verbal
01191 
01192     if {$status < [llength $RETURN_CODES]} {
01193         set status [lindex $RETURN_CODES $status]
01194     }
01195 
01196     lappend message "status" $status
01197     lappend message "result" $rc
01198 
01199     # Display the leave message
01200 
01201     set message [list $op $message]
01202     ::logger::tree::${service}::tracecmd $message
01203 
01204     return 1
01205 }
01206 
01207 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1