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"]

Reply via email to