Hello,
I am using the default Logger of Julia, and in the documentation it is mentioned that you can create custom log messages.
I could not find out how, though.
This works:
using Logging
logger = ConsoleLogger(stdout, Logging.Info) # Warn or Info or Debug
global_logger(logger)
@info ("Running script test_gen_query2.jl...")
Now I would like to do:
using Logging
logger = ConsoleLogger(stdout, Logging.Trace) # Warn or Info or Debug
global_logger(logger)
@trace ("Special trace message ...")
And how can I set the level of my custom message?
It should be between warn and info messages. If logging at “Trace” level @info messages should not be logged.
You’re looking for @logmsg. If you want to create a new loglevel between info and warn, you need to create one with the appropriate level via LogLevel(level). I don’t know the levels of info and warn off the top of my head, but I remember there being space between them for precisely this usecase.
It’s all only semi well documented since at the moment the Logging stdlib just reexports base stuff.
I am trying your suggestion, but it doesn’t work yet:
using Logging
const Trace = LogLevel(500)
macro trace(exs...) logmsg_code((@_sourceinfo)..., :Trace, exs...) end
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 == Trace print(io, "Trace")
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
logger = ConsoleLogger(stdout, Logging.Info) # Warn or Info or Debug
global_logger(logger)
@trace "A trace message ..."
I get the error message:
ERROR: LoadError: LoadError: UndefVarError: @_sourceinfo not defined
Stacktrace:
[1] top-level scope
[2] include at ./boot.jl:328 [inlined]
[3] include_relative(::Module, ::String) at ./loading.jl:1094
[4] include(::Module, ::String) at ./Base.jl:31
[5] include(::String) at ./client.jl:431
[6] top-level scope at REPL[1]:1
in expression starting at /home/ufechner/n4Server/local_tests/test_gen_query2.jl:9
in expression starting at /home/ufechner/n4Server/local_tests/test_gen_query2.jl:9
julia>
So my current impression is that it is not possible to create and use custom log messages in a clean way with the standard log module of Julia. So I might create an issue for that.
Do note that this is type piracy and only works once since you’re overwriting Base.show, so it’s not really suited for a package or something like that. Also, since CoreLogging is technically not exported and internal, this might change at any time and shouldn’t be relied upon.
One more thing: this obviously won’t work for other custom loggers, since this is basically hard coding the macro contents to those of Base.CoreLogging.
So, we’re considering improving the support for custom log levels over on the github issue. Conceptually I am thinking of log levels as a category which can also be mapped to a default (integer) importance level, and which have an semantic which can be agreed on independently of which module they are used in.
But perhaps this is not the best model! So I thought I’d also cross post back here in an attempt to better understand what people really want from custom log levels. If you have some use cases in mind please do post them here or on the github issue.
How do you think custom levels would help to better understand your application?
Why not just define a new log level type? Since logging code mostly do duck typing, it works with a few methods and there is no need for type-piracy. The downside may be that some third-party loggers may explicitly expect LogLevel.
using Logging
struct MyLogLevel
level::Int32
end
const Trace = MyLogLevel(-10_000)
Base.isless(a::MyLogLevel, b::LogLevel) = isless(a.level, b.level)
Base.isless(a::LogLevel, b::MyLogLevel) = isless(a.level, b.level)
Base.convert(::Type{LogLevel}, level::MyLogLevel) = LogLevel(level.level)
Base.show(io::IO, level::MyLogLevel) =
if level == Trace
print(io, "Trace")
else
show(io, LogLevel(level))
end
Logging.disable_logging(Logging.BelowMinLevel)
with_logger(ConsoleLogger(stderr, convert(LogLevel, Trace))) do
@logmsg Trace "message"
end
Having said that, I think this could be a lot nicer and certainly more well documented. The fact that you need to define Base.convert(::Type{LogLevel}, l::MyLevel) is kinda weird.
Great! Good to know that this was the intended usecase and my code was not accidentally working.
Also, my definition of show is kind of a lie. It’s certainly not repr-compatible. Maybe it was better to overload print. Specifying something like this in the documentation would be nice. (Or even do everything automatically with something like @enum MyLevel <: LogLevel Trace = -10_000.)
I’ve been using this pattern but it seems to have been broken by type restrictions introduced in Julia 1.6.0. After updating I’m getting errors like the following: