This file is indexed.

/usr/share/tcltk/tcllib1.18/log/logger.tcl is in tcllib 1.18-dfsg-3.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

   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
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
# logger.tcl --
#
#   Tcl implementation of a general logging facility.
#
# Copyright (c) 2003      by David N. Welton <davidw@dedasys.com>
# Copyright (c) 2004-2011 by Michael Schlenker <mic42@users.sourceforge.net>
# Copyright (c) 2006,2015 by Andreas Kupries <andreas_kupries@users.sourceforge.net>
#
# See the file license.terms.

# The logger package provides an 'object oriented' log facility that
# lets you have trees of services, that inherit from one another.
# This is accomplished through the use of Tcl namespaces.


package require Tcl 8.2
package provide logger 0.9.4

namespace eval ::logger {
    namespace eval tree {}
    namespace export init enable disable services servicecmd import

    # The active services.
    variable services {}

    # The log 'levels'.
    variable levels [list debug info notice warn error critical alert emergency]

    # The default global log level used for new logging services
    variable enabled "debug"

    # Tcl return codes (in numeric order)
    variable RETURN_CODES   [list "ok" "error" "return" "break" "continue"]
}

# Try to load msgcat and fall back to format if it fails
if {[catch {package require msgcat}]} {
  interp alias {} ::logger::mc {} ::format
} else {
  namespace eval ::logger {
    namespace import ::msgcat::mc
  }
}

# ::logger::_nsExists --
#
#   Workaround for missing namespace exists in Tcl 8.2 and 8.3.
#

if {[package vcompare [package provide Tcl] 8.4] < 0} {
    proc ::logger::_nsExists {ns} {
        expr {![catch {namespace parent $ns}]}
    }
} else {
    proc ::logger::_nsExists {ns} {
        namespace exists $ns
    }
}

# ::logger::_cmdPrefixExists --
#
# Utility function to check if a given callback prefix exists,
# this should catch all oddities in prefix names, including spaces,
# glob patterns, non normalized namespaces etc.
#
# Arguments:
#   prefix - The command prefix to check
#
# Results:
#   1 or 0 for yes or no
#
proc ::logger::_cmdPrefixExists {prefix} {
    set cmd [lindex $prefix 0]
    set full [namespace eval :: namespace which [list $cmd]]
    if {[string equal $full ""]} {return 0} else {return 1}
    # normalize namespaces
    set ns [namespace qualifiers $cmd]
    set cmd ${ns}::[namespace tail $cmd]
    set matches [::info commands ${ns}::*]
    if {[lsearch -exact $matches $cmd] != -1} {return 1}
    return 0
}

# ::logger::walk --
#
#   Walk namespaces, starting in 'start', and evaluate 'code' in
#   them.
#
# Arguments:
#   start - namespace to start in.
#   code - code to execute in namespaces walked.
#
# Side Effects:
#   Side effects of code executed.
#
# Results:
#   None.

proc ::logger::walk { start code } {
    set children [namespace children $start]
    foreach c $children {
    logger::walk $c $code
    namespace eval $c $code
    }
}

