This is an automated email from the ASF dual-hosted git repository.
AlinsRan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new 7f67c2089 fix(log-rotate): reopen logs after partial rotation (#13375)
7f67c2089 is described below
commit 7f67c20899f185631cf3b5dab93a0e9bb07730fe
Author: Zhi Yuan Ju <[email protected]>
AuthorDate: Wed Jul 8 14:38:48 2026 +0800
fix(log-rotate): reopen logs after partial rotation (#13375)
---
apisix/plugins/log-rotate.lua | 8 ++-
t/plugin/log-rotate.t | 147 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 152 insertions(+), 3 deletions(-)
diff --git a/apisix/plugins/log-rotate.lua b/apisix/plugins/log-rotate.lua
index f90226d89..f963226c1 100644
--- a/apisix/plugins/log-rotate.lua
+++ b/apisix/plugins/log-rotate.lua
@@ -218,11 +218,13 @@ local function rotate_file(files, now_time, max_kept,
timeout)
for _, file in ipairs(files) do
local now_date = os_date("%Y-%m-%d_%H-%M-%S", now_time)
local new_file = rename_file(default_logs[file], now_date)
- if not new_file then
- return
+ if new_file then
+ core.table.insert(new_files, new_file)
end
+ end
- core.table.insert(new_files, new_file)
+ if core.table.isempty(new_files) then
+ return
end
-- send signal to reopen log files
diff --git a/t/plugin/log-rotate.t b/t/plugin/log-rotate.t
index 4d88ae4e8..3af19e8de 100644
--- a/t/plugin/log-rotate.t
+++ b/t/plugin/log-rotate.t
@@ -28,6 +28,7 @@ add_block_preprocessor(sub {
my $extra_yaml_config = <<_EOC_;
plugins: # plugin list
- log-rotate
+ - serverless-post-function
plugin_attr:
log-rotate:
@@ -247,3 +248,149 @@ true
}
--- response_body
passed
+
+
+
+=== TEST 6: reopen plugin and access logs even if one configured log file is
missing
+# Log-phase plugins run after the response is sent to the client. Keep the
+# rotation setup, request trigger, and assertions in separate requests so the
+# previous request's log-phase output is flushed before checking the log files.
+--- timeout: 30
+--- config
+ location /setup {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local prefix = ngx.config.prefix()
+ local access_log = prefix .. "/logs/access.log"
+ local function fail(msg)
+ ngx.status = 500
+ ngx.say(msg)
+ ngx.exit(ngx.HTTP_OK)
+ end
+
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "plugins": {
+ "serverless-post-function": {
+ "phase": "log",
+ "functions" : ["return function(conf, ctx)
require('apisix.core').log.info('serverless post-rotation marker') end"]
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/hello"
+ }]]
+ )
+
+ if code >= 300 then
+ fail(body)
+ end
+
+ -- remove access.log so the rotation renames error.log but fails on
+ -- access.log, exercising the partial-rotation path. Confirm the
+ -- file is actually gone, otherwise the test would silently rotate
+ -- both logs and pass for the wrong path.
+ local lfs = require("lfs")
+ local ok, err = os.remove(access_log)
+ if not ok and lfs.attributes(access_log) then
+ fail("failed to remove access log: " .. (err or "unknown
error"))
+ end
+
+ ngx.sleep(2.5)
+
+ local data = [[
+apisix:
+ node_listen: 1984
+ admin_key: null
+plugins:
+ - serverless-post-function
+ ]]
+ require("lib.test_admin").set_config_yaml(data)
+ code, _, body = t('/apisix/admin/plugins/reload',
+ ngx.HTTP_PUT)
+ if code >= 300 then
+ fail(body)
+ end
+
+ -- plugins/reload only posts an event; the privileged agent where
+ -- the log-rotate timer runs handles it asynchronously, so a late
+ -- rotation tick can still fire after reload returns. Wait until no
+ -- new rotated file shows up for a full interval, otherwise a stray
+ -- rotation between /hello and /verify would move the freshly
+ -- written logs away and make the assertions flaky.
+ local function count_rotated()
+ local n = 0
+ for file_name in lfs.dir(prefix .. "/logs/") do
+ if string.match(file_name, "__error.log$") then
+ n = n + 1
+ end
+ end
+ return n
+ end
+
+ local prev = count_rotated()
+ local stable = 0
+ while stable < 2 do
+ ngx.sleep(1.2)
+ local cur = count_rotated()
+ if cur == prev then
+ stable = stable + 1
+ else
+ prev = cur
+ stable = 0
+ end
+ end
+
+ ngx.say("passed")
+ }
+ }
+ location /verify {
+ content_by_lua_block {
+ local prefix = ngx.config.prefix()
+ local access_log = prefix .. "/logs/access.log"
+ local error_log = prefix .. "/logs/error.log"
+ local marker = "serverless post-rotation marker"
+ local function fail(msg)
+ ngx.status = 500
+ ngx.say(msg)
+ ngx.exit(ngx.HTTP_OK)
+ end
+
+ ngx.sleep(0.5)
+
+ local f, err = io.open(error_log, "r")
+ if not f then
+ fail("failed to open current error log: " .. err)
+ end
+ local error_content = f:read("*all")
+ f:close()
+
+ f, err = io.open(access_log, "r")
+ if not f then
+ fail("failed to open current access log: " .. err)
+ end
+ local access_content = f:read("*all")
+ f:close()
+
+ if not string.find(error_content, marker, 1, true) then
+ fail("current error log missed post-rotation plugin log")
+ end
+
+ if not string.find(access_content, "GET /hello", 1, true) then
+ fail("current access log missed post-rotation request")
+ end
+
+ ngx.say("passed")
+ }
+ }
+--- pipelined_requests eval
+["GET /setup", "GET /hello", "GET /verify"]
+--- error_code eval
+[200, 200, 200]
+--- response_body eval
+["passed\n", "hello world\n", "passed\n"]