This is an automated email from the ASF dual-hosted git repository.

spacewander 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 db758902e feat(log-rorate): log-rotate plugin support max_size (#7749)
db758902e is described below

commit db758902e4cb5ad3fd9ad80fd76e22f15dc2f6f8
Author: HaiYan <[email protected]>
AuthorDate: Fri Aug 26 14:12:13 2022 +0800

    feat(log-rorate): log-rotate plugin support max_size (#7749)
    
    Co-authored-by: 罗泽轩 <[email protected]>
    Co-authored-by: Fei Han <[email protected]>
    Co-authored-by: qihaiyan <[email protected]>
---
 apisix/plugins/log-rotate.lua        | 121 +++++++++++++++---------------
 conf/config-default.yaml             |   1 +
 docs/en/latest/plugins/log-rotate.md |   8 +-
 docs/zh/latest/plugins/log-rotate.md |   2 +
 t/plugin/log-rotate3.t               | 141 +++++++++++++++++++++++++++++++++++
 5 files changed, 211 insertions(+), 62 deletions(-)

diff --git a/apisix/plugins/log-rotate.lua b/apisix/plugins/log-rotate.lua
index 794593717..571fc9b52 100644
--- a/apisix/plugins/log-rotate.lua
+++ b/apisix/plugins/log-rotate.lua
@@ -21,6 +21,7 @@ local plugin = require("apisix.plugin")
 local process = require("ngx.process")
 local signal = require("resty.signal")
 local shell = require("resty.shell")
+local ipairs = ipairs
 local ngx = ngx
 local ngx_time = ngx.time
 local ngx_update_time = ngx.update_time
@@ -43,6 +44,7 @@ local local_conf
 local plugin_name = "log-rotate"
 local INTERVAL = 60 * 60    -- rotate interval (unit: second)
 local MAX_KEPT = 24 * 7     -- max number of log files will be kept
+local MAX_SIZE = -1         -- max size of file will be rotated
 local COMPRESSION_FILE_SUFFIX = ".tar.gz" -- compression file suffix
 local rotate_time
 local default_logs
@@ -123,34 +125,22 @@ local function tab_sort_comp(a, b)
 end
 
 
-local function scan_log_folder()
-    local t = {
-        access = {},
-        error = {},
-    }
+local function scan_log_folder(log_file_name)
+    local t = {}
 
-    local log_dir, access_name = get_log_path_info("access.log")
-    local _, error_name = get_log_path_info("error.log")
-
-    if enable_compression then
-        access_name = access_name .. COMPRESSION_FILE_SUFFIX
-        error_name = error_name .. COMPRESSION_FILE_SUFFIX
-    end
+    local log_dir, _ = get_log_path_info(log_file_name)
 
     for file in lfs.dir(log_dir) do
         local n = get_last_index(file, "__")
         if n ~= nil then
             local log_type = file:sub(n + 2)
-            if log_type == access_name then
-                tab_insert(t.access, file)
-            elseif log_type == error_name then
-                tab_insert(t.error, file)
+            if log_type == log_file_name then
+                tab_insert(t, file)
             end
         end
     end
 
-    tab_sort(t.access, tab_sort_comp)
-    tab_sort(t.error, tab_sort_comp)
+    tab_sort(t, tab_sort_comp)
     return t, log_dir
 end
 
@@ -219,18 +209,62 @@ local function init_default_logs(logs_info, log_type)
 end
 
 
+local function file_size(file)
+    local attr = lfs.attributes(file)
+    if attr then
+        return attr.size
+    end
+    return 0
+end
+
+
+local function rotate_file(files, now_time, max_kept)
+    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
+        end
+
+        local pid = process.get_master_pid()
+        core.log.warn("send USR1 signal to master process [", pid, "] for 
reopening log file")
+        local ok, err = signal.kill(pid, signal.signum("USR1"))
+        if not ok then
+            core.log.error("failed to send USR1 signal for reopening log file: 
", err)
+        end
+
+        if enable_compression then
+            compression_file(new_file)
+        end
+
+        -- clean the oldest file
+        local log_list, log_dir = scan_log_folder(file)
+        for i = max_kept + 1, #log_list do
+            local path = log_dir .. log_list[i]
+            local ok, err = os_remove(path)
+            if err then
+               core.log.error("remove old log file: ", path, " err: ", err, "  
res:", ok)
+            end
+        end
+    end
+end
+
+
 local function rotate()
     local interval = INTERVAL
     local max_kept = MAX_KEPT
+    local max_size = MAX_SIZE
     local attr = plugin.plugin_attr(plugin_name)
     if attr then
         interval = attr.interval or interval
         max_kept = attr.max_kept or max_kept
+        max_size = attr.max_size or max_size
         enable_compression = attr.enable_compression or enable_compression
     end
 
     core.log.info("rotate interval:", interval)
     core.log.info("rotate max keep:", max_kept)
+    core.log.info("rotate max size:", max_size)
 
     if not default_logs then
         -- first init default log filepath and filename
@@ -248,53 +282,22 @@ local function rotate()
         return
     end
 
-    if now_time < rotate_time then
-        -- did not reach the rotate time
-        core.log.info("rotate time: ", rotate_time, " now time: ", now_time)
-        return
-    end
+    if now_time >= rotate_time then
+        local files = {DEFAULT_ACCESS_LOG_FILENAME, DEFAULT_ERROR_LOG_FILENAME}
+        rotate_file(files, now_time, max_kept)
 
-    local now_date = os_date("%Y-%m-%d_%H-%M-%S", now_time)
-    local access_new_file = 
rename_file(default_logs[DEFAULT_ACCESS_LOG_FILENAME], now_date)
-    local error_new_file = 
rename_file(default_logs[DEFAULT_ERROR_LOG_FILENAME], now_date)
-    if not access_new_file and not error_new_file then
         -- reset rotate time
         rotate_time = rotate_time + interval
-        return
-    end
-
-    core.log.warn("send USR1 signal to master process [",
-                  process.get_master_pid(), "] for reopening log file")
-    local ok, err = signal.kill(process.get_master_pid(), 
signal.signum("USR1"))
-    if not ok then
-        core.log.error("failed to send USR1 signal for reopening log file: ", 
err)
-    end
-
-    if enable_compression then
-        compression_file(access_new_file)
-        compression_file(error_new_file)
-    end
-
-    -- clean the oldest file
-    local log_list, log_dir = scan_log_folder()
-    for i = max_kept + 1, #log_list.error do
-        local path = log_dir .. log_list.error[i]
-        ok, err = os_remove(path)
-        if err then
-           core.log.error("remove old error file: ", path, " err: ", err, "  
res:", ok)
+    elseif max_size > 0 then
+        local access_log_file_size = 
file_size(default_logs[DEFAULT_ACCESS_LOG_FILENAME].file)
+        local error_log_file_size = 
file_size(default_logs[DEFAULT_ERROR_LOG_FILENAME].file)
+        if access_log_file_size >= max_size then
+            rotate_file({DEFAULT_ACCESS_LOG_FILENAME}, now_time, max_kept)
         end
-    end
-
-    for i = max_kept + 1, #log_list.access do
-        local path = log_dir .. log_list.access[i]
-        ok, err = os_remove(path)
-        if err then
-           core.log.error("remove old error file: ", path, " err: ", err, "  
res:", ok)
+        if error_log_file_size >= max_size then
+            rotate_file({DEFAULT_ERROR_LOG_FILENAME}, now_time, max_kept)
         end
     end
-
-    -- reset rotate time
-    rotate_time = rotate_time + interval
 end
 
 
diff --git a/conf/config-default.yaml b/conf/config-default.yaml
index b4074cd31..e9ae1ffcd 100755
--- a/conf/config-default.yaml
+++ b/conf/config-default.yaml
@@ -495,6 +495,7 @@ plugin_attr:
   log-rotate:
     interval: 3600    # rotate interval (unit: second)
     max_kept: 168     # max number of log files will be kept
+    max_size: -1      # max size bytes of log files to be rotated, size check 
would be skipped with a value less than 0
     enable_compression: false    # enable log file compression(gzip) or not, 
default false
   skywalking:
     service_name: APISIX
diff --git a/docs/en/latest/plugins/log-rotate.md 
b/docs/en/latest/plugins/log-rotate.md
index 3c4cc7ecc..3aee947fe 100644
--- a/docs/en/latest/plugins/log-rotate.md
+++ b/docs/en/latest/plugins/log-rotate.md
@@ -39,6 +39,7 @@ You can configure how often the logs are rotated and how many 
logs to keep. When
 
|--------------------|---------|----------|---------|------------------------------------------------------------------------------------------------|
 | interval           | integer | True     | 60 * 60 | Time in seconds 
specifying how often to rotate the logs.                                       |
 | max_kept           | integer | True     | 24 * 7  | Maximum number of 
historical logs to keep. If this number is exceeded, older logs are deleted. |
+| max_size           | integer | False    | -1      | Max size(Bytes) of log 
files to be rotated, size check would be skipped with a value less than 0 or 
time is up specified by interval. |
 | enable_compression | boolean | False    | false   | When set to `true`, 
compresses the log file (gzip). Requires `tar` to be installed.            |
 
 ## Enabling the Plugin
@@ -51,9 +52,10 @@ plugins:
 
 plugin_attr:
     log-rotate:
-        interval: 3600
-        max_kept: 168
-        enable_compression: false
+        interval: 3600    # rotate interval (unit: second)
+        max_kept: 168     # max number of log files will be kept
+        max_size: -1      # max size of log files will be kept
+        enable_compression: false    # enable log file compression(gzip) or 
not, default false
 ```
 
 ## Example usage
diff --git a/docs/zh/latest/plugins/log-rotate.md 
b/docs/zh/latest/plugins/log-rotate.md
index 58b675946..14f01c837 100644
--- a/docs/zh/latest/plugins/log-rotate.md
+++ b/docs/zh/latest/plugins/log-rotate.md
@@ -39,6 +39,7 @@ description: 云原生 API 网关 Apache APISIX log-rotate 插件用于定期切
 | ------------------ | ------- | ------ | ------- | ------------- | 
---------------------------------------------------------------------------- |
 | interval           | integer | 是     | 60 * 60 |               | 
每间隔多长时间切分一次日志,以秒为单位。                                        |
 | max_kept           | integer | 是     | 24 * 7  |               | 
最多保留多少份历史日志,超过指定数量后,自动删除老文件。                         |
+| max_size           | integer | 否     | -1      |               | 
日志文件超过指定大小时进行切分,单位为 Byte 。如果 `max_size` 小于 0 或者根据 `interval` 计算的时间到达时,将不会根据 
`max_size` 切分日志。 |
 | enable_compression | boolean | 否     | false   | [false, true] | 当设置为 `true` 
时,启用日志文件压缩。该功能需要在系统中安装 `tar` 。     |
 
 开启该插件后,就会按照参数自动切分日志文件了。比如以下示例是根据 `interval: 10` 和 `max_kept: 10` 得到的样本。
@@ -92,6 +93,7 @@ plugin_attr:
     log-rotate:
         interval: 3600    # rotate interval (unit: second)
         max_kept: 168     # max number of log files will be kept
+        max_size: -1      # max size of log files will be kept
         enable_compression: false    # enable log file compression(gzip) or 
not, default false
 ```
 
diff --git a/t/plugin/log-rotate3.t b/t/plugin/log-rotate3.t
new file mode 100644
index 000000000..bfab0f9b6
--- /dev/null
+++ b/t/plugin/log-rotate3.t
@@ -0,0 +1,141 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+use t::APISIX 'no_plan';
+
+repeat_each(1);
+no_long_string();
+no_shuffle();
+no_root_location();
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if (!defined $block->yaml_config) {
+        my $yaml_config = <<_EOC_;
+apisix:
+  node_listen: 1984
+  admin_key: ~
+plugins:
+  - log-rotate
+plugin_attr:
+  log-rotate:
+    interval: 86400
+    max_size: 9
+    max_kept: 3
+    enable_compression: false
+_EOC_
+
+        $block->set_value("yaml_config", $yaml_config);
+    }
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+});
+
+run_tests;
+
+__DATA__
+
+=== TEST 1: log rotate by max_size
+--- config
+    location /t {
+        content_by_lua_block {
+            ngx.log(ngx.ERR, "start xxxxxx")
+            ngx.sleep(2)
+            local has_split_access_file = false
+            local has_split_error_file = false
+            local lfs = require("lfs")
+            for file_name in lfs.dir(ngx.config.prefix() .. "/logs/") do
+                if string.match(file_name, "__access.log$") then
+                    has_split_access_file = true
+                end
+
+                if string.match(file_name, "__error.log$") then
+                    has_split_error_file = true
+                end
+            end
+
+            if not has_split_access_file and has_split_error_file then
+               ngx.status = 200
+            else
+               ngx.status = 500
+            end
+        }
+    }
+
+
+
+=== TEST 2: in current log
+--- config
+    location /t {
+        content_by_lua_block {
+            ngx.sleep(0.1)
+            ngx.log(ngx.WARN, "start xxxxxx")
+            ngx.say("done")
+        }
+    }
+--- response_body
+done
+--- error_log
+start xxxxxx
+
+
+
+=== TEST 3: check file changes
+--- config
+    location /t {
+        content_by_lua_block {
+            ngx.sleep(1)
+
+            local default_logs = {}
+            for file_name in lfs.dir(ngx.config.prefix() .. "/logs/") do
+                if string.match(file_name, "__error.log$") or 
string.match(file_name, "__access.log$") then
+                    local filepath = ngx.config.prefix() .. "/logs/" .. 
file_name
+                    local attr = lfs.attributes(filepath)
+                    if attr then
+                        default_logs[filepath] = { change = attr.change, size 
= attr.size }
+                    end
+                end
+            end
+
+            ngx.sleep(1)
+
+            local passed = false
+            for filepath, origin_attr in pairs(default_logs) do
+                local check_attr = lfs.attributes(filepath)
+                if check_attr.change == origin_attr.change and check_attr.size 
== origin_attr.size then
+                    passed = true
+                else
+                    passed = false
+                    break
+                end
+            end
+
+            if passed then
+                ngx.say("passed")
+            end
+        }
+    }
+--- response_body
+passed

Reply via email to