proc ::logger::init {service} {
    variable levels
    variable services
    variable enabled

    if {[string length [string trim $service {:}]] == 0} {
        return -code error \
               -errorcode [list LOGGER EMPTY_SERVICENAME] \
               [::logger::mc "Service name invalid. May not consist only of : or be empty"]
    }
    # We create a 'tree' namespace to house all the services, so
    # they are in a 'safe' namespace sandbox, and won't overwrite
    # any commands.
    namespace eval tree::${service} {
        variable service
        variable levels
        variable oldname
        variable enabled
    }

    lappend services $service

    set [namespace current]::tree::${service}::service $service
    set [namespace current]::tree::${service}::levels $levels
    set [namespace current]::tree::${service}::oldname $service
    set [namespace current]::tree::${service}::enabled $enabled

    namespace eval tree::${service} {
	# Callback to use when the service in question is shut down.
	variable delcallback [namespace current]::no-op

	# Callback when the loglevel is changed
	variable levelchangecallback [namespace current]::no-op

	# State variable to decide when to call levelcallback
	variable inSetLevel 0

	# The currently configured levelcommands
	variable lvlcmds
	array set lvlcmds {}

	# List of procedures registered via the trace command
	variable traceList ""

	# Flag indicating whether or not tracing is currently enabled
	variable tracingEnabled 0

	# We use this to disable a service completely.  In Tcl 8.4
	# or greater, by using this, disabled log calls are a
	# no-op!

	proc no-op args {}

	proc stdoutcmd {level text} {
	    variable service
	    puts "\[[clock format [clock seconds]]\] \[$service\] \[$level\] \'$text\'"
	}

	proc stderrcmd {level text} {
	    variable service
	    puts stderr "\[[clock format [clock seconds]]\] \[$service\] \[$level\] \'$text\'"
	}


	# setlevel --
	#
	#   This command differs from enable and disable in that
	#   it disables all the levels below that selected, and
	#   then enables all levels above it, which enable/disable
	#   do not do.
	#
	# Arguments:
	#   lv - the level, as defined in $levels.
	#
	# Side Effects:
	#   Runs disable for the level, and then enable, in order
	#   to ensure that all levels are set correctly.
	#
	# Results:
	#   None.


	proc setlevel {lv} {
	    variable inSetLevel 1
	    set oldlvl [currentloglevel]

	    # do not allow enable and disable to do recursion
	    if {[catch {
		disable $lv 0
		set newlvl [enable $lv 0]
	    } msg] == 1} {
		return -code error -errorcode $::errorCode $msg
	    }
	    # do the recursion here
	    logger::walk [namespace current] [list setlevel $lv]

	    set inSetLevel 0
	    lvlchangewrapper $oldlvl $newlvl
	    return
	}

	# enable --
	#
	#   Enable a particular 'level', and above, for the
	#   service, and its 'children'.
	#
	# Arguments:
	#   lv - the level, as defined in $levels.
	#
	# Side Effects:
	#   Enables logging for the particular level, and all
	#   above it (those more important).  It also walks
	#   through all services that are 'children' and enables
	#   them at the same level or above.
	#
	# Results:
	#   None.

	proc enable {lv {recursion 1}} {
	    variable levels
	    set lvnum [lsearch -exact $levels $lv]
	    if { $lvnum == -1 } {
		return -code error \
		    -errorcode [list LOGGER INVALID_LEVEL] \
		    [::logger::mc "Invalid level '%s' - levels are %s" $lv $levels]
	    }

	    variable enabled
	    set newlevel $enabled
	    set elnum [lsearch -exact $levels $enabled]
	    if {($elnum == -1) || ($elnum > $lvnum)} {
		set newlevel $lv
	    }

	    variable service
	    while { $lvnum <  [llength $levels] } {
		interp alias {} [namespace current]::[lindex $levels $lvnum] \
		    {} [namespace current]::[lindex $levels $lvnum]cmd
		incr lvnum
	    }

	    if {$recursion} {
		logger::walk [namespace current] [list enable $lv]
	    }
	    lvlchangewrapper $enabled $newlevel
	    set enabled $newlevel
	}

	# disable --
	#
	#   Disable a particular 'level', and below, for the
	#   service, and its 'children'.
	#
	# Arguments:
	#   lv - the level, as defined in $levels.
	#
	# Side Effects:
	#   Disables logging for the particular level, and all
	#   below it (those less important).  It also walks
	#   through all services that are 'children' and disables
	#   them at the same level or below.
	#
	# Results:
	#   None.

	proc disable {lv {recursion 1}} {
	    variable levels
	    set lvnum [lsearch -exact $levels $lv]
	    if { $lvnum == -1 } {
		return -code error \
		    -errorcode [list LOGGER INVALID_LEVEL] \
		    [::logger::mc "Invalid level '%s' - levels are %s" $lv $levels]
	    }

	    variable enabled
	    set newlevel $enabled
	    set elnum [lsearch -exact $levels $enabled]
	    if {($elnum > -1) && ($elnum <= $lvnum)} {
		if {$lvnum+1 >= [llength $levels]} {
		    set newlevel "none"
		} else {
		    set newlevel [lindex $levels [expr {$lvnum+1}]]
		}
	    }

	    while { $lvnum >= 0 } {

		interp alias {} [namespace current]::[lindex $levels $lvnum] {} \
		    [namespace current]::no-op
		incr lvnum -1
	    }
	    if {$recursion} {
		logger::walk [namespace current] [list disable $lv]
	    }
	    lvlchangewrapper $enabled $newlevel
	    set enabled $newlevel
	}

	# currentloglevel --
	#
	#   Get the currently enabled log level for this service.
	#
	# Arguments:
	#   none
	#
	# Side Effects:
	#   none
	#
	# Results:
	#   current log level
	#

	proc currentloglevel {} {
	    variable enabled
	    return $enabled
	}

	# lvlchangeproc --
	#
	#   Set or introspect a callback for when the logger instance
	#   changes its loglevel.
	#
	# Arguments:
	#   cmd - the Tcl command to call, it is called with two parameters, old and new log level.
	#   or none for introspection
	#
	# Side Effects:
	#   None.
	#
	# Results:
	#   If no arguments are given return the current callback cmd.

	proc lvlchangeproc {args} {
	    variable levelchangecallback

	    switch -exact -- [llength [::info level 0]] {
                1   {return $levelchangecallback}
                2   {
		    if {[::logger::_cmdPrefixExists [lindex $args 0]]} {
                        set levelchangecallback [lindex $args 0]
		    } else {
                        return -code error \
			    -errorcode [list LOGGER INVALID_CMD] \
			    [::logger::mc "Invalid cmd '%s' - does not exist" [lindex $args 0]]
		    }
		}
                default {
                    return -code error \
			-errorcode [list LOGGER WRONG_NUM_ARGS] \
			[::logger::mc "Wrong # of arguments. Usage: \${log}::lvlchangeproc ?cmd?"]
                }
	    }
	}

	proc lvlchangewrapper {old new} {
	    variable inSetLevel

	    # we are called after disable and enable are finished
	    if {$inSetLevel} {return}

	    # no action if level does not change
	    if {[string equal $old $new]} {return}

	    variable levelchangecallback
	    # no action if levelchangecallback isn't a valid command
	    if {[::logger::_cmdPrefixExists $levelchangecallback]} {
		catch {
		    uplevel \#0 [linsert $levelchangecallback end $old $new]
		}
	    }
	}

	# logproc --
	#
	#   Command used to create a procedure that is executed to
	#   perform the logging.  This could write to disk, out to
	#   the network, or something else.
	#   If two arguments are given, use an existing command.
	#   If three arguments are given, create a proc.
	#
	# Arguments:
	#   lv - the level to log, which must be one of $levels.
	#   args - either zero, one or two arguments.
	#          if zero this returns the current command registered
	#          if one, this is a cmd name that is called for this level
	#          if two, these are an argument and proc body
	#
	# Side Effects:
	#   Creates a logging command to take care of the details
	#   of logging an event.
	#
	# Results:
	#   If called with zero length args, returns the name of the currently
	#   configured logging procedure.
	#
	#

	proc logproc {lv args} {
	    variable levels
	    variable lvlcmds

	    set lvnum [lsearch -exact $levels $lv]
	    if { ($lvnum == -1) && ($lv != "trace") } {
		return -code error \
		    -errorcode [list LOGGER INVALID_LEVEL] \
		    [::logger::mc "Invalid level '%s' - levels are %s" $lv $levels]
	    }
	    switch -exact -- [llength $args] {
		0  {
		    return $lvlcmds($lv)
		}
		1  {
		    set cmd [lindex $args 0]
		    if {[string equal "[namespace current]::${lv}cmd" $cmd]} {return}
		    if {[llength [::info commands $cmd]]} {
			proc ${lv}cmd args [format {
			    uplevel 1 [list %s [expr {[llength $args]==1 ? [lindex $args end] : $args}]]
			} $cmd]
		    } else {
			return -code error \
			    -errorcode [list LOGGER INVALID_CMD] \
			    [::logger::mc "Invalid cmd '%s' - does not exist" $cmd]
		    }
		    set lvlcmds($lv) $cmd
		}
		2  {
		    foreach {arg body} $args {break}
		    proc ${lv}cmd args [format {\
						    _setservicename args
			set val [%s [expr {[llength $args]==1 ? [lindex $args end] : $args}]]
			_restoreservice
			set val} ${lv}customcmd]
		    proc ${lv}customcmd $arg $body
		    set lvlcmds($lv) [namespace current]::${lv}customcmd
		}
		default {
		    return -code error \
			-errorcode [list LOGGER WRONG_USAGE] \
			[::logger::mc \
			     "Usage: \${log}::logproc level ?cmd?\nor \${log}::logproc level argname body" ]
		}
	    }
	}


	# delproc --
	#
	#   Set or introspect a callback for when the logger instance
	#   is deleted.
	#
	# Arguments:
	#   cmd - the Tcl command to call.
	#   or none for introspection
	#
	# Side Effects:
	#   None.
	#
	# Results:
	#   If no arguments are given return the current callback cmd.

	proc delproc {args} {
	    variable delcallback

	    switch -exact -- [llength [::info level 0]] {
                1   {return $delcallback}
                2   { if {[::logger::_cmdPrefixExists [lindex $args 0]]} {
		    set delcallback [lindex $args 0]
		} else {
		    return -code error \
			-errorcode [list LOGGER INVALID_CMD] \
			[::logger::mc "Invalid cmd '%s' - does not exist" [lindex $args 0]]
		}
		}
                default {
                    return -code error \
			-errorcode [list LOGGER WRONG_NUM_ARGS] \
			[::logger::mc "Wrong # of arguments. Usage: \${log}::delproc ?cmd?"]
                }
	    }
	}


	# delete --
	#
	#   Delete the namespace and its children.

	proc delete {} {
	    variable delcallback
	    variable service

	    logger::walk [namespace current] delete
	    if {[::logger::_cmdPrefixExists $delcallback]} {
		uplevel \#0 [lrange $delcallback 0 end]
	    }
	    # clean up the global services list
	    set idx [lsearch -exact [logger::services] $service]
	    if {$idx !=-1} {
		set ::logger::services [lreplace [logger::services] $idx $idx]
	    }

	    namespace delete [namespace current]

	}

	# services --
	#
	#   Return all child services

	proc services {} {
	    variable service

	    set children [list]
	    foreach srv [logger::services] {
		if {[string match "${service}::*" $srv]} {
		    lappend children $srv
		}
	    }
	    return $children
	}

	# servicename --
	#
	#   Return the name of the service

	proc servicename {} {
	    variable service
	    return $service
	}

	proc _setservicename {argname} {
	    variable service
	    variable oldname
	    upvar 1 $argname arg
	    if {[llength $arg] <= 1} {
		return
	    }

	    set count -1
	    set newname ""
	    while {[string equal [lindex $arg [expr {$count+1}]] "-_logger::service"]} {
		incr count 2
		set newname [lindex $arg $count]
	    }
	    if {[string equal $newname ""]} {
		return
	    }
	    set oldname $service
	    set service $newname
	    # Pop off "-_logger::service <service>" from argument list
	    set arg [lreplace $arg 0 $count]
	}

	proc _restoreservice {} {
	    variable service
	    variable oldname
	    set service $oldname
	    return
	}

	proc trace { action args } {
	    variable service

	    # Allow other boolean values (true, false, yes, no, 0, 1) to be used
	    # as synonymns for "on" and "off".

	    if {[string is boolean $action]} {
		set xaction [expr {($action && 1) ? "on" : "off"}]
	    } else {
		set xaction $action
	    }

	    # Check for required arguments for actions/subcommands and dispatch
	    # to the appropriate procedure.

	    switch -- $xaction {
		"status" {
		    return [uplevel 1 [list logger::_trace_status $service $args]]
		}
		"on" {
		    if {[llength $args]} {
			return -code error \
			    -errorcode [list LOGGER WRONG_NUM_ARGS] \
                            [::logger::mc "wrong # args: should be \"trace on\""]
		    }
		    return [logger::_trace_on $service]
		}
		"off" {
		    if {[llength $args]} {
			return -code error \
			    -errorcode [list LOGGER WRONG_NUM_ARGS] \
                            [::logger::mc "wrong # args: should be \"trace off\""]
		    }
		    return [logger::_trace_off $service]
		}
		"add" {
		    if {![llength $args]} {
			return -code error \
			    -errorcode [list LOGGER WRONG_NUM_ARGS] \
			    [::logger::mc "wrong # args: should be \"trace add ?-ns? <proc> ...\""]
		    }
		    return [uplevel 1 [list ::logger::_trace_add $service $args]]
		}
		"remove" {
		    if {![llength $args]} {
			return -code error \
			    -errorcode [list LOGGER WRONG_NUM_ARGS] \
                            [::logger::mc "wrong # args: should be \"trace remove ?-ns? <proc> ...\""]
		    }
		    return [uplevel 1 [list ::logger::_trace_remove $service $args]]
		}

		default {
		    return -code error \
			-errorcode [list LOGGER INVALID_ARG] \
			[::logger::mc "Invalid action \"%s\": must be status, add, remove,\
                    on, or off" $action]
		}
	    }
	}

	# Walk the parent service namespaces to see first, if they
	# exist, and if any are enabled, and then, as a
	# consequence, enable this one
	# too.

	enable $enabled
	variable parent [namespace parent]
	while {[string compare $parent "::logger::tree"]} {
	    # If the 'enabled' variable doesn't exist, create the
	    # whole thing.
	    if { ! [::info exists ${parent}::enabled] } {
		logger::init [string range $parent 16 end]
	    }
	    set enabled [set ${parent}::enabled]
	    enable $enabled
	    set parent [namespace parent $parent]
	}
    }

    # Now create the commands for different levels.

    namespace eval tree::${service} {
	set parent [namespace parent]

	# We 'inherit' the commands from the parents.  This
	# means that, if you want to share the same methods with
	# children, they should be instantiated after the parent's
	# methods have been defined.

	variable lvl ; # prevent creative writing to the global scope
	if {[string compare $parent "::logger::tree"]} {
	    foreach lvl [::logger::levels] {
		# OPTIMIZE: do not allow multiple aliases in the hierarchy
		#           they can always be replaced by more efficient
		#           direct aliases to the target procs.
		interp alias {} [namespace current]::${lvl}cmd \
		    {} ${parent}::${lvl}cmd -_logger::service $service
	    }
	    # inherit the starting loglevel of the parent service
	    setlevel [${parent}::currentloglevel]
	} else {
	    foreach lvl [concat [::logger::levels] "trace"] {
		proc ${lvl}cmd args [format {\
						 _setservicename args
		    set val [stdoutcmd %s [expr {[llength $args]==1 ? [lindex $args end] : $args}]]
		    _restoreservice
		    set val } $lvl]

		set lvlcmds($lvl) [namespace current]::${lvl}cmd
	    }
	    setlevel $::logger::enabled
	}
	unset lvl ; # drop the temp iteration variable
    }

    return ::logger::tree::${service}
}

