Write to file and stdout

I have a number of println statements in a script. I know I can also do println(io,blah) to write to a file. When I do that, output no longer goes to stdout. Can I do both at once?

2 Likes

maybe just do it twice?

function myprintln(io, blah)
    println(blah)
    println(io, blah)
end
2 Likes

This needs a lot more work to define a proper IO interface, but

struct Tee{TIO <: Tuple} <: IO
    streams::TIO
end

Tee(streams...) = Tee(streams)

function _do_tee(tee, f, xs...)
    for io in tee.streams
        f(io, xs...)
    end
end

Base.write(tee::Tee, x) = _do_tee(tee, write, x)
Base.write(tee::Tee, x::Union{SubString{String},String}) = _do_tee(tee, write, x)

print(Tee(stdout, stderr), "will appear twice")
8 Likes

You may also consider using Logging module, it looks like it’s more natural. You can combine it with Tamas solution to simplify logger setup, and get something like

@info <some piece of code>

in a very customizable way (even with the possibility to turn it off completely).

Not sure whether the above package is still runnable.

Okay its not working.

Thanks for the suggestions everybody. I tried doing as @jling suggested which would have probably been fine in most cases. But I only need to do this if a boolean flag is set to true, which made that way a bit complicated and RegressionTables.jl also doesn’t work as nicely with this method.

What I ended up doing is having a single IOBuffer that all output is written to. After each section of my code, I check if the LOG_RESULTS is set to true and if it is then I additionally take from the IOBuffer to write to the output file like so

if LOG_RESULTS
    println(logfile, String(take!(copy(myio))))
end
println(String(take!(myio)))

You could actually I guess just do this once at the end of the program but in my case I like having the results print as they are available so I do that at the end of each section.

The package LoggingExtras might help too (in particular, there’s a TeeLogger that looks relevant, although I haven’t used it).

1 Like

Is there any references out there on what other methods need to be implemented for a proper IO subtype?

Or maybe more importantly, under what circumstances would you expect this implementation to be insufficient?

There is no formal spec as far as I can tell, but the manual should be informative. Basic write may be fine, it is just that the implementation above is not heavily tested. FWIW, I can’t even recall why I defined two methods for Base.write.

1 Like

One use case where the above doesn’t work: using Tee as the output stream in run(pipeline(command, tee))

I’ve tried some patches*, but couldn’t get it to work yet

*namely

  • adding Vector{UInt8} to the type union here:
  • defining
Base.write(to::Tee, from::IO) = invoke(write, Tuple{IO,IO}, to, from)
1 Like

(For reference and for anyone stumbling upon this: I solved my task – namely to capture a subprocess’s output but also to live-print it to stdout; not via a Tee but as follows:

buf = IOBuffer()
pos = 0
process = run(pipeline(cmd, buf), wait=false)
while process_running(process)
    sleep(0.1)
    seek(buf, pos)
    new = read(buf, String)
    print(new)
    pos += sizeof(new)
end
output = String(take!(buf))

)

4 Likes