Skip to content

Commit 6e1f046

Browse files
committed
Define and test the rotating file sink
1 parent 45455c9 commit 6e1f046

File tree

5 files changed

+172
-0
lines changed

5 files changed

+172
-0
lines changed

lua/structlog/sinks/console.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ setmetatable(Console, {
1616
-- @param opts Optional parameters
1717
-- @param opts.async Make the logger async, default: True
1818
-- @param opts.processors The list of processors to chain the log entries in
19+
-- @param opts.formatter The formatter to format the log entries
1920
function Console:new(opts)
2021
opts = opts or {}
2122

lua/structlog/sinks/file.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ setmetatable(File, {
1515
-- @param path The path to the logging file
1616
-- @param opts Optional parameters
1717
-- @param opts.processors The list of processors to chain the log entries in
18+
-- @param opts.formatter The formatter to format the log entries
1819
function File:new(path, opts)
1920
opts = opts or {}
2021

lua/structlog/sinks/init.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
-- @table M
99
-- @field Console The class to write to nvim console.
1010
-- @field File The class to write to a file.
11+
-- @field RotatingFile The class to write to a rotating file.
1112
local M = {
1213
Console = require("structlog.sinks.console"),
1314
File = require("structlog.sinks.file"),
15+
RotatingFile = require("structlog.sinks.rotating_file"),
1416
}
1517

1618
return M
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
--- Write log entries to a rotating file.
2+
3+
--- The Rotating File sink class.
4+
-- @type RotatingFile
5+
local RotatingFile = {}
6+
local File = require("structlog.sinks.file")
7+
8+
setmetatable(RotatingFile, {
9+
__call = function(cls, ...)
10+
return cls:new(...)
11+
end,
12+
})
13+
14+
--- Create a new rotating file writer.
15+
-- @param path The path to the logging file
16+
-- @param opts Optional parameters
17+
-- @param opts.processors The list of processors to chain the log entries in
18+
-- @param opts.formatter The formatter to format the log entries
19+
-- @param opts.max_size Maximum size of the file in bytes
20+
-- @param opts.max_age Maximum age of the file is seconds
21+
-- @param opts.time_format The time format used for renaming the log, default: "%F-%H:%M:%S"
22+
function RotatingFile:new(path, opts)
23+
opts = opts or {}
24+
25+
local file = {}
26+
27+
file.sink = File(path, opts)
28+
file.path = path
29+
file.max_size = opts.max_size
30+
file.max_age = opts.max_age
31+
file.time_format = opts.time_format or "%F-%H:%M:%S"
32+
33+
file.uv = opts.uv or vim.loop
34+
35+
RotatingFile.__index = RotatingFile
36+
setmetatable(file, self)
37+
38+
return file
39+
end
40+
41+
function RotatingFile:write(message)
42+
local stat = self.uv.fs_stat(self.path)
43+
if stat then
44+
local cu_time = os.time()
45+
if
46+
(self.max_size and stat.size >= self.max_size)
47+
or (self.max_age and cu_time >= stat.birthtime.sec + self.max_age)
48+
then
49+
cu_time = os.date(self.time_format, cu_time)
50+
51+
local parts = { self.path:match("(.*)%.(.*)$") }
52+
local archive_file
53+
if #parts == 2 then
54+
archive_file = string.format("%s-%s.%s", parts[1], cu_time, parts[2])
55+
else
56+
archive_file = string.format("%s-%s", self.path, cu_time)
57+
end
58+
self.uv.fs_rename(self.path, archive_file)
59+
end
60+
end
61+
62+
self.sink:write(message)
63+
end
64+
65+
return RotatingFile
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
local log = require("structlog")
2+
local RotatingFile = log.sinks.RotatingFile
3+
4+
local stub = require("luassert.stub")
5+
local match = require("luassert.match")
6+
7+
describe("RotatingFile", function()
8+
local fp = {}
9+
stub(fp, "write")
10+
stub(fp, "close")
11+
12+
local iolib = {
13+
open = function()
14+
return fp
15+
end,
16+
}
17+
18+
local stat = {
19+
atime = {
20+
nsec = 862184517,
21+
sec = 1629806034,
22+
},
23+
birthtime = {
24+
nsec = 862184517,
25+
sec = 1629806034,
26+
},
27+
blksize = 4096,
28+
blocks = 8,
29+
ctime = {
30+
nsec = 862184517,
31+
sec = 1629806034,
32+
},
33+
dev = 2049,
34+
flags = 0,
35+
gen = 0,
36+
gid = 1000,
37+
ino = 11698217,
38+
mode = 33188,
39+
mtime = {
40+
nsec = 862184517,
41+
sec = 1629806034,
42+
},
43+
nlink = 1,
44+
rdev = 0,
45+
size = 5,
46+
type = "file",
47+
uid = 1000,
48+
}
49+
50+
local uv = {
51+
fs_stat = function(_)
52+
return stat
53+
end,
54+
}
55+
stub(uv, "fs_rename")
56+
57+
local file_path = "./path.log"
58+
59+
it("should use the io lib to write to the file sink", function()
60+
local file = RotatingFile(file_path, { iolib = iolib })
61+
file:write("test")
62+
63+
assert.stub(fp.write).was_called_with(match.is_ref(fp), "test")
64+
assert.stub(fp.write).was_called_with(match.is_ref(fp), "\n")
65+
assert.stub(fp.close).called()
66+
end)
67+
it("should not rotate the file if stat returns nil", function()
68+
local uv_cpy = {
69+
fs_stat = function(_)
70+
return nil
71+
end,
72+
}
73+
stub(uv_cpy, "fs_rename")
74+
local file = RotatingFile(file_path, { max_size = 0, iolib = iolib, uv = uv_cpy })
75+
file:write("test")
76+
77+
assert.stub(uv_cpy.fs_rename).was_not_called()
78+
end)
79+
it("should not rotate the file if max_size is not exceeded", function()
80+
local file = RotatingFile(file_path, { max_size = math.huge, iolib = iolib, uv = uv })
81+
file:write("test")
82+
83+
assert.stub(uv.fs_rename).was_not_called()
84+
end)
85+
it("should not rotate the file if max_age is not exceeded", function()
86+
local file = RotatingFile(file_path, { max_age = math.huge, iolib = iolib, uv = uv })
87+
file:write("test")
88+
89+
assert.stub(uv.fs_rename).was_not_called()
90+
end)
91+
it("should rotate the file if max_size is exceeded", function()
92+
local file = RotatingFile(file_path, { max_size = 0, iolib = iolib, uv = uv })
93+
file:write("test")
94+
95+
assert.stub(uv.fs_rename).was_called()
96+
end)
97+
it("should rotate the file if max_age is exceeded", function()
98+
local file = RotatingFile(file_path, { max_age = 0, iolib = iolib, uv = uv })
99+
file:write("test")
100+
101+
assert.stub(uv.fs_rename).was_called()
102+
end)
103+
end)

0 commit comments

Comments
 (0)