# ::logger::services --
#
#   Returns a list of all active services.
#
# Arguments:
#   None.
#
# Side Effects:
#   None.
#
# Results:
#   List of active services.

proc ::logger::services {} {
    variable services
    return $services
}

# ::logger::enable --
#
#   Global enable for a certain level.  NOTE - this implementation
#   isn't terribly effective at the moment, because it might hit
#   children before their parents, who will then walk down the
#   tree attempting to disable the children again.
#
# Arguments:
#   lv - level above which to enable logging.
#
# Side Effects:
#   Enables logging in a given level, and all higher levels.
#
# Results:
#   None.

proc ::logger::enable {lv} {
    variable services
    if {[catch {
        foreach sv $services {
        ::logger::tree::${sv}::enable $lv
        }
    } msg] == 1} {
        return -code error -errorcode $::errorCode $msg
    }
}

proc ::logger::disable {lv} {
    variable services
    if {[catch {
        foreach sv $services {
        ::logger::tree::${sv}::disable $lv
        }
    } msg] == 1} {
        return -code error -errorcode $::errorCode $msg
    }
}

proc ::logger::setlevel {lv} {
    variable services
    variable enabled
    variable levels
    if {[lsearch -exact $levels $lv] == -1} {
        return -code error \
               -errorcode [list LOGGER INVALID_LEVEL] \
               [::logger::mc "Invalid level '%s' - levels are %s" $lv $levels]
    }
    set enabled $lv
    if {[catch {
        foreach sv $services {
        ::logger::tree::${sv}::setlevel $lv
        }
    } msg] == 1} {
        return -code error -errorcode $::errorCode $msg
    }
}

