Skip to content

Commit d09ad05

Browse files
committed
added logging() for redirection of info/warn/error messages
1 parent 97add8e commit d09ad05

File tree

11 files changed

+230
-40
lines changed

11 files changed

+230
-40
lines changed

NEWS.md

+3
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ Language changes
8080
When `x` is global, `x::T = ...` and `global x::T` used to mean type assertion,
8181
but this syntax is now reserved for type declaration ([#964]).
8282

83+
* `logging` can be used to redirect `info`, `warn`, and `error` messages
84+
either universally or on a per-module/function basis ([#16213]).
85+
8386
Command-line option changes
8487
---------------------------
8588

base/REPL.jl

+1-2
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,7 @@ function print_response(errio::IO, val::ANY, bt, show_value::Bool, have_color::B
139139
while true
140140
try
141141
if bt !== nothing
142-
display_error(errio, val, bt)
143-
println(errio)
142+
Base.display_error(errio, val, bt)
144143
iserr, lasterr = false, ()
145144
else
146145
if val !== nothing && show_value

base/client.jl

+6-3
Original file line numberDiff line numberDiff line change
@@ -93,14 +93,17 @@ function repl_cmd(cmd, out)
9393
nothing
9494
end
9595

96-
display_error(er) = display_error(er, [])
97-
function display_error(er, bt)
98-
with_output_color(:red, STDERR) do io
96+
function display_error(io::IO, er, bt)
97+
sf = StackTraces.remove_frames!(stacktrace(bt), :error)[1]
98+
io, mo, fu = redirect(io, log_error_to, sf)
99+
with_output_color(:red, io) do io
99100
print(io, "ERROR: ")
100101
showerror(io, er, bt)
101102
println(io)
102103
end
103104
end
105+
display_error(er, bt) = display_error(STDERR, er, bt)
106+
display_error(er) = display_error(er, [])
104107

105108
function eval_user_input(ast::ANY, show_value)
106109
errcount, lasterr, bt = 0, (), nothing

base/docs/helpdb/Base.jl

-21
Original file line numberDiff line numberDiff line change
@@ -3341,13 +3341,6 @@ behavior, including program corruption or segfaults, at any later time.
33413341
"""
33423342
unsafe_convert
33433343

3344-
"""
3345-
warn(msg)
3346-
3347-
Display a warning. Argument `msg` is a string describing the warning to be displayed.
3348-
"""
3349-
warn
3350-
33513344
"""
33523345
erfinv(x)
33533346
@@ -4277,13 +4270,6 @@ multiple of four, this is equivalent to a `copy`.
42774270
"""
42784271
rotl90(A, k)
42794272

4280-
"""
4281-
info(msg)
4282-
4283-
Display an informational message. Argument `msg` is a string describing the information to be displayed.
4284-
"""
4285-
info
4286-
42874273
"""
42884274
eigmin(A)
42894275
@@ -6279,13 +6265,6 @@ Airy function ``\\operatorname{Ai}(x)``.
62796265
"""
62806266
airyai
62816267

6282-
"""
6283-
error(message::AbstractString)
6284-
6285-
Raise an `ErrorException` with the given message.
6286-
"""
6287-
error
6288-
62896268
"""
62906269
less(file::AbstractString, [line])
62916270

base/error.jl

+8
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@
1919
## native julia error handling ##
2020

2121
error(s::AbstractString) = throw(ErrorException(s))
22+
23+
"""
24+
error(msg...)
25+
26+
Raise an `ErrorException` with the given message.
27+
28+
See also `logging`.
29+
"""
2230
error(s...) = throw(ErrorException(Main.Base.string(s...)))
2331

2432
rethrow() = ccall(:jl_rethrow, Bottom, ())

base/exports.jl

+1
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,7 @@ export
837837
isxdigit,
838838
join,
839839
lcfirst,
840+
logging,
840841
lowercase,
841842
lpad,
842843
lstrip,

base/sysimg.jl

+4-4
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,10 @@ include("REPLCompletions.jl")
289289
include("REPL.jl")
290290
include("client.jl")
291291

292+
# Stack frames and traces
293+
include("stacktraces.jl")
294+
importall .StackTraces
295+
292296
# misc useful functions & macros
293297
include("util.jl")
294298

@@ -326,10 +330,6 @@ include("libgit2/libgit2.jl")
326330
# package manager
327331
include("pkg/pkg.jl")
328332

329-
# Stack frames and traces
330-
include("stacktraces.jl")
331-
importall .StackTraces
332-
333333
# profiler
334334
include("profile.jl")
335335
importall .Profile

base/util.jl

+69-6
Original file line numberDiff line numberDiff line change
@@ -247,18 +247,79 @@ println_with_color(color::Symbol, msg::AbstractString...) =
247247

248248
## warnings and messages ##
249249

250-
function info(io::IO, msg...; prefix="INFO: ")
251-
println_with_color(info_color(), io, prefix, chomp(string(msg...)))
250+
const have_warned = Set()
251+
const log_info_to = Dict()
252+
const log_warn_to = Dict()
253+
const log_error_to = Dict()
254+
255+
function redirect(io::IO, log_to::Dict, sf::StackTraces.StackFrame)
256+
mo = sf.outer_linfo.value.def.module
257+
fu = sf.func
258+
if !isempty(log_to)
259+
if haskey(log_to, (mo,fu))
260+
io = log_to[(mo,fu)]
261+
elseif haskey(log_to, (mo,nothing))
262+
io = log_to[(mo,nothing)]
263+
elseif haskey(log_to, (nothing,nothing))
264+
io = log_to[(nothing,nothing)]
265+
end
266+
end
267+
io, mo, fu
252268
end
253-
info(msg...; prefix="INFO: ") = info(STDERR, msg..., prefix=prefix)
254269

255-
# print a warning only once
270+
"""
271+
logging(io [, m [, f]][; kind=:all])
272+
logging([; kind=:all])
273+
274+
Stream output of informational, warning, and/or error messages to `io`,
275+
overriding what was otherwise specified. Optionally, divert stream only for
276+
module `m`, or specifically function `f` within `m`. `kind` can also be
277+
`:info`, `:warn`, and `:error`. See `Base.log_{info,warn,error}_to` for the
278+
current set of redirections. Call `logging` with no arguments (or just the `kind`)
279+
to reset everything.
280+
"""
281+
function logging(io::IO, m::Union{Module,Void}=nothing, f::Union{Symbol,Void}=nothing;
282+
kind::Symbol=:all)
283+
(kind==:all || kind==:info) && (log_info_to[(m,f)] = io)
284+
(kind==:all || kind==:warn) && (log_warn_to[(m,f)] = io)
285+
(kind==:all || kind==:error) && (log_error_to[(m,f)] = io)
286+
nothing
287+
end
256288

257-
const have_warned = Set()
289+
function logging(; kind::Symbol=:all)
290+
(kind==:all || kind==:info) && empty!(log_info_to)
291+
(kind==:all || kind==:warn) && empty!(log_warn_to)
292+
(kind==:all || kind==:error) && empty!(log_error_to)
293+
nothing
294+
end
295+
296+
"""
297+
info([io, ] msg..., [prefix="INFO: "])
298+
299+
Display an informational message. Argument `msg` is a string describing the information to be displayed.
300+
301+
See also `logging`.
302+
"""
303+
function info(io::IO, msg...; prefix="INFO: ")
304+
sf = StackTraces.remove_frames!(stacktrace(), :info)[1]
305+
io, mo, fu = redirect(io, log_info_to, sf)
306+
println_with_color(info_color(), io, prefix, chomp(string(msg...)), " ($mo.$fu)")
307+
end
308+
info(msg...; prefix="INFO: ") = info(STDERR, msg..., prefix=prefix)
258309

259310
warn_once(io::IO, msg...) = warn(io, msg..., once=true)
260311
warn_once(msg...) = warn(STDERR, msg..., once=true)
261312

313+
"""
314+
warn([io, ] msg..., [prefix="WARNING: ", once=false, key=nothing, bt=nothing, filename=nothing, lineno::Int=0])
315+
316+
Display a warning. Argument `msg` is a string describing the warning to be
317+
displayed. Set `once` to true and specify a `key` to only display `msg` the
318+
first time `warn` is called. If `bt` is not nothing a backtrace is displayed.
319+
If `filename` is not nothing both it and `lineno` are displayed.
320+
321+
See also `logging`.
322+
"""
262323
function warn(io::IO, msg...;
263324
prefix="WARNING: ", once=false, key=nothing, bt=nothing,
264325
filename=nothing, lineno::Int=0)
@@ -270,7 +331,9 @@ function warn(io::IO, msg...;
270331
(key in have_warned) && return
271332
push!(have_warned, key)
272333
end
273-
print_with_color(warn_color(), io, prefix, str)
334+
sf = StackTraces.remove_frames!(stacktrace(), :warn)[1]
335+
io, mo, fu = redirect(io, log_warn_to, sf)
336+
print_with_color(warn_color(), io, prefix, str, " ($mo.$fu)")
274337
if bt !== nothing
275338
show_backtrace(io, bt)
276339
end

doc/stdlib/base.rst

+3-1
Original file line numberDiff line numberDiff line change
@@ -1052,12 +1052,14 @@ System
10521052
Errors
10531053
------
10541054

1055-
.. function:: error(message::AbstractString)
1055+
.. function:: error(msg...)
10561056

10571057
.. Docstring generated from Julia source
10581058
10591059
Raise an ``ErrorException`` with the given message.
10601060

1061+
See also ``logging``\ .
1062+
10611063
.. function:: throw(e)
10621064

10631065
.. Docstring generated from Julia source

doc/stdlib/io-network.rst

+14-3
Original file line numberDiff line numberDiff line change
@@ -486,17 +486,28 @@ Text I/O
486486

487487
``color`` may take any of the values ``:normal``\ , ``:bold``\ , ``:black``\ , ``:blue``\ , ``:cyan``\ , ``:green``\ , ``:magenta``\ , ``:red``\ , ``:white``\ , or ``:yellow``\ .
488488

489-
.. function:: info(msg)
489+
.. function:: info([io, ] msg..., [prefix="INFO: "])
490490

491491
.. Docstring generated from Julia source
492492
493493
Display an informational message. Argument ``msg`` is a string describing the information to be displayed.
494494

495-
.. function:: warn(msg)
495+
See also ``logging``\ .
496+
497+
.. function:: warn([io, ] msg..., [prefix="WARNING: ", once=false, key=nothing, bt=nothing, filename=nothing, lineno::Int=0])
498+
499+
.. Docstring generated from Julia source
500+
501+
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.
502+
503+
See also ``logging``\ .
504+
505+
.. function:: logging(io [, m [, f]][; kind=:all])
506+
logging([; kind=:all])
496507

497508
.. Docstring generated from Julia source
498509
499-
Display a warning. Argument ``msg`` is a string describing the warning to be displayed.
510+
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.
500511

501512
.. function:: @printf([io::IOStream], "%Fmt", args...)
502513

test/misc.jl

+121
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,127 @@ let bt = backtrace()
2828
end
2929
end
3030

31+
module Foo
32+
function bar(io)
33+
info(io,"barinfo")
34+
warn(io,"barwarn")
35+
end
36+
function pooh(io)
37+
info(io,"poohinfo")
38+
warn(io,"poohwarn")
39+
end
40+
end
41+
function foo(io)
42+
info(io,"fooinfo")
43+
warn(io,"foowarn")
44+
end
45+
46+
@test ( tmp = sprint(Foo.bar);
47+
all(map(x->contains(tmp,x),
48+
["INFO: barinfo (Foo.bar)", "WARNING: barwarn (Foo.bar)"])) )
49+
@test ( tmp = sprint(Foo.pooh);
50+
all(map(x->contains(tmp,x),
51+
["INFO: poohinfo (Foo.pooh)", "WARNING: poohwarn (Foo.pooh)"])) )
52+
@test ( tmp = sprint(foo);
53+
all(map(x->contains(tmp,x),
54+
["INFO: fooinfo (Main.foo)", "WARNING: foowarn (Main.foo)"])) )
55+
56+
logging(DevNull, Foo, :bar; kind=:info)
57+
@test contains(sprint(Foo.bar), "WARNING: barwarn (Foo.bar)")
58+
@test ( tmp = sprint(Foo.pooh);
59+
all(map(x->contains(tmp,x),
60+
["INFO: poohinfo (Foo.pooh)", "WARNING: poohwarn (Foo.pooh)"])) )
61+
@test ( tmp = sprint(foo);
62+
all(map(x->contains(tmp,x),
63+
["INFO: fooinfo (Main.foo)", "WARNING: foowarn (Main.foo)"])) )
64+
65+
logging(DevNull, Foo; kind=:info)
66+
@test contains(sprint(Foo.bar), "WARNING: barwarn (Foo.bar)")
67+
@test contains(sprint(Foo.pooh), "WARNING: poohwarn (Foo.pooh)")
68+
@test ( tmp = sprint(foo);
69+
all(map(x->contains(tmp,x),
70+
["INFO: fooinfo (Main.foo)", "WARNING: foowarn (Main.foo)"])) )
71+
72+
logging(DevNull; kind=:info)
73+
@test contains(sprint(Foo.bar), "WARNING: barwarn (Foo.bar)")
74+
@test contains(sprint(Foo.pooh), "WARNING: poohwarn (Foo.pooh)")
75+
@test contains(sprint(foo), "WARNING: foowarn (Main.foo)")
76+
77+
logging(kind=:info)
78+
@test ( tmp = sprint(Foo.bar);
79+
all(map(x->contains(tmp,x),
80+
["INFO: barinfo (Foo.bar)", "WARNING: barwarn (Foo.bar)"])) )
81+
@test ( tmp = sprint(Foo.pooh);
82+
all(map(x->contains(tmp,x),
83+
["INFO: poohinfo (Foo.pooh)", "WARNING: poohwarn (Foo.pooh)"])) )
84+
@test ( tmp = sprint(foo);
85+
all(map(x->contains(tmp,x),
86+
["INFO: fooinfo (Main.foo)", "WARNING: foowarn (Main.foo)"])) )
87+
88+
logging(DevNull, Foo, :bar; kind=:warn)
89+
@test contains(sprint(Foo.bar), "INFO: barinfo (Foo.bar)")
90+
@test ( tmp = sprint(Foo.pooh);
91+
all(map(x->contains(tmp,x),
92+
["INFO: poohinfo (Foo.pooh)", "WARNING: poohwarn (Foo.pooh)"])) )
93+
@test ( tmp = sprint(foo);
94+
all(map(x->contains(tmp,x),
95+
["INFO: fooinfo (Main.foo)", "WARNING: foowarn (Main.foo)"])) )
96+
97+
logging(DevNull, Foo; kind=:warn)
98+
@test contains(sprint(Foo.bar), "INFO: barinfo (Foo.bar)")
99+
@test contains(sprint(Foo.pooh), "INFO: poohinfo (Foo.pooh)")
100+
@test ( tmp = sprint(foo);
101+
all(map(x->contains(tmp,x),
102+
["INFO: fooinfo (Main.foo)", "WARNING: foowarn (Main.foo)"])) )
103+
104+
logging(DevNull; kind=:warn)
105+
@test contains(sprint(Foo.bar), "INFO: barinfo (Foo.bar)")
106+
@test contains(sprint(Foo.pooh), "INFO: poohinfo (Foo.pooh)")
107+
@test contains(sprint(foo), "INFO: fooinfo (Main.foo)")
108+
109+
logging(kind=:warn)
110+
@test ( tmp = sprint(Foo.bar);
111+
all(map(x->contains(tmp,x),
112+
["INFO: barinfo (Foo.bar)", "WARNING: barwarn (Foo.bar)"])) )
113+
@test ( tmp = sprint(Foo.pooh);
114+
all(map(x->contains(tmp,x),
115+
["INFO: poohinfo (Foo.pooh)", "WARNING: poohwarn (Foo.pooh)"])) )
116+
@test ( tmp = sprint(foo);
117+
all(map(x->contains(tmp,x),
118+
["INFO: fooinfo (Main.foo)", "WARNING: foowarn (Main.foo)"])) )
119+
120+
logging(DevNull, Foo, :bar)
121+
@test sprint(Foo.bar) == ""
122+
@test ( tmp = sprint(Foo.pooh);
123+
all(map(x->contains(tmp,x),
124+
["INFO: poohinfo (Foo.pooh)", "WARNING: poohwarn (Foo.pooh)"])) )
125+
@test ( tmp = sprint(foo);
126+
all(map(x->contains(tmp,x),
127+
["INFO: fooinfo (Main.foo)", "WARNING: foowarn (Main.foo)"])) )
128+
129+
logging(DevNull, Foo)
130+
@test sprint(Foo.bar) == ""
131+
@test sprint(Foo.pooh) == ""
132+
@test ( tmp = sprint(foo);
133+
all(map(x->contains(tmp,x),
134+
["INFO: fooinfo (Main.foo)", "WARNING: foowarn (Main.foo)"])) )
135+
136+
logging(DevNull)
137+
@test sprint(Foo.bar) == ""
138+
@test sprint(Foo.pooh) == ""
139+
@test sprint(foo) == ""
140+
141+
logging()
142+
@test ( tmp = sprint(Foo.bar);
143+
all(map(x->contains(tmp,x),
144+
["INFO: barinfo (Foo.bar)", "WARNING: barwarn (Foo.bar)"])) )
145+
@test ( tmp = sprint(Foo.pooh);
146+
all(map(x->contains(tmp,x),
147+
["INFO: poohinfo (Foo.pooh)", "WARNING: poohwarn (Foo.pooh)"])) )
148+
@test ( tmp = sprint(foo);
149+
all(map(x->contains(tmp,x),
150+
["INFO: fooinfo (Main.foo)", "WARNING: foowarn (Main.foo)"])) )
151+
31152
# test assert() method
32153
@test_throws AssertionError assert(false)
33154
let res = assert(true)

0 commit comments

Comments
 (0)