Skip to content

Commit 3726aa9

Browse files
authored
Merge pull request #39 from fredrikekre/fe/fmt
Make it possible to use a FormatLogger with DatetimeRotatingLogger
2 parents c9ef7b8 + a6d0359 commit 3726aa9

File tree

5 files changed

+61
-22
lines changed

5 files changed

+61
-22
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,8 @@ julia> filter(f -> endswith(f, ".log"), readdir(pwd()))
316316
```
317317
318318
The user implicitly controls when the files will be rolled over based on the `DateFormat` given.
319+
To control the logging output it is possible to pass a formatter function as the first argument
320+
in the constructor. See `FormatLogger` for the requirements on the formatter function.
319321
320322
## `FormatLogger` (*Sink*)
321323
The `FormatLogger` is a sink that formats the message and prints to a wrapped IO.

src/LoggingExtras.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ include("activefiltered.jl")
3838
include("earlyfiltered.jl")
3939
include("minlevelfiltered.jl")
4040
include("filelogger.jl")
41-
include("datetime_rotation.jl")
4241
include("formatlogger.jl")
42+
include("datetime_rotation.jl")
4343
include("deprecated.jl")
4444

4545
end # module

src/datetime_rotation.jl

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,64 @@ using Dates
22
import Base: isless
33

44
raw"""
5-
DatetimeRotatingFileLogger
5+
DatetimeRotatingFileLogger(dir, file_pattern; always_flush=true)
6+
DatetimeRotatingFileLogger(f::Function, dir, file_pattern; always_flush=true)
67
7-
Constructs a FileLogger that rotates its file based on the current date.
8+
Construct a `DatetimeRotatingFileLogger` that rotates its file based on the current date.
9+
The constructor takes a log output directory, `dir`, and a filename pattern.
810
The filename pattern given is interpreted through the `Dates.format()` string formatter,
911
allowing for yearly all the way down to millisecond-level log rotation. Note that if you
1012
wish to have a filename portion that is not interpreted as a format string, you may need
11-
to escape portions of the filename, as shown below:
13+
to escape portions of the filename, as shown in the example below.
1214
13-
Usage example:
15+
It is possible to pass a formatter function as the first argument to control the output.
16+
The formatting function should be of the form `f(io::IOContext, log_args::NamedTuple)`
17+
where `log_args` has the following fields:
18+
`(level, message, _module, group, id, file, line, kwargs)`.
19+
See `?LoggingExtra.handle_message_args` for more information about what each field represents.
1420
15-
logger = DatetimeRotatingFileLogger(log_dir, raw"\a\c\c\e\s\s-YYYY-mm-dd.\l\o\g")
21+
# Examples
22+
23+
```julia
24+
# Logger that logs to a new file every day
25+
logger = DatetimeRotatingFileLogger(log_dir, raw"\a\c\c\e\s\s-yyyy-mm-dd.\l\o\g")
26+
27+
# Logger with a formatter function that rotates the log file hourly
28+
logger = DatetimeRotatingFileLogger(log_dir, raw"yyyy-mm-dd-HH.\l\o\g") do io, args
29+
println(io, args.level, " | ", args.message)
30+
end
1631
"""
1732
mutable struct DatetimeRotatingFileLogger <: AbstractLogger
18-
logger::SimpleLogger
33+
logger::Union{SimpleLogger,FormatLogger}
1934
dir::String
2035
filename_pattern::DateFormat
2136
next_reopen_check::DateTime
2237
always_flush::Bool
2338
end
2439

2540
function DatetimeRotatingFileLogger(dir, filename_pattern; always_flush=true)
26-
format = DateFormat(filename_pattern)
27-
return DatetimeRotatingFileLogger(
28-
SimpleLogger(open(calc_logpath(dir, filename_pattern), "a"), BelowMinLevel),
29-
dir,
30-
format,
31-
next_datetime_transition(format),
32-
always_flush,
33-
)
41+
DatetimeRotatingFileLogger(nothing, dir, filename_pattern; always_flush=always_flush)
42+
end
43+
function DatetimeRotatingFileLogger(f::Union{Function,Nothing}, dir, filename_pattern; always_flush=true)
44+
# Construct the backing logger with a temp IOBuffer that will be replaced
45+
# by the correct filestream in the call to reopen! below
46+
logger = if f === nothing
47+
SimpleLogger(IOBuffer(), BelowMinLevel)
48+
else # f isa Function
49+
FormatLogger(f, IOBuffer(); always_flush=false) # no need to flush twice
50+
end
51+
# abspath in case user constructs the logger with a relative path and later cd's.
52+
drfl = DatetimeRotatingFileLogger(logger, abspath(dir),
53+
DateFormat(filename_pattern), now(), always_flush)
54+
reopen!(drfl)
55+
return drfl
3456
end
3557

58+
similar_logger(::SimpleLogger, io) = SimpleLogger(io, BelowMinLevel)
59+
similar_logger(l::FormatLogger, io) = FormatLogger(l.f, io, l.always_flush)
3660
function reopen!(drfl::DatetimeRotatingFileLogger)
37-
drfl.logger = SimpleLogger(open(calc_logpath(drfl.dir, drfl.filename_pattern), "a"), BelowMinLevel)
61+
io = open(calc_logpath(drfl.dir, drfl.filename_pattern), "a")
62+
drfl.logger = similar_logger(drfl.logger, io)
3863
drfl.next_reopen_check = next_datetime_transition(drfl.filename_pattern)
3964
return nothing
4065
end

src/formatlogger.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
struct FormatLogger <: AbstractLogger
33
f::Function
4-
io::IO
4+
stream::IO
55
always_flush::Bool
66
end
77

@@ -39,10 +39,10 @@ function handle_message(logger::FormatLogger, args...; kwargs...)
3939
# We help the user by passing an IOBuffer to the formatting function
4040
# to make sure that everything writes to the logger io in one go.
4141
iob = IOBuffer()
42-
ioc = IOContext(iob, logger.io)
42+
ioc = IOContext(iob, logger.stream)
4343
logger.f(ioc, log_args)
44-
write(logger.io, take!(iob))
45-
logger.always_flush && flush(logger.io)
44+
write(logger.stream, take!(iob))
45+
logger.always_flush && flush(logger.stream)
4646
return nothing
4747
end
4848
shouldlog(logger::FormatLogger, arg...) = true

test/runtests.jl

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,10 @@ end
126126
mktempdir() do dir
127127
drfl_sec = DatetimeRotatingFileLogger(dir, raw"\s\e\c-YYYY-mm-dd-HH-MM-SS.\l\o\g")
128128
drfl_min = DatetimeRotatingFileLogger(dir, raw"\m\i\n-YYYY-mm-dd-HH-MM.\l\o\g")
129-
sink = TeeLogger(drfl_sec, drfl_min)
129+
func = (io, args) -> println(io, reverse(args.message))
130+
drfl_fmt = DatetimeRotatingFileLogger(func, dir, raw"\f\m\t-YYYY-mm-dd-HH-MM-SS.\l\o\g")
131+
132+
sink = TeeLogger(drfl_sec, drfl_min, drfl_fmt)
130133
with_logger(sink) do
131134
while millisecond(now()) < 100 || millisecond(now()) > 200
132135
sleep(0.001)
@@ -146,6 +149,9 @@ end
146149
min_files = filter(f -> startswith(basename(f), "min-"), files)
147150
@test length(min_files) == 1
148151

152+
fmt_files = filter(f -> startswith(basename(f), "fmt-"), files)
153+
@test length(fmt_files) == 2
154+
149155
sec1_data = String(read(sec_files[1]))
150156
@test occursin("first", sec1_data)
151157
@test occursin("second", sec1_data)
@@ -156,6 +162,12 @@ end
156162
@test occursin("first", min_data)
157163
@test occursin("second", min_data)
158164
@test occursin("third", min_data)
165+
166+
fmt_data = String(read(fmt_files[1]))
167+
@test occursin("tsrif", fmt_data)
168+
@test occursin("dnoces", fmt_data)
169+
fmt_data = String(read(fmt_files[2]))
170+
@test occursin("driht", fmt_data)
159171
end
160172
end
161173

@@ -186,7 +198,7 @@ end
186198
@test logger.always_flush
187199
# Test constructor with default io and kwarg
188200
logger = FormatLogger(x -> x; always_flush=false)
189-
@test logger.io === stderr
201+
@test logger.stream === stderr
190202
@test !logger.always_flush
191203
end
192204

0 commit comments

Comments
 (0)