# ::logger::levels --
#
#   Introspect the available log levels.  Provided so a caller does
#   not need to know implementation details or code the list
#   himself.
#
# Arguments:
#   None.
#
# Side Effects:
#   None.
#
# Results:
#   levels - The list of valid log levels accepted by enable and disable

proc ::logger::levels {} {
    variable levels
    return $levels
}

# ::logger::servicecmd --
#
#   Get the command token for a given service name.
#
# Arguments:
#   service - name of the service.
#
# Side Effects:
#   none
#
# Results:
#   log - namespace token for this service

proc ::logger::servicecmd {service} {
    variable services
    if {[lsearch -exact $services $service] == -1} {
        return -code error \
               -errorcode [list LOGGER NO_SUCH_SERVICE] \
               [::logger::mc "Service \"%s\" does not exist." $service]
    }
    return "::logger::tree::${service}"
}

# ::logger::import --
#
#   Import the logging commands.
#
# Arguments:
#   service - name of the service.
#
# Side Effects:
#   creates aliases in the target namespace
#
# Results:
#   none

proc ::logger::import {args} {
    variable services

    if {[llength $args] == 0 || [llength $args] > 7} {
    return -code error \
           -errorcode [list LOGGER WRONG_NUM_ARGS] \
           [::logger::mc \
                       "Wrong # of arguments: \"logger::import ?-all?\
                        ?-force?\
                        ?-prefix prefix? ?-namespace namespace? service\""]
    }

    # process options
    #
    set import_all 0
    set force 0
    set prefix ""
    set ns [uplevel 1 namespace current]
    while {[llength $args] > 1} {
        set opt [lindex $args 0]
        set args [lrange $args 1 end]
        switch  -exact -- $opt {
            -all    { set import_all 1}
            -prefix { set prefix [lindex $args 0]
                      set args [lrange $args 1 end]
                    }
            -namespace {
                      set ns [lindex $args 0]
                      set args [lrange $args 1 end]
            }
            -force {
                     set force 1
            }
            default {
                return -code error \
                       -errorcode [list LOGGER UNKNOWN_ARG] \
                       [::logger::mc \
                       "Unknown argument: \"%s\" :\nUsage:\
                      \"logger::import ?-all? ?-force?\
                        ?-prefix prefix? ?-namespace namespace? service\"" $opt]
            }
        }
    }

    #
    # build the list of commands to import
    #

    set cmds [logger::levels]
    lappend cmds "trace"
    if {$import_all} {
        lappend cmds setlevel enable disable logproc delproc services
        lappend cmds servicename currentloglevel delete
    }

    #
    # check the service argument
    #

    set service [lindex $args 0]
    if {[lsearch -exact $services $service] == -1} {
            return -code error \
                   -errorcode [list LOGGER NO_SUCH_SERVICE] \
                   [::logger::mc "Service \"%s\" does not exist." $service]
    }

    #
    # setup the namespace for the import
    #

    set sourcens [logger::servicecmd $service]
    set localns  [uplevel 1 namespace current]

    if {[string match ::* $ns]} {
        set importns $ns
    } else {
        set importns ${localns}::$ns
    }

    # fake namespace exists for Tcl 8.2 - 8.3
    if {![_nsExists $importns]} {
        namespace eval $importns {}
    }


    #
    # prepare the import
    #

    set imports ""
    foreach cmd $cmds {
        set cmdname ${importns}::${prefix}$cmd
        set collision [llength [info commands $cmdname]]
        if {$collision && !$force} {
            return -code error \
                   -errorcode [list LOGGER IMPORT_NAME_EXISTS] \
                   [::logger::mc "can't import command \"%s\": already exists" $cmdname]
        }
        lappend imports ${importns}::${prefix}$cmd ${sourcens}::${cmd}
    }

    #
    # and execute the aliasing after checking all is well
    #

    foreach {target source} $imports {
        proc $target {args} "uplevel 1 \[linsert \$args 0 $source \]"
    }
}

