Skip to content

Commit 1c737d5

Browse files
authored
Merge pull request #9 from Arkoniak/flush_threshold
docstrings and flush delay
2 parents c683384 + d15f67f commit 1c737d5

File tree

5 files changed

+108
-5
lines changed

5 files changed

+108
-5
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "MiniLoggers"
22
uuid = "93f3dd0f-005d-4452-894a-a31841fa4078"
33
authors = ["Andrey Oskin"]
4-
version = "0.2.2"
4+
version = "0.2.3"
55

66
[deps]
77
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# MiniLogger
1+
# MiniLoggers.jl
22

33
| **Documentation** | **Build Status** | **JuliaHub** |
44
|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|

src/minilogger.jl

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,53 @@ struct MiniLogger{IOT1 <: IO, IOT2 <: IO, DFT <: DateFormat} <: AbstractLogger
88
format::Vector{Token}
99
dtformat::DFT
1010
squash_message::Bool
11+
flush_threshold::Int
12+
lastflush::Base.RefValue{Int}
1113
end
1214

1315
getio(io) = io
1416
getio(io::AbstractString) = open(io)
1517

16-
function MiniLogger(; io = stdout, ioerr = stderr, errlevel = Error, minlevel = Info, message_limits = Dict{Any, Int}(), flush = true, format = "{[{datetime}]:func} {message}", dtformat = dateformat"yyyy-mm-dd HH:MM:SS", squash_message = true)
18+
getflushthreshold(x::Integer) = x
19+
getflushthreshold(x::TimePeriod) = Dates.value(Millisecond(x))
20+
21+
"""
22+
MiniLogger(; <keyword arguments>)
23+
24+
MiniLogger constructor creates custom logger which can be used with usual `@info`, `@debug` commands.
25+
26+
Supported keyword arguments include:
27+
28+
* `io` (default `stdout`): IO stream which is used to output log messages below `errlevel` level. Can be either `IO` or `String`, in the latter case it is treated as a name of the output file.
29+
* `ioerr` (default `stderr`): IO stream which is used to output log messages above `errlevel` level. Can be either `IO` or `String`, in the latter case it is treated as a name of the output file.
30+
* `errlevel` (default `Error`): determines which output IO to use for log messages. If you want for all messages to go to `io`, set this parameter to `MiniLoggers.AboveMaxLevel`. If you want for all messages to go to `ioerr`, set this parameter to `MiniLoggers.BelowMinLevel`.
31+
* `minlevel` (default: `Info`): messages below this level are ignored. For example with default setting `@debug "foo"` is ignored.
32+
* `squash_message` (default: `true`): if `squash_message` is set to `true`, then message is squashed to a single line, i.e. all `\\n` are changed to ` ` and `\\r` are removed.
33+
* `flush` (default: `true`): whether to `flush` IO stream for each log message. Flush behaviour also affected by `flush_threshold` argument.
34+
* `flush_threshold::Union{Integer, TimePeriod}` (default: 0): if this argument is nonzero and `flush` is `true`, then `io` is flushed only once per `flush_threshold` milliseconds. I.e. if time between two consecutive log messages is less then `flush_threshold`, then second message is not flushed and will have to wait for the next log event.
35+
* `dtformat` (default: "yyyy-mm-dd HH:MM:SS"): if `datetime` parameter is used in `format` argument, this dateformat is applied for output timestamps.
36+
* `format` (default: "{[{datetime}]:func} {message}"): format for output log message. It accepts following keywords, which should be provided in curly brackets:
37+
* `datetime`: timestamp of the log message
38+
* `level`: name of log level (Debug, Info, etc)
39+
* `filepath`: filepath of the file, which produced log message
40+
* `basename`: basename of the filepath of the file, which produced log message
41+
* `line`: line number of the log command in the file, which produced log message
42+
* `group`: log group
43+
* `module`: name of the module, which contains log command
44+
* `id`: log message id
45+
* `message`: message itself
46+
47+
Each keyword accepts color information, which should be added after colon inside curly brackets. Colors can be either from `Base.text_colors` or special keyword `func`, in which case is used automated coloring. Additionaly, `bold` modifier is accepted by the `format` argument. For example: `{line:red}`, `{module:cyan:bold}`, `{group:func}` are all valid parts of the format command.
48+
49+
Colour information is applied recursively without override, so `{line {module:cyan} group:red}` is equivalent to `{line:red} {module:cyan} {group:red}`.
50+
51+
If part of the format is not a recognised keyword, then it is just used as is, for example `{foo:red}` means that output log message contain word "foo" printed in red.
52+
"""
53+
function MiniLogger(; io = stdout, ioerr = stderr, errlevel = Error, minlevel = Info, message_limits = Dict{Any, Int}(), flush = true, format = "{[{datetime}]:func} {message}", dtformat = dateformat"yyyy-mm-dd HH:MM:SS", squash_message = true, flush_threshold = 0)
1754
tio = getio(io)
1855
tioerr = io == ioerr ? tio : getio(ioerr)
19-
MiniLogger(tio, tioerr, errlevel, minlevel, message_limits, flush, tokenize(format), dtformat, squash_message)
56+
lastflush = Dates.value(Dates.now())
57+
MiniLogger(tio, tioerr, errlevel, minlevel, message_limits, flush, tokenize(format), dtformat, squash_message, getflushthreshold(flush_threshold), Ref(lastflush))
2058
end
2159

