Description
It seems global_logger
does not work well with tasks in the following scenario:
- A task using the logger is launched.
- A new logger is defined (e.g., via import).
- The new logger is set as the global logger.
- The task tries to log something.
As an example, running the following code in a fresh julia
process (i.e., the one that has not imported TerminalLoggers
yet) throws a method error.
using Logging
ch = Channel() do ch
for _ in 1:2
try
@info take!(ch)
catch err
bt = catch_backtrace()
with_logger(ConsoleLogger()) do
@error "`@info` threw" exception = (err, bt)
end
end
end
end
put!(ch, "hello to ConsoleLogger")
using TerminalLoggers
# yield()
global_logger(TerminalLogger())
put!(ch, "hello to TerminalLogger")
Output:
[ Info: hello to ConsoleLogger
┌ Error: `@info` threw
│ exception =
│ MethodError: no method matching shouldlog(::TerminalLogger, ::Base.CoreLogging.LogLevel, ::Module, ::String, ::Symbol)
│ The applicable method may be too new: running in world age 26999, while current world is 27001.
│ Closest candidates are:
│ shouldlog(::TerminalLogger, ::Any, ::Any, ::Any, ::Any) at /home/takafumi/.julia/dev/TerminalLoggers/src/TerminalLogger.jl:40 (method too new to be called from this world context.)
│ shouldlog(::Base.CoreLogging.NullLogger, ::Any...) at logging.jl:84
│ shouldlog(::Base.CoreLogging.SimpleLogger, ::Any, ::Any, ::Any, ::Any) at logging.jl:529
│ ...
│ Stacktrace:
│ [1] macro expansion at ./logging.jl:316 [inlined]
│ [2] (::var"#3#5")(::Channel{Any}) at /home/takafumi/demo.jl:6
│ [3] (::Base.var"#630#631"{var"#3#5",Channel{Any}})() at ./channels.jl:129
└ @ Main ~/junk/2019/11/15-174444.jl:10
ERROR: LoadError: InvalidStateException("Channel is closed.", :closed)
Stacktrace:
[1] try_yieldto(::typeof(Base.ensure_rescheduled), ::Base.RefValue{Task}) at ./task.jl:611
[2] wait() at ./task.jl:668
[3] wait(::Base.GenericCondition{ReentrantLock}) at ./condition.jl:106
[4] put_unbuffered(::Channel{Any}, ::String) at ./channels.jl:350
[5] put!(::Channel{Any}, ::String) at ./channels.jl:325
[6] top-level scope at /home/takafumi/demo.jl:22
[7] include(::Module, ::String) at ./Base.jl:377
[8] exec_options(::Base.JLOptions) at ./client.jl:296
[9] _start() at ./client.jl:492
Here I'm using https://github.com/c42f/TerminalLoggers.jl (as that's the easiest to set up) but it happens with other packages like https://github.com/oxinabox/LoggingExtras.jl.
Note that uncommenting # yield()
fixes the problem. So, as a short term solution, is calling yield()
inside global_logger(logger)
reasonable? Or maybe implicitly call yield()
after using
/import
?
As a long term solution, maybe start discouraging the use of global_logger
and add an API to set up a logger for the RPL, as @c42f suggested in https://discourse.julialang.org/t/different-logging-output-between-juno-execute-and-julia-repl/30933/5? IIUC, with this solution, the task-local logger is inherited to the "background" task at the time it is started and pre-launched tasks will not try to log to the logger that is defined after it is launched. But I'm not 100% sure this is the desired behavior. Maybe it is desirable to be able to swap the logger after it is started?