# ::logger::initNamespace --
#
#   Creates a logger for the specified namespace and makes the log
#   commands available to said namespace as well. Allows the initial
#   setting of a default log level.
#
# Arguments:
#   ns    - Namespace to initialize, is also the service name, modulo a ::-prefix
#   level - Initial log level, optional, defaults to 'warn'.
#
# Side Effects:
#   creates aliases in the target namespace
#
# Results:
#   none

proc ::logger::initNamespace {ns {level {}}} {
    set service [string trimleft $ns :]
    if {$level == ""} {
	# No user-specified level. Figure something out.
	# - If the parent service exists then the 'logger::init'
	#   below will automatically inherit its level. Good enough.
	# - Without a parent service go and use a default level of 'warn'.
	set parent    [string trimleft [namespace qualifiers $service] :]
	set hasparent [expr {($parent != {}) && [_nsExists ::logger::tree::${parent}]}]
	if {!$hasparent} {
	    set level warn
	}
    }

    namespace eval $ns [list ::logger::init $service]
    namespace eval $ns [list ::logger::import -force -all -namespace log $service]
    if {$level != ""} {
	namespace eval $ns [list log::setlevel $level]
    }
    return
}

# This procedure handles the "logger::trace status" command.  Given no
# arguments, returns a list of all procedures that have been registered
# via "logger::trace add".  Given one or more procedure names, it will
# return 1 if all were registered, or 0 if any were not.

