Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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 <[email protected]>", "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
35 changes: 22 additions & 13 deletions src/Sinks/formatlogger.jl
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@

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)`
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.

# 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,35 +30,44 @@ 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.

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
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
34 changes: 30 additions & 4 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -197,16 +197,27 @@ end
end
end

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 +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

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