Skip to content

Commit

Permalink
Add @create_log_macro for making custom styled logging macros (#52196)
Browse files Browse the repository at this point in the history
  • Loading branch information
IanButterworth authored Jan 2, 2024
1 parent 1b183b9 commit 7baa577
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 14 deletions.
5 changes: 5 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ Standard library changes
* 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]).
* `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]).

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

#### Printf

#### Profile
Expand Down
18 changes: 11 additions & 7 deletions base/logging.jl
Original file line number Diff line number Diff line change
Expand Up @@ -162,14 +162,18 @@ const AboveMaxLevel = LogLevel( 1000001)
# Global log limiting mechanism for super fast but inflexible global log limiting.
const _min_enabled_level = Ref{LogLevel}(Debug)

# stored as LogLevel => (name, color)
const custom_log_levels = Dict{LogLevel,Tuple{Symbol,Union{Symbol,Int}}}()

function show(io::IO, level::LogLevel)
if level == BelowMinLevel print(io, "BelowMinLevel")
elseif level == Debug print(io, "Debug")
elseif level == Info print(io, "Info")
elseif level == Warn print(io, "Warn")
elseif level == Error print(io, "Error")
elseif level == AboveMaxLevel print(io, "AboveMaxLevel")
else print(io, "LogLevel($(level.level))")
if haskey(custom_log_levels, level) print(io, custom_log_levels[level][1])
elseif level == BelowMinLevel print(io, "BelowMinLevel")
elseif level == Debug print(io, "Debug")
elseif level == Info print(io, "Info")
elseif level == Warn print(io, "Warn")
elseif level == Error print(io, "Error")
elseif level == AboveMaxLevel print(io, "AboveMaxLevel")
else print(io, "LogLevel($(level.level))")
end
end

Expand Down
14 changes: 13 additions & 1 deletion stdlib/Logging/docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ automatically extracted. Let's examine the user-defined data first:
* The *log level* is a broad category for the message that is used for early
filtering. There are several standard levels of type [`LogLevel`](@ref);
user-defined levels are also possible.
Each is distinct in purpose:
Each built-in log level is distinct in purpose:
- [`Logging.Debug`](@ref) (log level -1000) is information intended for the developer of
the program. These events are disabled by default.
- [`Logging.Info`](@ref) (log level 0) is for general information to the user.
Expand All @@ -70,6 +70,17 @@ automatically extracted. Let's examine the user-defined data first:
Often this log-level is unneeded as throwing an exception can convey
all the required information.

You can create logging macros for custom log levels. For instance:
```julia-repl
julia> using Logging
julia> @create_log_macro MyLog 200 :magenta
@mylog (macro with 1 method)
julia> @mylog "hello"
[ MyLog: hello
```
* The *message* is an object describing the event. By convention
`AbstractString`s passed as messages are assumed to be in markdown format.
Other types will be displayed using `print(io, obj)` or `string(obj)` for
Expand Down Expand Up @@ -298,6 +309,7 @@ Logging.Debug
Logging.Info
Logging.Warn
Logging.Error
Logging.@create_log_macro
```

### [Processing events with AbstractLogger](@id AbstractLogger-interface)
Expand Down
1 change: 1 addition & 0 deletions stdlib/Logging/src/ConsoleLogger.jl
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ end
showvalue(io, ex::Exception) = showerror(io, ex)

function default_logcolor(level::LogLevel)
level in keys(custom_log_levels) ? custom_log_levels[level][2] :
level < Info ? Base.debug_color() :
level < Warn ? Base.info_color() :
level < Error ? Base.warn_color() :
Expand Down
37 changes: 37 additions & 0 deletions stdlib/Logging/src/Logging.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ for sym in [
Symbol("@warn"),
Symbol("@error"),
Symbol("@logmsg"),
:custom_log_levels,
:with_logger,
:current_logger,
:global_logger,
Expand All @@ -29,6 +30,41 @@ for sym in [
@eval const $sym = Base.CoreLogging.$sym
end

"""
@create_log_macro(name::Symbol, level::Int, color::Union{Int,Symbol})
Creates a custom log macro like `@info`, `@warn` etc. with a given `name`, `level` and
`color`. The macro created is named with the lowercase form of `name` but the given form
is used for the printing.
The available color keys can be seen by typing `Base.text_colors` in the help mode of the REPL
```julia-repl
julia> @create_log_macro(:MyLog, 200, :magenta)
@mylog (macro with 1 method)
julia> @mylog "hello"
[ MyLog: hello
```
"""
macro create_log_macro(name, level, color)
macro_name = Symbol(lowercase(string(name)))
macro_string = QuoteNode(name)
loglevel = LogLevel(level)
if loglevel in (BelowMinLevel, Debug, Info, Warn, Error, AboveMaxLevel)
throw(ArgumentError("Cannot use the same log level as a built in log macro"))
end
if haskey(custom_log_levels, loglevel)
throw(ArgumentError("Custom log macro already exists for given log level"))
end
quote
$(custom_log_levels)[$(esc(loglevel))] = ($(macro_string), $(esc(color)))
macro $(esc(macro_name))(exs...)
$(Base.CoreLogging.logmsg_code)(($(Base.CoreLogging.@_sourceinfo))..., $(esc(loglevel)), exs...)
end
end
end

# LogLevel aliases (re-)documented here (JuliaLang/julia#40978)
"""
Debug
Expand Down Expand Up @@ -67,6 +103,7 @@ export
@warn,
@error,
@logmsg,
@create_log_macro,
with_logger,
current_logger,
global_logger,
Expand Down
20 changes: 14 additions & 6 deletions stdlib/Logging/test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import Logging: min_enabled_level, shouldlog, handle_message
@noinline func1() = backtrace()

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

@testset "Logging" begin

Expand Down Expand Up @@ -280,16 +280,24 @@ end
end

@testset "custom log macro" begin
@test_logs (CustomLog, "a") min_level=CustomLog @customlog "a"
llevel = LogLevel(-500)

@test_logs (llevel, "foo") min_level=llevel @customlog1 "foo"

buf = IOBuffer()
io = IOContext(buf, :displaysize=>(30,80), :color=>false)
logger = ConsoleLogger(io, CustomLog)
logger = ConsoleLogger(io, llevel)

with_logger(logger) do
@customlog1 "foo"
end
@test occursin("CustomLog1: foo", String(take!(buf)))


with_logger(logger) do
@customlog "a"
@customlog2 "hello"
end
@test occursin("LogLevel(-500): a", String(take!(buf)))
@test occursin("CustomLog2: hello", String(take!(buf)))
end

end

0 comments on commit 7baa577

Please sign in to comment.