proc ::logger::_trace_status { service procList } {
    upvar #0 ::logger::tree::${service}::traceList traceList

    # If no procedure names were given, just return the registered list

    if {![llength $procList]} {
        return $traceList
    }

    # Get caller's namespace for qualifying unqualified procedure names

    set caller_ns [uplevel 1 namespace current]
    set caller_ns [string trimright $caller_ns ":"]

    # Search for any specified proc names that are *not* registered

    foreach procName $procList {
        # Make sure the procedure namespace is qualified

        if {![string match "::*" $procName]} {
            set procName ${caller_ns}::$procName
        }

        # Check if the procedure has been registered for tracing

        if {[lsearch -exact $traceList $procName] == -1} {
	    return 0
        }
    }

    return 1
}

# This procedure handles the "logger::trace on" command.  If tracing
# is turned off, it will enable Tcl trace handlers for all of the procedures
# registered via "logger::trace add".  Does nothing if tracing is already
# turned on.

proc ::logger::_trace_on { service } {
    set tcl_version [package provide Tcl]

    if {[package vcompare $tcl_version "8.4"] < 0} {
        return -code error \
               -errorcode [list LOGGER TRACE_NOT_AVAILABLE] \
              [::logger::mc "execution tracing is not available in Tcl %s" $tcl_version]
    }

    namespace eval ::logger::tree::${service} {
        if {!$tracingEnabled} {
            set tracingEnabled 1
            ::logger::_enable_traces $service $traceList
        }
    }

    return 1
}

