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 May 11, 2016
1 parent c9d4051 commit d3767f7
Show file tree
Hide file tree
Showing 10 changed files with 206 additions and 42 deletions.
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
---------------------------

Expand Down
2 changes: 2 additions & 0 deletions base/REPL.jl
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ function start_repl_backend(repl_channel::Channel, response_channel::Channel)
end

function display_error(io::IO, er, bt)
sf = StackTraces.remove_frames!(stacktrace(bt), :error)[1]
io, mo, fu = Base.redirect(io, Base.log_error_to, sf)
Base.with_output_color(:red, io) do io
print(io, "ERROR: ")
Base.showerror(io, er, bt)
Expand Down
8 changes: 1 addition & 7 deletions base/client.jl
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,7 @@ function repl_cmd(cmd, out)
end

display_error(er) = display_error(er, [])
function display_error(er, bt)
with_output_color(:red, STDERR) do io
print(io, "ERROR: ")
showerror(io, er, bt)
println(io)
end
end
display_error(er, bt) = REPL.display_error(STDERR, er, bt)

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 @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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])
Expand Down
7 changes: 7 additions & 0 deletions base/error.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@

## native julia error handling ##

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

Expand Down
8 changes: 4 additions & 4 deletions base/sysimg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down Expand Up @@ -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
Expand Down
85 changes: 79 additions & 6 deletions base/util.jl
Original file line number Diff line number Diff line change
Expand Up @@ -332,18 +332,89 @@ 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)
if kind==:all || kind==:info
log_info_to[(m,f)]=io
elseif kind==:all || kind==:warn
log_warn_to[(m,f)]=io
elseif kind==:all || kind==:error
log_error_to[(m,f)]=io
end
nothing
end

const have_warned = Set()
function logging(; kind::Symbol=:all)
if kind==:all || kind==:info
empty!(log_info_to)
elseif kind==:all || kind==:warn
empty!(log_warn_to)
elseif kind==:all || kind==:error
empty!(log_error_to)
end
nothing
end

export logging

"""
info(msg...)
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(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 @@ -355,7 +426,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 @@ -1026,12 +1026,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 @@ -464,17 +464,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(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 ``logging``\ .

.. 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 ``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
93 changes: 93 additions & 0 deletions test/misc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,99 @@ let bt = backtrace()
end
end

# sprint() with error() throws an error, but not with info() or warn(). why?
module Foo
function bar(io)
info(io,"barinfo")
warn(io,"barwarn")
#error(io,"barerror")
end
function pooh(io)
info(io,"poohinfo")
warn(io,"poohwarn")
#error(io,"pooherror")
end
end
function foo(io)
info(io,"fooinfo")
warn(io,"foowarn")
#error(io,"fooerror")
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)"])) )

# test assert() method
@test_throws AssertionError assert(false)
let res = assert(true)
Expand Down

0 comments on commit d3767f7

Please sign in to comment.