From c86ca315c74e1cf9bad3f9b45d81149a21ca3585 Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Wed, 1 Oct 2025 14:47:30 -0500 Subject: [PATCH 1/7] Support function-like objects in `FormatLogger` --- src/Sinks/formatlogger.jl | 27 ++++++++++++++++++--------- test/runtests.jl | 26 ++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/src/Sinks/formatlogger.jl b/src/Sinks/formatlogger.jl index a69c654..a1305c9 100644 --- a/src/Sinks/formatlogger.jl +++ b/src/Sinks/formatlogger.jl @@ -1,15 +1,15 @@ struct FormatLogger <: AbstractLogger - f::Function + formatter 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)` +The formatting function should be of the form `formatter(io::IOContext, log_args::NamedTuple)` where `log_args` 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. @@ -30,12 +30,12 @@ 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. @@ -43,9 +43,9 @@ to `FileLogger` 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...) @@ -54,7 +54,7 @@ function handle_message(logger::FormatLogger, args...; kwargs...) # 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_args) write(logger.stream, take!(iob)) logger.always_flush && flush(logger.stream) return nothing @@ -62,3 +62,12 @@ end shouldlog(logger::FormatLogger, arg...) = true min_enabled_level(logger::FormatLogger) = BelowMinLevel catch_exceptions(logger::FormatLogger) = true # Or false? SimpleLogger doesn't, ConsoleLogger does. + +# For backwards compatibility +function Base.getproperty(logger::FormatLogger, f::Symbol) + return if f === :f + getfield(logger, :formatter) + else + getfield(logger, f) + end +end diff --git a/test/runtests.jl b/test/runtests.jl index b2dd2cc..4fcfa4d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -197,6 +197,17 @@ end end end +Base.@kwdef struct BasicLogFormatter + include_module::Bool=true +end + +function (formatter::BasicLogFormatter)(io::IO, args::NamedTuple) + if formatter.include_module + print(io, args._module, " | ") + end + println(io, "[", args.level, "] ", args.message) +end + @testset "FormatLogger" begin io = IOBuffer() logger = FormatLogger(io) do io, args @@ -242,6 +253,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 From 1f5efa9557b740877a3c3898d5562cb4771e71e6 Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Wed, 1 Oct 2025 14:48:42 -0500 Subject: [PATCH 2/7] Rename `log_args` to `log` --- src/Sinks/formatlogger.jl | 12 ++++++------ test/runtests.jl | 14 +++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Sinks/formatlogger.jl b/src/Sinks/formatlogger.jl index a1305c9..06a359f 100644 --- a/src/Sinks/formatlogger.jl +++ b/src/Sinks/formatlogger.jl @@ -9,8 +9,8 @@ end 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 `formatter(io::IOContext, log_args::NamedTuple)` -where `log_args` has the following fields: +The formatting function 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. @@ -18,8 +18,8 @@ See [`LoggingExtras.handle_message_args`](@ref) for more information on what fie ```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 @@ -49,12 +49,12 @@ function FormatLogger(formatter, path::AbstractString; append::Bool=false, 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.formatter(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 4fcfa4d..c130d47 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -201,23 +201,23 @@ Base.@kwdef struct BasicLogFormatter include_module::Bool=true end -function (formatter::BasicLogFormatter)(io::IO, args::NamedTuple) +function (formatter::BasicLogFormatter)(io::IO, log::NamedTuple) if formatter.include_module - print(io, args._module, " | ") + print(io, log._module, " | ") end - println(io, "[", args.level, "] ", args.message) + 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 @@ -242,7 +242,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 From e550d46ee049ab23c7286c606cdb3f963a672ce9 Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Wed, 1 Oct 2025 14:48:53 -0500 Subject: [PATCH 3/7] Set project version to 1.2.0 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" From 9738b3c61f1a18280f8b34d4d28788054758b1fd Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Thu, 2 Oct 2025 11:07:03 -0500 Subject: [PATCH 4/7] Add comment to `BasicLogFormatter` Co-authored-by: Frames White --- test/runtests.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/runtests.jl b/test/runtests.jl index c130d47..f43f563 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -197,6 +197,7 @@ end end end +# NOTE: this intentionally does not subtype Function Base.@kwdef struct BasicLogFormatter include_module::Bool=true end From 14bfd1bee3bffc745e7e77c7e8a55483031cb58f Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Thu, 2 Oct 2025 12:35:12 -0500 Subject: [PATCH 5/7] Remove backwards compatible field access --- src/Sinks/datetime_rotation.jl | 2 +- src/Sinks/formatlogger.jl | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) 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 06a359f..ea0fb26 100644 --- a/src/Sinks/formatlogger.jl +++ b/src/Sinks/formatlogger.jl @@ -62,12 +62,3 @@ end shouldlog(logger::FormatLogger, arg...) = true min_enabled_level(logger::FormatLogger) = BelowMinLevel catch_exceptions(logger::FormatLogger) = true # Or false? SimpleLogger doesn't, ConsoleLogger does. - -# For backwards compatibility -function Base.getproperty(logger::FormatLogger, f::Symbol) - return if f === :f - getfield(logger, :formatter) - else - getfield(logger, f) - end -end From b02ddade2cb1518963e3ff5f439a6382a9c015f3 Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Thu, 2 Oct 2025 12:35:31 -0500 Subject: [PATCH 6/7] Make `FormatLogger` parametric --- src/Sinks/formatlogger.jl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Sinks/formatlogger.jl b/src/Sinks/formatlogger.jl index ea0fb26..4c197ba 100644 --- a/src/Sinks/formatlogger.jl +++ b/src/Sinks/formatlogger.jl @@ -1,6 +1,5 @@ - -struct FormatLogger <: AbstractLogger - formatter +struct FormatLogger{T} <: AbstractLogger + formatter::T stream::IO always_flush::Bool end From 2da92d6af5e822a8d616907189aab4a9e80240d8 Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Thu, 2 Oct 2025 12:35:39 -0500 Subject: [PATCH 7/7] Docstring and comment updates --- src/Sinks/formatlogger.jl | 11 ++++++----- test/runtests.jl | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Sinks/formatlogger.jl b/src/Sinks/formatlogger.jl index 4c197ba..f573d6e 100644 --- a/src/Sinks/formatlogger.jl +++ b/src/Sinks/formatlogger.jl @@ -8,10 +8,11 @@ end 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 `formatter(io::IOContext, log::NamedTuple)` -where `log` 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 @@ -36,8 +37,8 @@ end """ 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. diff --git a/test/runtests.jl b/test/runtests.jl index f43f563..48bf0be 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -197,7 +197,7 @@ end end end -# NOTE: this intentionally does not subtype Function +# Intentionally not subtype `Function` here to test function-like object support Base.@kwdef struct BasicLogFormatter include_module::Bool=true end