# This procedure handles the "logger::trace off" command.  If tracing
# is turned on, it will disable Tcl trace handlers for all of the procedures
# registered via "logger::trace add", leaving them in the list so they
# tracing on all of them can be enabled again with "logger::trace on".
# Does nothing if tracing is already turned off.

proc ::logger::_trace_off { service } {
    namespace eval ::logger::tree::${service} {
        if {$tracingEnabled} {
            ::logger::_disable_traces $service $traceList
            set tracingEnabled 0
        }
    }

    return 1
}

# This procedure is used by the logger::trace add and remove commands to
# process the arguments in a common fashion.  If the -ns switch is given
# first, this procedure will return a list of all existing procedures in
# all of the namespaces given in remaining arguments.  Otherwise, each
# argument is taken to be either a pattern for a glob-style search of
# procedure names or, failing that, a namespace, in which case this
# procedure returns a list of all the procedures matching the given
# pattern (or all in the named namespace, if no procedures match).

proc ::logger::_trace_get_proclist { inputList } {
    set procList ""

    if {[string equal [lindex $inputList 0] "-ns"]} {
	# Verify that at least one target namespace was supplied

	set inputList [lrange $inputList 1 end]
	if {![llength $inputList]} {
	    return -code error \
                   -errorcode [list LOGGER TARGET_MISSING] \
                   [::logger::mc "Must specify at least one namespace target"]
	}

	# Rebuild the argument list to contain namespace procedures

	foreach namespace $inputList {
            # Don't allow tracing of the logger (or child) namespaces

	    if {![string match "::logger::*" $namespace]} {
		set nsProcList  [::info procs ${namespace}::*]
                set procList    [concat $procList $nsProcList]
            }
	}
    } else {
        # Search for procs or namespaces matching each of the specified
        # patterns.

        foreach pattern $inputList {
	    set matches [uplevel 1 ::info proc $pattern]

	    if {![llength $matches]} {
	        if {[uplevel 1 namespace exists $pattern]} {
		    set matches [::info procs ${pattern}::*]
	        }

                # Matched procs will be qualified due to above pattern

                set procList [concat $procList $matches]
            } elseif {[string match "::*" $pattern]} {
                # Patterns were pre-qualified - add them directly

                set procList [concat $procList $matches]
            } else {
                # Qualify each proc with the namespace it was in

                set ns [uplevel 1 namespace current]
                if {$ns == "::"} {
                    set ns ""
                }
                foreach proc $matches {
                    lappend procList ${ns}::$proc
                }
            }
        }
    }

    return $procList
}

# This procedure handles the "logger::trace add" command.  If the tracing
# feature is enabled, it will enable the Tcl entry and leave trace handlers
# for each procedure specified that isn't already being traced.  Each
# procedure is added to the list of procedures that the logger trace feature
# should log when tracing is enabled.

proc ::logger::_trace_add { service procList } {
    upvar #0 ::logger::tree::${service}::traceList traceList

    # Handle -ns switch and glob search patterns for procedure names

    set procList [uplevel 1 [list logger::_trace_get_proclist $procList]]

    # Enable tracing for each procedure that has not previously been
    # specified via logger::trace add.  If tracing is off, this will just
    # store the name of the procedure for later when tracing is turned on.

    foreach procName $procList {
        if {[lsearch -exact $traceList $procName] == -1} {
            lappend traceList $procName
            ::logger::_enable_traces $service [list $procName]
        }
    }
}

# This procedure handles the "logger::trace remove" command.  If the tracing
# feature is enabled, it will remove the Tcl entry and leave trace handlers
# for each procedure specified.  Each procedure is removed from the list
# of procedures that the logger trace feature should log when tracing is
# enabled.