2260
shouldlog(logger::MiniLogger, level, _module, group, id) =
@@ -139,7 +177,15 @@ function handle_message(logger::MiniLogger, level, message, _module, group, id,
139177
print(iob, "\n")
140178
write(io, take!(buf))
141179
if logger.flush
142-
flush(io)
180+
if logger.flush_threshold <= 0
181+
flush(io)
182+
else
183+
t = Dates.value(Dates.now())
184+
if t - logger.lastflush[] >= logger.flush_threshold
185+
logger.lastflush[] = t
186+
flush(io)
187+
end
188+
end
143189
end
144190
nothing
145191
end

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ using ReTest
44

55
include("test01_tokenizer.jl")
66
include("test02_loggerformat.jl")
7+
include("test03_misc.jl")
78

89
end # module
910

test/test03_misc.jl

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
module MiscTest
2+
3+
using ReTest
4+
using MiniLoggers
5+
using Dates
6+
7+
@testset "flush" begin
8+
logger = MiniLogger(flush_threshold = 1000)
9+
@test logger.flush_threshold[] == 1000
10+
11+
logger = MiniLogger(flush_threshold = Second(1))
12+
@test logger.flush_threshold[] == 1000
13+
end
14+
15+
mutable struct MockIORecord
16+
buf::String
17+
flushed::Bool
18+
end
19+
MockIORecord(buf) = MockIORecord(buf, false)
20+
21+
mutable struct MockIO <: IO
22+
recs::Vector{MockIORecord}
23+
end
24+
MockIO() = MockIO(MockIORecord[])
25+
function Base.flush(io::MockIO)
26+
foreach(io.recs) do rec
27+
rec.flushed = true
28+
end
29+
end
30+
Base.isopen(io::MockIO) = true
31+
Base.write(io::MockIO, a::Vector{UInt8}) = push!(io.recs, MockIORecord(String(copy(a))))
32+
33+
@testset "delayed flush" begin
34+
io = MockIO()
35+
logger = MiniLogger(io = io, flush_threshold = 100, format = "{message}")
36+
sleep(0.2)
37+
with_logger(logger) do
38+
@info "Foo"
39+
@info "Bar"
40+
end
41+
@test length(io.recs) == 2
42+
@test io.recs[1].buf == "Foo\n"
43+
@test io.recs[2].buf == "Bar\n"
44+
@test io.recs[1].flushed
45+
@test !io.recs[2].flushed
46+
47+
sleep(0.2)
48+
with_logger(logger) do
49+
@info "Baz"
50+
end
51+
@test length(io.recs) == 3
52+
@test all(x -> x.flushed, io.recs)
53+
@test io.recs[3].buf == "Baz\n"
54+
end
55+
56+
end # module

0 commit comments

Comments
 (0)