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
|
|
@ -1,5 +1,5 @@
|
|||
-- src/http_server.lua
|
||||
-- HTTP Server Core for Furt API-Gateway
|
||||
-- HTTP Server Core for Furt API-Gateway with Structured Logging
|
||||
-- Dragons@Work Digital Sovereignty Project
|
||||
|
||||
local socket = require("socket")
|
||||
|
|
@ -10,6 +10,7 @@ end
|
|||
|
||||
local config = require("config.server")
|
||||
local Auth = require("src.auth")
|
||||
local Logger = require("src.logger")
|
||||
|
||||
-- HTTP-Server Module
|
||||
local FurtServer = {}
|
||||
|
|
@ -32,11 +33,22 @@ function FurtServer:add_route(method, path, handler)
|
|||
self.routes[method] = {}
|
||||
end
|
||||
self.routes[method][path] = handler
|
||||
|
||||
Logger.debug("Route registered", {
|
||||
method = method,
|
||||
path = path
|
||||
})
|
||||
end
|
||||
|
||||
-- Add protected route (requires authentication)
|
||||
function FurtServer:add_protected_route(method, path, required_permission, handler)
|
||||
self:add_route(method, path, Auth.create_protected_route(required_permission, handler))
|
||||
|
||||
Logger.debug("Protected route registered", {
|
||||
method = method,
|
||||
path = path,
|
||||
permission = required_permission
|
||||
})
|
||||
end
|
||||
|
||||
-- Parse HTTP request
|
||||
|
|
@ -49,6 +61,7 @@ function FurtServer:parse_request(client)
|
|||
-- Parse request line: "POST /v1/mail/send HTTP/1.1"
|
||||
local method, path, protocol = request_line:match("(%w+) (%S+) (%S+)")
|
||||
if not method then
|
||||
Logger.warn("Invalid request line", { request_line = request_line })
|
||||
return nil
|
||||
end
|
||||
|
||||
|
|
@ -174,22 +187,62 @@ function FurtServer:get_status_text(status)
|
|||
return status_texts[status] or "Unknown"
|
||||
end
|
||||
|
||||
-- Get client IP address with X-Forwarded-For support
|
||||
function FurtServer:get_client_ip(client, headers)
|
||||
-- Check for X-Forwarded-For header (proxy support)
|
||||
local forwarded_for = headers["x-forwarded-for"]
|
||||
if forwarded_for then
|
||||
-- Take first IP in case of multiple proxies
|
||||
local first_ip = forwarded_for:match("([^,]+)")
|
||||
if first_ip then
|
||||
return first_ip:match("^%s*(.-)%s*$") -- trim whitespace
|
||||
end
|
||||
end
|
||||
|
||||
-- Check for X-Real-IP header
|
||||
local real_ip = headers["x-real-ip"]
|
||||
if real_ip then
|
||||
return real_ip
|
||||
end
|
||||
|
||||
-- Fallback to direct connection
|
||||
local peer_ip = client:getpeername()
|
||||
return peer_ip or "unknown"
|
||||
end
|
||||
|
||||
-- Handle client request
|
||||
function FurtServer:handle_client(client)
|
||||
-- Generate request ID and start timing
|
||||
local request_id = Logger.generate_request_id()
|
||||
local start_time = socket.gettime()
|
||||
|
||||
Logger.debug("Request started", {
|
||||
request_id = request_id
|
||||
})
|
||||
|
||||
local request = self:parse_request(client)
|
||||
if not request then
|
||||
local duration_ms = math.floor((socket.gettime() - start_time) * 1000)
|
||||
local response = self:create_response(400, {error = "Invalid request"}, nil, nil, nil)
|
||||
client:send(response)
|
||||
|
||||
Logger.log_request("INVALID", "unknown", 400, duration_ms, "unknown", request_id)
|
||||
return
|
||||
end
|
||||
|
||||
print(string.format("[%s] %s %s", os.date("%Y-%m-%d %H:%M:%S"),
|
||||
request.method, request.path))
|
||||
-- Get client IP
|
||||
local client_ip = self:get_client_ip(client, request.headers)
|
||||
|
||||
-- Add request_id to request context for handlers
|
||||
request.request_id = request_id
|
||||
|
||||
-- Handle OPTIONS preflight requests (CORS)
|
||||
if request.method == "OPTIONS" then
|
||||
local duration_ms = math.floor((socket.gettime() - start_time) * 1000)
|
||||
local response = self:create_response(204, "", "text/plain", nil, request)
|
||||
client:send(response)
|
||||
|
||||
Logger.log_request("OPTIONS", request.path, 204, duration_ms, client_ip, request_id)
|
||||
return
|
||||
end
|
||||
|
||||
|
|
@ -199,49 +252,78 @@ function FurtServer:handle_client(client)
|
|||
handler = self.routes[request.method][request.path]
|
||||
end
|
||||
|
||||
local status = 404
|
||||
if handler then
|
||||
local success, result = pcall(handler, request, self)
|
||||
if success then
|
||||
client:send(result)
|
||||
-- Extract status from response (rough parsing)
|
||||
status = tonumber(result:match("HTTP/1%.1 (%d+)")) or 200
|
||||
else
|
||||
print("Handler error: " .. tostring(result))
|
||||
local error_response = self:create_response(500, {error = "Internal server error"}, nil, nil, request)
|
||||
Logger.log_error("Handler error", {
|
||||
request_id = request_id,
|
||||
method = request.method,
|
||||
path = request.path,
|
||||
client_ip = client_ip
|
||||
}, tostring(result))
|
||||
|
||||
status = 500
|
||||
local error_response = self:create_response(500, {
|
||||
error = "Internal server error",
|
||||
request_id = request_id
|
||||
}, nil, nil, request)
|
||||
client:send(error_response)
|
||||
end
|
||||
else
|
||||
print("Route not found: " .. request.method .. " " .. request.path)
|
||||
local response = self:create_response(404, {error = "Route not found", method = request.method, path = request.path}, nil, nil, request)
|
||||
Logger.debug("Route not found", {
|
||||
request_id = request_id,
|
||||
method = request.method,
|
||||
path = request.path
|
||||
})
|
||||
|
||||
local response = self:create_response(404, {
|
||||
error = "Route not found",
|
||||
method = request.method,
|
||||
path = request.path,
|
||||
request_id = request_id
|
||||
}, nil, nil, request)
|
||||
client:send(response)
|
||||
end
|
||||
|
||||
-- Log completed request with performance metrics
|
||||
local duration_ms = math.floor((socket.gettime() - start_time) * 1000)
|
||||
Logger.log_request(request.method, request.path, status, duration_ms, client_ip, request_id)
|
||||
end
|
||||
|
||||
-- Start HTTP server
|
||||
function FurtServer:start()
|
||||
self.server = socket.bind(self.host, self.port)
|
||||
if not self.server then
|
||||
Logger.error("Failed to bind server", {
|
||||
host = self.host,
|
||||
port = self.port
|
||||
})
|
||||
error("Failed to bind to " .. self.host .. ":" .. self.port)
|
||||
end
|
||||
|
||||
local HealthRoute = require("src.routes.health")
|
||||
local version_info = HealthRoute.get_version_info()
|
||||
|
||||
print(string.format("Furt HTTP-Server started on %s:%d", self.host, self.port))
|
||||
print("Version: " .. version_info.version .. " (merkwerk)")
|
||||
print("Content-Hash: " .. (version_info.content_hash or "unknown"))
|
||||
print("VCS: " .. (version_info.vcs_info and version_info.vcs_info.hash or "none"))
|
||||
print("API-Key authentication: ENABLED")
|
||||
-- Structured startup logging
|
||||
Logger.log_startup(self.host, self.port, version_info)
|
||||
|
||||
-- Show actual configured rate limits
|
||||
-- Log configuration details
|
||||
local rate_limits = config.security and config.security.rate_limits
|
||||
if rate_limits then
|
||||
print(string.format("Rate limiting: ENABLED (%d req/hour per API key, %d req/hour per IP)",
|
||||
rate_limits.api_key_max, rate_limits.ip_max))
|
||||
else
|
||||
print("Rate limiting: ENABLED (default values)")
|
||||
end
|
||||
Logger.log_config_summary({
|
||||
cors_origins_count = #config.cors.allowed_origins,
|
||||
rate_limiting_enabled = rate_limits ~= nil,
|
||||
api_key_max = rate_limits and rate_limits.api_key_max,
|
||||
ip_max = rate_limits and rate_limits.ip_max,
|
||||
test_endpoint_enabled = config.security and config.security.enable_test_endpoint,
|
||||
log_level = Logger.get_log_level()
|
||||
})
|
||||
|
||||
print("CORS enabled for " .. (#config.cors.allowed_origins) .. " configured origins")
|
||||
print("Press Ctrl+C to stop")
|
||||
Logger.info("Furt server ready - Press Ctrl+C to stop")
|
||||
|
||||
while true do
|
||||
local client = self.server:accept()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue