This Lua logging module facilitates colorized log messages to visually distinguish between different log levels such as DEBUG, INFO, WARN, ERR, and CRIT. It integrates with NGINX through the ngx.log
method to log directly to NGINX's error log and a custom lua log file at appropriate levels, in addition to writing to specified log files (default: /var/log/lua/default.log
). It supports conditional logging based on a maximum log level setting and can serialize table messages to JSON format. It is suitable for both development and production environments, with recommendations to adjust log levels appropriately.
lua-cjson
(>= 2.1.0.10-1) -> https://luarocks.org/modules/openresty/lua-cjson
local cjson = require "cjson.safe"
local LuaLogger = {}
LuaLogger.__index = LuaLogger
-- Constructor
function LuaLogger:new(options)
options = options or {}
local self = setmetatable({}, LuaLogger)
self.log_file_path = options.log_file_path or "/var/log/lua/default.log"
self.max_log_level = options.max_log_level or "ERR"
return self
end
-- Colorize log level (private)
function LuaLogger:_colorize_log_level_message(level)
local color_codes = {
DEBUG = "\27[38;5;45m", -- Lighter blue
INFO = "\27[32m", -- Green
WARN = "\27[33m", -- Yellow
ERR = "\27[31m", -- Red
CRIT = "\27[31;1m" -- Bold red
}
return color_codes[level]
.. "[" .. level .. " @ "
.. os.date("%Y-%m-%d %H:%M:%S") .. "]"
.. "\27[0m"
end
-- Log method (private)
function LuaLogger:_log(level, message, ...)
local all_log_levels = {
DEBUG = 1, INFO = 2, WARN = 3, ERR = 4, CRIT = 5
}
local log_level = all_log_levels[level]
local max_level = all_log_levels[self.max_log_level]
if not max_level then
ngx.log(ngx.ERR, "Invalid max log level specified: " .. self.max_log_level)
return
end
--if max_level >= log_level then
if log_level >= max_level then
local file = io.open(self.log_file_path, "a+")
if file then
local args = {...} or {}
-- Check if the message is a table and serialize it if so.
if type(message) == "table" then
local success, result = pcall(cjson.encode, message)
if success then
message = result
else
message = "Error serializing table: " .. result
end
else
message = tostring(message)
end
-- Process and append any additional arguments.
for _, arg in ipairs(args) do
if type(arg) ~= "nil" then
if type(arg) == "table" then
-- Serialize tables.
message = message .. " " .. cjson.encode(arg)
else
message = message .. " " .. tostring(arg)
end
end
end
-- Append a new line to the log file.
local log_entry = self:_colorize_log_level_message(level) .. " " .. message .. "\n"
file:write(log_entry)
file:close()
if level == "DEBUG" then
ngx.log(ngx.DEBUG, message)
elseif level == "INFO" then
ngx.log(ngx.INFO, message)
elseif level == "WARN" then
ngx.log(ngx.WARN, message)
elseif level == "ERR" then
ngx.log(ngx.ERR, message)
elseif level == "CRIT" then
ngx.log(ngx.CRIT, message)
else
ngx.log(ngx.ERR, "Invalid log level specified: " .. level)
end
else
ngx.log(ngx.ERR, "Unable to open log file")
end
end
end
-- Debug log method (public)
function LuaLogger:debug(message, ...)
self:_log("DEBUG", message, ...)
end
-- Info log method (public)
function LuaLogger:info(message, ...)
self:_log("INFO", message, ...)
end
-- Warn log method (public)
function LuaLogger:warn(message, ...)
self:_log("WARN", message, ...)
end
-- Error log method (public)
function LuaLogger:err(message, ...)
self:_log("ERR", message, ...)
end
-- Critical log method (public)
function LuaLogger:crit(message, ...)
self:_log("CRIT", message, ...)
end
return LuaLogger
Make sure the lua_package_path
includes the directory containing the lua_logging.lua
module.
Use init_by_lua_block
to include and instantiate the LuaLogger
class with or without options.
This will make logger
globally available!
init_by_lua_block {
local LuaLogger = require "lua_logging";
logger = LuaLogger:new();
-- or with options
logger = LuaLogger:new({
log_file_path = "/var/log/lua/custom_logger.log",
max_log_level = "DEBUG"
});
}
server {
listen 80;
server_name localhost;
...
location = / {
content_by_lua_block {
logger:debug("This is a DEBUG message")
logger:info("This is an INFO message")
logger:warn("This is a WARNING message")
logger:err("This is an ERROR message")
logger:crit("This is a CRITICAL message")
ngx.say("OK");
}
}
![image](https://private-user-images.githubusercontent.com/2648783/321406060-61fcc29e-671c-45f9-b4ec-1c4d9a244086.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjE3MDI2MDYsIm5iZiI6MTcyMTcwMjMwNiwicGF0aCI6Ii8yNjQ4NzgzLzMyMTQwNjA2MC02MWZjYzI5ZS02NzFjLTQ1ZjktYjRlYy0xYzRkOWEyNDQwODYucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MDcyMyUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDA3MjNUMDIzODI2WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9ZDYwNTMzMTI3YzNlODkzNGMwN2UxNmEwNDMyODNlNjEzYWUzMTQ3MTljYjJhNjQyZWU4NjJhNTgzNWZlZDNmMiZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmYWN0b3JfaWQ9MCZrZXlfaWQ9MCZyZXBvX2lkPTAifQ.gWOA0iTqVf6UmGif8BLu8SLhhZf7HkERWiafov5QP1M)