diff --git a/Project.toml b/Project.toml index f45c266..098050f 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "LoggingExtras" uuid = "e6f89c97-d47a-5376-807f-9c37f3926c36" authors = ["Frames White ", "Collaborators "] -version = "1.1.0" +version = "1.2.0" [deps] Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" diff --git a/src/Sinks/datetime_rotation.jl b/src/Sinks/datetime_rotation.jl index 9a9100e..21323fb 100644 --- a/src/Sinks/datetime_rotation.jl +++ b/src/Sinks/datetime_rotation.jl @@ -71,7 +71,7 @@ function DatetimeRotatingFileLogger(f::Union{Function,Nothing}, dir, filename_pa end similar_logger(::SimpleLogger, io) = SimpleLogger(io, BelowMinLevel) -similar_logger(l::FormatLogger, io) = FormatLogger(l.f, io, l.always_flush) +similar_logger(l::FormatLogger, io) = FormatLogger(l.formatter, io, l.always_flush) function reopen!(drfl::DatetimeRotatingFileLogger) if drfl.current_file !== nothing # close the old IOStream and pass the file to the callback diff --git a/src/Sinks/formatlogger.jl b/src/Sinks/formatlogger.jl index a69c654..f573d6e 100644 --- a/src/Sinks/formatlogger.jl +++ b/src/Sinks/formatlogger.jl @@ -1,25 +1,25 @@ - -struct FormatLogger <: AbstractLogger - f::Function +struct FormatLogger{T} <: AbstractLogger + formatter::T stream::IO always_flush::Bool end """ - FormatLogger(f::Function, io::IO=stderr; always_flush=true) + FormatLogger(formatter, io::IO=stderr; always_flush=true) Logger sink that formats the message and finally writes to `io`. -The formatting function should be of the form `f(io::IOContext, log_args::NamedTuple)` -where `log_args` has the following fields: +The formatting function or callable object should be of the form +`formatter(io::IOContext, log::NamedTuple)` where `log` has the following fields: `(level, message, _module, group, id, file, line, kwargs)`. -See [`LoggingExtras.handle_message_args`](@ref) for more information on what field is. + +See [`LoggingExtras.handle_message_args`](@ref) for more information on what each field is. # Examples ```julia-repl julia> using Logging, LoggingExtras -julia> logger = FormatLogger() do io, args - println(io, args._module, " | ", "[", args.level, "] ", args.message) +julia> logger = FormatLogger() do io, log + println(io, log._module, " | ", "[", log.level, "] ", log.message) end; julia> with_logger(logger) do @@ -30,31 +30,31 @@ Main | [Info] This is an informational message. Main | [Warn] This is a warning, should take a look. ``` """ -function FormatLogger(f::Function, io::IO=stderr; always_flush=true) - return FormatLogger(f, io, always_flush) +function FormatLogger(formatter, io::IO=stderr; always_flush=true) + return FormatLogger(formatter, io, always_flush) end """ - FormatLogger(f::Function, path::AbstractString; append=false, always_flush=true) + FormatLogger(formatter, path::AbstractString; append=false, always_flush=true) -Logger sink that formats the message and writes it to the file at `path`. This is similar -to `FileLogger` except that it allows specifying the printing format. +Logger sink that formats the message and writes it to the file at `path`. This is similar +to [`FileLogger`](@ref) except that it allows specifying the printing format. To append to the file (rather than truncating the file first), use `append=true`. If `always_flush=true` the stream is flushed after every handled log message. """ -function FormatLogger(f::Function, path::AbstractString; append::Bool=false, kw...) +function FormatLogger(formatter, path::AbstractString; append::Bool=false, kw...) io = open(path, append ? "a" : "w") - return FormatLogger(f, io; kw...) + return FormatLogger(formatter, io; kw...) end function handle_message(logger::FormatLogger, args...; kwargs...) - log_args = handle_message_args(args...; kwargs...) + log = handle_message_args(args...; kwargs...) # We help the user by passing an IOBuffer to the formatting function # to make sure that everything writes to the logger io in one go. iob = IOBuffer() ioc = IOContext(iob, logger.stream) - logger.f(ioc, log_args) + logger.formatter(ioc, log) write(logger.stream, take!(iob)) logger.always_flush && flush(logger.stream) return nothing diff --git a/test/runtests.jl b/test/runtests.jl index b2dd2cc..48bf0be 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -197,16 +197,28 @@ end end end +# Intentionally not subtype `Function` here to test function-like object support +Base.@kwdef struct BasicLogFormatter + include_module::Bool=true +end + +function (formatter::BasicLogFormatter)(io::IO, log::NamedTuple) + if formatter.include_module + print(io, log._module, " | ") + end + println(io, "[", log.level, "] ", log.message) +end + @testset "FormatLogger" begin io = IOBuffer() - logger = FormatLogger(io) do io, args + logger = FormatLogger(io) do io, log # Put in some bogus sleep calls just to test that # log records writes in one go - print(io, args.level) + print(io, log.level) sleep(rand()) print(io, ": ") sleep(rand()) - println(io, args.message) + println(io, log.message) end with_logger(logger) do @sync begin @@ -231,7 +243,7 @@ end mktempdir() do dir f = joinpath(dir, "test.log") - logger = FormatLogger(f) do io, args + logger = FormatLogger(f) do io, log println(io, "log message") end @@ -242,6 +254,21 @@ end l = read(f, String) @test startswith(l, "log message") end + + # test function-like objects/functor are supported + io = IOBuffer() + with_logger(FormatLogger(BasicLogFormatter(; include_module=true), io)) do + @info "test message" + end + str = String(take!(io)) + @test str == "$(@__MODULE__()) | [Info] test message\n" + + io = IOBuffer() + with_logger(FormatLogger(BasicLogFormatter(; include_module=false), io)) do + @warn "test message" + end + str = String(take!(io)) + @test str == "[Warn] test message\n" end @testset "LevelOverrideLogger" begin