Skip to content

Commit 7baa577

Browse files
Add @create_log_macro for making custom styled logging macros (#52196)
1 parent 1b183b9 commit 7baa577

File tree

6 files changed

+81
-14
lines changed

6 files changed

+81
-14
lines changed

NEWS.md

+5
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,11 @@ Standard library changes
118118
* Structured matrices now retain either the axes of the parent (for `Symmetric`/`Hermitian`/`AbstractTriangular`/`UpperHessenberg`), or that of the principal diagonal (for banded matrices) ([#52480]).
119119
* `bunchkaufman` and `bunchkaufman!` now work for any `AbstractFloat`, `Rational` and their complex variants. `bunchkaufman` now supports `Integer` types, by making an internal conversion to `Rational{BigInt}`. Added new function `inertia` that computes the inertia of the diagonal factor given by the `BunchKaufman` factorization object of a real symmetric or Hermitian matrix. For complex symmetric matrices, `inertia` only computes the number of zero eigenvalues of the diagonal factor ([#51487]).
120120

121+
#### Logging
122+
* New `@create_log_macro` macro for creating new log macros like `@info`, `@warn` etc. For instance
123+
`@create_log_macro MyLog 1500 :magenta` will create `@mylog` to be used like `@mylog "hello"` which
124+
will show as `┌ MyLog: hello` etc. ([#52196])
125+
121126
#### Printf
122127

123128
#### Profile

base/logging.jl

+11-7
Original file line numberDiff line numberDiff line change
@@ -162,14 +162,18 @@ const AboveMaxLevel = LogLevel( 1000001)
162162
# Global log limiting mechanism for super fast but inflexible global log limiting.
163163
const _min_enabled_level = Ref{LogLevel}(Debug)
164164

165+
# stored as LogLevel => (name, color)
166+
const custom_log_levels = Dict{LogLevel,Tuple{Symbol,Union{Symbol,Int}}}()
167+
165168
function show(io::IO, level::LogLevel)
166-
if level == BelowMinLevel print(io, "BelowMinLevel")
167-
elseif level == Debug print(io, "Debug")
168-
elseif level == Info print(io, "Info")
169-
elseif level == Warn print(io, "Warn")
170-
elseif level == Error print(io, "Error")
171-
elseif level == AboveMaxLevel print(io, "AboveMaxLevel")
172-
else print(io, "LogLevel($(level.level))")
169+
if haskey(custom_log_levels, level) print(io, custom_log_levels[level][1])
170+
elseif level == BelowMinLevel print(io, "BelowMinLevel")
171+
elseif level == Debug print(io, "Debug")
172+
elseif level == Info print(io, "Info")
173+
elseif level == Warn print(io, "Warn")
174+
elseif level == Error print(io, "Error")
175+
elseif level == AboveMaxLevel print(io, "AboveMaxLevel")
176+
else print(io, "LogLevel($(level.level))")
173177
end
174178
end
175179

stdlib/Logging/docs/src/index.md

+13-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ automatically extracted. Let's examine the user-defined data first:
5858
* The *log level* is a broad category for the message that is used for early
5959
filtering. There are several standard levels of type [`LogLevel`](@ref);
6060
user-defined levels are also possible.
61-
Each is distinct in purpose:
61+
Each built-in log level is distinct in purpose:
6262
- [`Logging.Debug`](@ref) (log level -1000) is information intended for the developer of
6363
the program. These events are disabled by default.
6464
- [`Logging.Info`](@ref) (log level 0) is for general information to the user.
@@ -70,6 +70,17 @@ automatically extracted. Let's examine the user-defined data first:
7070
Often this log-level is unneeded as throwing an exception can convey
7171
all the required information.
7272

73+
You can create logging macros for custom log levels. For instance:
74+
```julia-repl
75+
julia> using Logging
76+
77+
julia> @create_log_macro MyLog 200 :magenta
78+
@mylog (macro with 1 method)
79+
80+
julia> @mylog "hello"
81+
[ MyLog: hello
82+
```
83+
7384
* The *message* is an object describing the event. By convention
7485
`AbstractString`s passed as messages are assumed to be in markdown format.
7586
Other types will be displayed using `print(io, obj)` or `string(obj)` for
@@ -298,6 +309,7 @@ Logging.Debug
298309
Logging.Info
299310
Logging.Warn
300311
Logging.Error
312+
Logging.@create_log_macro
301313
```
302314

303315
### [Processing events with AbstractLogger](@id AbstractLogger-interface)

stdlib/Logging/src/ConsoleLogger.jl

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ end
5858
showvalue(io, ex::Exception) = showerror(io, ex)
5959

6060
function default_logcolor(level::LogLevel)
61+
level in keys(custom_log_levels) ? custom_log_levels[level][2] :
6162
level < Info ? Base.debug_color() :
6263
level < Warn ? Base.info_color() :
6364
level < Error ? Base.warn_color() :

stdlib/Logging/src/Logging.jl

+37
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ for sym in [
2121
Symbol("@warn"),
2222
Symbol("@error"),
2323
Symbol("@logmsg"),
24+
:custom_log_levels,
2425
:with_logger,
2526
:current_logger,
2627
:global_logger,
@@ -29,6 +30,41 @@ for sym in [
2930
@eval const $sym = Base.CoreLogging.$sym
3031
end
3132

33+
"""
34+
@create_log_macro(name::Symbol, level::Int, color::Union{Int,Symbol})
35+
36+
Creates a custom log macro like `@info`, `@warn` etc. with a given `name`, `level` and
37+
`color`. The macro created is named with the lowercase form of `name` but the given form
38+
is used for the printing.
39+
40+
The available color keys can be seen by typing `Base.text_colors` in the help mode of the REPL
41+
42+
```julia-repl
43+
julia> @create_log_macro(:MyLog, 200, :magenta)
44+
@mylog (macro with 1 method)
45+
46+
julia> @mylog "hello"
47+
[ MyLog: hello
48+
```
49+
"""
50+
macro create_log_macro(name, level, color)
51+
macro_name = Symbol(lowercase(string(name)))
52+
macro_string = QuoteNode(name)
53+
loglevel = LogLevel(level)
54+
if loglevel in (BelowMinLevel, Debug, Info, Warn, Error, AboveMaxLevel)
55+
throw(ArgumentError("Cannot use the same log level as a built in log macro"))
56+
end
57+
if haskey(custom_log_levels, loglevel)
58+
throw(ArgumentError("Custom log macro already exists for given log level"))
59+
end
60+
quote
61+
$(custom_log_levels)[$(esc(loglevel))] = ($(macro_string), $(esc(color)))
62+
macro $(esc(macro_name))(exs...)
63+
$(Base.CoreLogging.logmsg_code)(($(Base.CoreLogging.@_sourceinfo))..., $(esc(loglevel)), exs...)
64+
end
65+
end
66+
end
67+
3268
# LogLevel aliases (re-)documented here (JuliaLang/julia#40978)
3369
"""
3470
Debug
@@ -67,6 +103,7 @@ export
67103
@warn,
68104
@error,
69105
@logmsg,
106+
@create_log_macro,
70107
with_logger,
71108
current_logger,
72109
global_logger,

stdlib/Logging/test/runtests.jl

+14-6
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import Logging: min_enabled_level, shouldlog, handle_message
77
@noinline func1() = backtrace()
88

99
# see "custom log macro" testset
10-
CustomLog = LogLevel(-500)
11-
macro customlog(exs...) Base.CoreLogging.logmsg_code((Base.CoreLogging.@_sourceinfo)..., esc(CustomLog), exs...) end
10+
@create_log_macro CustomLog1 -500 :magenta
11+
@create_log_macro CustomLog2 1500 1
1212

1313
@testset "Logging" begin
1414

@@ -280,16 +280,24 @@ end
280280
end
281281

282282
@testset "custom log macro" begin
283-
@test_logs (CustomLog, "a") min_level=CustomLog @customlog "a"
283+
llevel = LogLevel(-500)
284+
285+
@test_logs (llevel, "foo") min_level=llevel @customlog1 "foo"
284286

285287
buf = IOBuffer()
286288
io = IOContext(buf, :displaysize=>(30,80), :color=>false)
287-
logger = ConsoleLogger(io, CustomLog)
289+
logger = ConsoleLogger(io, llevel)
290+
291+
with_logger(logger) do
292+
@customlog1 "foo"
293+
end
294+
@test occursin("CustomLog1: foo", String(take!(buf)))
295+
288296

289297
with_logger(logger) do
290-
@customlog "a"
298+
@customlog2 "hello"
291299
end
292-
@test occursin("LogLevel(-500): a", String(take!(buf)))
300+
@test occursin("CustomLog2: hello", String(take!(buf)))
293301
end
294302

295303
end

0 commit comments

Comments
 (0)