0

I have an application making use of some packages. Looking at their source code I see they're doing a simple puts stderr ... to dump out debug information. The problem is if you wrap the program with something like FreeWrap or the TDK, you lose access to the console; so we'd like to forward that stderr output to a file instead so we can see what's being printed.

I saw somewhere on StackOverflow that you can simply close the stderr channel, open a new one, and it should automatically replace the most recently closed channel like so:

close stderr
set out [open "outfile.txt" w]

puts stderr "hello world" # should output to the file 

Unfortunatly this doesn't work. When I try it I get the error message: can not find channel named "stderr"

Vee
  • 729
  • 10
  • 27
  • try `puts $out "hello word" ; close $out` – Mkn Jun 22 '20 at 17:46
  • Hi @Mkn, that works but it doesn't help since the package code does `puts stderr ` ... I need a way to basically replace the stderr channel so the library can dump stuff to a file. – Vee Jun 22 '20 at 17:54
  • 1
    Have a look at https://wiki.tcl-lang.org/page/Changing+stdout%2C+redefining+puts+and+avoiding+console+show – glenn jackman Jun 22 '20 at 19:14

2 Answers2

2

You can override puts so that printing to stderr can be intercepted:

set error_file [open "outfile.txt" w]

rename puts __tcl__puts

proc puts {args} {
    if {[llength $args] == 2 && [lindex $args 0] eq "stderr"} {
        set args [list $::error_file [lindex $args end]]
    }
    __tcl__puts {*}$args
}
glenn jackman
  • 238,783
  • 38
  • 220
  • 352
2

This has been addressed a number of times: Use a channel interceptor (covered before, for capturing Tcl test suite output):

A channel interceptor is implemented as a channel transform; and has been covered here before.

Step 1: Define a Channel Interceptor

oo::class create ChannelSink {
    variable fileHandle
    method initialize {handle mode} {
        set fileHandle [open "outfile.txt" w]
        fconfigure $fileHandle -translation binary
        return {finalize initialize write}
    }
    method finalize {handle} {
        catch {close $fileHandle}
    }

    method write {handle bytes} {
        puts -nonewline $fileHandle $bytes 
        return
    }
}

The above snippet was derived from Donal's.

Step 2: Register the interceptor with stderr around your printing code

set cs [ChannelSink new]
chan push stderr $cs

puts stderr "hello world"

chan pop stderr
mrcalvin
  • 3,291
  • 12
  • 18
  • 1
    The good things about using an interceptor are that it's really easy to undo once you don't want it any more, and you don't have to understand the details of how the channel is written to (`puts` isn't the simplest command, syntactically). – Donal Fellows Jun 23 '20 at 10:21