Updated 2015-11-16 12:36:10 by MHo

Scott Nichols

Below is a synchronized Tcl thread logger based on example 21-9, page 333, in the book Practical Programming in Tcl and Tk 4th edition, by Brent B. Welch, Ken Jones with Jeffrey Hobbs. The thread logger allows synchronized screen prints from a child thread to the main thread (needed for Windows and Maconintosh) and for synchronized file access. When the log command is executed from a thread, the trace (file append) will provide the date time, thread id, and custom message. The log command can be called from the main thread or child threads. Performance wise this seem to run just fine. Performance is not impacted because the Tcl interpreter caches the file writes when opening a file in a+ mode until the memory buffer is full. Comments or suggestions welcome.

Copy the code below in the main thread only.
        package require Thread
        # logPath parameter should include the file name in the path.
        # If not provided the file name will be today's date.

        # Set to false if you only want to do screen prints
        tsv::set logging ID true

        # File Path and File to use for logging.
        tsv::set logPath ID ./myFile

        # Put this code in the main thread only.
        # Needed for screen prints from other threads.
        tsv::set mainThread ID [thread::id]
        # Put this code in main thread only
        set ::threadLogger [::thread::create {
            proc log { value logging {logPath "./"}} {
                global yesterday logid
                if { [catch {

                    set currentday [clock seconds]
                    set seconds $currentday

                    thread::send -async [tsv::get mainThread ID] [list puts "-------------------------------------------------------------------------------"]
                    thread::send -async [tsv::get mainThread ID] [list puts "[clock format $seconds -format "%D|%T|"]$value"]

                    if { $logging } {

                        set currentday [clock format $currentday -format %d]

                        if { ! [info exists logid] } {
                            set logid [open "${logPath}[clock format $seconds -format %m-%d-%y].log" a+]

                         # Create new file
                         if { [info exists yesterday] } {
                             if { $currentday != $yesterday } {
                                 set yesterday $currentday
                                 close $fileid
                                 set logid [open "${logPath}[clock format $seconds -format %m-%d-%y].log" a+]
                         } else {
                             set yesterday $currentday

                        puts $logid "-------------------------------------------------------------------------------"
                        puts $logid "[clock format $seconds -format "%D|%T|"]$value"   
                } errorString] } {
                     thread::send -async [tsv::get mainThread ID] [list puts puts "proc log ERROR: \"$errorString\""]

        tsv::set loggerThread ID $::threadLogger

Copy the code below in the main thread and all child threads
        # This proc needs to be copied into each child thread.
        # This code must be copied into each child thread on thread initialization
        proc log { value } {
            if { [catch {
                thread::send -async [tsv::get loggerThread ID] [list log "Thread[thread::id]|$value" [tsv::get logging ID] [tsv::get logPath ID]]
            } errorString] } {
                puts "proc log ERROR: \"$errorString\""

        log "Hello. This is a test"

MHo 2015-11-16:

  • When did the log gets closed?
  • From the perspective of the main program, how to tell if all requests, which are sent async to the logger thread, are processed?
  • How to end the logger thread? Simply ending the mainprog doesn't take pending requests in account...