Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "LoggingExtras"
uuid = "e6f89c97-d47a-5376-807f-9c37f3926c36"
authors = ["Frames White <oxinabox@ucc.asn.au>", "Collaborators <https://github.com/JuliaLogging/LoggingExtras.jl/graphs/contributors>"]
version = "1.1.0"
version = "1.2.0"

[deps]
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
Expand Down
2 changes: 1 addition & 1 deletion src/Sinks/datetime_rotation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
36 changes: 18 additions & 18 deletions src/Sinks/formatlogger.jl
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down
35 changes: 31 additions & 4 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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
Expand Down
Loading