feat(apisix): add Cloudron package
- Implements Apache APISIX packaging for Cloudron platform. - Includes Dockerfile, CloudronManifest.json, and start.sh. - Configured to use Cloudron's etcd addon. 🤖 Generated with Gemini CLI Co-Authored-By: Gemini <noreply@google.com>
This commit is contained in:
@@ -0,0 +1,327 @@
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
|
||||
local core = require("apisix.core")
|
||||
local timers = require("apisix.timers")
|
||||
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
|
||||
local lfs = require("lfs")
|
||||
local type = type
|
||||
local io_open = io.open
|
||||
local os_date = os.date
|
||||
local os_remove = os.remove
|
||||
local os_rename = os.rename
|
||||
local str_sub = string.sub
|
||||
local str_format = string.format
|
||||
local str_byte = string.byte
|
||||
local ngx_sleep = require("apisix.core.utils").sleep
|
||||
local string_rfind = require("pl.stringx").rfind
|
||||
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
|
||||
local enable_compression = false
|
||||
local DEFAULT_ACCESS_LOG_FILENAME = "access.log"
|
||||
local DEFAULT_ERROR_LOG_FILENAME = "error.log"
|
||||
local SLASH_BYTE = str_byte("/")
|
||||
|
||||
local schema = {
|
||||
type = "object",
|
||||
properties = {},
|
||||
}
|
||||
|
||||
|
||||
local _M = {
|
||||
version = 0.1,
|
||||
priority = 100,
|
||||
name = plugin_name,
|
||||
schema = schema,
|
||||
scope = "global",
|
||||
}
|
||||
|
||||
|
||||
local function file_exists(path)
|
||||
local file = io_open(path, "r")
|
||||
if file then
|
||||
file:close()
|
||||
end
|
||||
return file ~= nil
|
||||
end
|
||||
|
||||
|
||||
local function get_log_path_info(file_type)
|
||||
local_conf = core.config.local_conf()
|
||||
local conf_path
|
||||
if file_type == "error.log" then
|
||||
conf_path = local_conf and local_conf.nginx_config and
|
||||
local_conf.nginx_config.error_log
|
||||
else
|
||||
conf_path = local_conf and local_conf.nginx_config and
|
||||
local_conf.nginx_config.http and
|
||||
local_conf.nginx_config.http.access_log
|
||||
end
|
||||
|
||||
local prefix = ngx.config.prefix()
|
||||
|
||||
if conf_path then
|
||||
-- relative path
|
||||
if str_byte(conf_path) ~= SLASH_BYTE then
|
||||
conf_path = prefix .. conf_path
|
||||
end
|
||||
local n = string_rfind(conf_path, "/")
|
||||
if n ~= nil and n ~= #conf_path then
|
||||
local dir = str_sub(conf_path, 1, n)
|
||||
local name = str_sub(conf_path, n + 1)
|
||||
return dir, name
|
||||
end
|
||||
end
|
||||
|
||||
return prefix .. "logs/", file_type
|
||||
end
|
||||
|
||||
|
||||
local function tab_sort_comp(a, b)
|
||||
return a > b
|
||||
end
|
||||
|
||||
|
||||
local function scan_log_folder(log_file_name)
|
||||
local t = {}
|
||||
|
||||
local log_dir, log_name = get_log_path_info(log_file_name)
|
||||
|
||||
local compression_log_type = log_name .. COMPRESSION_FILE_SUFFIX
|
||||
for file in lfs.dir(log_dir) do
|
||||
local n = string_rfind(file, "__")
|
||||
if n ~= nil then
|
||||
local log_type = file:sub(n + 2)
|
||||
if log_type == log_name or log_type == compression_log_type then
|
||||
core.table.insert(t, file)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
core.table.sort(t, tab_sort_comp)
|
||||
return t, log_dir
|
||||
end
|
||||
|
||||
|
||||
local function rename_file(log, date_str)
|
||||
local new_file
|
||||
if not log.new_file then
|
||||
core.log.warn(log.type, " is off")
|
||||
return
|
||||
end
|
||||
|
||||
new_file = str_format(log.new_file, date_str)
|
||||
if file_exists(new_file) then
|
||||
core.log.info("file exist: ", new_file)
|
||||
return new_file
|
||||
end
|
||||
|
||||
local ok, err = os_rename(log.file, new_file)
|
||||
if not ok then
|
||||
core.log.error("move file from ", log.file, " to ", new_file,
|
||||
" res:", ok, " msg:", err)
|
||||
return
|
||||
end
|
||||
|
||||
return new_file
|
||||
end
|
||||
|
||||
|
||||
local function compression_file(new_file, timeout)
|
||||
if not new_file or type(new_file) ~= "string" then
|
||||
core.log.info("compression file: ", new_file, " invalid")
|
||||
return
|
||||
end
|
||||
|
||||
local n = string_rfind(new_file, "/")
|
||||
local new_filepath = str_sub(new_file, 1, n)
|
||||
local new_filename = str_sub(new_file, n + 1)
|
||||
local com_filename = new_filename .. COMPRESSION_FILE_SUFFIX
|
||||
local cmd = str_format("cd %s && tar -zcf %s %s", new_filepath,
|
||||
com_filename, new_filename)
|
||||
core.log.info("log file compress command: " .. cmd)
|
||||
|
||||
local ok, stdout, stderr, reason, status = shell.run(cmd, nil, timeout, nil)
|
||||
if not ok then
|
||||
core.log.error("compress log file from ", new_filename, " to ", com_filename,
|
||||
" fail, stdout: ", stdout, " stderr: ", stderr, " reason: ", reason,
|
||||
" status: ", status)
|
||||
return
|
||||
end
|
||||
|
||||
ok, stderr = os_remove(new_file)
|
||||
if stderr then
|
||||
core.log.error("remove uncompressed log file: ", new_file,
|
||||
" fail, err: ", stderr, " res:", ok)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function init_default_logs(logs_info, log_type)
|
||||
local filepath, filename = get_log_path_info(log_type)
|
||||
logs_info[log_type] = { type = log_type }
|
||||
if filename ~= "off" then
|
||||
logs_info[log_type].file = filepath .. filename
|
||||
logs_info[log_type].new_file = filepath .. "/%s__" .. filename
|
||||
end
|
||||
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, timeout)
|
||||
if core.table.isempty(files) then
|
||||
return
|
||||
end
|
||||
|
||||
local new_files = core.table.new(2, 0)
|
||||
-- rename the log files
|
||||
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
|
||||
|
||||
core.table.insert(new_files, new_file)
|
||||
end
|
||||
|
||||
-- send signal to reopen log files
|
||||
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
|
||||
-- Waiting for nginx reopen files
|
||||
-- to avoid losing logs during compression
|
||||
ngx_sleep(0.5)
|
||||
|
||||
for _, new_file in ipairs(new_files) do
|
||||
compression_file(new_file, timeout)
|
||||
end
|
||||
end
|
||||
|
||||
for _, file in ipairs(files) do
|
||||
-- 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)
|
||||
local timeout = 10000 -- default timeout 10 seconds
|
||||
if attr then
|
||||
interval = attr.interval or interval
|
||||
max_kept = attr.max_kept or max_kept
|
||||
max_size = attr.max_size or max_size
|
||||
timeout = attr.timeout or timeout
|
||||
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)
|
||||
core.log.info("rotate timeout:", timeout)
|
||||
|
||||
if not default_logs then
|
||||
-- first init default log filepath and filename
|
||||
default_logs = {}
|
||||
init_default_logs(default_logs, DEFAULT_ACCESS_LOG_FILENAME)
|
||||
init_default_logs(default_logs, DEFAULT_ERROR_LOG_FILENAME)
|
||||
end
|
||||
|
||||
ngx_update_time()
|
||||
local now_time = ngx_time()
|
||||
if not rotate_time then
|
||||
-- first init rotate time
|
||||
rotate_time = now_time + interval - (now_time % interval)
|
||||
core.log.info("first init rotate time is: ", rotate_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, timeout)
|
||||
|
||||
-- reset rotate time
|
||||
rotate_time = rotate_time + interval
|
||||
|
||||
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)
|
||||
local files = core.table.new(2, 0)
|
||||
|
||||
if access_log_file_size >= max_size then
|
||||
core.table.insert(files, DEFAULT_ACCESS_LOG_FILENAME)
|
||||
end
|
||||
|
||||
if error_log_file_size >= max_size then
|
||||
core.table.insert(files, DEFAULT_ERROR_LOG_FILENAME)
|
||||
end
|
||||
|
||||
rotate_file(files, now_time, max_kept, timeout)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function _M.init()
|
||||
timers.register_timer("plugin#log-rotate", rotate, true)
|
||||
end
|
||||
|
||||
|
||||
function _M.destroy()
|
||||
timers.unregister_timer("plugin#log-rotate", true)
|
||||
end
|
||||
|
||||
|
||||
return _M
|
Reference in New Issue
Block a user