furt/src/logger.lua
michael 8fecb0188c stop(logging): implement structured JSON logging then halt due to YAGNI
Add comprehensive structured logging system, then stop development:

- Create Logger module with JSON output and configurable log levels
- Implement hash-based request ID generation for request tracing
- Add performance timing and client IP detection to HTTP server
- Enhance startup logging with module loading and configuration checks

STOPPED: Feature violates Low-Tech principles
- 200+ lines logging vs 100 lines business logic (code bloat)
- JSON serialization overhead reduces performance
- No current production need for structured monitoring
- Simple print() statements sufficient for current scale

Branch parked for future consideration when monitoring requirements
actually emerge. Issue #54 deferred to v0.2.x milestone.
2025-09-05 20:31:27 +02:00

200 lines
5 KiB
Lua

-- 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