Skip to content

Instantly share code, notes, and snippets.

@bulletinmybeard
Last active April 11, 2024 01:06
Show Gist options
  • Save bulletinmybeard/b93090a597b8c85fae88e7f5e62242f7 to your computer and use it in GitHub Desktop.
Save bulletinmybeard/b93090a597b8c85fae88e7f5e62242f7 to your computer and use it in GitHub Desktop.
Lua logging module for NGINX

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.

Prerequisites

lua-cjson (>= 2.1.0.10-1) -> https://luarocks.org/modules/openresty/lua-cjson

Lua Module (lua_logging.lua)

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

NGINX Config (e.g., /etc/nginx/nginx.conf)

Make sure the lua_package_path includes the directory containing the lua_logging.lua module.

Site Config (e.g., /etc/nginx/sites-enabled/local.conf)

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;

...

Use it like so

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment