blob: 4a03a802a30b313f2fa94c3a8c60d13f68d8069f [file] [log] [blame]
--
-- 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 process = require("ngx.process")
local signal = require("resty.signal")
local ngx = ngx
local lfs = require("lfs")
local io = io
local os = os
local table = table
local string = string
local str_find = core.string.find
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 schema = {
type = "object",
properties = {},
additionalProperties = false,
}
local _M = {
version = 0.1,
priority = 100,
name = plugin_name,
schema = schema,
}
local function file_exists(path)
local file = io.open(path, "r")
if file then
file:close()
end
return file ~= nil
end
local function get_last_index(str, key)
local rev = string.reverse(str)
local _, idx = str_find(rev, key)
local n
if idx then
n = #rev - idx + 1
end
return n
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
local root = string.sub(conf_path, 1, 1)
-- relative path
if root ~= "/" then
conf_path = prefix .. conf_path
end
local n = get_last_index(conf_path, "/")
if n ~= nil and n ~= #conf_path then
local dir = string.sub(conf_path, 1, n)
local name = string.sub(conf_path, n + 1)
return dir, name
end
end
return prefix .. "logs/", file_type
end
local function rotate_file(date_str, file_type)
local log_dir, filename = get_log_path_info(file_type)
core.log.info("rotate log_dir:", log_dir)
core.log.info("rotate filename:", filename)
local file_path = log_dir .. date_str .. "__" .. filename
if file_exists(file_path) then
core.log.info("file exist: ", file_path)
return false
end
local file_path_org = log_dir .. filename
local ok, msg = os.rename(file_path_org, file_path)
core.log.info("move file from ", file_path_org, " to ", file_path,
" res:", ok, " msg:", msg)
return true
end
local function tab_sort(a, b)
return a > b
end
local function scan_log_folder()
local t = {
access = {},
error = {},
}
local log_dir, access_name = get_log_path_info("access.log")
local _, error_name = get_log_path_info("error.log")
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
table.insert(t.access, file)
elseif log_type == error_name then
table.insert(t.error, file)
end
end
end
table.sort(t.access, tab_sort)
table.sort(t.error, tab_sort)
return t
end
local function rotate()
local local_conf = core.config.local_conf()
local interval = INTERVAL
local max_kept = MAX_KEPT
local attr = core.table.try_read_attr(local_conf, "plugin_attr",
"log-rotate")
if attr then
interval = attr.interval or interval
max_kept = attr.max_kept or max_kept
end
core.log.info("rotate interval:", interval)
core.log.info("rotate max keep:", max_kept)
local time = ngx.time()
if time % interval == 0 then
time = time - interval
else
time = time - time % interval
end
local date_str = os.date("%Y-%m-%d_%H-%M-%S", time)
local ok1 = rotate_file(date_str, "access.log")
local ok2 = rotate_file(date_str, "error.log")
if not ok1 and not ok2 then
return
end
core.log.warn("send USER1 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 USER1 signal for reopening log file: ",
err)
end
-- clean the oldest file
local log_list = scan_log_folder()
local log_dir, _ = get_log_path_info("access.log")
for i = max_kept + 1, #log_list.error do
local path = log_dir .. log_list.error[i]
local ok = os.remove(path)
core.log.warn("remove old error file: ", path, " ret: ", ok)
end
for i = max_kept + 1, #log_list.access do
local path = log_dir .. log_list.access[i]
local ok = os.remove(path)
core.log.warn("remove old access file: ", path, " ret: ", ok)
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