-- src/logger.lua -- Structured JSON Logger for Furt API-Gateway -- Dragons@Work Digital Sovereignty Project local found_cjson, cjson = pcall(require, 'cjson') if not found_cjson then cjson = require('dkjson') end local config = require("config.server") -- Hash-based request ID generator for collision resistance local function generate_request_id() local data = string.format("%d-%d-%d-%d", os.time(), math.random(1000000, 9999999), math.random(1000000, 9999999), os.clock() * 1000000) -- Simple hash function (Lua-native, no dependencies) local hash = 0 for i = 1, #data do hash = (hash * 31 + string.byte(data, i)) % 2147483647 end return string.format("req-%x", hash) end -- Export request ID generator Logger = {} Logger.generate_request_id = generate_request_id local Logger = {} -- Log levels with numeric values for filtering local LOG_LEVELS = { debug = 1, info = 2, warn = 3, error = 4 } -- Current log level from config local current_log_level = LOG_LEVELS[config.log_level] or LOG_LEVELS.info -- Service identification local SERVICE_NAME = "furt-lua" -- Generate timestamp in ISO format local function get_timestamp() return os.date("!%Y-%m-%dT%H:%M:%SZ") end -- Core logging function local function log_structured(level, message, context) -- Skip if log level is below threshold if LOG_LEVELS[level] < current_log_level then return end -- Build log entry local log_entry = { timestamp = get_timestamp(), level = level, service = SERVICE_NAME, message = message } -- Add context data if provided if context then for key, value in pairs(context) do log_entry[key] = value end end -- Output as JSON local json_output = cjson.encode(log_entry) print(json_output) end -- Public logging functions function Logger.debug(message, context) log_structured("debug", message, context) end function Logger.info(message, context) log_structured("info", message, context) end function Logger.warn(message, context) log_structured("warn", message, context) end function Logger.error(message, context) log_structured("error", message, context) end -- Request logging with performance metrics function Logger.log_request(method, path, status, duration_ms, client_ip, request_id) if not config.log_requests then return end local context = { method = method, path = path, status = status, duration_ms = duration_ms, client_ip = client_ip } -- Add request_id if provided if request_id then context.request_id = request_id end log_structured("info", "HTTP request", context) end -- Service startup logging function Logger.log_startup(host, port, version_info) Logger.info("Furt HTTP-Server starting", { host = host, port = port, version = version_info.version, content_hash = version_info.content_hash, vcs_hash = version_info.vcs_info and version_info.vcs_info.hash }) end -- Service health logging function Logger.log_health_check(status, details) local level = status == "healthy" and "info" or "warn" log_structured(level, "Health check", { health_status = status, details = details }) end -- Error logging with stack trace support function Logger.log_error(error_message, context, stack_trace) local error_context = context or {} if stack_trace then error_context.stack_trace = stack_trace end log_structured("error", error_message, error_context) end -- Configuration logging function Logger.log_config_summary(summary) Logger.info("Configuration loaded", summary) end -- Rate limiting events function Logger.log_rate_limit(api_key, client_ip, limit_type) Logger.warn("Rate limit exceeded", { api_key = api_key and "***masked***" or nil, client_ip = client_ip, limit_type = limit_type }) end -- SMTP/Mail logging function Logger.log_mail_event(event_type, recipient, success, error_message) local level = success and "info" or "error" log_structured(level, "Mail event", { event_type = event_type, recipient = recipient, success = success, error = error_message }) end -- Set log level dynamically (useful for debugging) function Logger.set_log_level(level) if LOG_LEVELS[level] then current_log_level = LOG_LEVELS[level] Logger.info("Log level changed", { new_level = level }) else Logger.error("Invalid log level", { attempted_level = level }) end end -- Get current log level function Logger.get_log_level() for level, value in pairs(LOG_LEVELS) do if value == current_log_level then return level end end return "unknown" end -- Check if a log level would be output function Logger.would_log(level) return LOG_LEVELS[level] and LOG_LEVELS[level] >= current_log_level end return Logger