@@ -2,13 +2,13 @@ using Dates
22import Base: isless
33
44raw """
5- DatetimeRotatingFileLogger(dir, file_pattern; always_flush=true)
6- DatetimeRotatingFileLogger(f::Function, dir, file_pattern; always_flush=true)
5+ DatetimeRotatingFileLogger(dir, file_pattern; always_flush=true, rotation_callback=identity )
6+ DatetimeRotatingFileLogger(f::Function, dir, file_pattern; always_flush=true, rotation_callback=identity )
77
88Construct a `DatetimeRotatingFileLogger` that rotates its file based on the current date.
99The constructor takes a log output directory, `dir`, and a filename pattern.
1010The filename pattern given is interpreted through the `Dates.format()` string formatter,
11- allowing for yearly all the way down to millisecond -level log rotation. Note that if you
11+ allowing for yearly all the way down to minute -level log rotation. Note that if you
1212wish to have a filename portion that is not interpreted as a format string, you may need
1313to escape portions of the filename, as shown in the example below.
1414
@@ -18,6 +18,11 @@ where `log_args` has the following fields:
1818`(level, message, _module, group, id, file, line, kwargs)`.
1919See `?LoggingExtra.handle_message_args` for more information about what each field represents.
2020
21+ It is also possible to pass `rotation_callback::Function` as a keyword argument. This function
22+ will be called every time a file rotation is happening. The function should accept one
23+ argument which is the absolute path to the just-rotated file. The logger will block until
24+ the callback function returns. Use `@async` if the callback is expensive.
25+
2126# Examples
2227
2328```julia
@@ -28,19 +33,27 @@ logger = DatetimeRotatingFileLogger(log_dir, raw"\a\c\c\e\s\s-yyyy-mm-dd.\l\o\g"
2833logger = DatetimeRotatingFileLogger(log_dir, raw"yyyy-mm-dd-HH.\l\o\g ") do io, args
2934 println(io, args.level, " | ", args.message)
3035end
36+
37+ # Example callback function to compress the recently-closed file
38+ compressor(file) = run(`gzip $(file)`)
39+ logger = DatetimeRotatingFileLogger(...; rotation_callback=compressor)
40+ ```
3141"""
3242mutable struct DatetimeRotatingFileLogger <: AbstractLogger
3343 logger:: Union{SimpleLogger,FormatLogger}
3444 dir:: String
3545 filename_pattern:: DateFormat
3646 next_reopen_check:: DateTime
3747 always_flush:: Bool
48+ reopen_lock:: ReentrantLock
49+ current_file:: Union{String,Nothing}
50+ rotation_callback:: Function
3851end
3952
40- function DatetimeRotatingFileLogger (dir, filename_pattern; always_flush= true )
41- DatetimeRotatingFileLogger (nothing , dir, filename_pattern; always_flush= always_flush)
53+ function DatetimeRotatingFileLogger (dir, filename_pattern; always_flush= true , rotation_callback = identity )
54+ DatetimeRotatingFileLogger (nothing , dir, filename_pattern; always_flush= always_flush, rotation_callback = rotation_callback )
4255end
43- function DatetimeRotatingFileLogger (f:: Union{Function,Nothing} , dir, filename_pattern; always_flush= true )
56+ function DatetimeRotatingFileLogger (f:: Union{Function,Nothing} , dir, filename_pattern; always_flush= true , rotation_callback = identity )
4457 # Construct the backing logger with a temp IOBuffer that will be replaced
4558 # by the correct filestream in the call to reopen! below
4659 logger = if f === nothing
@@ -50,15 +63,22 @@ function DatetimeRotatingFileLogger(f::Union{Function,Nothing}, dir, filename_pa
5063 end
5164 # abspath in case user constructs the logger with a relative path and later cd's.
5265 drfl = DatetimeRotatingFileLogger (logger, abspath (dir),
53- DateFormat (filename_pattern), now (), always_flush)
66+ DateFormat (filename_pattern), now (), always_flush, ReentrantLock (), nothing , rotation_callback )
5467 reopen! (drfl)
5568 return drfl
5669end
5770
5871similar_logger (:: SimpleLogger , io) = SimpleLogger (io, BelowMinLevel)
5972similar_logger (l:: FormatLogger , io) = FormatLogger (l. f, io, l. always_flush)
6073function reopen! (drfl:: DatetimeRotatingFileLogger )
61- io = open (calc_logpath (drfl. dir, drfl. filename_pattern), " a" )
74+ if drfl. current_file != = nothing
75+ # close the old IOStream and pass the file to the callback
76+ close (drfl. logger. stream)
77+ drfl. rotation_callback (drfl. current_file)
78+ end
79+ new_file = calc_logpath (drfl. dir, drfl. filename_pattern)
80+ drfl. current_file = new_file
81+ io = open (new_file, " a" )
6282 drfl. logger = similar_logger (drfl. logger, io)
6383 drfl. next_reopen_check = next_datetime_transition (drfl. filename_pattern)
6484 return nothing
@@ -104,15 +124,19 @@ function next_datetime_transition(fmt::DateFormat)
104124
105125 tokens = filter (t -> isa (t, Dates. DatePart), collect (fmt. tokens))
106126 minimum_timescale = first (sort (map (t -> token_timescales[extract_token (t)], tokens), lt= custom_isless))
107- return ceil (now (), minimum_timescale) - Second (1 )
127+ if minimum_timescale < Minute (1 )
128+ throw (ArgumentError (" rotating the logger with sub-minute resolution not supported" ))
129+ end
130+ return ceil (now (), minimum_timescale)
108131end
109132
110133calc_logpath (dir, filename_pattern) = joinpath (dir, Dates. format (now (), filename_pattern))
111134
112135function handle_message (drfl:: DatetimeRotatingFileLogger , args... ; kwargs... )
113- if now () >= drfl. next_reopen_check
114- flush (drfl. logger. stream)
115- reopen! (drfl)
136+ lock (drfl. reopen_lock) do
137+ if now () >= drfl. next_reopen_check
138+ reopen! (drfl)
139+ end
116140 end
117141 handle_message (drfl. logger, args... ; kwargs... )
118142 drfl. always_flush && flush (drfl. logger. stream)
0 commit comments