From 86dc94b8a2ac0cd4b6c2408f856093e45563d6c2 Mon Sep 17 00:00:00 2001 From: Ben Arthur Date: Thu, 5 May 2016 10:50:46 -0400 Subject: [PATCH] added log{info,warn} for redirection of messages --- NEWS.md | 3 ++ base/docs/helpdb/Base.jl | 14 -------- base/util.jl | 75 +++++++++++++++++++++++++++++++++++++-- doc/stdlib/io-network.rst | 22 ++++++++++-- test/misc.jl | 47 ++++++++++++++++++++++++ 5 files changed, 142 insertions(+), 19 deletions(-) diff --git a/NEWS.md b/NEWS.md index 0293bc5fe5bcb..4310791a3a0f4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -48,6 +48,9 @@ Language changes * The built-in `NTuple` type has been removed; `NTuple{N,T}` is now implemented internally as `Tuple{Vararg{T,N}}` ([#11242]). + * `loginfo` and `logwarn` can be used to redirect `info` and `warn` messages, + respectively, either universally or on a per-module/function basis ([#16213]). + Command-line option changes --------------------------- diff --git a/base/docs/helpdb/Base.jl b/base/docs/helpdb/Base.jl index 67fd9a92cc28b..2dc6f649f7cb3 100644 --- a/base/docs/helpdb/Base.jl +++ b/base/docs/helpdb/Base.jl @@ -3599,13 +3599,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) @@ -4648,13 +4641,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) diff --git a/base/util.jl b/base/util.jl index 82a4396c581bc..38427d260f773 100644 --- a/base/util.jl +++ b/base/util.jl @@ -332,18 +332,77 @@ println_with_color(color::Symbol, msg::AbstractString...) = ## warnings and messages ## +""" + info(msg...) + +Display an informational message. Argument `msg` is a string describing the information to be displayed. + +See also `loginfo`. +""" function info(io::IO, msg...; prefix="INFO: ") - println_with_color(info_color(), io, prefix, chomp(string(msg...))) + st = StackTraces.remove_frames!(stacktrace(), :info)[1] + mo = st.outer_linfo.value.def.module + fu = st.func + if !isempty(log_info_to) + if haskey(log_info_to, (mo,fu)) + io = log_info_to[(mo,fu)] + elseif haskey(log_info_to, mo) + io = log_info_to[mo] + elseif haskey(log_info_to, nothing) + io = log_info_to[nothing] + end + end + println_with_color(info_color(), io, prefix, chomp(string(msg...)), " ($mo.$fu)") end info(msg...; prefix="INFO: ") = info(STDERR, msg..., prefix=prefix) # print a warning only once const have_warned = Set() +const log_warn_to = Dict() +const log_info_to = Dict() + +""" + logwarn([io [, m [, f]]]) + +Stream output of warnings to `io`, overriding what was otherwise specified. +Optionally, divert stream only for module `m`, or specifically function `f` +within `m`. See `Base.log_warn_to` for the current set of redirections. Call +`logwarn` with no arguments to reset everything. +""" +logwarn(io::IO, m::Module, f::Symbol) = log_warn_to[(m,f)]=io +logwarn(io::IO, m::Module) = log_warn_to[m]=io +logwarn(io::IO) = log_warn_to[nothing]=io +logwarn() = empty!(log_warn_to) + +""" + loginfo([io [, m [, f]]]) + +Stream output of informational messages to `io`, overriding what was otherwise +specified. Optionally, divert stream only for module `m`, or specifically +function `f` within `m`. See `Base.log_warn_to` for the current set of +redirections. Call `loginfo` with no arguments to reset everything. +""" +loginfo(io::IO, m::Module, f::Symbol) = log_info_to[(m,f)]=io +loginfo(io::IO, m::Module) = log_info_to[m]=io +loginfo(io::IO) = log_info_to[nothing]=io +loginfo() = empty!(log_info_to) + +export logwarn, loginfo warn_once(io::IO, msg...) = warn(io, msg..., once=true) warn_once(msg...) = warn(STDERR, msg..., once=true) +""" + warn(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 `logwarn`. +""" function warn(io::IO, msg...; prefix="WARNING: ", once=false, key=nothing, bt=nothing, filename=nothing, lineno::Int=0) @@ -355,7 +414,19 @@ function warn(io::IO, msg...; (key in have_warned) && return push!(have_warned, key) end - print_with_color(warn_color(), io, prefix, str) + st = StackTraces.remove_frames!(stacktrace(), :warn)[1] + mo = st.outer_linfo.value.def.module + fu = st.func + if !isempty(log_warn_to) + if haskey(log_warn_to, (mo,fu)) + io = log_warn_to[(mo,fu)] + elseif haskey(log_warn_to, mo) + io = log_warn_to[mo] + elseif haskey(log_warn_to, nothing) + io = log_warn_to[nothing] + end + end + print_with_color(warn_color(), io, prefix, str, " ($mo.$fu)") if bt !== nothing show_backtrace(io, bt) end diff --git a/doc/stdlib/io-network.rst b/doc/stdlib/io-network.rst index 1e7f5d857ea65..c0e05892b20df 100644 --- a/doc/stdlib/io-network.rst +++ b/doc/stdlib/io-network.rst @@ -464,17 +464,33 @@ 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(msg...) .. 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 ``loginfo``\ . + +.. function:: warn(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 ``logwarn``\ . + +.. function:: loginfo([io [, m [, f]]]) + + .. Docstring generated from Julia source + + Stream output of informational messages to ``io``\ , overriding what was otherwise specified. Optionally, divert stream only for module ``m``\ , or specifically function ``f`` within ``m``\ . See ``Base.log_warn_to`` for the current set of redirections. Call ``loginfo`` with no arguments to reset everything. + +.. function:: logwarn([io [, m [, f]]]) .. Docstring generated from Julia source - Display a warning. Argument ``msg`` is a string describing the warning to be displayed. + Stream output of warnings to ``io``\ , overriding what was otherwise specified. Optionally, divert stream only for module ``m``\ , or specifically function ``f`` within ``m``\ . See ``Base.log_warn_to`` for the current set of redirections. Call ``logwarn`` with no arguments to reset everything. .. function:: @printf([io::IOStream], "%Fmt", args...) diff --git a/test/misc.jl b/test/misc.jl index 7b6cc4d8e0697..c8dd1dcfdcf39 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -28,6 +28,53 @@ let bt = backtrace() end end +module Foo + barw(io) = warn(io,"barw") + poohw(io) = warn(io,"poohw") + bari(io) = info(io,"bari") + poohi(io) = info(io,"poohi") +end + +@test contains(sprint(Foo.barw), "WARNING: barw (Foo.barw)") +@test contains(sprint(Foo.poohw), "WARNING: poohw (Foo.poohw)") +@test contains(sprint(io->warn(io,"test")), "WARNING: test (Main") +logwarn(DevNull, Foo, :barw) +@test sprint(Foo.barw) == "" +@test contains(sprint(Foo.poohw), "WARNING: poohw (Foo.poohw)") +@test contains(sprint(io->warn(io,"test")), "WARNING: test (Main") +logwarn(DevNull, Foo) +@test sprint(Foo.barw) == "" +@test sprint(Foo.poohw) == "" +@test contains(sprint(io->warn(io,"test")), "WARNING: test (Main") +logwarn(DevNull) +@test sprint(Foo.barw) == "" +@test sprint(Foo.poohw) == "" +@test sprint(io->warn(io,"test")) == "" +logwarn() +@test contains(sprint(Foo.barw), "WARNING: barw (Foo.barw)") +@test contains(sprint(Foo.poohw), "WARNING: poohw (Foo.poohw)") +@test contains(sprint(io->warn(io,"test")), "WARNING: test (Main") + +@test contains(sprint(Foo.bari), "INFO: bari (Foo.bari)") +@test contains(sprint(Foo.poohi), "INFO: poohi (Foo.poohi)") +@test contains(sprint(io->info(io,"test")), "INFO: test (Main") +loginfo(DevNull, Foo, :bari) +@test sprint(Foo.bari) == "" +@test contains(sprint(Foo.poohi), "INFO: poohi (Foo.poohi)") +@test contains(sprint(io->info(io,"test")), "INFO: test (Main") +loginfo(DevNull, Foo) +@test sprint(Foo.bari) == "" +@test sprint(Foo.poohi) == "" +@test contains(sprint(io->info(io,"test")), "INFO: test (Main") +loginfo(DevNull) +@test sprint(Foo.bari) == "" +@test sprint(Foo.poohi) == "" +@test sprint(io->info(io,"test")) == "" +loginfo() +@test contains(sprint(Foo.bari), "INFO: bari (Foo.bari)") +@test contains(sprint(Foo.poohi), "INFO: poohi (Foo.poohi)") +@test contains(sprint(io->info(io,"test")), "INFO: test (Main") + # test assert() method @test_throws AssertionError assert(false) let res = assert(true)