diff --git a/NEWS.md b/NEWS.md index d5ef405b7a913..ab5410ff1014f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -116,6 +116,11 @@ Standard library changes argument is the output of `bunchkaufman` or `lu` ([#50471]). * 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]). +#### 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 diff --git a/base/logging.jl b/base/logging.jl index f6a34aee2f516..1853caa16148c 100644 --- a/base/logging.jl +++ b/base/logging.jl @@ -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 diff --git a/stdlib/Logging/docs/src/index.md b/stdlib/Logging/docs/src/index.md index 55d24c7ae0a26..882c66af2baef 100644 --- a/stdlib/Logging/docs/src/index.md +++ b/stdlib/Logging/docs/src/index.md @@ -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. @@ -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 @@ -298,6 +309,7 @@ Logging.Debug Logging.Info Logging.Warn Logging.Error +Logging.@create_log_macro ``` ### [Processing events with AbstractLogger](@id AbstractLogger-interface) diff --git a/stdlib/Logging/src/ConsoleLogger.jl b/stdlib/Logging/src/ConsoleLogger.jl index 747f8a2b22966..1d45296c907d1 100644 --- a/stdlib/Logging/src/ConsoleLogger.jl +++ b/stdlib/Logging/src/ConsoleLogger.jl @@ -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() : diff --git a/stdlib/Logging/src/Logging.jl b/stdlib/Logging/src/Logging.jl index 0743c650326cc..83e93ee396361 100644 --- a/stdlib/Logging/src/Logging.jl +++ b/stdlib/Logging/src/Logging.jl @@ -21,6 +21,7 @@ for sym in [ Symbol("@warn"), Symbol("@error"), Symbol("@logmsg"), + :custom_log_levels, :with_logger, :current_logger, :global_logger, @@ -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 @@ -67,6 +103,7 @@ export @warn, @error, @logmsg, + @create_log_macro, with_logger, current_logger, global_logger, diff --git a/stdlib/Logging/test/runtests.jl b/stdlib/Logging/test/runtests.jl index 3a793c4e0bc33..e7b3fbca2098d 100644 --- a/stdlib/Logging/test/runtests.jl +++ b/stdlib/Logging/test/runtests.jl @@ -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 @@ -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