@@ -2,13 +2,13 @@ using Dates
2
2
import Base: isless
3
3
4
4
raw """
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 )
7
7
8
8
Construct a `DatetimeRotatingFileLogger` that rotates its file based on the current date.
9
9
The constructor takes a log output directory, `dir`, and a filename pattern.
10
10
The 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
12
12
wish to have a filename portion that is not interpreted as a format string, you may need
13
13
to escape portions of the filename, as shown in the example below.
14
14
@@ -18,6 +18,11 @@ where `log_args` has the following fields:
18
18
`(level, message, _module, group, id, file, line, kwargs)`.
19
19
See `?LoggingExtra.handle_message_args` for more information about what each field represents.
20
20
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
+
21
26
# Examples
22
27
23
28
```julia
@@ -28,19 +33,27 @@ logger = DatetimeRotatingFileLogger(log_dir, raw"\a\c\c\e\s\s-yyyy-mm-dd.\l\o\g"
28
33
logger = DatetimeRotatingFileLogger(log_dir, raw"yyyy-mm-dd-HH.\l\o\g ") do io, args
29
34
println(io, args.level, " | ", args.message)
30
35
end
36
+
37
+ # Example callback function to compress the recently-closed file
38
+ compressor(file) = run(`gzip $(file)`)
39
+ logger = DatetimeRotatingFileLogger(...; rotation_callback=compressor)
40
+ ```
31
41
"""
32
42
mutable struct DatetimeRotatingFileLogger <: AbstractLogger
33
43
logger:: Union{SimpleLogger,FormatLogger}
34
44
dir:: String
35
45
filename_pattern:: DateFormat
36
46
next_reopen_check:: DateTime
37
47
always_flush:: Bool
48
+ reopen_lock:: ReentrantLock
49
+ current_file:: Union{String,Nothing}
50
+ rotation_callback:: Function
38
51
end
39
52
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 )
42
55
end
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 )
44
57
# Construct the backing logger with a temp IOBuffer that will be replaced
45
58
# by the correct filestream in the call to reopen! below
46
59
logger = if f === nothing
@@ -50,15 +63,22 @@ function DatetimeRotatingFileLogger(f::Union{Function,Nothing}, dir, filename_pa
50
63
end
51
64
# abspath in case user constructs the logger with a relative path and later cd's.
52
65
drfl = DatetimeRotatingFileLogger (logger, abspath (dir),
53
- DateFormat (filename_pattern), now (), always_flush)
66
+ DateFormat (filename_pattern), now (), always_flush, ReentrantLock (), nothing , rotation_callback )
54
67
reopen! (drfl)
55
68
return drfl
56
69
end
57
70
58
71
similar_logger (:: SimpleLogger , io) = SimpleLogger (io, BelowMinLevel)
59
72
similar_logger (l:: FormatLogger , io) = FormatLogger (l. f, io, l. always_flush)
60
73
function 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" )
62
82
drfl. logger = similar_logger (drfl. logger, io)
63
83
drfl. next_reopen_check = next_datetime_transition (drfl. filename_pattern)
64
84
return nothing
@@ -104,15 +124,19 @@ function next_datetime_transition(fmt::DateFormat)
104
124
105
125
tokens = filter (t -> isa (t, Dates. DatePart), collect (fmt. tokens))
106
126
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)
108
131
end
109
132
110
133
calc_logpath (dir, filename_pattern) = joinpath (dir, Dates. format (now (), filename_pattern))
111
134
112
135
function 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
116
140
end
117
141
handle_message (drfl. logger, args... ; kwargs... )
118
142
drfl. always_flush && flush (drfl. logger. stream)
0 commit comments