# Redirect output and error to a file

**URL:** <https://discourse.julialang.org/t/redirect-output-and-error-to-a-file/58908>\
**Category:** New to Julia\
**Tags:** io\
**Created:** [April 9, 2021, 8:40am UTC](https://discourse.julialang.org/t/redirect-output-and-error-to-a-file/58908 "2021-04-09T08:40:38Z")\
**Posts on this page:** 1\
**Showing post:** 5

<div class="post-metadata">

**Author:** ![FPGro](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/fpgro/32/20822_2.png) [@FPGro](https://discourse.julialang.org/u/FPGro)\
**Post date:** [April 13, 2021, 12:07pm UTC](https://discourse.julialang.org/t/redirect-output-and-error-to-a-file/58908/5 "2021-04-13T12:07:59Z")

</div>

> [@fsantos](#):
>
> This particular point really looks odd to me. I mean, is it something specific to Julia, or is it like that in most interpreted languages? (Unless I’m deeply wrong, that’s something very easy to achieve in R, nope?)

I think it’s just something that nobody missed before 😃 It’s actually pretty easy to do, take this proof of concept: (and if somebody did already implement that before me: I’m sorry, my search-foo is pretty weak. It’s also still pretty brittle.)  
`copytorepl.jl`

> **File content**
>
> ```julia
> struct CopyToREPL <: AbstractDisplay
> io::IO
> copyto::AbstractDisplay
> insertnewlines::Bool
> end
> 
> import Base: flush, close, display, displayable 
> 
> flush(d::CopyToREPL) = flush(d.io)
> close(d::CopyToREPL) = close(d.io) # not the copyto display, we don't want to tear it down too
> 
> display(d::CopyToREPL, @nospecialize x) = display(d, MIME"text/plain"(), x)
> display(d::CopyToREPL, M::MIME"text/plain", @nospecialize x) = _display(d, M, x)
> function _display(d::CopyToREPL, M, @nospecialize x)
> show(d.io, M, x)
> d.insertnewlines && println(d.io)
> # you may insert a flush(d.io) here if you want to be able to kill the window without loosing content
> show(d.copyto, M, x)
> end
> 
> displayable(d::CopyToREPL, M::MIME) = istextmime(M)
> # it's probably better not to catch every MIME, idk
> #function display(d::CopyToREPL, M::MIME, @nospecialize x)
> # displayable(d, M) || throw(MethodError(display, (d, M, x)))
> # _display(d.io, M, x)
> #end
> 
> function gethighesttextdisplay()
> displays = Base.Multimedia.displays
> message = "Probing displays!" 
> # because we need to find the last display that actually outputs text 
> # or we may end up backing up to a plotting pane or so. There may be a better way to do this.
> for i = length(displays):-1:1
> if Base.Multimedia.xdisplayable(displays[i], message)
> try
> display(displays[i], message)
> return displays[i]
> catch e
> isa(e, MethodError) && (e.f === display || e.f === show) ||
> rethrow()
> end
> end
> end
> error("No text display found, that's weird.")
> end
> 
> function pophighestcopyto()
> displays = Base.Multimedia.displays
> for i = length(displays):-1:1
> if displays[i] isa CopyToREPL
> return splice!(displays, i)
> end
> end
> throw(KeyError(CopyToREPL))
> end
> 
> function startREPLcopy(noIO; append = false)
> io = open(noIO, write = true, append = append)
> startREPLcopy(io)
> end
> 
> stdoutbackup = []
> 
> function startREPLcopy(io::IO; insertnewlines = true)
> # insertnewlines because show(...) usually doesn't insert newlines after display,
> # so the output is kinda mashed together. There may be cleaner way to do this.
> backupdisplay = gethighesttextdisplay()
> cpdisplay = CopyToREPL(io, backupdisplay, insertnewlines)
> @info "Started copying REPL output to the specified location!"
> pushdisplay(cpdisplay)
> # TODO: It would be better to check here if the stdout hadn't been redirected before
> # So technically, this may snatch a stdout and display more than the REPL
> push!(stdoutbackup, Base.stdout)
> Base.redirect_stdout(io) 
> # TODO I think this does only work for IOStreams, so maybe unwrap contexts and such
> # TODO: may or may not be desirable to handle stderr
> nothing
> end
> 
> function endREPLcopy()
> cpdisplay = try
> pophighestcopyto()
> catch e
> isa(e, KeyError) || rethrow()
> @warn "Couldn't stop copying the REPL output because it wasn't even started."
> end
> flush(cpdisplay)
> oldstdout = pop!(stdoutbackup)
> Base.redirect_stdout(oldstdout)
> close(cpdisplay)
> @info "Copying of REPL output stopped!"
> nothing
> end
> 
> ```

and a demonstration:

```julia
julia> include("copytorepl.jl")
endREPLcopy (generic function with 1 method)

julia> startREPLcopy("testlog.log")
"Probing displays!"
[ Info: Started copying REPL output to the specified location!

julia> test = 2
2

julia> test
2

julia> println("Bla-Keks")

julia> show("αβ↑")

julia> notdefined
ERROR: UndefVarError: notdefined not defined

julia> # note that the error does not print

julia> rand(8)
8-element Vector{Float64}:
 0.48184594197528385
 0.4667812261893638
 0.43096133172880347
 0.791219818003811
 0.12263780916474376
 0.15859996424283396
 0.28204060573062506
 0.3664465404400239

julia> "Bye"
"Bye"

julia> endREPLcopy()
[ Info: Copying of REPL output stopped!

```

gives:  
`testlog.log`

```julia
2
2
Bla-Keks
"αβ↑"8-element Vector{Float64}:
 0.48184594197528385
 0.4667812261893638
 0.43096133172880347
 0.791219818003811
 0.12263780916474376
 0.15859996424283396
 0.28204060573062506
 0.3664465404400239
"Bye"

```

May need some more formatting and cleanup but it is doable in principle.

---

_[View the full topic](https://discourse.julialang.org/t/redirect-output-and-error-to-a-file/58908)._
