Skip to content

Commit

Permalink
added logging() for redirection of info/warn/error messages
Browse files Browse the repository at this point in the history
  • Loading branch information
bjarthur committed Jul 26, 2016
1 parent 97add8e commit d09ad05
Show file tree
Hide file tree
Showing 11 changed files with 230 additions and 40 deletions.
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ Language changes
When `x` is global, `x::T = ...` and `global x::T` used to mean type assertion,
but this syntax is now reserved for type declaration ([#964]).

* `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
---------------------------

Expand Down
3 changes: 1 addition & 2 deletions base/REPL.jl
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,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
Expand Down
9 changes: 6 additions & 3 deletions base/client.jl
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,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
Expand Down
21 changes: 0 additions & 21 deletions base/docs/helpdb/Base.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3341,13 +3341,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)
Expand Down Expand Up @@ -4277,13 +4270,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)
Expand Down Expand Up @@ -6279,13 +6265,6 @@ Airy function ``\\operatorname{Ai}(x)``.
"""
airyai

"""
error(message::AbstractString)
Raise an `ErrorException` with the given message.
"""
error

"""
less(file::AbstractString, [line])
Expand Down
8 changes: 8 additions & 0 deletions base/error.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@
## native julia error handling ##

error(s::AbstractString) = throw(ErrorException(s))

"""
error(msg...)
Raise an `ErrorException` with the given message.
See also `logging`.
"""
error(s...) = throw(ErrorException(Main.Base.string(s...)))

rethrow() = ccall(:jl_rethrow, Bottom, ())
Expand Down
1 change: 1 addition & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,7 @@ export
isxdigit,
join,
lcfirst,
logging,
lowercase,
lpad,
lstrip,
Expand Down
8 changes: 4 additions & 4 deletions base/sysimg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,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")

Expand Down Expand Up @@ -326,10 +330,6 @@ include("libgit2/libgit2.jl")
# package manager
include("pkg/pkg.jl")

# Stack frames and traces
include("stacktraces.jl")
importall .StackTraces

# profiler
include("profile.jl")
importall .Profile
Expand Down
75 changes: 69 additions & 6 deletions base/util.jl
Original file line number Diff line number Diff line change
Expand Up @@ -247,18 +247,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)
Expand All @@ -270,7 +331,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
Expand Down
4 changes: 3 additions & 1 deletion doc/stdlib/base.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1052,12 +1052,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
Expand Down
17 changes: 14 additions & 3 deletions doc/stdlib/io-network.rst
Original file line number Diff line number Diff line change
Expand Up @@ -486,17 +486,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...)

Expand Down
121 changes: 121 additions & 0 deletions test/misc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit d09ad05

Please sign in to comment.