proc ::logger::_trace_remove { service procList } {
    upvar #0 ::logger::tree::${service}::traceList traceList

    # Handle -ns switch and glob search patterns for procedure names

    set procList [uplevel 1 [list logger::_trace_get_proclist $procList]]

    # Disable tracing for each proc that previously had been specified
    # via logger::trace add.  If tracing is off, this will just
    # remove the name of the procedure from the trace list so that it
    # will be excluded when tracing is turned on.

    foreach procName $procList {
        set index [lsearch -exact $traceList $procName]
        if {$index != -1} {
            set traceList [lreplace $traceList $index $index]
            ::logger::_disable_traces $service [list $procName]
        }
    }
}

# This procedure enables Tcl trace handlers for all procedures specified.
# It is used both to enable Tcl's tracing for a single procedure when
# removed via "logger::trace add", as well as to enable all traces
# via "logger::trace on".

proc ::logger::_enable_traces { service procList } {
    upvar #0 ::logger::tree::${service}::tracingEnabled tracingEnabled

    if {$tracingEnabled} {
        foreach procName $procList {
            ::trace add execution $procName enter \
                [list ::logger::_trace_enter $service]
            ::trace add execution $procName leave \
                [list ::logger::_trace_leave $service]
        }
    }
}

# This procedure disables Tcl trace handlers for all procedures specified.
# It is used both to disable Tcl's tracing for a single procedure when
# removed via "logger::trace remove", as well as to disable all traces
# via "logger::trace off".

proc ::logger::_disable_traces { service procList } {
    upvar #0 ::logger::tree::${service}::tracingEnabled tracingEnabled

    if {$tracingEnabled} {
        foreach procName $procList {
            ::trace remove execution $procName enter \
                [list ::logger::_trace_enter $service]
            ::trace remove execution $procName leave \
                [list ::logger::_trace_leave $service]
        }
    }
}

########################################################################
# Trace Handlers
########################################################################

# This procedure is invoked upon entry into a procedure being traced
# via "logger::trace add" when tracing is enabled via "logger::trace on"
# to log information about how the procedure was called.

proc ::logger::_trace_enter { service cmd op } {
    # Parse the command
    set procName [uplevel 1 namespace origin [lindex $cmd 0]]
    set args     [lrange $cmd 1 end]

    # Display the message prefix
    set callerLvl [expr {[::info level] - 1}]
    set calledLvl [::info level]

    lappend message "proc" $procName
    lappend message "level" $calledLvl
    lappend message "script" [uplevel ::info script]

    # Display the caller information
    set caller ""
    if {$callerLvl >= 1} {
	# Display the name of the caller proc w/prepended namespace
	catch {
	    set callerProcName [lindex [::info level $callerLvl] 0]
	    set caller [uplevel 2 namespace origin $callerProcName]
	}
    }

    lappend message "caller" $caller

    # Display the argument names and values
    set argSpec [uplevel 1 ::info args $procName]
    set argList ""
    if {[llength $argSpec]} {
	foreach argName $argSpec {
            lappend argList $argName

	    if {$argName == "args"} {
                lappend argList $args
                break
	    } else {
	        lappend argList [lindex $args 0]
	        set args [lrange $args 1 end]
            }
	}
    }

    lappend message "procargs" $argList
    set message [list $op $message]

    ::logger::tree::${service}::tracecmd $message
}

# This procedure is invoked upon leaving into a procedure being traced
# via "logger::trace add" when tracing is enabled via "logger::trace on"
# to log information about the result of the procedure call.

proc ::logger::_trace_leave { service cmd status rc op } {
    variable RETURN_CODES

    # Parse the command
    set procName [uplevel 1 namespace origin [lindex $cmd 0]]

    # Gather the caller information
    set callerLvl [expr {[::info level] - 1}]
    set calledLvl [::info level]

    lappend message "proc" $procName "level" $calledLvl
    lappend message "script" [uplevel ::info script]

    # Get the name of the proc being returned to w/prepended namespace
    set caller ""
    catch {
        set callerProcName [lindex [::info level $callerLvl] 0]
        set caller [uplevel 2 namespace origin $callerProcName]
    }

    lappend message "caller" $caller

    # Convert the return code from numeric to verbal

    if {$status < [llength $RETURN_CODES]} {
        set status [lindex $RETURN_CODES $status]
    }

    lappend message "status" $status
    lappend message "result" $rc

    # Display the leave message

    set message [list $op $message]
    ::logger::tree::${service}::tracecmd $message

    return 1
}