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.
This commit is contained in:
parent
ddbb232de2
commit
8fecb0188c
3 changed files with 418 additions and 33 deletions
200
src/logger.lua
Normal file
200
src/logger.lua
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
-- 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
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue