diff --git a/NEWS.md b/NEWS.md index 30b115340e750..cd790fc16b512 100644 --- a/NEWS.md +++ b/NEWS.md @@ -52,6 +52,9 @@ Language changes * The built-in `NTuple` type has been removed; `NTuple{N,T}` is now implemented internally as `Tuple{Vararg{T,N}}` ([#11242]). + * `logging` can be used to redirect `info`, `warn`, and `error` messages + either universally or on a per-module/function basis ([#16213]). + Command-line option changes --------------------------- diff --git a/base/REPL.jl b/base/REPL.jl index dcfd69f2e90e3..0bfedb9841490 100644 --- a/base/REPL.jl +++ b/base/REPL.jl @@ -95,13 +95,6 @@ function start_repl_backend(repl_channel::Channel, response_channel::Channel) backend end -function display_error(io::IO, er, bt) - Base.with_output_color(:red, io) do io - print(io, "ERROR: ") - Base.showerror(io, er, bt) - end -end - immutable REPLDisplay{R<:AbstractREPL} <: Display repl::R end @@ -124,8 +117,7 @@ function print_response(errio::IO, val::ANY, bt, show_value::Bool, have_color::B while true try if bt !== nothing - display_error(errio, val, bt) - println(errio) + Base.display_error(errio, val, bt) iserr, lasterr = false, () else if val !== nothing && show_value diff --git a/base/client.jl b/base/client.jl index e77b3de9aded6..0111d8385fb9d 100644 --- a/base/client.jl +++ b/base/client.jl @@ -90,14 +90,17 @@ function repl_cmd(cmd, out) nothing end -display_error(er) = display_error(er, []) -function display_error(er, bt) - with_output_color(:red, STDERR) do io +function display_error(io::IO, er, bt) + sf = StackTraces.remove_frames!(stacktrace(bt), :error)[1] + io, mo, fu = redirect(io, log_error_to, sf) + with_output_color(:red, io) do io print(io, "ERROR: ") showerror(io, er, bt) println(io) end end +display_error(er, bt) = display_error(STDERR, er, bt) +display_error(er) = display_error(er, []) function eval_user_input(ast::ANY, show_value) errcount, lasterr, bt = 0, (), nothing diff --git a/base/docs/helpdb/Base.jl b/base/docs/helpdb/Base.jl index 5252a07cc526d..efc941010fb40 100644 --- a/base/docs/helpdb/Base.jl +++ b/base/docs/helpdb/Base.jl @@ -3592,13 +3592,6 @@ behavior, including program corruption or segfaults, at any later time. """ unsafe_convert -""" - warn(msg) - -Display a warning. Argument `msg` is a string describing the warning to be displayed. -""" -warn - """ erfinv(x) @@ -4641,13 +4634,6 @@ multiple of four, this is equivalent to a `copy`. """ rotl90(A, k) -""" - info(msg) - -Display an informational message. Argument `msg` is a string describing the information to be displayed. -""" -info - """ eigmin(A) @@ -6858,13 +6844,6 @@ Airy function ``\\operatorname{Ai}(x)``. """ airyai -""" - error(message::AbstractString) - -Raise an `ErrorException` with the given message. -""" -error - """ less(file::AbstractString, [line]) diff --git a/base/error.jl b/base/error.jl index b237db748ee76..4b3cdbee9869f 100644 --- a/base/error.jl +++ b/base/error.jl @@ -19,7 +19,15 @@ ## native julia error handling ## error(s::AbstractString) = throw(Main.Base.ErrorException(s)) -error(s...) = throw(Main.Base.ErrorException(Main.Base.string(s...))) + +""" + error(msg...) + +Raise an `ErrorException` with the given message. + +See also `logging`. +""" +error(msg...) = error(Main.Base.string(msg...)) rethrow() = ccall(:jl_rethrow, Bottom, ()) rethrow(e) = ccall(:jl_rethrow_other, Bottom, (Any,), e) diff --git a/base/exports.jl b/base/exports.jl index b1cba3ce80895..4cb3d0aa25a28 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -846,6 +846,7 @@ export isxdigit, join, lcfirst, + logging, lowercase, lpad, lstrip, diff --git a/base/sysimg.jl b/base/sysimg.jl index aed8231efd4c9..ac7276d555ee6 100644 --- a/base/sysimg.jl +++ b/base/sysimg.jl @@ -272,6 +272,10 @@ include("REPLCompletions.jl") include("REPL.jl") include("client.jl") +# Stack frames and traces +include("stacktraces.jl") +importall .StackTraces + # misc useful functions & macros include("util.jl") @@ -314,10 +318,6 @@ include("libgit2.jl") include("pkg.jl") const Git = Pkg.Git -# Stack frames and traces -include("stacktraces.jl") -importall .StackTraces - # profiler include("profile.jl") importall .Profile diff --git a/base/util.jl b/base/util.jl index 82a4396c581bc..10b8defa6d1b5 100644 --- a/base/util.jl +++ b/base/util.jl @@ -332,18 +332,79 @@ println_with_color(color::Symbol, msg::AbstractString...) = ## warnings and messages ## -function info(io::IO, msg...; prefix="INFO: ") - println_with_color(info_color(), io, prefix, chomp(string(msg...))) +const have_warned = Set() +const log_info_to = Dict() +const log_warn_to = Dict() +const log_error_to = Dict() + +function redirect(io::IO, log_to::Dict, sf::StackTraces.StackFrame) + mo = sf.outer_linfo.value.def.module + fu = sf.func + if !isempty(log_to) + if haskey(log_to, (mo,fu)) + io = log_to[(mo,fu)] + elseif haskey(log_to, (mo,nothing)) + io = log_to[(mo,nothing)] + elseif haskey(log_to, (nothing,nothing)) + io = log_to[(nothing,nothing)] + end + end + io, mo, fu end -info(msg...; prefix="INFO: ") = info(STDERR, msg..., prefix=prefix) -# print a warning only once +""" + logging(io [, m [, f]][; kind=:all]) + logging([; kind=:all]) + +Stream output of informational, warning, and/or error messages to `io`, +overriding what was otherwise specified. Optionally, divert stream only for +module `m`, or specifically function `f` within `m`. `kind` can also be +`:info`, `:warn`, and `:error`. See `Base.log_{info,warn,error}_to` for the +current set of redirections. Call `logging` with no arguments (or just the `kind`) +to reset everything. +""" +function logging(io::IO, m::Union{Module,Void}=nothing, f::Union{Symbol,Void}=nothing; + kind::Symbol=:all) + (kind==:all || kind==:info) && (log_info_to[(m,f)] = io) + (kind==:all || kind==:warn) && (log_warn_to[(m,f)] = io) + (kind==:all || kind==:error) && (log_error_to[(m,f)] = io) + nothing +end -const have_warned = Set() +function logging(; kind::Symbol=:all) + (kind==:all || kind==:info) && empty!(log_info_to) + (kind==:all || kind==:warn) && empty!(log_warn_to) + (kind==:all || kind==:error) && empty!(log_error_to) + nothing +end + +""" + info([io, ] msg..., [prefix="INFO: "]) + +Display an informational message. Argument `msg` is a string describing the information to be displayed. + +See also `logging`. +""" +function info(io::IO, msg...; prefix="INFO: ") + sf = StackTraces.remove_frames!(stacktrace(), :info)[1] + io, mo, fu = redirect(io, log_info_to, sf) + println_with_color(info_color(), io, prefix, chomp(string(msg...)), " ($mo.$fu)") +end +info(msg...; prefix="INFO: ") = info(STDERR, msg..., prefix=prefix) warn_once(io::IO, msg...) = warn(io, msg..., once=true) warn_once(msg...) = warn(STDERR, msg..., once=true) +""" + warn([io, ] msg..., [prefix="WARNING: ", once=false, key=nothing, bt=nothing, filename=nothing, lineno::Int=0]) + +Display a warning. Argument `msg` is a string describing the warning to be +displayed. Set `once` to true and specify a `key` to only display `msg` the +first time `warn` is called. If `bt` is not nothing a backtrace is displayed. +If `filename` is not nothing both it and `lineno` are displayed. + +See also `logging`. +""" function warn(io::IO, msg...; prefix="WARNING: ", once=false, key=nothing, bt=nothing, filename=nothing, lineno::Int=0) @@ -355,7 +416,9 @@ function warn(io::IO, msg...; (key in have_warned) && return push!(have_warned, key) end - print_with_color(warn_color(), io, prefix, str) + sf = StackTraces.remove_frames!(stacktrace(), :warn)[1] + io, mo, fu = redirect(io, log_warn_to, sf) + print_with_color(warn_color(), io, prefix, str, " ($mo.$fu)") if bt !== nothing show_backtrace(io, bt) end diff --git a/doc/stdlib/base.rst b/doc/stdlib/base.rst index 931386ee44eb0..8791e88c8e986 100644 --- a/doc/stdlib/base.rst +++ b/doc/stdlib/base.rst @@ -1028,12 +1028,14 @@ System Errors ------ -.. function:: error(message::AbstractString) +.. function:: error(msg...) .. Docstring generated from Julia source Raise an ``ErrorException`` with the given message. + See also ``logging``\ . + .. function:: throw(e) .. Docstring generated from Julia source diff --git a/doc/stdlib/io-network.rst b/doc/stdlib/io-network.rst index ecc18858e6cf2..739326a1a062e 100644 --- a/doc/stdlib/io-network.rst +++ b/doc/stdlib/io-network.rst @@ -470,17 +470,28 @@ Text I/O ``color`` may take any of the values ``:normal``\ , ``:bold``\ , ``:black``\ , ``:blue``\ , ``:cyan``\ , ``:green``\ , ``:magenta``\ , ``:red``\ , ``:white``\ , or ``:yellow``\ . -.. function:: info(msg) +.. function:: info([io, ] msg..., [prefix="INFO: "]) .. Docstring generated from Julia source Display an informational message. Argument ``msg`` is a string describing the information to be displayed. -.. function:: warn(msg) + See also ``logging``\ . + +.. function:: warn([io, ] msg..., [prefix="WARNING: ", once=false, key=nothing, bt=nothing, filename=nothing, lineno::Int=0]) + + .. Docstring generated from Julia source + + Display a warning. Argument ``msg`` is a string describing the warning to be displayed. Set ``once`` to true and specify a ``key`` to only display ``msg`` the first time ``warn`` is called. If ``bt`` is not nothing a backtrace is displayed. If ``filename`` is not nothing both it and ``lineno`` are displayed. + + See also ``logging``\ . + +.. function:: logging(io [, m [, f]][; kind=:all]) + logging([; kind=:all]) .. Docstring generated from Julia source - Display a warning. Argument ``msg`` is a string describing the warning to be displayed. + Stream output of informational, warning, and/or error messages to ``io``\ , overriding what was otherwise specified. Optionally, divert stream only for module ``m``\ , or specifically function ``f`` within ``m``\ . ``kind`` can also be ``:info``\ , ``:warn``\ , and ``:error``\ . See ``Base.log_{info,warn,error}_to`` for the current set of redirections. Call ``logging`` with no arguments (or just the ``kind``\ ) to reset everything. .. function:: @printf([io::IOStream], "%Fmt", args...) diff --git a/test/misc.jl b/test/misc.jl index 7b6cc4d8e0697..6a1b655b74e98 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -28,6 +28,127 @@ let bt = backtrace() end end +module Foo + function bar(io) + info(io,"barinfo") + warn(io,"barwarn") + end + function pooh(io) + info(io,"poohinfo") + warn(io,"poohwarn") + end +end +function foo(io) + info(io,"fooinfo") + warn(io,"foowarn") +end + +@test ( tmp = sprint(Foo.bar); + all(map(x->contains(tmp,x), + ["INFO: barinfo (Foo.bar)", "WARNING: barwarn (Foo.bar)"])) ) +@test ( tmp = sprint(Foo.pooh); + all(map(x->contains(tmp,x), + ["INFO: poohinfo (Foo.pooh)", "WARNING: poohwarn (Foo.pooh)"])) ) +@test ( tmp = sprint(foo); + all(map(x->contains(tmp,x), + ["INFO: fooinfo (Main.foo)", "WARNING: foowarn (Main.foo)"])) ) + +logging(DevNull, Foo, :bar; kind=:info) +@test contains(sprint(Foo.bar), "WARNING: barwarn (Foo.bar)") +@test ( tmp = sprint(Foo.pooh); + all(map(x->contains(tmp,x), + ["INFO: poohinfo (Foo.pooh)", "WARNING: poohwarn (Foo.pooh)"])) ) +@test ( tmp = sprint(foo); + all(map(x->contains(tmp,x), + ["INFO: fooinfo (Main.foo)", "WARNING: foowarn (Main.foo)"])) ) + +logging(DevNull, Foo; kind=:info) +@test contains(sprint(Foo.bar), "WARNING: barwarn (Foo.bar)") +@test contains(sprint(Foo.pooh), "WARNING: poohwarn (Foo.pooh)") +@test ( tmp = sprint(foo); + all(map(x->contains(tmp,x), + ["INFO: fooinfo (Main.foo)", "WARNING: foowarn (Main.foo)"])) ) + +logging(DevNull; kind=:info) +@test contains(sprint(Foo.bar), "WARNING: barwarn (Foo.bar)") +@test contains(sprint(Foo.pooh), "WARNING: poohwarn (Foo.pooh)") +@test contains(sprint(foo), "WARNING: foowarn (Main.foo)") + +logging(kind=:info) +@test ( tmp = sprint(Foo.bar); + all(map(x->contains(tmp,x), + ["INFO: barinfo (Foo.bar)", "WARNING: barwarn (Foo.bar)"])) ) +@test ( tmp = sprint(Foo.pooh); + all(map(x->contains(tmp,x), + ["INFO: poohinfo (Foo.pooh)", "WARNING: poohwarn (Foo.pooh)"])) ) +@test ( tmp = sprint(foo); + all(map(x->contains(tmp,x), + ["INFO: fooinfo (Main.foo)", "WARNING: foowarn (Main.foo)"])) ) + +logging(DevNull, Foo, :bar; kind=:warn) +@test contains(sprint(Foo.bar), "INFO: barinfo (Foo.bar)") +@test ( tmp = sprint(Foo.pooh); + all(map(x->contains(tmp,x), + ["INFO: poohinfo (Foo.pooh)", "WARNING: poohwarn (Foo.pooh)"])) ) +@test ( tmp = sprint(foo); + all(map(x->contains(tmp,x), + ["INFO: fooinfo (Main.foo)", "WARNING: foowarn (Main.foo)"])) ) + +logging(DevNull, Foo; kind=:warn) +@test contains(sprint(Foo.bar), "INFO: barinfo (Foo.bar)") +@test contains(sprint(Foo.pooh), "INFO: poohinfo (Foo.pooh)") +@test ( tmp = sprint(foo); + all(map(x->contains(tmp,x), + ["INFO: fooinfo (Main.foo)", "WARNING: foowarn (Main.foo)"])) ) + +logging(DevNull; kind=:warn) +@test contains(sprint(Foo.bar), "INFO: barinfo (Foo.bar)") +@test contains(sprint(Foo.pooh), "INFO: poohinfo (Foo.pooh)") +@test contains(sprint(foo), "INFO: fooinfo (Main.foo)") + +logging(kind=:warn) +@test ( tmp = sprint(Foo.bar); + all(map(x->contains(tmp,x), + ["INFO: barinfo (Foo.bar)", "WARNING: barwarn (Foo.bar)"])) ) +@test ( tmp = sprint(Foo.pooh); + all(map(x->contains(tmp,x), + ["INFO: poohinfo (Foo.pooh)", "WARNING: poohwarn (Foo.pooh)"])) ) +@test ( tmp = sprint(foo); + all(map(x->contains(tmp,x), + ["INFO: fooinfo (Main.foo)", "WARNING: foowarn (Main.foo)"])) ) + +logging(DevNull, Foo, :bar) +@test sprint(Foo.bar) == "" +@test ( tmp = sprint(Foo.pooh); + all(map(x->contains(tmp,x), + ["INFO: poohinfo (Foo.pooh)", "WARNING: poohwarn (Foo.pooh)"])) ) +@test ( tmp = sprint(foo); + all(map(x->contains(tmp,x), + ["INFO: fooinfo (Main.foo)", "WARNING: foowarn (Main.foo)"])) ) + +logging(DevNull, Foo) +@test sprint(Foo.bar) == "" +@test sprint(Foo.pooh) == "" +@test ( tmp = sprint(foo); + all(map(x->contains(tmp,x), + ["INFO: fooinfo (Main.foo)", "WARNING: foowarn (Main.foo)"])) ) + +logging(DevNull) +@test sprint(Foo.bar) == "" +@test sprint(Foo.pooh) == "" +@test sprint(foo) == "" + +logging() +@test ( tmp = sprint(Foo.bar); + all(map(x->contains(tmp,x), + ["INFO: barinfo (Foo.bar)", "WARNING: barwarn (Foo.bar)"])) ) +@test ( tmp = sprint(Foo.pooh); + all(map(x->contains(tmp,x), + ["INFO: poohinfo (Foo.pooh)", "WARNING: poohwarn (Foo.pooh)"])) ) +@test ( tmp = sprint(foo); + all(map(x->contains(tmp,x), + ["INFO: fooinfo (Main.foo)", "WARNING: foowarn (Main.foo)"])) ) + # test assert() method @test_throws AssertionError assert(false) let res = assert(true)